icon안동민 개발노트

기본 물리 기반 캐릭터 제어


 언리얼 엔진의 Character 클래스는 물리 기반 캐릭터 제어를 위한 강력한 기능을 제공합니다.

 이 절에서는 C++ 관점에서 물리 시스템을 활용한 캐릭터 제어 방법을 살펴보겠습니다.

Character 클래스의 물리 관련 속성과 함수

 Character 클래스는 물리 시뮬레이션을 위한 여러 속성과 함수를 제공합니다.

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()
 
public:
    AMyCharacter();
 
    virtual void Tick(float DeltaTime) override;
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Movement")
    class UCharacterMovementComponent* MyMovementComponent;
 
    UFUNCTION(BlueprintCallable, Category = "Movement")
    void Jump() override;
 
    UFUNCTION(BlueprintCallable, Category = "Movement")
    void StopJumping() override;
};

기본 동작 구현

 이동

void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);
    PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);
}
 
void AMyCharacter::MoveForward(float Value)
{
    if ((Controller != nullptr) && (Value != 0.0f))
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
        AddMovementInput(Direction, Value);
    }
}
 
void AMyCharacter::MoveRight(float Value)
{
    if ((Controller != nullptr) && (Value != 0.0f))
    {
        const FRotator Rotation = Controller->GetControlRotation();
        const FRotator YawRotation(0, Rotation.Yaw, 0);
        const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
        AddMovementInput(Direction, Value);
    }
}

 점프

void AMyCharacter::Jump()
{
    Super::Jump();
}
 
void AMyCharacter::StopJumping()
{
    Super::StopJumping();
}

CharacterMovementComponent 활용

 CharacterMovementComponent는 캐릭터의 이동과 관련된 다양한 속성을 제공합니다.

void AMyCharacter::CustomizeMovement()
{
    if (MyMovementComponent)
    {
        MyMovementComponent->GravityScale = 1.5f;
        MyMovementComponent->JumpZVelocity = 600.0f;
        MyMovementComponent->AirControl = 0.2f;
        MyMovementComponent->MaxWalkSpeed = 600.0f;
        MyMovementComponent->MaxAcceleration = 2048.0f;
        MyMovementComponent->BrakingDecelerationWalking = 2048.0f;
    }
}

물리 기반 애니메이션 연동

 물리 기반 애니메이션을 캐릭터와 연동하여 더 자연스러운 움직임을 구현할 수 있습니다.

UCLASS()
class UMyAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    float Speed;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    bool bIsInAir;
};
 
void UMyAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    APawn* Pawn = TryGetPawnOwner();
    if (Pawn)
    {
        Speed = Pawn->GetVelocity().Size();
        bIsInAir = Cast<ACharacter>(Pawn)->GetMovementComponent()->IsFalling();
    }
}

환경과의 상호작용

 미끄러운 표면

 미끄러운 표면에서의 이동을 구현하기 위해 CharacterMovementComponent를 커스터마이징할 수 있습니다.

UCLASS()
class UMyCharacterMovementComponent : public UCharacterMovementComponent
{
    GENERATED_BODY()
 
public:
    virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Movement")
    float SlipperyFactor;
};
 
void UMyCharacterMovementComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
    Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
 
    if (CharacterOwner)
    {
        // 미끄러운 표면 감지 및 처리
        if (IsMovingOnGround() && CurrentFloor.HitResult.GetActor())
        {
            if (CurrentFloor.HitResult.GetActor()->ActorHasTag("Slippery"))
            {
                GroundFriction = SlipperyFactor;
            }
            else
            {
                GroundFriction = 8.0f; // 기본값
            }
        }
    }
}

 사다리 오르기*

 사다리 오르기 기능을 구현하기 위해 새로운 이동 모드를 추가할 수 있습니다.

UENUM(BlueprintType)
enum class ECustomMovementMode : uint8
{
    CMOVE_Ladder UMETA(DisplayName = "Ladder"),
    CMOVE_MAX UMETA(Hidden)
};
 
void UMyCharacterMovementComponent::PhysCustom(float deltaTime, int32 Iterations)
{
    Super::PhysCustom(deltaTime, Iterations);
 
    switch (CustomMovementMode)
    {
        case ECustomMovementMode::CMOVE_Ladder:
            PhysLadder(deltaTime, Iterations);
            break;
        default:
            break;
    }
}
 
void UMyCharacterMovementComponent::PhysLadder(float deltaTime, int32 Iterations)
{
    // 사다리 오르기 물리 구현
}

성능 고려사항

  1. 물리 시뮬레이션 복잡도 최적화
  2. LOD (Level of Detail) 시스템 활용
  3. 물리 업데이트 빈도 조절
