Unreal Engine/GCC Class (5.5.4)

Unreal Engine 5 배워보기 - 32일차

보별 2025. 5. 8. 15:10

 

컴퓨터 및 게임 관련 전공인이 아닌 문외한 일반인이 언리얼 엔진을 배우면서 정리한 내용입니다.
그날그날 배웠던 내용을 제가 나중에 보기 위해 정리한 것으로, 지금 당장 보기에 부족한 점이 아주아주 많고 추후에 수정이 될 수도 있습니다.
오탈자 및 잘못 기재된 내용 지적, 부족한 내용 설명은 언제든지 환영입니다.

본글은 언리얼 엔진 5.5.4 버전 영문판을 기준으로 합니다.


----------------------------------------------------------------------------------------------------------------

 

 

32일차 학습 내용

- 코드 구조 구상

- 적 타입 별 분리 및 스켈레탈 메시 추가

- 적 타입 별 ABP 연결 및 크기 수정

- 적 AI Controller 연결

 

 

코드 구조 구상

이번 시간부터 지난 시간에 BP로 작성했던 것들을 다시 C++ 코드로 바꿔보는 작업을 해보도록 하겠다.

우선은 적을 구성할 코드들을 어떤 식으로 분리하여 구성할지 생각해보아야한다.

필자는 위 그림과 같이 구조를 나눠서 코드를 짜줄 생각이다.

기본적인 설정에서 적의 스켈레탈 메시, ABP, HP를 지정해주고, 이를 상속받는 "Normal" 이라는 코드를 만들어 여기에 적을 조종하는 AI 및 애니메이션 설정을 연결해준다.

그리고 이 모든 것이 연결된 "Normal" 에서 적 타입 별로 나눠서 설정을 해줄 것이다.

기본적인 설정 (Base) 에 모든 코드들을 몰아서 작성해도 되지만, 그렇게 되면 코드가 너무 길어져서 유지 보수 시에 불편해지기에 따로 관리하기 위해 만들었다.

 

적 타입 별 분리 및 스켈레탈 메시 추가

우선은 Character 형식의 C++ 클래스를 하나 생성해주고, 해당 C++ 이름을 'ZombieBase' 라고 명명해주었다.

그리고 아래와 같이 코드를 작성해준다.

ZombieBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UENUM(BlueprintType)
enum class EZombieType :uint8
{
    HumanZombie UMETA(DisplayName = "HumanZombie"),
    DogZombie UMETA(DisplayName = "DogZombie"),
    WingZombie UMETA(DisplayName = "WingZombie"),
};
 
UCLASS()
class MYPROJECT_API AZombieBase : public ACharacter
{
    GENERATED_BODY()
.
.
.
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 좀비 별 타입 지정
    EZombieType zombieType;
 
    TMap<EZombieType, USkeletalMesh*> ZombieMeshes;
 
private:
    void InitMeshes(); // 생성자에서 불러온 뒤 초기화
    void SetZombie(); // 좀비 별 스켈레탈 메시 설정
};
cs

ZombieBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "Components/SkeletalMeshComponent.h"
 
 
AZombieBase::AZombieBase()
{
     // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
 
    InitMeshes(); // 생성자에서 초기화 함수 추가
}
 
// Called when the game starts or when spawned
void AZombieBase::BeginPlay()
{
    Super::BeginPlay();
    
    SetZombie(); // BeginPlay에서 스켈레탈 메시 설정 
}
 
.
.
.
 
void AZombieBase::InitMeshes()
{
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> HumanZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/BookHeadMonster/Meshes/BookHeadMonster.BookHeadMonster'"));
 
    if (HumanZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::HumanZombie, HumanZombieMesh.Object);
    }
    
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> DogZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/Wood_Monster/CharacterParts/Meshes/SK_wood_giant_01_a.SK_wood_giant_01_a'"));
 
    if (DogZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::DogZombie, DogZombieMesh.Object);
    }
 
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> WingZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/ParagonSevarog/Characters/Heroes/Sevarog/Meshes/Sevarog.Sevarog'"));
 
    if (WingZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::WingZombie, WingZombieMesh.Object);
    }
}
 
void AZombieBase::SetZombie()
{
    if (zombieType == EZombieType::HumanZombie)
    {
        GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::HumanZombie]);
    }
    else if (zombieType == EZombieType::DogZombie)
    {
        GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::DogZombie]);
    }
    else if (zombieType == EZombieType::WingZombie)
    {
        GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::WingZombie]);
    }
    GetMesh()->SetRelativeLocationAndRotation(FVector(00-90), FRotator(0-900));
}
cs

