프로젝트를 생성할때 자동으로 PangaeaPlayerController 클래스가 생성한다.
TopDownCharacter는 EnhancementInput을 위해 2개의InputAction과 IMC가 마련되어 있다.
SetupInputComponent()에서 Binding되어 있다. Attack는 나중에 만들어 추가할것이다.
void APangaeaPlayerController::SetupInputComponent()
{
// set up gameplay key bindings
Super::SetupInputComponent();
InputComponent->BindAction("SetDestination", IE_Pressed, this, &APangaeaPlayerController::OnSetDestinationPressed);
InputComponent->BindAction("SetDestination", IE_Released, this, &APangaeaPlayerController::OnSetDestinationReleased);
InputComponent->BindAction("Attack", IE_Pressed, this, &APangaeaPlayerController::OnAttackPressed);
// support touch devices
InputComponent->BindTouch(EInputEvent::IE_Pressed, this, &APangaeaPlayerController::OnTouchPressed);
InputComponent->BindTouch(EInputEvent::IE_Released, this, &APangaeaPlayerController::OnTouchReleased);
}
처음 플레이해보셨겠지만 좌클릭을 하면 그곳으로 이동한다.
한번 열어 로직을 보자.
PlayerTick()은 bInputPressd가 true일때 HitLocation을 향해 움직인다.
void APangaeaPlayerController::PlayerTick(float DeltaTime)
{
Super::PlayerTick(DeltaTime);
if(bInputPressed)
{
FollowTime += DeltaTime;
// Look for the touch location
FVector HitLocation = FVector::ZeroVector;
FHitResult Hit;
if(bIsTouch)
{
GetHitResultUnderFinger(ETouchIndex::Touch1, ECC_Visibility, true, Hit);
}
else
{
GetHitResultUnderCursor(ECC_Visibility, true, Hit);
}
HitLocation = Hit.Location;
// Direct the Pawn towards that location
APawn* const MyPawn = GetPawn();
if(MyPawn)
{
FVector WorldDirection = (HitLocation - MyPawn->GetActorLocation()).GetSafeNormal();
MyPawn->AddMovementInput(WorldDirection, 1.f, false);
}
}
else
{
FollowTime = 0.f;
}
}
bInputPressed변수는 좌클릭 Press, Release InputAction과 biding되는 함수안에서 컨트롤되는데 처음 Press될경우 true로 되어 AddMovementInput()에 의해 Tick()에서 움직이다가 릴리스되면 bInputPressed 는 false가 되어 작동하지 않지만 SimpleMoveToLocation()로직에 의해 자동으로 움직이게 된다.
SimpleMoveToLocation()은
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, HitLocation); 이 코드는 캐릭터를 HitLocation으로 이동하게 해주는 역할을 합니다.
좀 더 자세히 설명하면:
- UAIBlueprintHelperLibrary: 언리얼 엔진에서 AI 관련 기능을 제공하는 유틸리티 클래스입니다. 이름에서 알 수 있듯이, 블루프린트에서도 쉽게 사용할 수 있도록 헬퍼 함수들을 모아놓은 라이브러리입니다.
- SimpleMoveToLocation: 이 클래스 내부에 정의된 함수 중 하나로, 특정 액터(여기서는 this로 지정된 PlayerController에 의해 제어되는 캐릭터)를 지정된 위치(HitLocation)로 간단하게 이동시키는 기능을 수행합니다. 일반적으로 내비게이션 메시(NavMesh)를 사용하여 해당 위치까지의 경로를 찾아 이동합니다.
- this: 이 코드가 실행되는 APangaeaPlayerController 인스턴스를 의미합니다. SimpleMoveToLocation 함수는 일반적으로 Controller를 인자로 받아서 해당 Controller가 제어하는 Pawn(캐릭터)을 이동시킵니다.
- HitLocation: 마우스 클릭으로 얻어낸 월드 상의 위치입니다. 즉, 플레이어가 클릭한 지점입니다.
따라서 이 라인은 "현재 플레이어 컨트롤러가 제어하는 캐릭터를 마우스로 클릭한 HitLocation으로 이동시켜라"는 명령입니다.
void APangaeaPlayerController::OnSetDestinationPressed()
{
// We flag that the input is being pressed
bInputPressed = true;
// Just in case the character was moving because of a previous short press we stop it
StopMovement();
}
void APangaeaPlayerController::OnSetDestinationReleased()
{
// Player is no longer pressing the input
bInputPressed = false;
// If it was a short press
if(FollowTime <= ShortPressThreshold)
{
// We look for the location in the world where the player has pressed the input
FVector HitLocation = FVector::ZeroVector;
FHitResult Hit;
GetHitResultUnderCursor(ECC_Visibility, true, Hit);
HitLocation = Hit.Location;
// We move there and spawn some particles
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, HitLocation);
UNiagaraFunctionLibrary::SpawnSystemAtLocation(this, FXCursor, HitLocation, FRotator::ZeroRotator, FVector(1.f, 1.f, 1.f), true, true, ENCPoolMethod::None, true);
}
}
처음에 SimpleMoveTo()와 AddMovementInput()이 충돌하는줄알고 제미나이한테 물어봤더니 bInputPressed에 의해 충돌을 피한다고 알켜주네요
네, 정확히 지적하셨습니다! PlayerTick() 함수 내의 AddMovementInput 로직과 OnSetDestinationReleased() 함수 내의 SimpleMoveToLocation 로직은 움직임 충돌 가능성이 있습니다.
언리얼 엔진의 기본적인 AI 이동 시스템과 플레이어 입력 기반 이동 시스템이 동시에 작동할 때 발생할 수 있는 시나리오를 이해하는 것이 중요합니다.
충돌 가능성 분석
- SimpleMoveToLocation (AI 이동):
- 이 함수는 언리얼 엔진의 **내비게이션 시스템(AI Navigation)**을 사용하여 캐릭터를 특정 목적지(HitLocation)로 이동시킵니다.
- 내부적으로는 AI 컨트롤러가 내비게이션 메시를 기반으로 경로를 계산하고, 캐릭터의 CharacterMovementComponent를 통해 이동을 제어합니다.
- 한번 호출되면 캐릭터가 목적지에 도달하거나, 다른 이동 명령이 들어올 때까지 자체적으로 이동을 시도합니다.
- AddMovementInput (플레이어 입력 기반 이동):
- 이 함수는 PlayerTick 함수 내에서 bInputPressed가 true인 동안 매 프레임(DeltaTime마다) 호출됩니다.
- 이 함수는 특정 방향(WorldDirection)으로 **이동 입력(Movement Input)**을 추가합니다.
- 캐릭터의 CharacterMovementComponent는 이러한 입력들을 매 프레임 수집하여 실제 캐릭터의 물리적인 움직임을 계산합니다.
충돌 시나리오 및 해결 방안
현재 로직에서 발생할 수 있는 충돌 시나리오:
- 짧게 클릭 (Short Press):
- OnSetDestinationReleased()가 호출되어 SimpleMoveToLocation이 실행됩니다. 캐릭터는 목적지로 이동하기 시작합니다.
- 하지만 OnSetDestinationReleased()가 호출되기 직전까지 (bInputPressed가 true였던 동안) PlayerTick()에서는 계속 AddMovementInput이 호출되고 있었습니다.
- 이 경우, SimpleMoveToLocation이 이동 명령을 내리면 기존에 AddMovementInput으로 들어갔던 (마우스를 누르고 있던 방향으로의) 입력은 SimpleMoveToLocation이 내부적으로 StopMovement()와 유사한 방식으로 이전 AI 이동 명령을 취소하기 때문에 크게 문제가 되지 않을 수 있습니다. AI 이동이 우선권을 가집니다.
- 길게 누르기 (Long Press) 후 손 떼기:
- OnSetDestinationReleased()가 호출됩니다. 하지만 FollowTime > ShortPressThreshold이므로 if(FollowTime <= ShortPressThreshold) 조건이 false가 되어 SimpleMoveToLocation은 호출되지 않습니다.
- 이 상황에서는 PlayerTick() 내의 AddMovementInput 로직만 유효하게 작동했습니다. 즉, 마우스를 누르고 있는 동안에는 캐릭터가 마우스 커서 방향으로 계속 움직였습니다. 손을 떼면 bInputPressed가 false가 되므로 AddMovementInput 호출이 멈추고 캐릭터도 멈춥니다.
- 이 경우는 충돌이 없습니다. 애초에 SimpleMoveToLocation이 호출되지 않으니까요.
- 가장 큰 충돌 가능성: SimpleMoveToLocation으로 이동 중인데 마우스를 다시 누르고 있는 경우
- 캐릭터가 SimpleMoveToLocation으로 특정 지점으로 이동하고 있습니다.
- 이동 중에 플레이어가 마우스 왼쪽 버튼을 다시 길게 누르기 시작합니다 (bInputPressed가 true가 됨).
- PlayerTick() 함수에서 AddMovementInput이 다시 호출되기 시작하고, SimpleMoveToLocation에 의한 AI 이동과 AddMovementInput에 의한 수동 이동 입력이 동시에 캐릭터의 CharacterMovementComponent에 적용되려고 시도할 수 있습니다.
- 언리얼 엔진의 CharacterMovementComponent는 이러한 입력을 처리하는 방식이 있지만, 일반적으로 AI 이동(AI Controller가 제어하는)이 플레이어 입력보다 우선권을 가지거나, 플레이어 입력이 AI 이동을 "방해"하여 AI 이동이 비정상적으로 보이거나 멈추는 상황이 발생할 수 있습니다. 예를 들어, SimpleMoveToLocation으로 경로를 따라가고 있는데 AddMovementInput이 다른 방향으로 힘을 가하면 캐릭터가 경로에서 벗어나거나, AI 이동이 강제로 취소될 수도 있습니다.
일반적인 해결 방법
이러한 충돌을 방지하기 위한 일반적인 접근 방식은 다음과 같습니다.
- AI 이동과 수동 이동 간의 명확한 상태 전이:
- AI 이동(예: SimpleMoveToLocation)이 활성화되면, 플레이어의 직접적인 입력(예: AddMovementInput을 통한 이동)을 비활성화합니다.
- AI 이동이 완료되거나, 플레이어가 새로운 수동 이동 입력을 시작하면 AI 이동을 취소하고 수동 이동을 활성화합니다.
- 이 TopDown 템플릿의 경우, StopMovement()가 OnSetDestinationPressed()에서 호출되는 것이 이 의도에 부합합니다. StopMovement()는 현재 진행 중인 AI 이동을 중지시킵니다.
- AI Controller와 Player Controller의 역할 분리 및 연동:
- 실제 캐릭터 이동은 보통 AController (여기서는 APangaeaPlayerController)가 담당하지만, 내부적으로 CharacterMovementComponent가 이를 처리합니다.
- SimpleMoveToLocation은 AI Controller의 기능을 활용하는 것이므로, 플레이어가 직접 이동하는 동안에는 AI Controller의 이동 로직을 잠시 멈추거나, 플레이어 입력이 들어오면 AI 이동을 명시적으로 중단하는 로직이 필요합니다.
템플릿의 로직 해석
제공된 템플릿 코드에서는 다음과 같은 의도로 보입니다:
- OnSetDestinationPressed(): 마우스를 누르는 순간 bInputPressed = true로 설정하고 StopMovement()를 호출합니다. 이 StopMovement()가 핵심입니다. 이 함수는 현재 진행 중인 AI 이동(예: 이전에 SimpleMoveToLocation으로 시작된 이동)을 즉시 중단시킵니다. 따라서 플레이어가 마우스를 누르기 시작하면, 이전의 AI 이동은 멈추고 PlayerTick에 의한 AddMovementInput이 우선적으로 작동하게 됩니다.
- PlayerTick(): bInputPressed가 true인 동안 (즉, 마우스를 누르고 있는 동안) 캐릭터를 마우스 커서 방향으로 계속 움직입니다. 이것은 "드래그"해서 이동하는 느낌을 줍니다.
- OnSetDestinationReleased(): 마우스 버튼을 놓는 순간 bInputPressed = false가 됩니다.
- 만약 FollowTime <= ShortPressThreshold (짧게 누름)라면, SimpleMoveToLocation을 호출하여 해당 지점으로 AI 이동을 시작합니다.
- 만약 FollowTime > ShortPressThreshold (길게 누름)라면, SimpleMoveToLocation은 호출되지 않습니다. 이 경우, PlayerTick에 의해 AddMovementInput으로 움직이다가 손을 떼면서 멈추게 됩니다.
결론적으로, 이 템플릿의 로직은 충돌을 최소화하기 위한 설계가 되어 있습니다.
- 마우스를 누르는 동안: StopMovement()로 기존 AI 이동을 중단시키고, AddMovementInput으로 플레이어 직접 제어를 합니다.
- 마우스를 떼는 순간: FollowTime에 따라 AI 이동을 시킬지 말지 결정합니다. AI 이동을 시키면 AI 이동이 우선권을 가집니다.
따라서 완전히 동시에 제어되는 것이 아니라, 마우스 상태(bInputPressed, FollowTime)에 따라 어떤 이동 방식이 활성화될지 전환하는 방식으로 설계되어 있습니다. 이 방식은 Top-down 게임에서 흔히 사용되는 제어 방식 중 하나입니다.
'언리얼러닝 > C++스크립트게임개발' 카테고리의 다른 글
EnhancedInput vs LegacyInput, Input Error (0) | 2025.05.27 |
---|---|
7장 CanAttack() Attack() 함수 구현하기 (0) | 2025.05.27 |
게임 액터 생성하기 (0) | 2025.05.16 |
언리얼 게임플레이 프레임워크 클래스 학습하기 (0) | 2025.05.16 |
게임프레임워크 링크 (0) | 2025.05.14 |