Unreal Engine/GCC Class (5.5.4)

Unreal Engine 5 배워보기 - 20일차

보별 2025. 4. 14. 18:31
반응형

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

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


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

 

20일차 학습 내용

- 충돌

   (Single) Line Trace by Channel

   Multi Line Trace

     Multi Line Trace by Channel

     Multi Line Trace for Object

- 샷건 추가

- Crosshair 및 조준 시 줌인 추가

- 아이템 습득

 

 

충돌

"Project Settings" 의 "Engine" 탭에서 "Collision" 에 들어가면 위 사진과 같은 창이 뜨는데, 해당 창에서 오브젝트 별 충돌 판정 설정을 할 수 있다.

새로운 "Object Channel" 의 충돌 설정 시 다른 특성의 충돌로 바꿀게 적은 것으로 미리 해두는 편이 좋다.

예를 들어 좀비 같은 경우는 대부분 관통할 일이 없고 충돌할 경우가 많기에, 처음부터 'Block' 으로 설정해두는 편이 좋다.

"Trace Channel" 은 주로 총알 관련 충돌 설정에 쓰인다.

대부분의 총알은 어느정도 관통할 일이 많기에, 'Ignore' 로 설정해두는 것이 좋다.

아래의 "Preset" 에서는 오브젝트의 "Object Channel" 와 "Trace Channel" 별로 충돌 설정을 해줄 수 있다.

위의 그림과 같이 'Player Capsule' 의 충돌 판정은 대부분의 경우 충돌로 인식해야할 게 많기에, 대부분을 "Overlap' 으로 설정해주고, 일부분만 'Block' 으로 변경해주면 된다.

플레이어가 충돌대상이 충돌을 일으키려면 "Block" 이나 "Overlap" 설정해주면 된다.

"Block" 의 경우에는 두 물체가 충돌하면 서로 막아서 지나가지 못하게 하고, "Overlap" 의 경우에는 서로 관통이 되지만 충돌 판정으로 인식된다.

"Ignore" 의 경우는 서로 관통하면서 충돌 판정도 발생하지 않는다.

충돌 관련 설정은 둘 중 하나의 설정을 따르는데, 우선 순위는 "Ignore" > "Overlap" > "Block" 순이 된다.

예를 들면 하나의 물체가 "Ignore" 로 설정되어 있고 다른 하나의 물체는 "Block" 으로 설정되어 있을 경우, 우선 순위가 높은 "Ignore" 가 있기에 두 물체 사이의 충돌은 "Ignore" 로 인식된다.

충돌 이벤트를 발생시키려면 "Generate Overlap Events" 를 체크해서 활성화 시켜줘야한다.

레벨 상에 오브젝트를 배치하고 "Collision" 의 "Collision Presets" 에서 위에서 설정한 "Collision Presets" 을 적용할 수 있고, 따로 설정해주고 싶을 경우 "Custom" 을 선택하여 개별로 설정해줄 수 있다.

게임 플레이 시 충돌 관련 오류가 나면, 플레이어의 BP에서 Character Mesh, Weapon Mesh, Capsule Component 등 충돌 설정이 각각 가능한 모든 부위들을 한 번씩 확인해보는 것이 좋다.

모두 충돌 설정이 따로 적용되기에 잘못 적용된 것이 하나라도 있다면 오류가 나는 것이다.

(Single) Line Trace by Channel

"(Single) Line Trace by Channel" 은 하나의 광선을 쏴서 가장 먼저 닿는 하나의 오브젝트만 탐지하는 방식이다.

해당 Line Trace는 관통되지 않기에, 권총의 총알 같은 관통되지 않는 총알의 제작에 쓰인다.

위 그림과 같이 BP를 작성해주면 관통되지 않는 일반적인 총알을 적용할 수 있다.

지난 16일차에서 작성한 것과 똑같으니 자세한 설명은 생략하도록 하겠다.

Multi Line Trace

"Multi Line Trace" 는 시작점부터 끝점까지 하나의 광선을 쏘고, 해당 경로에 닿는 모든 오브젝트들을 탐지하는 방식이다.

해당 Line Trace는 관통되는 성질이 있기에, 광선총이나 관통력이 좋은 총알의 제작에 쓰인다.

Multi Line Trace by Channel

"Trace Channel" 에 지정해준 설정에 따라 오브젝트가 파괴된다.

중간에 위치한 액터에 투사될 "Line Trace" 의 설정을 "Block" 으로 하면, 해당 액터까지만 파괴되고 그 뒤의 액터는 영향을 받지 않는다.

Multi Line Trace for Object

