컴퓨터 및 게임 관련 전공인이 아닌 문외한 일반인이 언리얼 엔진을 배우면서 정리한 내용입니다.
그날그날 배웠던 내용을 제가 나중에 보기 위해 정리한 것으로, 지금 당장 보기에 부족한 점이 아주아주 많고 추후에 수정이 될 수도 있습니다.
오탈자 및 잘못 기재된 내용 지적, 부족한 내용 설명은 언제든지 환영입니다.
본글은 언리얼 엔진 5.5.4 버전 영문판을 기준으로 합니다.
----------------------------------------------------------------------------------------------------------------
15일차 학습 내용
- 캐릭터 애니메이션 설정
"Blend Space" 를 사용한 애니메이션 추가
무기 추가
조준 및 사격 애니메이션 추가 (Animation)
조준 및 사격 애니메이션 추가 (Animation Montage)
캐릭터 애니메이션 설정
Skeletal Mesh나 애니메이션 관련 코드를 C++ 로 코딩 후에 BP를 따로 생성해주는 이유는, C++ 상에서는 제대로 적용됐는지 확인이 어렵기에 BP로 위치나 회전, 크기 같은 값을 조정한 뒤에 조정한 값을 넣기 위해서이다.
"Blend Space" 를 사용한 애니메이션 추가
우선은 새로운 C++ 클래스를 생성해줘야한다.

위 그림처럼 'All Alasses' 에서 'AnimInstance' 를 선택해서 생성해준다.
그 다음 Animation BP (이하 ABP라고 칭함) 를 생성해준다.

ABP를 생성할 때, 애니메이션을 적용할 Skeletal Mesh를 선택해주고 Parent Class로 위에서 생성한 C++ 클래스를 지정해준다.
위에서 생성한 C++ 클래스를 Visual Studio에서 열고 아래와 같이 코드를 작성해준다.
PlayerControl.cpp
|
1
2
3
4
5
6
7 8 |
#include "PlayerAnimInstance.h"
static ConstructorHelpers::FClassFinder<UAnimInstance>AnimInstance(TEXT("/Game/Blueprint/ABP_Player.ABP_Player_C")); if (AnimInstance.Class)
{
GetMesh()->SetAnimInstanceClass(AnimInstance.Class);
}
|
cs |
*클래스의 경우는 파일명 뒤에 "_C" 추가; 경로 상에 '가 들어가있으면 안됨
컴파일 후 "Player_Bp" 에서 'Anim Class' 가 위에서 생성한 C++로 바뀌어져 있으면 제대로 된 것이다.
이제 애니메이션을 만들어서 넣어줄 차례다.

ABP에서 "State Machine" 노드를 생성하고 위 그림과 같이 연결해준다.

이동 관련 애니메이션을 넣기 위해 "Blend Space" 를 생성해주고, 위 그림과 같이 만들어준다.
Visual Studio에서 아래의 코드를 입력해준다.
PlayerAnimInstance.h
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 |
public:
UPlayerAnimInstance();
protected:
virtual void NativeInitializeAnimation() override; // 애니메이션이 생성되면 호출되는 함수
virtual void NativeUpdateAnimation(float DeltaSeconds) override; // 프레임마다 호출 함수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
class ACharacter* Owner; // Character Actor
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
class UCharacterMovementComponent* Movement; //Movement Component
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
FVector Velocity; // Movement에 Velocity 속력을 저장하는 변수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
float MoveSpeed; // 속력에 벡터의 2D 구성요소의 길이를 저장하는 변수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
bool isFalling = false; // Ground에 닿여있는지에 대한 여부
|
cs |
PlayerAnimInstance.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
|
#include "GameFramework/Character.h"
#include "GameFramework//CharacterMovementComponent.h"
void UPlayerAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
Owner = Cast<ACharacter>(GetOwningActor());
if (Owner)
{
Movement = Owner->GetCharacterMovement();
}
}
void UPlayerAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (Movement)
{
Velocity = Movement->Velocity;
MoveSpeed = Velocity.Size2D();
isFalling = Movement->IsFalling();
}
}
|
cs |