위에서 생성한 코드인 'ZombieBase' 를 바탕으로 하는 BP를 하나 생성해준다.

BP를 더블클릭하면 위와 같이 타입을 지정할 수 있고 타입 지정 시 스켈레탈 메시는 표시가 되지 않는데, 스켈레탈 메시를 지정해주었을 뿐 생성해주지는 않았기 때문에 스켈레탈 메시는 표시가 안되는 것이 맞다.

위 코드를 보면 알 수 있는데, 생성자에서 적 개체 별 스켈레탈 메시를 지정해놓고, 각 적 개체 별로 스켈레톤 메시 출력은 BeginPlay 중에 생성되도록 코드를 짜주었다.

위에서 생성한 BP를 레벨에 배치 후에 각 타입 별로 설정해주고 게임을 실행해보면, 각 타입 별로 적용한 스켈레탈 메시가 정상적으로 출력되는 것을 확인할 수 있다.

 

적 타입 별 ABP 연결 및 크기 수정

이번엔 적 타입 별로 ABP를 지정해주면서 적들의 크기도 타입 별로 각각 다르게 설정해주도록 하겠다.

아래와 같이 코드를 작성해준다.

ZombieBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
UCLASS()
class MYPROJECT_API AZombieBase : public ACharacter
{
    GENERATED_BODY()
 
.
.
.
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 좀비 별 타입 지정
    EZombieType zombieType;
 
    TMap<EZombieType, USkeletalMesh*> ZombieMeshes;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZombieAnim"// 좀비 별 ABP 지정을 위해 
    TMap<EZombieType, TSubclassOf<UAnimInstance>> ZombieAnimBlueprints;
 
    FVector ZombieSize; // 좀비 별 사이즈 설정을 위해 추가
 
private:
    void InitMeshes();
    void InitAnimInstance(); // ABP도 생성자에서 불러온 뒤 초기화
    void SetZombie();
};
cs

ZombieBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
AZombieBase::AZombieBase()
{
     // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
 
    InitMeshes();
    InitAnimInstance(); // 추가
}
 
.
.
.
 
 
void AZombieBase::InitAnimInstance() // 좀비 별 ABP 부여
{
    static ConstructorHelpers::FClassFinder<UAnimInstance> HumanZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Zombie.ABP_Zombie_C"));
    if (HumanZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::HumanZombie, HumanZombieAnim.Class);
    }
 
    static ConstructorHelpers::FClassFinder<UAnimInstance> DogZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Zombie.ABP_Zombie_C"));
    if (DogZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::DogZombie, DogZombieAnim.Class);
    }
 
    static ConstructorHelpers::FClassFinder<UAnimInstance> WingZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Zombie.ABP_Zombie_C"));
    if (WingZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::WingZombie, WingZombieAnim.Class);
    }
}
 
void AZombieBase::SetZombie()
{
    if (zombieType == EZombieType::HumanZombie)
    {
        if (ZombieMeshes[EZombieType::HumanZombie] && ZombieAnimBlueprints[EZombieType::HumanZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::HumanZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::HumanZombie]); // 좀비 메시에 ABP 부여
        }
 
        ZombieSize = FVector(1.3f, 1.3f, 1.3f); // 좀비 별 크기 부여
    }
    else if (zombieType == EZombieType::DogZombie)
    {
        if (ZombieMeshes[EZombieType::DogZombie] && ZombieAnimBlueprints[EZombieType::DogZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::DogZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::DogZombie]);
        }
    
        ZombieSize = FVector(0.9f, 0.9f, 0.9f);
    }
    else if (zombieType == EZombieType::WingZombie)
    {
        if (ZombieMeshes[EZombieType::WingZombie] && ZombieAnimBlueprints[EZombieType::WingZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::WingZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::WingZombie]);
        }
 
        ZombieSize = FVector(1.5f, 1.5f, 1.5f);
    }
 
    GetMesh()->SetRelativeLocationAndRotation(FVector(00-90), FRotator(0-900));
 
    SetActorScale3D(ZombieSize); // 좀비 별 크기 설정
}
cs

코드를 작성 후 컴파일 한 후에 실행해보면, 아래 그림과 같이 적 타입 별로 애니메이션이 재생되고 크기도 다르게 적용되는 것을 확인할 수 있다.

