Unreal : CharacterMovementComponent최적화

2 분 소요

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 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로직을 항상 타게 될 것임.

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으로만 설정하면 드라마틱한 변화를 줄 수 있다.

태그:

카테고리:

업데이트: