1. 몬스터 6마리 메시 선정 및 제작
몬스터는 포식자와 먹이, 두 부류로 분류하였습니다.
포식자: 코뿔소, 늑대, 사자
먹이: 얼룩말, 사슴, 돼지
포식자와 먹이에 따라 AI 컨트롤러를 따로 만들어서 포식자는 플레이어를 공격하고 먹이는 플레이어를 만나면 도망치도록 만들 것입니다.
2. 몬스터 애니메이션 제작
몬스터도 플레이어의 애니메이션과 마찬가지로 블렌드 포즈를 이용해 애니메이션을 설정하였습니다.
void UAnimalAnim::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
// 자신이 가지고 있는 몬스터 객체를 가져온다
AAnimal* pOwner = Cast<AAnimal>(TryGetPawnOwner());
if (IsValid(pOwner))
{
// 몬스터 객체의 움직임을 얻어온다
UCharacterMovementComponent* pMovement = pOwner->GetCharacterMovement();
if (IsValid(pMovement))
{
// 몬스터가 땅 위에 있는지 판단한다
if (pMovement->IsMovingOnGround())
{
// pMovement의 사이즈를 통해 몬스터가 움직이고 있는지 판단한다
if (pMovement->Velocity.Size() > 0.f)
{
AnimType = (uint8)EAnimalAnim::Run;
}
else
{
}
}
}
}
}
NativeUpdateAnimation 함수를 이용해 매초마다 몬스터의 움직임을 감지해서 움직임이 발견되면 Run을 적용하여 몬스터가 달리는 모션을 취할 수 있도록 하였습니다.
3. C++을 이용한 메시와 애니메이션 적용
ALion::ALion()
{
PrimaryActorTick.bCanEverTick = true;
// 스켈레탈 메시를 얻어온다
static ConstructorHelpers::FObjectFinder<USkeletalMesh> MeshAsset(TEXT("SkeletalMesh'/Game/AfricanAnimalsPack/LionAndLioness/Meshes/SK_Lion_LOD0.SK_Lion_LOD0'"));
// 스켈레탈 메시가 유효할 경우 메시 적용
if (MeshAsset.Succeeded())
GetMesh()->SetSkeletalMesh(MeshAsset.Object);
// 애니메이션 블루프린트 클래스를 얻어온다
static ConstructorHelpers::FClassFinder<ULionAnim> AnimAsset(TEXT("AnimBlueprint'/Game/Animal/BPLionAnim.BPLionAnim_C'"));
// 블루프린트 클래스가 유효할 경우 적용
if (AnimAsset.Succeeded())
GetMesh()->SetAnimInstanceClass(AnimAsset.Class);
}
각 몬스터의 생성자에서 ConstructorHelpers를 이용해서 스켈레탈 메시를 적용하였습니다.
또한, 같은 방식으로 블루프린트로 제작해 놓은 애니메이션 애셋을 적용하였습니다.
4. 몬스터 스폰 시스템 제작
protected:
// 몬스터 타입에 대한 정보를 가지고 있는 변수
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
TSubclassOf<class AAnimal> SpawnType;
// 몬스터가 스폰하는데 걸리는 시간
UPROPERTY(EditAnywhere, BlueprintReadWrite, Meta = (AllowPrivateAccess = "true"))
float SpawnTime;
float SpawnDuration; // 시간 계산을 위한 변수
bool bSpawnEnable; // 스폰이 가능한지에 대한 변수
class AAnimal* SpawnAnimal; // 몬스터 객체를 저장하는 변수
몬스터들의 스폰 타입, 스폰 시간, 스폰 가능한지의 여부, 자신의 몬스터 객체에 대한 정보를 저장하는 변수들을 선언하고 여기서 스폰 타입은 UClass 정보를 저장하는 TSubclassOf를 사용해서 선언하였습니다.
ASpawnPoint::ASpawnPoint()
{
PrimaryActorTick.bCanEverTick = true;
bSpawnEnable = true;
SpawnTime = 1.f;
SpawnDuration = 0.f;
SpawnAnimal = nullptr;
}
생성자에서 선언된 변수들을 정의하였습니다.
처음 시작할때, 스폰이 가능해야 하기 때문에 bSpawnEnable = true로 설정
스폰 타임은 일단 1초로 설정
SpawnDuration은 스폰 타임까지 시간이 도달했는지 계산하는 변수이기 때문에 0으로 설정
SpawnAnimal은 일단 nullptr로 설정
void ASpawnPoint::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (IsValid(SpawnType))
{
if (bSpawnEnable)
{
SpawnDuration += DeltaTime;
// SpawnDuration과 SpawnTime을 비교해서 몬스터를 생성할지 판단한다
if (SpawnDuration >= SpawnTime)
{
SpawnDuration = 0.f; // 생성시에 0으로 초기화
bSpawnEnable = false; // 몬스터가 한마리만 생성되야 하므로 false로 초기화
// 몬스터 월드에 배치
FActorSpawnParameters params;
params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
SpawnAnimal = GetWorld()->SpawnActor<AAnimal>(SpawnType, GetActorLocation(), GetActorRotation(), params);
// 몬스터가 죽을시에 리스폰을 위해서 현재 스폰 포인트에 대한 정보를 몬스터 객체에게 전달한다
SpawnAnimal->SetSpawnPoint(this);
}
}
}
}
Tick 함수는 프로그램 실행시 매초마다 들어오는 함수입니다.
먼저, 스폰 타입의 유효성 검사를 하고 스폰이 가능한지의 여부를 체크한 뒤
스폰이 가능하다면 SpawnDuration 변수를 1초씩 증가시켜서 스폰 타임과 비교하였습니다.
SpawnDuration 변수가 스폰 타임에 도달했을 때, GetWorld()->SpawnActor 함수를 사용해서 자신이 가지고 있던 스폰 타입의 객체를 월드에 배치하고 더 이상 스폰되지 않게 하기 위해서 bSpawnEnable 변수 false로 초기화하였습니다.
마지막에 SetSpawnPoint 함수는 몬스터가 죽을 시에 리스폰을 하기 위해서 자신의 스폰 포인트 정보를 몬스터가 가지고 있게 하기 위해서 Animal 클래스에 선언한 함수입니다.
월드에 배치를 편하게 하기 위해서 제작한 스폰 포인트 클래스를 상속받는 블루프린트 클래스를 생성하였습니다.
그러면, 이렇게 블루프린트 클래스를 통해 월드에 배치되는 모습을 직접 확인할 수 있습니다.
그 다음에 블루프린트 클래스의 스폰 타입을 결정하면
설정된 몬스터들이 월드에 배치되는 것을 확인할 수 있습니다.
다음에 해야할 일
플레이어와 몬스터의 충돌 시스템 제작
'게임 개발 (언리얼 엔진)' 카테고리의 다른 글
UE4 생존게임 제작 - 6 (PreyAnimal AI Controller 및 Data Table 제작) (0) | 2020.11.04 |
---|---|
UE4 생존게임 제작 - 5 (PredatorAnimal AI Controller 제작) (0) | 2020.11.03 |
UE4 생존게임 제작 - 4 (랜드스케이프 제작) (0) | 2020.11.01 |
UE4 생존게임 제작 - 3 (충돌 시스템 제작) (0) | 2020.10.31 |
UE4 생존게임 제작 - 1 (캐릭터 제작) (1) | 2020.10.29 |