*위 코드 작성 시 크래시가 날 확률이 높은데, 오타 외에도 여러 이유가 있음; 스켈레탈 메시 자체의 문제, 지정해주는 스켈레탈 메시 및 ABP가 비어있는 경우 등

*스켈레탈 메시나 ABP를 수정 후에 ZombieBase BP를 레벨에 재배치 해야, 수정한 스켈레탈 메시나 ABP가 적용되는 것을 확인 가능

 

적 AI Controller 연결

위에서 생성한 'ZombieBase' C++ 코드를 상속받는 새로운 C++ 클래스를 생성해주고, 이름은 "Normal Zombie" 로 명명해주었다.

지금 생성하는 C++ 클래스는 좀비의 AI와 애니메이션 설정을 관리하기 위해 생성하는 것이다.

생성한 'Normal Zombie.h' 쪽을 보면 'ZombieBase' 를 상속받은 것을 확인할 수 있다.

이번에는 적의 애니메이션을 관리할 C++ 클래스를 AnimInstance를 바탕으로 하여 생성해준다.

이어서 적을 조종할 AI를 담당하는 C++ 클래스를 AIController를 바탕으로 하여 생성해준다.

 'Normal Zombie' 를 바탕으로 하는 BP를 위에서 지정한 타입 별 적의 수 만큼 생성하고 이름을 지어준 뒤, 각 타입 별로 위 그림과 같이 적의 타입을 지정해준다.

레벨에 위에서 생성한 BP들을 배치하고 실행해보면 제대로 적용되는지 확인이 가능하다.

위 그림과 같이 Contents Drawer에서 우클릭을 한 뒤, "Aritficial Intelligence" 에 있는 "Behavior Tree" 와 "Blackboard" 를 각각 하나씩 생성해준다.

"Behavior Tree" 와 "Blackboard" 는 AI를 관리할 때 사용하기에, 'AI' 폴더를 새로 생성한 후 해당 폴더에 넣어주는 것을 추천한다.

그리고 위에서 AIController를 바탕으로 하여 생성한 적의 C++ 클래스를 부모로 하는 BP를 하나 생성해준다.

위에서 생성한 Behavior Tree를 실행하고, 우측에서 해당 Behavior Tree의 Blackboard가 방금 생성한 Blackboard로 적용돼있는지 확인해준다.

원래 Behavior Tree는 적의 타입 별로 각각 나눠서 생성해줘야하지만, 일단은 하나로 통일한 채로 진행하도록 하겠다.

그리고 아래와 같이 코드를 작성해준다.

NormalZombie.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
UCLASS()
class MYPROJECT_API ANormalZombie : public AZombieBase
{
    GENERATED_BODY()
 
public:
    ANormalZombie();
 
private:
    virtual void BeginPlay() override;
 
public:
    void InitAI();
};
cs

NormalZombie.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "NormalZombie.h"
#include "ZombieAnimInstance.h" // 추가
#include "AIController.h" // 추가
#include "ZombieAIController.h" // 추가
 
ANormalZombie::ANormalZombie()
{
    InitAI();
}
 
void ANormalZombie::BeginPlay()
{
    Super::BeginPlay();
}
 
void ANormalZombie::InitAI()
{
    AIControllerClass = AZombieAIController::StaticClass();
    AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
cs

적들에게 AI를 부여해주었고, 해당 AI는 적이 레벨에 생성될 때 부여되도록 코드를 짜준 것이다.

ZombieAIController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "CoreMinimal.h"
#include "AIController.h" // 추가
#include "BehaviorTree/BehaviorTree.h" // 추가
#include "BehaviorTree/BlackboardComponent.h" // 추가
#include "ZombieAIController.generated.h" // 추가
 
/**
 * 
 */
UCLASS()
class MYPROJECT_API AZombieAIController : public AAIController
{
    GENERATED_BODY()
 
public:
    AZombieAIController();
 
protected:
    virtual void BeginPlay() override;
 
public:
    UPROPERTY(EditAnywhere, Category = "AI")
    UBehaviorTree* BehaviorTree;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBlackboardData* BlackboardData;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBlackboardComponent* BlackboardComponent;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBehaviorTreeComponent* BehaviorTreeComponent;
};
cs

ZombieAIController.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
AZombieAIController::AZombieAIController()
{
    BlackboardComponent = CreateDefaultSubobject<UBlackboardComponent>(TEXT("BlackboardComponent"));
    BehaviorTreeComponent = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("BehaviorTreeComponent"));
 
    static ConstructorHelpers::FObjectFinder<UBlackboardData> BB(TEXT("/Script/AIModule.BlackboardData'/Game/AI/BB_Zombie.BB_Zombie'"));
    static ConstructorHelpers::FObjectFinder<UBehaviorTree> BT(TEXT("/Script/AIModule.BehaviorTree'/Game/AI/BT_Zombie.BT_Zombie'"));
    
    if (BB.Succeeded())
    {
        BlackboardData = BB.Object;
    }
 
    if (BT.Succeeded())
    {
        BehaviorTree = BT.Object;
    }
}
 