컴파일 후 "Show Inherited Variables" 를 체크하면, 위 사진과 같이 C++ 헤더에서 생성한 변수들이 보이게 된다.

6일차에서 했던 것처럼 "Motion State" 에 들어가 위에서 제작한 이동 관련 "Blend Space" 를 드래그하여 꺼낸 뒤 연결해준다.

위에서 배치한 "Blend Space" 에 들어가 다시 "Blend Space" 를 드래그하여 꺼내고, 해당 "Blend Space" 와 관련된 변수인 'Move Speed' 를 연결해준다.
'Angle' 에 관한 변수가 하나 더 필요한데 빠졌으니, C++에서 추가해준다.
PlayerAnimInstance.h
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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; // Angle 값
|
cs |

그리고 'Angle' 변수를 위 사진과 같이 연결해주면 된다.
*타 프로젝트에 있는 애니메이션을 현 프로젝트로 가져올 때 'Migrate' 를 사용

무기 추가
이번엔 캐릭터에 무기를 들려주는 것을 해보도록 하겠다.
오른손에 총을 들려주고 싶기에 Skeletal Mesh 창을 열어서 Skeletal Mesh 부위 중 'hand_r' 을 찾는다.
우클릭을 하여 "Add Socket" 으로 "Weapon_Socket_R" 이라는 이름으로 하나 생성해준다.

그리고 생성된 "Weapon_Socket_R" 을 우클릭 하고 "Add Preview Asset" 을 통해 무기 관련 'Static Mesh' 를 넣어준다.
*반드시 'Static Mesh' 를 넣어줘야함



그리고 손에 맞도록 위치 및 회전값을 조절해주고, 위 그림처럼 BP 창에서 "Parent Socket" 을 위에서 생성한 Socket으로 적용시켜주면 위의 큰 그림과 같이 정상적으로 적용되는 걸 확인할 수 있다.

위에서 사용한 Skeletal Mesh에 자세히 보면 "hand_r" 하위 항목으로 "weapon_r" 이 있는데, 제작자가 따로 무기를 넣을 Socket을 제작해둔 것이다.

"weapon_r" 에 Add Preview Asset" 을 통해 무기를 넣고 각도를 맞춰주고 "Parent Socket" 을 적용해주면, 이번엔 위 그림과 같이 어긋나 보이게 된다.
그 이유는 제작자가 "weapon_r" 에 넣으면 알아서 손 위치에 맞게 들어가도록 미리 위치 및 회전값을 넣어뒀기 때문이다.
그걸 수정했기에 위 그림과 같이 어긋나게 된 것이다.
그럼 이제 다시 Visual Studio를 통해 위 작업을 수행해보도록 하겠다.
아래와 같이 코드를 작성해준다.
PlayerControl.h
|
1
2
3
|
private:
UPROPERTY(EditAnywhere)
class UStaticMeshComponent* SM_Weapon;
|
cs |
PlayerControl.cpp
|
1
2
3
4
5
6
7
8
9
10
|
#include "Components/StaticMeshComponent.h"
SM_Weapon = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh")); // 무기 소켓 추가
SM_Weapon->SetupAttachment(GetMesh());
static ConstructorHelpers::FObjectFinder<UStaticMesh>weaponMesh(TEXT("/Script/Engine.StaticMesh'/Game/Asset/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SM_AR4.SM_AR4'")); // 무기 SM 가져옴
if (weaponMesh.Succeeded()) // SM 체크
{
SM_Weapon->SetStaticMesh(weaponMesh.Object);
}
SM_Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("hand_rSocket")); // 소켓에 SM 장착
|
cs |
*#include에 헤더파일 추가 확인

이렇게 하면 위 그림처럼 "Parent Socket" 까지 완벽히 적용된 결과물이 나오는 걸 BP 창에서 확인할 수 있다.
조준 및 사격 애니메이션 추가 (Animation)
무기를 추가했으면 이제 조준 및 사격 애니메이션을 넣어줄 차례이다.
조준 및 사격은 각각 단일 애니메이션만 있으면 되게에 "Blend Space" 를 사용할 필요가 없고, 조준과 사격 모두 마우스 눌렀다뗐다만을 확인하면 되기에 bool 변수를 적용시켜주면 된다.