"Multi Line Trace for Object" 은 배열로 지정해준 오브젝트들만 Line Trace에 닿을 시 파괴된다.

중간에 위치한 액터에 투사될 "Line Trace" 의 설정을 "Block" 으로 하면, 해당 액터를 제외하고 파괴된다.

 

샷건 추가

"For Loop" 를 통해 0~10까지 총 11개의 "Line Trace" 를 출력하고, "Random Float Range" 노드를 통해 "Line Trace" 를 x, y, z축으로 퍼지게 만들었다.

그리고 "Line Trace" 에 충돌 가능한 액터 중 물리법칙이 적용되는 액터만을 선출해, '10000' 의 충격으로 해당 액터를 앞으로 밀어내며, 딜레이 노드를 뒤에다 줘서 총알이 액터에 탄착 후 소리 나도록 설정하였다.

그러면 위 사진과 같이 넓은 탄착군을 가지는 샷건을 제작할 수 있다.

Crosshair 및 조준 시 줌인 추가

18일차에 배운 내용과 동일하므로 자세한 설명은 생략하도록 하겠다.

PlayerControl.h

1
2
3
4
5
UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 조준 시 크로스헤어 추가를 위해 생성
void OnAim_BP(); 
 
UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 비조준 시 크로스헤어 제거를 위해 생성
void OffAim_BP();
cs

PlayerControl.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
void APlayerCharacterControl::OnAim() // 조준 상태 애니메이션 연결
{
    OnAim_BP(); // 조준 시 크로스헤어 추가를 위해 생성
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
    animInstance->isAim = true;
}
 
void APlayerCharacterControl::OffAim()  // 비조준 상태 애니메이션 연결
{
   OffAim_BP();  // 비조준 시 크로스헤어 제거를 위해 생성
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
    animInstance->isAim = false;
}
cs

우선 위와 같이 코드를 추가하고 컴파일을 해준다.

그러면 위 사진과 같이 플레이어 BP에 "On_Aim" 과 "Off_Aim" 에 관한 이벤트를 생성할 수 있고, 나타낼 Widget에 위에서 생성한 크로스헤어 WBP를 넣어준 뒤, 위 그림과 같이 노드들을 연결해주면 된다.

그리고 위에서 작성한 BP 뒷부분에 위 그림과 같이 노드들을 연결해주면, Lerp와 Timeline을 이용하여, Spring Arm의 거리 조절로 조준 상태와 비조준 상태일 때의 줌인, 줌아웃을 부여해줄 수 있다.

그러면 위 사진과 같이 조준 상태일 때는 줌인이 되며 크로스헤어가 보이게 된다.

*사격 애니메이션 Montage에서 "Add Notify" 의 "Play Particle Effect" 를 추가하여, 사격 시 총구에서 불이 나는 효과를 부여해줄 수 있음

 

아이템 습득

아이템 습득의 충돌 판정과 전체적인 과정을 보면 아래와 같다.

충돌 판정의 경우, 플레이어가 아이템 근처에 가면 상호작용을 유도해야하기에, 아이템의 Collision Box는 "Overlap" 의 성질을 갖고 있어야하고, 아이템은 플레이어와 충돌하면 안되기에 아이템의 Mesh는 "Block" 의 성질을 갖고 있어야한다.

(아이템의 성질에 따라 항상 "Block" 일 필요는 없다.)

1. 플레이어가 아이템 근처에 가면 위젯 보임

2. 반경에 들어가면 플레이어가 키를 누름

3. 플레이어가 키를 눌렀을 때, 플레이어와 아이템 사이의 Collision에서 아이템 습득 여부 감지

(플레이어가 아이템을 바라본 채로 습득해야하지, 플레이어가 뒤돌아본 채로 아이템 습득은 불가)

4.아이템 습득

우선은 "Item" 과 "ItemOperator" 라는 "Object Channel" 을 생성해주고, "Item" 과 "ItemOperator" 의 'Default Response' 를 "Ignore" 로 설정해준다.

그리고 "Presets" 에서 해당 Channel들의 설정을, "Item" 같은 경우는 'Visibility', 'Camera', 'Pawn', 'Player' 는 "Block" 으로, 'Operator' 는 "Overlap" 으로 설정해주고, "ItemOperator" 는 'Item' 과만 반응해야하고 충돌은 없어야하기에 'Item' 만 "Overlap" 으로 설정해준다.

Collsion Sphere의 크기는 좀 더 크게 해주는 것이 좋음