void AZombieAIController::BeginPlay()
{
    Super::BeginPlay();
 
    if (UseBlackboard(BlackboardData, BlackboardComponent))
    {
        BlackboardComponent = GetBlackboardComponent();
        RunBehaviorTree(BehaviorTree);
    }
}
cs

적 AIController C++ 클래스에 위에서 생성한 "Behavior Tree" 와 "Blackboard" 를 연결해주는 코드이다.

ZombieAnimInstance.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
UCLASS()
class MYPROJECT_API UZombieAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    UZombieAnimInstance();
 
protected:
    virtual void NativeInitializeAnimation() override;
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    class ACharacter* Owner;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    class UCharacterMovementComponent* Movement;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    FVector Velocity;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    float MoveSpeed;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    bool isFalling = false;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    float Angle;
};
cs

ZombieAnimInstance.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include "ZombieAnimInstance.h"
#include "GameFramework/Character.h" // 추가
#include "GameFramework//CharacterMovementComponent.h" // 추가
#include "Kismet/KismetMathLibrary.h" // 추가
 
UZombieAnimInstance::UZombieAnimInstance()
{
 
}
 
void UZombieAnimInstance::NativeInitializeAnimation()
{
    Super::NativeInitializeAnimation();
 
    Owner = Cast<ACharacter>(GetOwningActor());
 
    if (Owner)
    {
        Movement = Owner->GetCharacterMovement();
    }
}
 
void UZombieAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    if (Movement)
    {
        Velocity = Movement->Velocity;
        MoveSpeed = Velocity.Size2D();
        isFalling = Movement->IsFalling();
 
        Angle = UAnimInstance::CalculateDirection(Velocity, Owner->GetActorRotation());
        FRotator DeltaRocator = UKismetMathLibrary::NormalizedDeltaRotator(Owner->GetActorRotation(), Owner->GetControlRotation());
        (Owner->GetActorRotation(),
            Owner->GetControlRotation());
    }
}
cs

'ZombieAnimInstance' 는 'PlayerAnimInstance' 에서 이동과 관련된 부분만 복사해서 붙여넣기를 해줬는데, 이렇게 해서 적 ABP에서 애니메이션 재생에 필요한 변수를 설정해주기 위함이다.

코드 작성이 끝나면 컴파일 후 아래의 것들을 확인해주면 된다.

 'Normal Zombie' 를 바탕으로 생성한 적 BP를 모두 열고, AI Controller가 위에서 생성한 적 AIController인지 확인해준다.

만약 아닐 경우 우측의 초기화 버튼을 누르면 AIController C++ 클래스로 적용될 수도 있다.

적 ABP를 실행하고 "Class Settings" 에서 Parent Class를 'ZombieAnimInstance' 로 지정해준다.

Event Graph로 가서 연결된 노드들을 다 지워준다.

"Show Inherited Variables" 를 체크해주면 'ZombieAnimInstance' 에서 설정한 변수들을 사용할 수 있게 되고, 위 그림과 같이 'ZombieAnimInstance' 내에 있는 "Move Speed" 변수를 Locomotion에서 조건으로 걸어준다.

마지막으로 적 AIController BP에 들어가 Behavior Tree와 Blackboard가 위에서 생성한 것들로 적용돼있는지 확인해주면 된다.

 

 

*오늘까지 작성한 코드 정리

ZombieBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "ZombieBase.generated.h"
 
UENUM(BlueprintType)
enum class EZombieType :uint8
{
    HumanZombie UMETA(DisplayName = "HumanZombie"),
    DogZombie UMETA(DisplayName = "DogZombie"),
    WingZombie UMETA(DisplayName = "WingZombie"),
};
 
