Hey @oSpace!
I really love your level designs - very creative! I also dug a bit deeper into your questions since they were good refreshers for myself and I wanted to be able to give you a couple different solutions to experiment with.
1. Kill Z
Checking if your actor has fallen under a World Z value that you set in Tick is fine, but it’s somewhat redundant because there is already code in the engine to handle this. If you open up World Settings from the Window menu and take a look at the World category, you’ll notice that there is a setting named Kill Z. This setting can be set to whatever value you want for the current level. For example:
If we were happy to just let any actors that fall below this threshold be destroyed, we’d be done - because that is exactly what happens by default. However, since we actually want our character to teleport to some location (like a checkpoint), we need to override the default behavior. This is a bit of an advanced topic for this point in the course, but it’s actually quite simple.
The function we want to override is called FellOutOfWorld which is a virtual member function inside AActor. This function is called by the engine when the actor falls below the Kill Z value we set in World Settings. In order to override the function, we need to create our own Character class. That may sound intimidating at first, but we’re only creating an ultra-thin child class of ACharacter so we can override the FellOutOfWorld function.
To create your own Character class, create a new C++ class just like we did for MovingPlatform. In the CHOOSE PARENT CLASS window, select Character as the parent class, click Next, then choose a name and click Create Class. I’m not very original, so I just called it MyCharacter. =]
Once your character class is created, you can delete all the generated functions and just add the code for FellOutOfWorld like so:
UCLASS()
class OBSTACLEASSAULT_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
virtual void FellOutOfWorld(const class UDamageType& dmgType) override;
};
Since I didn’t create a fancy checkpoint system like you did, I just went with the simple option of teleporting the player to the first (and only) PlayerStart in my level:
void AMyCharacter::FellOutOfWorld(const class UDamageType& dmgType)
{
TArray<AActor*> PlayerStarts;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), APlayerStart::StaticClass(), PlayerStarts);
if (PlayerStarts.Num() > 0)
{
SetActorLocation(PlayerStarts[0]->GetActorLocation());
}
}
You can of course modify FellOutOfWorld to use your own checkpoint system. Also, you might be wondering about the dmgType parameter - we need to include it in the function signature for the override but we’re not actually using it for anything so don’t worry about it for right now. If interested, you can read more about it in DamageType.h.
2. Wrecking Ball
Now this one was interesting. I love the creative use of InterpSinInOut
- you’re on the right track! That said, I thought I might be able to help you out with the stuttering issue and offer a few alternative solutions.
The stuttering you are seeing is caused by spikes in your framerate. I actually saw the same problem on my machine using your code. My first thought was to actually log each of the variables out to see what was actually going inside the RotateWreckingBall
function. Here’s the logging code:
UE_LOG(LogTemp, Warning, TEXT("%f * %f * %f = %f"), RealtimeSeconds, DeltaTime, PlatformRange,
RealtimeSeconds * DeltaTime * PlatformRange);
The output below shows the log output for the first dozen frames or so:
Take a look at the last column - notice that the value being fed into InterpSinInOut
is all over the place. After the first frame, the value for RealtimeSeconds
is relatively stable, and PlatfomRange
never changes. However, my DeltaTime
is all over the place due to framerate spikes. Another issue is the fact that InterpSinInOut
expects the last parameter, or the Alpha value to be in the range of 0-1 (a.k.a unit value). These two factors are why you see stuttering in your rotations.
Here are a couple solutions I came up with that you can feel free to experiment with:
Solution 1:
This solution also uses InterpSinInOut
, but uses an alpha value that stays within the range (0-1) expected by the function:
SetActorRotation(FMath::InterpSinInOut(MinRotation, MaxRotation, CurrentAlpha));
CurrentAlpha += DeltaTime * AngularVelocity;
if (CurrentAlpha < 0.f || CurrentAlpha > 1.f)
{
CurrentAlpha = FMath::Clamp(CurrentAlpha, 0.f, 1.f);
AngularVelocity = -AngularVelocity;
}
Notice that I’m clamping the value of CurrentAlpha
to prevent any under/overflow of our range in case there are large spikes in DeltaTime. Our relative error is also much less because of our limited range.
Feel free to experiment with MinRotation
, MaxRotation
, and AngularVelocity
. You may want to expose them in the editor so you can play around with them, but I’ll leave that as an exercise for you. =]
Solution 2:
This solution trades InterpSinInOut
for the SmoothStep
function (also found in the FMath library). I like this solution because it achieves the same results, however it performs slightly better because it doesn’t call the relatively expensive Sin
or Cos
functions every frame.
const float CurrentAlpha = FMath::SmoothStep(MinRotation.Roll, MaxRotation.Roll, CurrentRoll);
SetActorRotation(FMath::Lerp(MinRotation, MaxRotation, CurrentAlpha));
CurrentRoll += DeltaTime * AngularVelocity;
if (CurrentRoll < MinRotation.Roll || CurrentRoll > MaxRotation.Roll)
{
CurrentRoll = FMath::Clamp(CurrentRoll, MinRotation.Roll, MaxRotation.Roll);
AngularVelocity = -AngularVelocity;
}
Hopefully I’ve answered your questions and opened up some new areas for you to explore. Let me know if you have more questions and keep up the great work!