플레이어 BP에서 "Item Operator" 역할을 할 "Collision Sphere" 를 하나 추가해주고, "Collision Presets" 을 위에서 만들어준 "Presets" 의 "ItemOperator" 로 지정해준다. 

플레이어 BP의 "Event Graph" 에서 위와 같이 BP를 작성해준다.

"ItemOperator" 가 "Item" 을 확인 가능한지 테스트하기 위한 BP이다.

맵에 아이템의 역할을 할 액터를 배치하고, 해당 액터의  "Collision Presets" 을 위에서 만들어준 "Presets" 의 "Item" 으로 지정해준다. 

플레이 했을 때 둘이 겹치고 위와 같이 메세지가 뜨면 정상적으로 작동하는 것이다.

정상적으로 작동했는지 확인됐으니 본격적으로 아이템 습득을 위한 BP 및 C++ 코드를 작성해보도록 하겠다.

우선은 IMC에서 키 바인딩을 해줘야한다.

늘 하던 것처럼 "IA_Operate" 를 bool 형식으로 생성 후, IMC에서 e키에 바인딩을 해준다.

그리고 아래의 코드를 입력해준다.

PlayerControl.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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"// 추가
    class UInputAction* OperateAction;
 
    void Operate();
 
    void Operate_BP();
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
static ConstructorHelpers::FObjectFinder<UInputAction>InputOperate(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Operate.IA_Operate'")); // IA_Operate 지정
if (InputOperate.Object != nullptr)
{
    OperateAction = InputOperate.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(OperateAction, ETriggerEvent::Started, this, &APlayerCharacterControl::Operate); // 아이템 감지 관련 추가
}
 
void APlayerCharacterControl::Operate()
{
    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();  // 아이템 습득 애니메이션 추가
    if (OperateMontage)
    {
        AnimInstance->Montage_Play(OperateMontage, 1.0f);
    }
}
cs

마무리로 플레이어 BP에 위와 같이 BP를 작성해주면 된다.

 "Item Operator" 역할을 할 "Collision Sphere" 에 "Item" 이 감지되면 'isItem' 변수를 True 값으로 set 하고, 감지되지 않을 경우에는 'isItem' 변수를 False 값으로 set 해준다.

'isItem' 변수가 True 값일 경우에만 e키를 눌러 "Collision Sphere" 과 충돌한 액터를 파괴해주는 BP이다.

*'isItem' 변수를 False 값으로 set 해주지 않을 경우, 아이템과 멀리 떨어져있어도 e키를 눌러 파괴 가능하므로 주의!

하지만 여기서 문제가 발생하는데, 아이템이 플레이어 근처에 없어도 e키를 누르면 아이템을 줍는 애니메이션이 나온다는 것이다.

위와 같이 작성해줄 경우 'isItem' 변수가 True 값일 경우에만 애니메이션이 재생되도록 할 수 있다.

PlayerControl.cpp

1
2
3
4
5
6
7
8
9
void APlayerCharacterControl::Operate()
{
    Operate_BP();
    /*UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance(); // 애니메이션 재생 코드를 비활성화
    if (OperateMontage)
    {
        AnimInstance->Montage_Play(OperateMontage, 1.0f);
    }*/
}
cs

대신 이 경우에는 'PlayerControl.cpp' 에서 코드 수정이 필요하다.

임시로 BP로 작성해준 것이고, 다음 시간에 C++ 코드로 수정하는 법을 배울 예정이다.

 

 

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

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Fill out your copyright notice in the Description page of Project Settings.
 
#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, BlueprintReadWrite) // readwrite 추가
    class USpringArmComponent* SpringArm;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 총알 발사 수정
    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")
    class UInputAction* OperateAction;
    
    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 Operate();
 
private: 
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* SM_Weapon;
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* RifleAimMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* RifleFireMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* OperateMontage;
 
    UPROPERTY(EditAnywhere, Category = "Sound")
    USoundBase* RifleSound; // 새로 추가
 
    void WeaponFireSound(); // 변수가 아니라 UPROPERTY 추가해주면 안됨
 
    UFUNCTION(BlueprintImplementableEvent, Category = "Input")
    void OnFire_BP(); // 정의 없이 선언만 있음; public으로 해야함! 중요!
 
   UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 줌인 시 크로스헤어 추가 생성
   void OnAim_BP(); 
 
   UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 줌아웃 시 크로스헤어 추가를 위해 생성
   void OffAim_BP();
 
   void Operate_BP();
};
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
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#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" // 새로 추가
#include "Kismet/GameplayStatics.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(00-90), FRotator(0-900)); // 스켈레톤에 이동 및 회전 부여
    }
 
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));  // Spring Arm 생성 및 회전 및 각도를 조절하여 Root에 장착
    SpringArm->SetupAttachment(RootComponent);
    SpringArm->SetWorldLocation(FVector(452255.0f));
    SpringArm->TargetArmLength = 200;
    SpringArm->SocketOffset = FVector(23300);
    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;
    }
 
    static ConstructorHelpers::FObjectFinder<UInputAction>InputOperate(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_Operate.IA_Operate'")); // IA_Operate 지정
    if (InputOperate.Object != nullptr)
    {
        OperateAction = InputOperate.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(OperateAction, ETriggerEvent::Started, this, &APlayerCharacterControl::Operate); // 아이템 감지 관련
}
 
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() // 조준 상태 애니메이션 연결
{
    OnAim_BP(); // 추가
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
    animInstance->isAim = true;
}
 
void APlayerCharacterControl::OffAim() // 비조준 상태 애니메이션 연결
{
    OffAim_BP(); // 추가
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
    animInstance->isAim = false;
}
 
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
    OnFire_BP(); // BP 동시실행 중요중요중요
    UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
    if (RifleFireMontage)
    {
        AnimInstance->Montage_Play(RifleFireMontage, 0.233333f);
    }
}
 