UCLASS()
class MYPROJECT_API AZombieBase : public ACharacter
{
    GENERATED_BODY()
 
public:
    // Sets default values for this character's properties
    AZombieBase();
 
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
 
public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;
 
    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 좀비 별 타입 지정
    EZombieType zombieType;
 
    TMap<EZombieType, USkeletalMesh*> ZombieMeshes;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ZombieAnim")
    TMap<EZombieType, TSubclassOf<UAnimInstance>> ZombieAnimBlueprints;
 
    FVector ZombieSize;
    
    float ZombieHP;
 
private:
    void InitMeshes(); // 생성자에서 불러온 뒤 초기화
    void InitAnimInstance();
    void SetZombie();
};
cs

ZombieBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "ZombieBase.h"
#include "Components/SkeletalMeshComponent.h"
 
// Sets default values
AZombieBase::AZombieBase()
{
     // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;
 
    InitMeshes();
    InitAnimInstance();
}
 
// Called when the game starts or when spawned
void AZombieBase::BeginPlay()
{
    Super::BeginPlay();
    
    SetZombie();
}
 
// Called every frame
void AZombieBase::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
}
 
// Called to bind functionality to input
void AZombieBase::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
}
 
void AZombieBase::InitMeshes()
{
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> HumanZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/BookHeadMonster/Meshes/BookHeadMonster.BookHeadMonster'"));
 
    if (HumanZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::HumanZombie, HumanZombieMesh.Object);
    }
    
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> DogZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/Wood_Monster/CharacterParts/Meshes/SK_wood_giant_01_a.SK_wood_giant_01_a'"));
 
    if (DogZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::DogZombie, DogZombieMesh.Object);
    }
 
    static ConstructorHelpers::FObjectFinder<USkeletalMesh> WingZombieMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/ParagonSevarog/Characters/Heroes/Sevarog/Meshes/Sevarog.Sevarog'"));
 
    if (WingZombieMesh.Succeeded())
    {
        ZombieMeshes.Add(EZombieType::WingZombie, WingZombieMesh.Object);
    }
}
 
void AZombieBase::InitAnimInstance()
{
    static ConstructorHelpers::FClassFinder<UAnimInstance> HumanZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Zombie.ABP_Zombie_C"));
    if (HumanZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::HumanZombie, HumanZombieAnim.Class);
    }
 
    static ConstructorHelpers::FClassFinder<UAnimInstance> DogZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Zombie.ABP_Zombie_C"));
    if (DogZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::DogZombie, DogZombieAnim.Class);
    }
 
    static ConstructorHelpers::FClassFinder<UAnimInstance> WingZombieAnim(TEXT("/Game/Blueprint/Zombie/ABP_Sev.ABP_Sev_C"));
    if (WingZombieAnim.Succeeded())
    {
        ZombieAnimBlueprints.Add(EZombieType::WingZombie, WingZombieAnim.Class);
    }
}
 
void AZombieBase::SetZombie()
{
    if (zombieType == EZombieType::HumanZombie)
    {
        if (ZombieMeshes[EZombieType::HumanZombie] && ZombieAnimBlueprints[EZombieType::HumanZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::HumanZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::HumanZombie]);
        }
 
        ZombieSize = FVector(1.3f, 1.3f, 1.3f);
        ZombieHP = 100;
    }
    else if (zombieType == EZombieType::DogZombie)
    {
        if (ZombieMeshes[EZombieType::DogZombie] && ZombieAnimBlueprints[EZombieType::DogZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::DogZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::DogZombie]);
        }
    
        ZombieSize = FVector(0.9f, 0.9f, 0.9f);
        ZombieHP = 80;
    }
    else if (zombieType == EZombieType::WingZombie)
    {
        if (ZombieMeshes[EZombieType::WingZombie] && ZombieAnimBlueprints[EZombieType::WingZombie])
        {
            GetMesh()->SetSkeletalMesh(ZombieMeshes[EZombieType::WingZombie]);
            GetMesh()->SetAnimInstanceClass(ZombieAnimBlueprints[EZombieType::WingZombie]);
        }
 
        ZombieSize = FVector(1.5f, 1.5f, 1.5f);
        ZombieHP = 150;
    }
 
    GetMesh()->SetRelativeLocationAndRotation(FVector(00-90), FRotator(0-900));
 
    SetActorScale3D(ZombieSize);
}
cs

NormalZombie.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "CoreMinimal.h"
#include "ZombieBase.h"
#include "NormalZombie.generated.h"
 
/**
 * 
 */
