I’d like to know where to find tutorials for how to implement your own custom movement mode. I’ve seen many tutorials going over high level concepts, replication and such, but none that actually explains what’s going on inside the Phys* functions (PhysWalking, PhysFalling, PhysFlying, etc). For example, what does RestorePreAdditiveRootMotionVelocity and ApplyRootMotionToVelocity do? How do I handle and react to collisions and modify Velocity and Position accordingly?
I’ve tried to create one as pasted below. It removes all friction with the ground, allowing the player to pick up speed when sliding down hills. It’s very flawed though and has a tendency to get stuck on (not so) uneven terrain, killing all momentum.
Preferably, I’d like some tutorial that breaks down the various Phys* functions that comes with the default CharacterMovementComponent, explaining what each function does and why. What I want to accomplish is the skiing mechanic from the Tribes Series, specifically Tribes: Ascend and have deemed the regular movement modes unfit for this mechanic.
Does anyone know any good material to learn from?
void UCustomCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
{
if (deltaTime < MIN_TICK_TIME)
{
return;
}
bJustTeleported = false;
float remainingTime = deltaTime;
const FVector Gravity(0.f, 0.f, GetGravityZ());
// Perform the move
while ((remainingTime >= MIN_TICK_TIME) && (Iterations < MaxSimulationIterations) && CharacterOwner && (CharacterOwner->Controller || bRunPhysicsWithNoController || HasAnimRootMotion() || CurrentRootMotion.HasOverrideVelocity() || (CharacterOwner->GetLocalRole() == ROLE_SimulatedProxy)))
{
Iterations++;
bJustTeleported = false;
const float timeTick = GetSimulationTimeStep(remainingTime, Iterations);
remainingTime -= timeTick;
RestorePreAdditiveRootMotionVelocity();
const FVector OldVelocity = Velocity;
if (!HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
Velocity.Z = 0.f;
CalcVelocity(timeTick, 0, false, 0);
Velocity.Z = OldVelocity.Z;
}
// Apply gravity
Velocity = NewFallVelocity(Velocity, Gravity, timeTick);
ApplyRootMotionToVelocity(timeTick);
FVector OldLocation = UpdatedComponent->GetComponentLocation();
const FVector Adjusted = Velocity * timeTick;
FHitResult Hit(1.f);
SafeMoveUpdatedComponent(Adjusted, UpdatedComponent->GetComponentQuat(), true, Hit);
if (Hit.Time < 1.f)
{
const FVector GravDir = FVector(0.f, 0.f, -1.f);
const FVector VelDir = Velocity.GetSafeNormal();
const float UpDown = GravDir | VelDir;
GEngine->AddOnScreenDebugMessage(-1, 0.f, FColor::Orange, FString::Printf(TEXT("UpDown: %f"), UpDown));
bool bSteppedUp = false;
if ((FMath::Abs(Hit.ImpactNormal.Z) < 0.2f) && (UpDown < 0.5f) && (UpDown > -0.2f) && CanStepUp(Hit))
{
float stepZ = UpdatedComponent->GetComponentLocation().Z;
bSteppedUp = StepUp(GravDir, Adjusted * (1.f - Hit.Time), Hit);
if (bSteppedUp)
{
OldLocation.Z = UpdatedComponent->GetComponentLocation().Z + (OldLocation.Z - stepZ);
}
}
if (!bSteppedUp)
{
//adjust and try again
HandleImpact(Hit, timeTick, Adjusted);
SlideAlongSurface(Adjusted, (1.f - Hit.Time), Hit.Normal, Hit, true);
}
}
if (!bJustTeleported && !HasAnimRootMotion() && !CurrentRootMotion.HasOverrideVelocity())
{
Velocity = (UpdatedComponent->GetComponentLocation() - OldLocation) / timeTick;
}
}
}