void APlayerCharacterControl::Operate()
{
    Operate_BP();
    /*UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
    if (OperateMontage)
    {
        AnimInstance->Montage_Play(OperateMontage, 1.0f);
    }*/
}
 
void APlayerCharacterControl::WeaponFireSound()
{
    if (RifleSound)
    {
        UGameplayStatics::PlaySound2D(this, RifleSound);
    }
}
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
47
48
49
50
51
// Fill out your copyright notice in the Description page of Project Settings.
 
#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;
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Character"// 상체 회전값을 주기 위해 변수 추가
    FRotator AimRotator;
};
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
34
35
36
37
38
39
40
41
42
43
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "PlayerAnimInstance.h"
#include "GameFramework/Character.h"
#include "GameFramework//CharacterMovementComponent.h"
#include "Kismet/KismetMathLibrary.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()); // Velocity: 현재 캐릭터의 속도 벡터, Owner->GetActorRotation(): 현재 액터의 회전값(방향), Velocity가 현재 회전 방형과 얼마나 차이 나는지도 반환
        FRotator DeltaRocator = UKismetMathLibrary::NormalizedDeltaRotator(Owner->GetActorRotation(), Owner->GetControlRotation()); // 카메라 방향과 캐릭터 방향의 차이를 정규화 계산
        (Owner->GetActorRotation(), // 기준이 되는 방향, 캐릭터의 현재 회전
            Owner->GetControlRotation()); // 목표 방향, 플레이어가 컨트롤하는 방향 (카메라 방향)
        AimRotator = FRotator(0, DeltaRocator.Yaw, DeltaRocator.Pitch);
    }
}
cs

SoundAnimNotifyState.h (오늘 수정 및 추가한 내용은 없음)

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.
 
#pragma once
 
#include "CoreMinimal.h"
#include "Animation/AnimNotifies/AnimNotifyState.h"
#include "SoundAnimNotifyState.generated.h"
 
/**
 * 
 */
UCLASS()
class MYPROJECT_API USoundAnimNotifyState : public UAnimNotifyState
{
    GENERATED_BODY()
 
public:
    virtual void NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration) override; // NotifyState가 시작될 때 호출 <- 총소리를 begin에 추가
 
    virtual void NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime) override; // Begin() 함수가 호출된 후 End가 울리기 전까지 반복 실행
 
    virtual void NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation) override; // NotifyState 구간이 끝날 때 호출
};
cs

SoundAnimNotifyState.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
// Fill out your copyright notice in the Description page of Project Settings.
 
 
#include "SoundAnimNotifyState.h"
#include "Engine/GameEngine.h"
#include "Kismet/GameplayStatics.h"
#include "PlayerCharacterControl.h"
 
 
void USoundAnimNotifyState::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
    //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("NotifyBegin"));
    //APlayerCharacterControl* player = Cast<APlayerCharacterControl>(UGameplayStatics::GetPlayerCharacter(GetWorld(), 0));
    //if (player)
    //{
        //player->WeaponFireSound();
    //}  //새로추가한 것
}
 
void USoundAnimNotifyState::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime)
{
    //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("NotifyTick"));
}
 
void USoundAnimNotifyState::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
    //GEngine->AddOnScreenDebugMessage(-1, 1.0f, FColor::Red, TEXT("NotifyEnd"));
}
cs