Unreal : CharacterMovementComponent최적화
CharacterMovementComponent 최적화
MMORPG에서 PC, 몬스터, 유저 캐릭터의 이동 로직 부하를 최소화 하기 위해 최적화를 진행하였다.
둘 다 NavMesh기반으로 이동 하게 할 것이다.
몬스터는 PawnMovementComopnent를 상속하여 커스터마이징 함.
void UNpcMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
if (ShouldSkipUpdate(DeltaTime))
return;
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!PawnOwner || !UpdatedComponent)
return;
FVector inputVector = ConsumeInputVector();
CalcVelocity(inputVector);
bool needUpdate = IsNeedUpdate(inputVector, DeltaTime);
FVector desiredMove = Velocity;
desiredMove.Z = 0.f;
const FVector oldLocation = GetActorLocation();
const FVector deltaMove = desiredMove * DeltaTime;
const bool bDeltaMoveNearlyZero = deltaMove.IsNearlyZero();
FVector adjustedDest = oldLocation + deltaMove;
FVector destNavLocation = FB2MoveHelper::FindFloorPawnPosition(FB2Util::GetWorld(), PawnOwner, adjustedDest);
if (needUpdate)
{
CreatureRotation(DeltaTime);
FHitResult hit(1.f);
SafeMoveUpdatedComponent(destNavLocation - oldLocation, UpdatedComponent->GetComponentQuat(), false, hit);
if (hit.IsValidBlockingHit())
{
//HandleImpact(hit, DeltaTime, delta);
// Try to slide the remaining distance along the surface.
//SlideAlongSurface(delta, 1.f - hit.Time, hit.Normal, hit, false);
}
UpdateComponentVelocity();
}
}
이동 로직에 꼭 필요한 로직
- CalcVelocity
- RequestedVelocity처리
- delta move
- delta move시
SafeMoveUpdatedComponent
을 호출 할 때 bSweep을 False로 한다. - blocking hit발생 해도 추가작업을 무시한다.
- delta move시
- delta rotation
유저는 CharacterMovementComponent를 상속하여 커스터마이징 한다.
void UUserPlayerMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
if (ShouldSkipUpdate(DeltaTime))
{
return;
}
UMovementComponent::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (!CharacterOwner || !UpdatedComponent)
{
return;
}
HandlePendingLaunch();
FNavLocation NavLocation;
if (FindNavFloor(GetActorFeetLocation(), NavLocation))
{
if (bAutoMove)
{
AbortAutoMove();
}
if (MovementMode == EMovementMode::MOVE_Walking)
{
SetMovementMode(EMovementMode::MOVE_NavWalking);
}
}
if (IsMovingOnGround())
bLaunched = false;
MoveComponentFlags = (CurrentLOD >= NpcOwner->SkipRootMotionLODValue) ? EMoveComponentFlags::MOVECOMP_SkipPhysicsMove | EMoveComponentFlags::MOVECOMP_IgnoreBases | EMoveComponentFlags::MOVECOMP_DisableBlockingOverlapDispatch
: EMoveComponentFlags::MOVECOMP_NoFlags;
bSweepWhileNavWalking = CurrentLOD < NpcOwner->SkipRootMotionLODValue;
if (MovementMode == MOVE_NavWalking && bWantsToLeaveNavWalking)
{
TryToLeaveNavWalking();
}
StartNewPhysics(DeltaTime, 0);
PhysicsRotation(DeltaTime);
UpdateComponentVelocity();
}
MovementMode가 NavWalking이어서 StartNewPhysics에서 PhysNavWalking로직을 항상 타게 될 것임.
NavMesh를 벗어나지 못하게 추가 작업이 필요함.
scopedmovementupdate를 활용하여 FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
PhysNavWalking시 bMoved가 false면 revertmove를 호출
SkeletalMesh의 LOD여부에 따라 Flag 및 Sweeping을 결정.
PC도 User와 동일하게 CharacterMovementComponent를 상속하여 커스터 마이징 한다.
void UlayerMovementComponent::PerformMovement(float InDelta)
{
if (CharacterOwner->IsLocallyControlled() ||
(!CharacterOwner->Controller && bRunPhysicsWithNoController) ||
(!CharacterOwner->Controller && CharacterOwner->IsPlayingRootMotion()))
{
{
CharacterOwner->CheckJumpInput(InDelta);
const FVector inputVector = ConsumeInputVector();
Acceleration = ScaleInputAcceleration(ConstrainInputAcceleration(inputVector));
AnalogInputModifier = ComputeAnalogInputModifier();
}
bForceNextFloorCheck |= (IsMovingOnGround() && UpdatedComponent->GetComponentLocation() != LastUpdateLocation);
if(ApplyCrestEffect(static_cast<int>(ECrestEffectInput::DoubleJump)))
{
HandlePendingLaunch();
}
CharacterOwner->ClearJumpInput(InDelta);
{
// 지연 업데이트.
FScopedMovementUpdate ScopedMovementUpdate(UpdatedComponent, EScopedUpdate::DeferredUpdates);
StartNewPhysics(InDelta, 0);
bHasRequestedVelocity = false;
}
UpdateComponentVelocity();
LastUpdateLocation = UpdatedComponent ? UpdatedComponent->GetComponentLocation() : FVector::ZeroVector;
LastUpdateRotation = UpdatedComponent ? UpdatedComponent->GetComponentQuat() : FQuat::Identity;
LastUpdateVelocity = Velocity;
}
}
로직은 User와 비슷함.
PhysNavWalking로직에 변화가 있다.(이는 최적화와 관련 없어서 다음 포스트에 내용 추가 함)
TickComponent로직 최소화로 최적화를 진행하면, 개선이 되긴 하나, 드라마틱한 변화를 기대할 수 없다.
드라마틱한 변화를 줄 수 있는 부분은 프로파일링을 해보면 알 수 있다.
- Movecomponent(Primitive)를 보면 UpdateOverlaps와 UpdateComponentToWorld의 비율이 높음을 알 수 있다.
- 이는 Root 아래 ChildComponents에 SceneComponent 기반 Component가 많이 붙어있으며, 해당 Component의 Physics세팅에 따라 호출이 발생한다.
- 따라서 충돌체크에 필요한 RootComponent(CapsuleComponent)를 제외한 나머지 Component의 Physics세팅을 NoCollision으로만 설정하면 드라마틱한 변화를 줄 수 있다.