우선 조준과 사격의 "Input Animation" 을 위 그림과 같은 설정대로 생성하고, "Input Mapping Context" 에 위 그림과 같이 추가해준다.
그리고 아래와 같이 코드를 작성해준다.
PlayerAnimInstance.h
|
1
2
3
4
5
6
7
8
|
UCLASS() 하위 항목으로 생성
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character") // public으로 변경 필수; PlayerControl에서 접근 가능하게 함
bool isAim = false;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
bool isFire = false;
|
cs |
PlayerControl.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
|
private:
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputMappingContext* DefaultContext;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* MoveAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* LookAction;
UPROPERTY(VisibleAnywhere, Category = "Input") // 새로 추가
class UInputAction* AimAction;
UPROPERTY(VisibleAnywhere, Category = "Input") // 새로 추가
class UInputAction* FireAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
float mousespeed = 30.0f;
void Move(const FInputActionValue& value);
void Look(const FInputActionValue& value);
void OnAim(); // 새로 추가
void OffAim(); // 새로 추가
void Fire(); // 새로 추가
void FireOff(); // 새로 추가
|
cs |
*alt + enter로 .cpp에서 정의 생성
PlayerControl.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
|
#include "PlayerAnimInstance.h"
APlayerControl::APlayerControl() // 하위 항목에 IA_ 항목 추가
{
static ConstructorHelpers::FObjectFinder<UInputAction>InputAim(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Aim.IA_Aim'"));
if (InputAim.Object != nullptr)
{
AimAction = InputAim.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputFire(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Fire.IA_Fire'"));
if (InputFire.Object != nullptr)
{
FireAction = InputFire.Object;
}
}
void APlayerCharacterControl::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlayerCharacterControl::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlayerCharacterControl::Look); // 기존 입력돼있는 것
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &APlayerCharacterControl::OnAim); // 조준 관련
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &APlayerCharacterControl::OffAim);
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &APlayerCharacterControl::Fire); // 사격 관련
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Completed, this, &APlayerCharacterControl::FireOff);
void APlayerCharacterControl::OnAim() // 조준 상태 애니메이션 연결
{
UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
animInstance->isAim = true;
}
void APlayerCharacterControl::OffAim() // 비조준 상태 애니메이션 연결
{
UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
animInstance->isAim = false;
}
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
animInstance->isFire = true;
}
void APlayerCharacterControl::FireOff() // 비사격 상태 애니메이션 연결
{
UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
animInstance->isFire = false;
}
|
cs |
*"Cast" 는 형변환을 할 때 사용됨
UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
'UPlayerAnimInstance' 의 자료형으로 " GetMesh()->GetAnimInstance()" 를 형변환함을 의미 (자기자신 지목)
"Cast" 앞뒤로 들어가는 자료형은 같아야함

컴파일 후 ABP 창을 보면 "isAim" 변수가 생성된 것을 확인 가능하다.



"State Machine" 에서 조준 관련 애니메이션을 넣어주고, 애니메이션 전환 조건에 "isAim" 변수를 알맞게 넣어 연결해주면 된다.
사격 관련 애니메이션도 똑같이 "isFire" 변수를 사용해 연결해주면 된다.
위와 같은 방식으로 만들면 문제가 하나 있는데, 파생되는 애니메이션이 많으면 노드 및 연결선 생성이 많아져서 너무 복잡해진다는 것이다.
그렇기에 파생되는 애니메이션이 많을 경우 아래의 방식을 사용한다.
*Skeletal Mesh에 "Retarget Animation" 을 했을 경우, 위 작업 도중 애니메이션이 정상적으로 출력되지 않거나 사라지는 현상이 발생하기도 함

