Ether One’s Item System
This is how Ether One (UE4 Version)'s item system works. I’ve commented some of the more important bits. It was also written long before I got to grips with C++ so please don’t judge the quality.
UpdateItem() is called from the component tick.
We use the camera’s rotation as the starting point always, and then add the rotational values on each time, rather than recalculating from where the item currently is. It seems to feel okay this way.
//Inline function in the header file. I've definitely stolen this from somewhere.
FORCEINLINE FRotator AddTwoRotators(FRotator A, FRotator B)
FQuat PQ = FRotator(A.Pitch, 0, 0).Quaternion();
FQuat YQ = FRotator(0, A.Yaw, 0).Quaternion();
FQuat RQ = FRotator(0, 0, A.Roll).Quaternion();
B = (B.Quaternion() * PQ * YQ * RQ).Rotator();
//This is from a custom subclass of staticmeshcomponent, owned by an itemzone actor.
//DistanceFromCamera is typically 32.f
//InitialLocation is either the itemzone actor location or a designated anchor location.
FRotator TargetRotation = FRotator(0, 0, 0);
//EChar is character reference.
if (EChar && EChar->IsValidLowLevel())
CamLoc = EChar->CurrentCameraLocation;
//Set the targetrotation initially to the camera rotation.
TargetRotation = EChar->CurrentCameraRotation;
if (bFromPlayerInventory == false) TargetLocation = CamLoc + FRotationMatrix(TargetRotation).GetScaledAxis(EAxis::X) * DistanceFromCamera;
else TargetLocation = EChar->GetRootComponent()->GetComponentLocation();
if (ItemAnchor != NULL)
//Set InitialLocation to be the anchor's location.
InitialLocation = ItemAnchor->GetRootComponent()->GetComponentLocation();
//Constantly update the InitialLocation, as the root may have moved.
InitialLocation = GetAttachParent()->GetComponentLocation();
InitialLocation.Z += HeightOffset;
NewLoc.X = FMath::Lerp(InitialLocation.X, TargetLocation.X, TravelTime);
NewLoc.Y = FMath::Lerp(InitialLocation.Y, TargetLocation.Y, TravelTime);
NewLoc.Z = FMath::Lerp(InitialLocation.Z, TargetLocation.Z, TravelTime);
NewLoc.X = FMath::Lerp(TargetLocation.X, InitialLocation.X, TravelTime);
NewLoc.Y = FMath::Lerp(TargetLocation.Y, InitialLocation.Y, TravelTime);
NewLoc.Z = FMath::Lerp(TargetLocation.Z, InitialLocation.Z, TravelTime);
//Add the mouse movement if the item can be rotated.
if (bNoRotation == false)
//PickupPitch and PickupYaw are fed to from axis bound functions provided we are examining.
TargetRotation = AddTwoRotators(FRotator(PickupPitch, -PickupYaw, 0), TargetRotation);
//InitialRotation is the rotation of the meshcomponent in component space at the point of initialization.
//Add the InitialRotation on top of the camera's rotation.
TargetRotation = AddTwoRotators(InitialRotation, TargetRotation);
//Add on any additional angle change
if (ViewAngleModifier != FRotator(0, 0, 0))
//This angle is used to get the item to face a certain angle when picked up.
TargetRotation = AddTwoRotators(ViewAngleModifier, TargetRotation);
//Get the component's original rotation in world space
if (ItemAnchor != NULL)
//If there is an anchor, override this base rotation with the anchor's rotation.
WorldRotation = ItemAnchor->GetRootComponent()->GetComponentRotation();
//Otherwise we just get the itemzone's rotation.
WorldRotation = GetAttachParent()->GetComponentRotation();
WorldRotation = AddTwoRotators(InitialRotation, WorldRotation);
//TravelTime is updated via component tick
//If the item is coming towards the player, we lerp the resulting rotation from the base rotation
TargetRotation = RLerp(WorldRotation, TargetRotation, TravelTime, true);
//And when putting it back we do the opposite.
TargetRotation = RLerp(TargetRotation, WorldRotation, TravelTime, true);
//This is specific to when we place item reels in zones, and they are set to spin.
TargetRotation = AddTwoRotators(FRotator(-ReelAngle, 0, 0), TargetRotation);
//Finally with our resulting rotation we set the rotation in world space.
So yeah, start with the camera’s rotation and add your total rotations on to that.