UCLASS()
class MYPROJECT_API ANormalZombie : public AZombieBase
{
    GENERATED_BODY()
 
public:
    ANormalZombie();
 
private:
    virtual void BeginPlay() override;
 
public:
    void InitAI();
};
cs

NormalZombie.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "NormalZombie.h"
#include "ZombieAnimInstance.h" // 추가
#include "AIController.h"
#include "ZombieAIController.h"
 
ANormalZombie::ANormalZombie()
{
    InitAI();
}
 
void ANormalZombie::BeginPlay()
{
    Super::BeginPlay();
}
 
void ANormalZombie::InitAI()
{
    AIControllerClass = AZombieAIController::StaticClass();
    AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
cs

ZombieAIController.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "CoreMinimal.h"
#include "AIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "ZombieAIController.generated.h"
 
/**
 * 
 */
UCLASS()
class MYPROJECT_API AZombieAIController : public AAIController
{
    GENERATED_BODY()
 
public:
    AZombieAIController();
 
protected:
    virtual void BeginPlay() override;
 
public:
    UPROPERTY(EditAnywhere, Category = "AI")
    UBehaviorTree* BehaviorTree;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBlackboardData* BlackboardData;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBlackboardComponent* BlackboardComponent;
 
    UPROPERTY(EditAnywhere, Category = "AI")
    UBehaviorTreeComponent* BehaviorTreeComponent;
};
cs

ZombieAIController.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "ZombieAIController.h"
 
AZombieAIController::AZombieAIController()
{
    BlackboardComponent = CreateDefaultSubobject<UBlackboardComponent>(TEXT("BlackboardComponent"));
    BehaviorTreeComponent = CreateDefaultSubobject<UBehaviorTreeComponent>(TEXT("BehaviorTreeComponent"));
 
    static ConstructorHelpers::FObjectFinder<UBlackboardData> BB(TEXT("/Script/AIModule.BlackboardData'/Game/AI/BB_Zombie.BB_Zombie'"));
    static ConstructorHelpers::FObjectFinder<UBehaviorTree> BT(TEXT("/Script/AIModule.BehaviorTree'/Game/AI/BT_Zombie.BT_Zombie'"));
    
    if (BB.Succeeded())
    {
        BlackboardData = BB.Object;
    }
 
    if (BT.Succeeded())
    {
        BehaviorTree = BT.Object;
    }
}
 
void AZombieAIController::BeginPlay()
{
    Super::BeginPlay();
 
    if (UseBlackboard(BlackboardData, BlackboardComponent))
    {
        BlackboardComponent = GetBlackboardComponent();
        RunBehaviorTree(BehaviorTree);
    }
}
cs

ZombieAnimInstance.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Fill out your copyright notice in the Description page of Project Settings.
 
#pragma once
 
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "ZombieAnimInstance.generated.h"
 
/**
 * 
 */
UCLASS()
class MYPROJECT_API UZombieAnimInstance : public UAnimInstance
{
    GENERATED_BODY()
 
public:
    UZombieAnimInstance();
 
protected:
    virtual void NativeInitializeAnimation() override;
    virtual void NativeUpdateAnimation(float DeltaSeconds) override;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    class ACharacter* Owner;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    class UCharacterMovementComponent* Movement;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    FVector Velocity;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    float MoveSpeed;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    bool isFalling = false;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
    float Angle;
};
cs

ZombieAnimInstance.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "ZombieAnimInstance.h"
#include "GameFramework/Character.h"
#include "GameFramework//CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.h"
 
UZombieAnimInstance::UZombieAnimInstance()
{
 
}
 
void UZombieAnimInstance::NativeInitializeAnimation()
{
    Super::NativeInitializeAnimation();
 
    Owner = Cast<ACharacter>(GetOwningActor());
 
    if (Owner)
    {
        Movement = Owner->GetCharacterMovement();
    }
}
 
void UZombieAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
    Super::NativeUpdateAnimation(DeltaSeconds);
 
    if (Movement)
    {
        Velocity = Movement->Velocity;
        MoveSpeed = Velocity.Size2D();
        isFalling = Movement->IsFalling();
 
        Angle = UAnimInstance::CalculateDirection(Velocity, Owner->GetActorRotation());
        FRotator DeltaRocator = UKismetMathLibrary::NormalizedDeltaRotator(Owner->GetActorRotation(), Owner->GetControlRotation());
        (Owner->GetActorRotation(),
            Owner->GetControlRotation());
    }
}
cs