해당 애니메이션의 "Additive Anim Type" 설정을 'Local Space' 를 'No Additive' 로 변경
조준 및 사격 애니메이션 추가 (Animation Montage)

위에서 사용했던 조준 및 사격 관련 애니메이션을 우클릭하여 "Create" 탭의 "Create AnimMontage" 를 눌러 "Animation Montage" 를 생성해준다.

그리고 ABP로 가서 위 사진과 같이 연결해주고, Visual Studio를 열고 아래의 코드를 입력해준다.
위 사진의 "Slot 'DefaultSlot'" 은 "Locomotion(Movement State)" 에서 설정한 'Montage' 들이 적용되도록 도와주는 역할을 한다.
PlayerControl.h
|
1
2
3
4
5
6
|
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
UAnimMontage* RifleAimMontage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
UAnimMontage* RifleFireMontage;
|
cs |
PlayerControl.cpp
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
void APlayerCharacterControl::OnAim() // 조준 상태 애니메이션 연결
{
//UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
//animInstance->isAim = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleAimMontage)
{
AnimInstance->Montage_Play(RifleAimMontage);
}
}
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
//UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
//animInstance->isFire = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleFireMontage)
{
AnimInstance->Montage_Play(RifleFireMontage);
}
}
|
cs |
*'Montage_Play' () 안에 위에서 생성한 "Animation Montage" 파일명 입력


위 코드까지 작성하고 나면 위 사진과 같이 'Montage' 들이 채워지게 된다.
그러나 여기서 문제가 있는데, 캐릭터 조준 및 사격 명령을 입력했을 때 애니메이션이 재생되지 않고 움찔움찔하는 행동을 하게된다.

