Trello에 진행상황을 기록하려고 한다.
https://trello.com/b/L76nrwoC/satellite
캐릭터는 위의 캐릭터로 정하였는데,
그 이유는 AI가 적용된 NPC와의 전투를 같이하는 요소를
구현하기 재밌어보여서이다.
다른 에셋들과는 다르게 위처럼 드론 에셋이 추가로 달려있다.
해당 NPC를 플레이어 주변 일정 반경을 돌아다니게 할 것이므로
인공위성이라는 느낌을 받았고,
추가로 인공위성에서 의문의 이유로 불시착한 행성을 배경으로
진상을 알아내는 게임으로 만들고자 한다.
아무래도 피드백없이 혼자 쭉 작성하다보면 어디서 틀렸는지
모르는 경우도 생기므로, Git으로 형상관리를 하려고 한다.
미리 UE 프로젝트를 만들고 - 해당 폴더명과 똑같은 이름의 레퍼지토리를 생성 - 그러면 해당 프로젝트가 관리가 된다.
주의할 점은, Git Ignore을 UnrealEngine으로 해야하는 것이고,
추가로 Git Ignore파일에 Content폴더를 적어넣었다.
적지 않으면 에셋하나 추가할때마다 억겁의 시간을 커밋해야만 한다
공간도 많이들게 되고, LFS 유료결제를 추천해주게 된다
협업에서도 보통 에셋과 바이너리파일을 함께 메일로 보낸다고 한다던데
당연히 이 방법이 더 효율적일 것 같다.
결론은 config와 소스코드만 형상관리를 한다
우주선은 아래 사이트에서 FBX파일을 다운받아 적용시켰다.
https://www.turbosquid.com/ko/
캐릭터는 언리얼 마켓에서 가져왔다.
맵도 가져오긴 했지만, 제작하면 로딩에 매번 시간을 소모하게 되므로,
우선 캐릭터의 기능과 보스의 전투 설정 이후
제작하는 것을 고려해보려고 한다.
자동생성된 SateliteGameMode를 통해
자동생성된 SateliteCharacter를 기본 폰으로 지정하고,
Mesh를 입히고, 생성해보니 위와 같았다.
에셋을 불러왔고,
SetRelativeLocationAndRotation을 설정한다.
Input의 설정
UE5.1에서 추가된 기능으로 보이는데,
InputComponent를 Cast하여 Enhanced Input System로 사용하는 코드가 존재했다.
해당 부분은 지우고, 일반 InputComponent코드를 사용하도록 한다.
사실 기존의 starter pack에 구현되어있는 내용인데,
딱 원하는 조작 방식이라 조그만 설정 외에 건드리지 않았다.
이제 점프, 이동은 되는 캐릭터가 생성되었다.
AnimInstance를 생성하고
BP를 생성하고 AnimInstance를 상속받도록 하고
Graph를 생성하고, Runs라는 기존의 애니메이션을 넣었다.
해당 애니메이션은 방향과 Speed값을 받아서
애니메이션 유형이 달라지도록 blend되었는데,
만들고보니 float값으로 방향을 받았다.
해당 노드를 사용하려면
우선 a를 누르면 그냥 왼쪽을 향해버리는 이동방식을 수정해야할 것이다.
즉, a를 눌러도 항상 캐릭터의 왼쪽으로만 가도록 수정.
친절히 주석이 달려있어서, 바로 해당 설정을 false로 만들었다.
이제 특정 키를 눌러도 해당 방향으로 회전하지 않는다.
그렇다면 blend애니메이션 노드에 들어갈 angle이란 float값을 구하기가 쉬워진다.
간단하게
+90도가 캐릭터기준 우측으로 이동하고,
-90도는 캐릭터기준 좌측으로 이동하겠고,
+-180이 뒤로 이동하므로, 해당 입력에 맞춰서 설정하면 될 것이다.
float PawnDirection이라는 변수를 만들고,
입력에 따라 값을 주도록 만들었다.
AnimInstance에선 해당 값을 Get하여 가져온다.
이제 연결할 수 있게 되었다.
왼쪽 오른쪽은 정상동작하지만,
앞 뒤의 경우에는 둘 다 오른쪽의 움직임을 하였다.
왜일까?
눌렀을 때만 동작하는게 아니다
기본값 90이 계속 뜬다
앞 뒤를 했을 때도 90으로 뜬다.
왼쪽을 했을 경우만 -90
보니까 뭔가 UD다음 무조건 RL이 눌린다.
그래서 RL의 결과가 무조건 Direction의 값이 되는 것이다.
왜?
그 이유는 Axis는 기본값이 0으로 지속해서 반환하고 있기 때문에,
Action을 사용하여 눌림, 떼짐을 구현해야한다.
하지만 Axis를 사용하고 싶었고,
Direction을 찾는 방법을 구현해냈다.
https://wecandev.tistory.com/160
처음에 찾아본 각도 구현방식
해당 방식을 사용하면, 앞 우 뒤 이동은 가능하다.
하지만 -90값인 왼쪽은 되지 않는다.
그 이유는,
아크 코사인 그래프가 0이상의 양수가 나오기 때문이다.
float ASatelliteCharacter::GetDirection()
{
// 1 이동하는 방향의 벡터 구하기
FVector Velocity = GetMovementComponent()->Velocity;
FVector MoveDirection = Velocity.GetSafeNormal();
// 2 캐릭터가 보고있는 정면 방향 구하기
FVector ForwardVector = GetActorForwardVector();
ForwardVector.Normalize();
// 3 각도 구하기
float AngleRadian = FMath::Atan2(MoveDirection.Y, MoveDirection.X) - FMath::Atan2(ForwardVector.Y, ForwardVector.X);
float AngleDegree = FMath::RadiansToDegrees(AngleRadian);
if (AngleDegree > 180.0f)
{
AngleDegree -= 360.0f;
}
return AngleDegree;
}
그래서 Atan2라는 요소를 사용하여, 방향을 구할 수 있었다.
정상동작하는 모습.
Idle State를 추가한다.
2보다 작다면 Idle을 실행, 반대면 Move를 실행
2로 설정한 이유는, 0으로 하면 소수의 비교가 오류가 나는 경우가 존재해서,
적절히 작은값으로 둔 것이다.
Jump관련 State는 2개 두었다.
노란색으로 표시한 트랜잭션은 IsInAir값에 따라 변화하고,
JumpEnd->Move 트랜잭션은 위처럼 자동으로 끝내도록 설정하였다.
정상적으로 동작하고,
추가로 점프에서 구현하고 싶은 기능이 있는데
Jump동안은 자유롭게 입력을 받지만,
Jump후 착지하는 애니메이션 잠깐동안은 입력을 제한하고 싶다.
그리고 Jumping이라는 State를 이득우 프로젝트에선 만들었는데,
애니메이션의 부자연스러움으로 뺐고,
추가할 경우, Raytracing으로 발 밑에 땅이 존재하지 않으면(플래그),
실행하도록 나중에 State를 추가하는 것을 고려해봐야겠다.
ASatelliteCharacter* Character = Cast<ASatelliteCharacter>(Pawn);
if (Character)
{
bIsInAir = Character->GetMovementComponent()->IsFalling();
CurrentPawnDirection = Character->GetDirection();
//D2_LOG(Warning, TEXT("Angle is : %f"), CurrentPawnDirection);
/// 점프후 착지시, 멈춤 구현
// bIsInAir 값이 true에서 false가 될 때 입력을 비활성화하도록 처리
if (bWasInAir && !bIsInAir)
{
D2_LOG(Error, TEXT("Jump Landing"));
//Timer호출
auto SPController = Cast<ASPController>(Character->GetOwner());
if (SPController)
{
// 먼저 입력을 비활성화
Character->DisableInput(SPController);
// Timer를 통해 해당 초 뒤에 enable로 바꿔놓음
GetWorld()->GetTimerManager().SetTimer(
InputDisableTimerHandle,
FTimerDelegate::CreateLambda(
[this]()->void {
APawn* Pawn = TryGetPawnOwner();
ASatelliteCharacter* Character = Cast<ASatelliteCharacter>(Pawn);
auto SPController = Cast<ASPController>(Character->GetOwner());
Character->EnableInput(SPController);
}), 0.18f, false);
// Timer해제
InputDisableTimerHandle.Invalidate();
}
}
bWasInAir = bIsInAir;
}
착지 후 멈추는 코드는 위와 같다.
먼저
점프 후 착지하는 타이밍을 구하기 위해서,
bool 변수를 2개 사용하였다.
bWasInAir, bIsInAir
참고로 구현한 곳이 NativeUpdateAnimation 내부라서,
마지막줄인 bWasInAir = bIsInAir;에 의해 Tick마다 값을 제공받게 된다.
로직은 해당 최신화 바로 전에 구현을 해두었다.
따라서 if (bWasInAir && !bIsInAir) 를 하면,
bWasInAir가 true이고 bIsInAir가 false인 상태
(= 직전에 공중에 떠있었지만, 현재 땅에 닿은 순간적인 상태)
해당 상태를 포착하여 if문 내의 로직이 실행되게 된다.
내부 로직은 간단히 먼저 입력을 바로 비활성화 한 후,
0.18초 있다가 활성화시키는 것이다.
착지 후 어떤 입력도 받지 못한다.
(이 부분이 조금 아쉽다. 마우스만 입력받게 하고 싶은데..)
착지시 멈추고자 설정했던 이유는,
착지 애니메이션의 딜레이 때문이었다.
https://cafe.naver.com/unrealenginekr/79652
하지만 UE 공식 카페에서 질문한 결과,
자연스럽게 착지->이동하는 방법을 구현하는 실마리에 대해서 알게되었고,
RootMotion, Action, additive setting 등의 방법중,
additive setting 방법을 사용하여 해결해보기로 하였다.
Additive Setting은 하나의 애니메이션의 끝에
다른 애니메이션을 Blending하는 옵션으로,
현재 보이는 JumpLand 애니메이션에
Additive로 Idle애니메이션을 넣고, 맨 아래 Ref Frame으로
18을 주었다.
(JumpLand가 끝나는 부분)
즉, JumpLand가 끝나는 부분에서 Idle로 자연스레 Blend되는 모션이 나오게 된다.
그래서 해당 애니메이션을 그대로 애니메이션 그래프에 넣어서
사용해도 되겠지만,
몽타주를 만들어 사용해보려고 한다.
그 이유는 아래와 같다.
그러니까 공중에서 착지하는 경우가 게임에선 많이 생기는 편이고,
그러므로 특정 상황에서 명시적으로 호출할 경우를 대비해서 몽타주로 생성해둔다고 이해했다.
위의 코드는 싹 삭제한다.
static ConstructorHelpers::FObjectFinder<UAnimMontage> JUMPLAND_MONTAGE(TEXT("/Script/Engine.AnimMontage'/Game/Animation/BP_JumpLandMont.BP_JumpLandMont'"));
if (JUMPLAND_MONTAGE.Succeeded())
{
JumpLandMontage = JUMPLAND_MONTAGE.Object;
}
1) AnimInstance에 Montage BP에셋을 가져온다.
// IsInAir는 Movement에서 가져올 수 있다.
// GetDirection으로 angle을 가져온다.
ASatelliteCharacter* Character = Cast<ASatelliteCharacter>(Pawn);
if (Character)
{
bIsInAir = Character->GetMovementComponent()->IsFalling();
CurrentPawnDirection = Character->GetDirection();
//D2_LOG(Warning, TEXT("Angle is : %f"), CurrentPawnDirection);
/// 점프후 착지시, 멈춤 구현
// bIsInAir 값이 true에서 false가 될 때 입력을 비활성화하도록 처리
if (bWasInAir && !bIsInAir)
{
D2_LOG(Error, TEXT("Jump Landing"));
bStartMontage = true;
PlayJumpLandMontage();
}
bWasInAir = bIsInAir;
}
2) NativeUpdateAnimation함수의 내용을 수정했다.
3) 위처럼 Montage를 실행하는 함수를 따로 두었다.
여기서 bStartMontage라는 함수가 사용되는데,
AnimGraph에선 위처럼 사용된다.
해석하면, 공중에 떠있지 않고, StartMontage도 false인 상태를 의미하는데,
JumpStart에서 Move로 돌아가는 경우에 사용되는 코드이다.
(Move -> JumpStart는 단지 IsInAir가 true)
그러니까 Montage관련 플래그이다.
Montage를 실행완료했고, 땅에도 착지 했다면, 다시 Move행동을 해라 라는 말이다.
로직판정에 의해 착지가 되어야만 Montage가 실행되고,
Montage가 실행되면, Idle과 블랜드된 착지 애니메이션이 실행된다.
실행되고 플래그 값이 바뀌며, Move 애니메이션 노드가 실행된다.