void AMyCharacter::OptimizePhysics()
{
    // 물리 시뮬레이션 복잡도 조절
    GetMesh()->SetPhysicsLinearVelocity(FVector::ZeroVector);
    GetMesh()->SetAllPhysicsLinearVelocity(FVector::ZeroVector);
 
    // LOD 설정
    GetMesh()->SetForcedLodModel(1);
 
    // 물리 업데이트 빈도 조절
    GetCharacterMovement()->MaxSimulationIterations = 4;
}

네트워크 동기화 전략

 네트워크 게임에서 캐릭터의 움직임을 동기화하는 것은 중요합니다.

void AMyCharacter::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
 
    DOREPLIFETIME_CONDITION(AMyCharacter, ReplicatedMovement, COND_SimulatedOnly);
}
 
void AMyCharacter::OnRep_ReplicatedMovement()
{
    Super::OnRep_ReplicatedMovement();
 
    // 클라이언트에서 서버의 위치로 부드럽게 보간
    if (!IsLocallyControlled())
    {
        SmoothCorrection();
    }
}
 
void AMyCharacter::SmoothCorrection()
{
    // 위치 보간 로직 구현
}

일반적인 문제 상황과 해결 방법

  1. 캐릭터 끼임 현상
  • 해결 : 캐릭터 콜리전 설정 조정 및 언스턱 로직 구현
void AMyCharacter::CheckStuckAndResolve()
{
    FVector Start = GetActorLocation();
    FVector End = Start + FVector::UpVector * 100.0f;
    
    FHitResult HitResult;
    FCollisionQueryParams QueryParams;
    QueryParams.AddIgnoredActor(this);
 
    if (GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Visibility, QueryParams))
    {
        // 캐릭터가 끼인 경우 위로 밀어올림
        SetActorLocation(GetActorLocation() + FVector::UpVector * 10.0f);
    }
}
  1. 네트워크 지연으로 인한 움직임 불일치
  • 해결 : 클라이언트 사이드 예측 및 서버 권위 모델 구현
void AMyCharacter::ClientSidePrediction()
{
    if (IsLocallyControlled())
    {
        FVector PredictedLocation = GetActorLocation() + GetVelocity() * GetWorld()->GetDeltaSeconds();
        SetActorLocation(PredictedLocation);
    }
}
  1. 불안정한 물리 시뮬레이션
  • 해결 : 물리 서브스테핑 활용 및 시뮬레이션 파라미터 조정
void AMyCharacter::StabilizePhysics()
{
    UWorld* World = GetWorld();
    if (World)
    {
        World->GetWorldSettings()->SetSubstepping(true);
        World->GetWorldSettings()->MaxSubstepDeltaTime = 0.016667f;
        World->GetWorldSettings()->MaxSubsteps = 6;
    }
}

Best Practices

  1. 캐릭터 움직임 파라미터 미세 조정
  • 지속적인 테스트와 피드백을 통해 최적의 움직임 파라미터를 찾습니다.
  1. 레이어드 애니메이션 시스템 활용
  • 다양한 애니메이션을 자연스럽게 블렌딩하여 부드러운 움직임을 구현합니다.
  1. 물리 기반 IK (Inverse Kinematics) 적용
  • 캐릭터의 발이 지형에 자연스럽게 닿도록 IK를 구현합니다.
  1. 성능과 정확도의 균형
  • 필요에 따라 물리 시뮬레이션의 복잡도를 조절하여 성능과 정확도의 균형을 맞춥니다.
  1. 디버깅 도구 활용
  • 언리얼 엔진의 디버깅 도구를 활용하여 물리 기반 움직임을 시각화하고 분석합니다.

 물리 기반 캐릭터 제어는 게임에 현실감 있는 움직임을 제공하지만, 구현과 최적화에 주의가 필요합니다. Character 클래스와 CharacterMovementComponent를 효과적으로 활용하고, 물리 시뮬레이션을 적절히 조정함으로써 반응성 있고 안정적인 캐릭터 컨트롤을 구현할 수 있습니다.

 네트워크 게임에서는 클라이언트 사이드 예측과 서버 권위 모델을 적절히 조합하여 지연을 최소화하고 일관된 경험을 제공하는 것이 중요합니다. 또한, 다양한 환경과의 상호작용을 구현하여 더욱 풍부한 게임플레이를 만들 수 있습니다.

 최종적으로, 지속적인 테스트와 최적화를 통해 부드럽고 반응성 있는 캐릭터 제어 시스템을 구축하는 것이 목표입니다. 이는 플레이어에게 몰입감 있는 게임 경험을 제공하는 데 핵심적인 역할을 합니다.