생성한 "Animation Montage" 를 클릭해서 보면, 위 사진과 같이 "Animation Montage" 마다 "Start Time" 과 "End Time" 을 통해 애니메이션의 재생시간이 있으며 다 다른 걸 확인할 수 있다.
"Animation Montage" 의 경우 애니메이션 재생 시간까지 Visual Studio에서 지정해줘야 정상작동하게 된다.
그러므로 애니메이션 재생시간을 알아낸 뒤, 아래와 같이 코드를 수정해준다.
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void APlayerCharacterControl::OnAim() // 조준 상태 애니메이션 연결
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleAimMontage)
{
AnimInstance->Montage_Play(RifleAimMontage, 0.03f); // 0.03f 은 해당 Montage 재생 시간
}
}
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleFireMontage)
{
AnimInstance->Montage_Play(RifleFireMontage, 0.03f); // 0.03f 은 해당 Montage 재생 시간
}
}
|
cs |
*위 사진에는 애니메이션 재생시간을 '0.03' 이라고만 적었으나, 오류를 막으려면 '0.033333' 을 다 적어주는게 맞음
하지만 여기서도 문제가 발생하는데, 우클릭을 꾹 누르거나 좌클릭을 연타해도 애니메이션이 유지되지 않고 한 번만 나오고 끊긴다.
이 문제 해결은 내일 배우기로 했으므로 내일 올리도록 하겠다.
*오늘까지 작성한 코드 정리
PlayerControl.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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "InputActionValue.h"
#include "PlayerCharacterControl.generated.h"
UCLASS()
class MYPROJECT_API APlayerCharacterControl : public ACharacter
{
GENERATED_BODY()
public:
// Sets default values for this character's properties
APlayerCharacterControl();
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;
protected:
UPROPERTY(EditAnywhere)
class USpringArmComponent* SpringArm;
UPROPERTY(EditAnywhere)
class UCameraComponent* Camera;
private:
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputMappingContext* DefaultContext;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* MoveAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* LookAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* AimAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
class UInputAction* FireAction;
UPROPERTY(VisibleAnywhere, Category = "Input")
float mousespeed = 30.0f;
void Move(const FInputActionValue& value);
void Look(const FInputActionValue& value);
void OnAim();
void OffAim();
void Fire();
void FireOff();
private:
UPROPERTY(EditAnywhere)
class UStaticMeshComponent* SM_Weapon;
public:
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
UAnimMontage* RifleAimMontage;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
UAnimMontage* RifleFireMontage;
};
|
cs |
PlayerControl.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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
#include "PlayerCharacterControl.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
#include "InputMappingContext.h"
#include "Components/StaticMeshComponent.h" // 새로 추가
#include "PlayerAnimInstance.h" // 새로 추가
// Sets default values
APlayerCharacterControl::APlayerCharacterControl()
{
// 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;
ConstructorHelpers::FObjectFinder<USkeletalMesh>PlayerMesh(TEXT("/Script/Engine.SkeletalMesh'/Game/Asset/Player/Survival_Character/Meshes/SK_Survival_Character.SK_Survival_Character'"));
if (PlayerMesh.Succeeded())
{
GetMesh()->SetSkeletalMesh(PlayerMesh.Object); // 스켈레톤 보유
GetMesh()->SetWorldLocationAndRotation(FVector(0, 0, -90), FRotator(0, -90, 0)); // 스켈레톤에 이동 및 회전 부여
}
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm")); // Spring Arm 생성 및 회전 및 각도를 조절하여 Root에 장착
SpringArm->SetupAttachment(RootComponent);
SpringArm->SetWorldLocation(FVector(45, 22, 55.0f));
SpringArm->TargetArmLength = 200;
SpringArm->SocketOffset = FVector(23, 40, 0);
SpringArm->bUsePawnControlRotation = true;
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera")); // Camera 생성 및 Spring Arm에 장착
Camera->SetupAttachment(SpringArm);
static ConstructorHelpers::FClassFinder<UAnimInstance>AnimInstance(TEXT("/Game/Blueprint/ABP_Player.ABP_Player_C")); // Animation BP 지정; 클래스의 경우는 _C 추가, 경로 상에 '가 들어가있으면 안됨
if (AnimInstance.Class)
{
GetMesh()->SetAnimInstanceClass(AnimInstance.Class);
}
SM_Weapon = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh")); // 무기 소켓 추가
SM_Weapon->SetupAttachment(GetMesh());
static ConstructorHelpers::FObjectFinder<UStaticMesh>weaponMesh(TEXT("/Script/Engine.StaticMesh'/Game/Asset/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SM_AR4.SM_AR4'")); // 무기 SM 가져옴
if (weaponMesh.Succeeded()) // SM 체크
{
SM_Weapon->SetStaticMesh(weaponMesh.Object);
}
SM_Weapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("hand_rSocket")); // 소켓에 SM 장착
static ConstructorHelpers::FObjectFinder<UInputMappingContext>InputContext(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Input/IMC_PlayerInput.IMC_PlayerInput'")); // IMC 지정
if (InputContext.Object != nullptr)
{
DefaultContext = InputContext.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputMove(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Move.IA_Move'")); // IA_Move 지정
if (InputMove.Object != nullptr)
{
MoveAction = InputMove.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputLook(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Look.IA_Look'")); // IA_Look 지정
if (InputLook.Object != nullptr)
{
LookAction = InputLook.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputAim(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Aim.IA_Aim'")); // IA_Aim 지정; OnAim, OffAim 따로 지정해주지 않아도 됨
if (InputAim.Object != nullptr)
{
AimAction = InputAim.Object;
}
static ConstructorHelpers::FObjectFinder<UInputAction>InputFire(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Fire.IA_Fire'")); // IA_Fire 지정
if (InputFire.Object != nullptr)
{
FireAction = InputFire.Object;
}
}
// Called when the game starts or when spawned
void APlayerCharacterControl::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PlayerController = Cast<APlayerController>(GetController())) // PlayerController 관련
{
if(UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultContext, 0);
}
}
}
// Called every frame
void APlayerCharacterControl::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Called to bind functionality to input
void APlayerCharacterControl::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) // IMC 관련
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent);
EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &APlayerCharacterControl::Move);
EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &APlayerCharacterControl::Look);
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Started, this, &APlayerCharacterControl::OnAim); // 조준 관련
EnhancedInputComponent->BindAction(AimAction, ETriggerEvent::Completed, this, &APlayerCharacterControl::OffAim);
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Started, this, &APlayerCharacterControl::Fire); // 사격 관련
EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Completed, this, &APlayerCharacterControl::FireOff);
}
void APlayerCharacterControl::Move(const FInputActionValue& value) // IA_Move 관련 값 계산
{
const FVector2D MovementVector = value.Get<FVector2D>();
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
void APlayerCharacterControl::Look(const FInputActionValue& value) // IA_Look 관련 값 계산
{
FVector2D LookAxisVector = value.Get<FVector2D>();
AddControllerYawInput(LookAxisVector.X * GetWorld()->DeltaTimeSeconds * mousespeed);
AddControllerPitchInput(LookAxisVector.Y * GetWorld()->DeltaTimeSeconds * -mousespeed);
}
void APlayerCharacterControl::OnAim() // 조준 상태 애니메이션 연결
{
//UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
//animInstance->isAim = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleAimMontage)
{
AnimInstance->Montage_Play(RifleAimMontage, 0.03f);
}
}
void APlayerCharacterControl::OffAim() // 비조준 상태 애니메이션 연결
{
//UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
//animInstance->isAim = false;
}
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
//UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
//animInstance->isFire = true;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (RifleFireMontage)
{
AnimInstance->Montage_Play(RifleFireMontage, 0.03f);
}
}
void APlayerCharacterControl::FireOff() // 비사격 상태 애니메이션 연결
{
// UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
// animInstance->isFire = false;
}
|
cs |
PlayerAnimInstance.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
|
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "PlayerAnimInstance.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API UPlayerAnimInstance : public UAnimInstance
{
GENERATED_BODY() // 기존
public:
UPlayerAnimInstance();
protected:
virtual void NativeInitializeAnimation() override; // 애니메이션이 생성되면 호출되는 함수
virtual void NativeUpdateAnimation(float DeltaSeconds) override; // 프레임마다 호출 함수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
class ACharacter* Owner; // Character Actor
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
class UCharacterMovementComponent* Movement; //Movement Component
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
FVector Velocity; // Movement에 Velocity 속력을 저장하는 변수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
float MoveSpeed; // 속력에 벡터의 2D 구성요소의 길이를 저장하는 변수
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
bool isFalling = false; // Ground에 닿여있는지에 대한 여부
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
float Angle; // Angle 값
public:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character") // public으로 변경 필수; PlayerControl에서 접근 가능하게 함
bool isAim = false;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character")
bool isFire = false;
};
|
cs |
PlayerAnimInstance.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
|
#include "PlayerAnimInstance.h"
#include "GameFramework/Character.h"
#include "GameFramework//CharacterMovementComponent.h"
UPlayerAnimInstance::UPlayerAnimInstance()
{
}
void UPlayerAnimInstance::NativeInitializeAnimation()
{
Super::NativeInitializeAnimation();
Owner = Cast<ACharacter>(GetOwningActor());
if (Owner)
{
Movement = Owner->GetCharacterMovement();
}
}
void UPlayerAnimInstance::NativeUpdateAnimation(float DeltaSeconds)
{
Super::NativeUpdateAnimation(DeltaSeconds);
if (Movement)
{
Velocity = Movement->Velocity;
MoveSpeed = Velocity.Size2D();
isFalling = Movement->IsFalling();
Angle = UAnimInstance::CalculateDirection(Velocity, Owner->GetActorRotation());
}
}
|
cs |
'Unreal Engine > GCC Class (5.5.4)' 카테고리의 다른 글
| Unreal Engine 5 배워보기 - 17일차 (0) | 2025.04.09 |
|---|---|
| Unreal Engine 5 배워보기 - 16일차 (0) | 2025.04.08 |
| Unreal Engine 5 배워보기 - 14일차 (0) | 2025.04.04 |
| Unreal Engine 5 배워보기 - 13일차 (0) | 2025.04.03 |
| Unreal Engine 5 배워보기 - 12일차 (0) | 2025.04.02 |