2023.05.08 ~ 2023.05.14 개발일지
도끼를 던지고 받는 것을 하다 보면, 가끔씩 도끼가 0,0,0 의 위치에 정확하게 들어오지 않는 문제들이 존재했다
이를 해결해주기 위해, 도끼를 잡은 이후, 한번 더 위치를 조정하는 작업을 더해주었다
CatchEnd 애니메이션에 돌입할때 실행될 CatchEnd 노티파이를 만들고, 그 안에서 실행할 델리게이트 OnCatchEnd
를 추가하였다
OnCatchEnd
는 람다식을 통해 바인딩 해주었다
AnimInstance->OnCatchEnd.BindLambda([this] { Axe->SetActorRelativeLocation(FVector::ZeroVector); });
저번주에 만든 움짤을 보면 시작과 동시에 화면이 빠르게 바뀌는 것을 볼 수 있다
확인 결과 시퀀서가 기본 카메라와 블랜딩 하는 과정에서 생긴 문제로서,
몽타주를 실행하면 bUseControllerRotationYaw
를 true가 되게 하였기 때문에 블랜딩하는 과정에서 서로 갈린 2가지 시점을
하나로 겹치게 해주는 작업이 필요했다
해결을 위해 bUseControllerRotationYaw = false
코드를 추가하고, 시퀀서가 끝난 이후 카메라가 캐릭터의 기본 위치에
존재하도록 코드를 수정했다
void AIFCharacter::Execute()
{
...
// Reset the camera to center and play sequence
bUseControllerRotationYaw = false;
Controller->SetControlRotation(Target->WarpPoint->GetComponentRotation());
ExecutionAssetData->Play();
}
수정된 모습은 다음과 같다
저번주에 처형 모션을 추가한 방식은 상당히 번거로운 방식을 가졌었다
캐릭터와 적에게 몽타주를 할당하는 것은 물론이고, 모션 별로 워프 포인트, 레벨 시퀀서, 시퀀서 플레이어 등을 추가해주어야 하고
이를 맞는 상황에 틀어야했기 때문에 추가로 만들기엔 상당히 불편한 방식이었다
이를 데이터 에셋을 통해 간편하게 추가할 수 있게 만들었다
ExecutionAssetData.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "ExecutionAssetData.generated.h"
/**
*
*/
UCLASS()
class INFINITEFIGHTER_API UExecutionAssetData : public UPrimaryDataAsset
{
GENERATED_BODY()
public:
UExecutionAssetData();
/* Create SequencePlayer for Sequence play */
void CreateSequencePlayer();
/* Function to play the levelSequencerPlayer so the character doesn't need to include header files */
void Play();
UPROPERTY(EditAnywhere, Category = Animation)
TObjectPtr<class UAnimMontage> AttackMontage;
UPROPERTY(EditAnywhere, Category = Animation)
TObjectPtr<class UAnimMontage> VictimMontage;
UPROPERTY(EditAnywhere, Category = Camera)
TObjectPtr<class ULevelSequence> LevelSequence;
UPROPERTY(EditAnywhere, Category = Camera)
TObjectPtr<class ULevelSequencePlayer> LevelSequencePlayer;
UPROPERTY(EditAnywhere, Category = Camera)
FVector WarpPoint;
};
캐릭터와 적이 사용할 몽타주, 실행될 레벨 시퀀서와 플레이어, 모션 워핑이 적용될 위치를 변수로 지정하고,
시퀀서 플레이어를 만드는 함수와 시퀀서를 플레이 하는 함수를 추가하였다
ExecutionAssetData.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "Character/ExecutionAssetData.h"
#include "LevelSequencePlayer.h"
#include "LevelSequence.h"
UExecutionAssetData::UExecutionAssetData()
{
WarpPoint = FVector::ZeroVector;
}
void UExecutionAssetData::CreateSequencePlayer()
{
if (LevelSequence)
{
FMovieSceneSequencePlaybackSettings Settings;
Settings.bDisableLookAtInput = true;
Settings.bDisableMovementInput = true;
Settings.bHideHud = true;
ALevelSequenceActor* SequenceActor;
LevelSequencePlayer = ULevelSequencePlayer::CreateLevelSequencePlayer(GWorld, LevelSequence, Settings, SequenceActor);
}
}
void UExecutionAssetData::Play()
{
LevelSequencePlayer->Play();
}
다음은 만들어둔 데이터 에셋을 통해 추가해주었다
만들어둔 데이터 에셋은 배열을 통해 추가하였고, Execute
함수가 실행될때 이를 랜덤으로 실행하도록 설정하였다
IFCharacter.cpp
void AIFCharacter::Execute()
{
if (Target != nullptr)
{
const int RandNum = FMath::RandRange(0, 2);
const auto& ExecutionAssetData = ExecutionArray[RandNum];
// Set Axe to Character's Back
Sheathe();
AnimInstance->SetAxeHolding(false);
AnimInstance->SetDrawState(false);
// Set MotionWarping position and warp
Target->WarpPoint->SetRelativeLocation(ExecutionAssetData->WarpPoint);
MotionWarpingComponent->AddOrUpdateWarpTargetFromTransform(TEXT("Target"), Target->WarpPoint->GetComponentTransform());
// Play the montage
AnimInstance->Montage_Play(ExecutionAssetData->AttackMontage);
Target->PlayMontage(ExecutionAssetData->VictimMontage);
// Reset the camera to center and play sequence
bUseControllerRotationYaw = false;
Controller->SetControlRotation(Target->WarpPoint->GetComponentRotation());
ExecutionAssetData->Play();
Target->SetCollisionDead();
}
}
추가 된 모션은 슬램과 태클이다
슬램 모션
태클 모션
도끼를 던질때와 돌아올때, collision을 설정해 적이 닿으면 상태의 변화를 주도록 해보았다
일단 현재는 눈에 잘 보이도록 Ragdoll상태로 두었지만, 게임을 발전시켜 나가며 데미지를 주는 방식으로 변경하고자 한다
방식은 OutHit의 BoneName이 존재할 경우 실행하도록 하였디
AIFAxe.cpp
void AIFAxe::LodgePosition(const FHitResult& InHit)
{
...
// when enemy is hitten
if (InHit.BoneName != NAME_None)
{
const auto& TargetPawn = Cast<AIFEnemy>(InHit.GetActor());
if (TargetPawn)
{
AttachToComponent(TargetPawn->GetMesh(), FAttachmentTransformRules::KeepWorldTransform, InHit.BoneName);
TargetPawn->SetCollisionDead();
TargetPawn->GetMesh()->SetCollisionProfileName(TEXT("Ragdoll"));
TargetPawn->GetMesh()->SetSimulatePhysics(true);
TargetPawn->GetMesh()->AddImpulseAtLocation((InHit.GetActor()->GetActorLocation() - CameraLocation).GetSafeNormal() * 20000,
TargetPawn->GetActorLocation(), InHit.BoneName);
}
};
}
...
void AIFAxe::UpdateReturnLocation(float InSpeed)
{
...
FHitResult OutHit;
bool bResult = GetWorld()->SweepSingleByChannel
(
OutHit,
GetActorLocation(),
ReturnLocation,
FQuat::Identity,
ECollisionChannel::ECC_Visibility,
FCollisionShape::MakeSphere(25.0f)
);
if (bResult && OutHit.BoneName != NAME_None)
{
const auto& TargetPawn = Cast<AIFEnemy>(OutHit.GetActor());
if (TargetPawn)
{
TargetPawn->SetCollisionDead();
TargetPawn->GetMesh()->SetCollisionProfileName(TEXT("Ragdoll"));
TargetPawn->GetMesh()->SetSimulatePhysics(true);
TargetPawn->GetMesh()->AddImpulseAtLocation((OutHit.GetActor()->GetActorLocation() - ReturnLocation).GetSafeNormal() * 20000,
TargetPawn->GetActorLocation(), OutHit.BoneName);
}
};
}
마지막으로 판정이 잘 되는지 확인해보자