Unreal Engine/GCC Class (5.5.4)

Unreal Engine 5 배워보기 - 28일차

보별 2025. 4. 28. 15:50
반응형

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

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


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

 

 

28일차 학습 내용

- Camera Shake 추가

- 장탄 수 표시 추가

- 탄약 보급 추가

- Widget C++로 구현

- 언리얼 엔진 프로젝트가 실행되지 않을 경우

 

 

 

Camera Shake 추가

27일 차에서 배운 내용이기에 자세한 설명은 생략하고 과정만 설명하도록 하겠다.

"Camera Shake" 는 사격을 할 때 타격감을 줄 수 있는 요소 중 하나이고, "Camera Shake" 는 총기 별 반동을 줄 때, 물에 들어갔을 때 등 카메라의 흔들림이 필요할 때 사용된다.

BP를 생성할 때 "All Classes" 에 "Legacy Camera Shake" 를 검색하여 생성해준다.

일반 Camera Shake와 다른 점은 "Legacy Camera Shake" 는 UE4 기반이라는 점이다.

"Legacy Camera Shake" BP를 더블클릭 하면 위 그림과 같은 창이 뜬다.

"details" 탭의 "Rot" (회전) , "Loc" (위치) , "FOV" 로 흔들리는 정도를 설정해줄 수 있다.

"Rot" 값을 조절할 경우 플레이어에게 멀미를 줄 가능성이 다분하기에, 주로 "Loc" 값을 주로 조절해준다.

"Inner Radius" 는 카메라 흔들림이 100% 적용되는 거리로 해당 범위 안에 있으면 최대 세기로 적용되고, "Outer Radius" 는 카메라 흔들림이 아예 사라지는 거리로 'Inner Radius' 와 'Outer Radius' 사이에서는 점점 약해지다가 'Outer Radius' 를 넘어가면 흔들림이 완전이 '0' 이 된다.

"Falloff" 는 'Inner Radius' 와 'Outer Radius' 사이에서 흔들림이 약해지는 비율로, 1.0이 기본값이며 수치가 높으면 더 급격하게 약해지고 수치가 낮으면 부드럽게 천천히 약해진다.

우선 지난 시간에 "OnFire_BP();" 주석처리 해줬기에 다시 활성해줘야한다.

"Legacy Camera Shake" BP의 "Rot" 값을 조정해주고, 위 그림과 같이 플레이어 BP에서 BP를 작성해주면 흔들림 효과가 적용되는 것을 확인할 수 있다.

이번엔 위에서 작성한 것을 C++ 코드로 다시 적용시켜보도록 하겠다.

위에서 작성한 BP의 연결을 끊어주고 아래와 같이 코드를 작성해준다.

PlayerControl.h

1
2
3
4
5
6
7
8
9
10
public: // ======================================================================CameraShake
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> RifleFireCameraShakeClass;
 
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> PistolFireCameraShakeClass;
 
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> SMGFireCameraShakeClass;
};
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
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
if (isAim)
{
    if (currentWeapon == EWeaponType::Rifle)
    {
        if (RifleFireCameraShakeClass)
        {
            GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(RifleFireCameraShakeClass);
        }
    }
 
    else if (currentWeapon == EWeaponType::Pistol)
    {
        if (PistolFireCameraShakeClass)
        {
            GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(PistolFireCameraShakeClass);
        }
    }
 
    else if (currentWeapon == EWeaponType::SMG)
    {
        if (SMGFireCameraShakeClass)
        {
            GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(SMGFireCameraShakeClass);
        }
    }
.
.
.}
cs

총기가 3종류인 이유는 필자가 사용할 총기가 3종류이기에 3종류로 나눠서 작성해주었다.

그리고 컴파일 후 플레이어 BP에서 Camera Shake Base를 적용시킨 뒤 실행해보면 Camera Shake가 정상적으로 실행되는 것을 확인할 수 있다.

 

장탄 수 표시 추가

우선 장탄 수를 나타낼 Widget을 만들어야한다.

저번에 제작한 크로스헤어 Widget에 장탄 수를 나타낼 TextBlock을 추가해주도록 하겠다.

위 그림과 같이 Text Block을 넣어준다.

Text Block을 넣고 나서 Anchor 설정을 해주는 것을 잊지 않도록 한다.

Text Block 오른쪽에 있는 총기 그림은 나중에 장비한 총기 상태 별로 띄워줄 Widget이기에 미리 넣어두었다.

이제 장탄 수를 C++ 코드에서 작성하고 작성한 코드를 WBP와 연결해보도록 하겠다.

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

PlayerControl.h

1
2
3
4
5
6
7
8
9
public:    
  UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EWeaponType currentWeapon = EWeaponType::None;
 
  UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TMap<EWeaponType, bool> IsWeaponMap;
 
   UPROPERTY(EditAnywhere, BlueprintReadOnly) // 현재 장탄 수 추가
   int RifleBulletCount = 30;
cs

*장탄 수를 임의의 값 30발로 일단 지정해줌

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
void APlayerCharacterControl::Fire()
{
    if (isAim)
    {
        if (currentWeapon == EWeaponType::Rifle)
        {
            if (RifleBulletCount > 0// 장탄 수가 0보다 클 경우
            {
                RifleBulletCount--; // 장탄 수 감소
            }
            
            if (RifleFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(RifleFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::Pistol)
        {
            if (PistolFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(PistolFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::SMG)
        {
            if (SMGFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(SMGFireCameraShakeClass);
            }
        }
.
.
.
}
cs

 

코드 작성이 끝나면 컴파일을 해준 뒤, WBP로 돌아가 "Text" 옆의 아이콘을 눌러 "Create Binding" 을 해준다.

그러면 위 그림과 같이 BP를 작성할 수 있는 창이 뜨는데, 위 그림과 같이 노드들을 연결해준다.

실행해보면 조준 시 Widget에 장탄 수가 표시되는 것을 확인할 수 있다.

다만 여기서 문제가 있는데 장탄 수가 하나만 줄어들고 그 이상은 줄어들지 않는다는 것이다.

이유는 총기의 장탄 수만 지정되고, 소비하고 난 뒤의 현재 총기의 장탄 수가 지정되지 않았기 때문이다.

그렇기에 아래와 같이 코드를 수정해준다.

PlayerControl.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public:    
   UPROPERTY(EditAnywhere, BlueprintReadOnly)
    EWeaponType currentWeapon = EWeaponType::None;
 
   UPROPERTY(EditAnywhere, BlueprintReadWrite)
    TMap<EWeaponType, bool> IsWeaponMap;
 
   UPROPERTY(EditAnywhere, BlueprintReadOnly) // 총기 별 장탄 수 추가
   int RifleBulletCount = 30;
 
   UPROPERTY(EditAnywhere, BlueprintReadOnly)
   int PistolBulletCount = 8;
 
   UPROPERTY(EditAnywhere, BlueprintReadOnly)
   int SMGBulletCount = 60;
 
   UPROPERTY(EditAnywhere, BlueprintReadOnly) // 현재 총기의 장탄 수 초기 값
   int CurrentBulletCount = 0;
cs

총기 3종류를 넣을 예정이기에 미리 3종류로 나눠서 작성해주었다.

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
void APlayerCharacterControl::Fire()
{
    if (isAim)
    {
        if (currentWeapon == EWeaponType::Rifle && RifleBulletCount > 0) // 아래에 작성했던 조건문을 위에 작성
        {
            CurrentBulletCount = RifleBulletCount;  // 소총 장탄 수를 현재 장탄 수에 대입
            CurrentBulletCount--; // 현재 장탄 수에서 하나씩 감소
                        
            if (RifleFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(RifleFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::Pistol && PistolBulletCount > 0)
        {
            CurrentBulletCount = PistolBulletCount;
            CurrentBulletCount--;
            
            if (PistolFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(PistolFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::SMG && SMGBulletCount > 0)
        {
            CurrentBulletCount = SMGBulletCount;
            CurrentBulletCount--;
            
            if (SMGFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(SMGFireCameraShakeClass);
            }
        }
.
.
.
}
cs

 

마지막으로 WBP로 들어가 "Rifle Bullet Count" 를 "Current Bullet Count" 로 바꿔주도록 한다.

그러면 위 그림과 같이 총기 별로 설정한 장탄 수가 출력되는 것을 확인할 수 있다.

이렇게 할 경우 무기가 추가될 때마다 무기 별 장탄 수를 표시할 Text Block을 일일이 만들어줄 필요가 없기에 매우 효율적이게 된다.

그리고 아래와 같이 코드를 다듬어주겠다.

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
void APlayerCharacterControl::OnAim()
{
    if (IsWeaponMap.Num() > 0)
    {
        
        if (IsWeaponMap[EWeaponType::Rifle] && IsWeaponMap.Contains(EWeaponType::Rifle) && currentWeapon == EWeaponType::Rifle) // Fire()에 있던 조건문 위치 수정
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
            isAim = true;
            animInstance->isAim = true;
            CurrentBulletCount = RifleBulletCount;
      }
// 총기 별 장탄 수를 현재 장탄 수에 대입
        else if (IsWeaponMap[EWeaponType::Pistol] && IsWeaponMap.Contains(EWeaponType::Pistol) && currentWeapon == EWeaponType::Pistol)
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
            isAim = true;
            animInstance->isAim = true;
            CurrentBulletCount = PistolBulletCount; // 추가
        }
        else if (IsWeaponMap[EWeaponType::SMG] && IsWeaponMap.Contains(EWeaponType::SMG) && currentWeapon == EWeaponType::SMG)
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
            isAim = true;
            animInstance->isAim = true;
            CurrentBulletCount = SMGBulletCount; // 추가
        }
    }
.
.
.
}
cs
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
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
    if (isAim)
    {
        if (currentWeapon == EWeaponType::Rifle && RifleBulletCount > 0)
        {
            CurrentBulletCount--;
            RifleBulletCount = CurrentBulletCount; // 줄어든 장탄 수를 저장
                        
            if (RifleFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(RifleFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::Pistol && PistolBulletCount > 0)
        {
            CurrentBulletCount--;
            PistolBulletCount = CurrentBulletCount;
            
            if (PistolFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(PistolFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::SMG && SMGBulletCount > 0)
        {
            //CurrentBulletCount = SMGBulletCount; // OnAim 함수로 이동
            CurrentBulletCount--;
            SMGBulletCount = CurrentBulletCount;
            
            if (SMGFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(SMGFireCameraShakeClass);
            }
        }
.
.
.
}
cs

조준할 때 현재 장비한 총기에 따라 해당 총기의 잔탕 수를 불러오도록 바꿔주었고, 사격 시에 잔탕 수가 줄어들도록 분리해서 설정해주었다.

 

탄약 보급 추가

장탄 수를 추가해줬으니 이번에는 장탄을 다 사용했을 경우 탄약을 보급하는 기능을 추가해보도록 하겠다.

우선은 아이템 BP를 제작해줘야한다.

저번에 생성했던 총기 BP를 복사한 뒤 스태틱메시만 바꿔주면 된다.

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

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
void APlayerCharacterControl::Operate() // 아이템 습득 관리
{
    if (!isItem) // 반대 연산자
    {
        return;
    }
    
    .
    .
    .
        GEngine->AddOnScreenDebugMessage(-110.0f, FColor::Red, FString::Printf(TEXT("%s"), *name));
 
        if (name==TEXT("BP_Pistol"))
        {
            IsWeaponMap[EWeaponType::Pistol] = true;
            //currentWeapon = EWeaponType::Pistol;
        }
        else if(name == TEXT("SMG"))
        {
            IsWeaponMap[EWeaponType::SMG] = true;
            //currentWeapon = EWeaponType::SMG;
        }
        else if (name == TEXT("Rifle"))
        {
            IsWeaponMap[EWeaponType::Rifle] = true;
            //currentWeapon = EWeaponType::Rifle;
        }
        
        else if (name == TEXT("BP_RifleBullet"))  // 라이플 탄약 확인 및 추가
        {
            RifleBulletCount += 30;  // 탄약 습득 시 장탄 30발 추가
            if (RifleBulletCount >= 200)  // 장탄이 200개 이상으로 넘어갈 경우
            {
                RifleBulletCount = 200;  // 200으로 고정
            }
        }
        else if (name == TEXT("BP_PistolBullet")) // 권총 탄약 확인 및 추가
        {
            PistolBulletCount += 8;
            if (RifleBulletCount >= 120)
            {
                RifleBulletCount = 120;
            }
        }
        else if (name == TEXT("BP_SMGBullet")) // SMG 탄약 확인 및 추가
        {
            SMGBulletCount += 30;
            if (SMGBulletCount >= 200)
            {
                SMGBulletCount = 200;
            }
        }
.
.
.
}
cs

이렇게 할 경우 아래 그림과 같이 탄약 보급 아이템을 습득 시 탄약이 보급되는 것을 확인할 수 있다.

 

Widget C++로 구현

이번엔 기존에 BP에서 적용했던 Widget을 C++로 다시 적용시켜보도록 하겠다.

아래와 같이 코드를 추가해주면 된다.

PlayerControl.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "Blueprint/UserWidget.h" // 추가
 
public: // ======================================================================Function
.
.
.    
 
    void InitFindWeaponMesh();
    void InitIsWeaponMap();
    void InitFindInput();
    void InitFindWidget(); // 추가
 
public: // ======================================================================Widget
 
    UPROPERTY()
    class UUserWidget* HUDWidget;
 
    UPROPERTY(EditAnywhere, Category = "Widget")
    TSubclassOf<class UUserWidget> HUDClass;
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
#include "Blueprint/UserWidget.h" // 위젯 추가
 
APlayerCharacterControl::APlayerCharacterControl()
{
.
.
.
    InitFindWeaponMesh();
    InitIsWeaponMap();
    InitFindInput();
    InitFindWidget(); // 초기화 추가
}
 
 
void APlayerCharacterControl::OnAim()
{
    if (IsWeaponMap.Num() > 0)
    {
        if (!HUDWidget && HUDClass)  // HUD Widget가 없고, HUD Class가 있을 경우
        {
            HUDWidget = CreateWidget(GetWorld()->GetFirstPlayerController(), HUDClass);  // HUD Widget 생성
        }
        
        if (HUDWidget)
        {
            HUDWidget->AddToViewport();  // HUD Widget 출력
        }
.
.
.
}
 
void APlayerCharacterControl::OffAim()
{
    if (HUDWidget)  // HUD Widget이 있을 경우
    {
        HUDWidget->RemoveFromViewport(); // HUD Widget 제거
    }
    OffAim_BP();
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
    isAim = false// 조준 불가능
    animInstance->isAim = false;
}
 
void APlayerCharacterControl::InitFindWidget()
{
    static ConstructorHelpers::FClassFinder<UUserWidget>HUD(TEXT("WidgetBlueprint'/Game/UMG/WB_HUD.WB_HUD_C'"));
    
    if (HUD.Succeeded())
    {
        HUDClass = HUD.Class;
    }
}
cs

Widget을 C++에서 추가해줄 경우 Crash가 자주 나므로 주의해야한다.

그렇기에 반드시 위 코드와 같이 항상 조건문을 달아서 안전장치를 걸어줘야한다.

 

언리얼 엔진 프로젝트가 실행되지 않을 경우

오늘 수업 종료 후 잠시 확인할 내용이 있어 수업을 진행했던 프로젝트를 실행해보았는데 실행되지 않았다.

프로젝트를 닫기 전까지 정상작동하는 것을 확인했는데, 닫고 다시 실행하려고 하니 73%쯤에서 멈춰서 "Blueprinting Compiling" 이라는 문구가 뜨며 실행되지 않았다.

이 경우 BP와 C++ 코드가 충돌이 일어나 제대로 실행되지 않는 경우인데, 대부분의 경우 복구가 힘들다고 한다.

하지만 강사님의 도움을 받아 복구하는데 성공했고, 복구했던 방법을 그대로 공유해보고자 한다.

제일 먼저 해봐야할 것은 가장 최근에 넣은 항목들부터 하나씩 주석처리를 해보는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void APlayerCharacterControl::OffAim()
{
    if (HUDWidget)  // HUD Widget이 있을 경우
    {
        HUDWidget->RemoveFromViewport();  // HUD Widget 제거
    }
    OffAim_BP();
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
    isAim = false// 조준 불가능
    animInstance->isAim = false;
}
 
//void APlayerCharacterControl::InitFindWidget()
//{
    //static ConstructorHelpers::FClassFinder<UUserWidget>HUD(TEXT("WidgetBlueprint'/Game/UMG/WB_HUD.WB_HUD_C'"));
    
    //if (HUD.Succeeded())
    //{
        //HUDClass = HUD.Class;
    //}
//}
cs

해당 프로젝트의 Visual Studio를 실행시키고 위와 같이 최근 추가된 것부터 하나씩 주석처리를 해가며 Generate 해보는 것이다.

언리얼 프로젝트 파일 우클릭 후 Genrate

이런 식으로 하다보면 어떤 코드가 문제인지 알 수 있다.

이렇게 오늘 작성한 모든 코드들을 주석 처리하고 Generate 해봤으나 복구되지 않았다.

이런 식으로 해도 되지 않는다면 "Binaires" 와 "Saved" 폴더를 지워보는 것이다.

해당 일자에 추가된 모든 항목을 우선 주석처리한 뒤, 프로젝트 폴더로 이동하여 위 그림에 나와있는 것처럼 "Binaires" 와 "Saved" 폴더를 삭제하고 다시 Generate 해보는 것이다.

경고문이 뜰 경우 'Ok' 를 눌러주면 된다.

이렇게 하니 프로젝트가 재실행되었다.

코드 주석을 하나씩 풀어가며 이유를 찾아봤는데, 문제는 역시나 Widget을 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
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
189
190
191
192
193
// 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 "Blueprint/UserWidget.h"
#include "PlayerCharacterControl.generated.h"
 
UENUM() // 아이템 목록 Enum
enum class EWeaponType : uint8
{
    None UMETA(DisplayName = "None"),
    Pistol UMETA(DisplayName = "Pistol"),
    SMG UMETA(DisplayName = "SMG"),
    Rifle UMETA(DisplayName = "Rifle"),
    
};
 
 
 
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: // ======================================================================Camera
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // readwrite 추가
    class USpringArmComponent* SpringArm;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 총알 발사 수정
    class UCameraComponent* Camera;
 
private: // ======================================================================Input
    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")
    class UInputAction* RifleChangeAction;
 
    UPROPERTY(VisibleAnywhere, Category = "Input")
    class UInputAction* PistolChangeAction;
    
    UPROPERTY(VisibleAnywhere, Category = "Input")
    float mousespeed = 30.0f;
 
public:
    UFUNCTION(BlueprintImplementableEvent, Category = "Input")
    void OnFire_BP(); // 정의 없이 선언만 있음; public으로 해야함! 중요!
 
    UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 줌인 시 크로스헤어 추가 생성
    void OnAim_BP();
 
    UFUNCTION(BlueprintImplementableEvent, Category = "Input"// 줌아웃 시 크로스헤어 추가를 위해 생성
    void OffAim_BP();
 
    UFUNCTION(BlueprintImplementableEvent, Category = "Input")
    void Operate_BP();
 
public: // ======================================================================Function
    void Move(const FInputActionValue& value);
    void Look(const FInputActionValue& value);
    void OnAim();
    void OffAim();
    void Fire();
    void Operate();
    void RifleMode();
    void PistolMode();
 
    void InitFindWeaponMesh();
    void InitIsWeaponMap();
    void InitFindInput();
    void InitFindWidget();
 
 
public: // ======================================================================Anim
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* RifleAimMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* PistolAimMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* RifleFireMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* PistolFireMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* OperateMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* WeaponChangeInMontage;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Animation")
    UAnimMontage* WeaponChangeOutMontage;
 
public: // ======================================================================Sound
    UPROPERTY(EditAnywhere, Category = "Sound")
    USoundBase* RifleSound;
 
    UPROPERTY(EditAnywhere, Category = "Sound")
    USoundBase* HitSound;
 
    UPROPERTY(EditAnywhere, Category = "Sound")
    USoundBase* WeaponChangeSound; // 새로 추가
 
    void WeaponFireSound(); // 변수가 아니라 UPROPERTY 추가해주면 안됨
 
private: // ======================================================================Weapon
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* SM_RifleWeapon;
 
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* SM_PistolWeapon;
 
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* SM_SMGWeapon;
 
public:    
    UPROPERTY(EditAnywhere, BlueprintReadOnly) // 초기 상태를 아무것도 들고 있지 않은 상태로 함
    EWeaponType currentWeapon = EWeaponType::None;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite) // 아이템 목록 map 추가
    TMap<EWeaponType, bool> IsWeaponMap;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int RifleBulletCount = 30;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int PistolBulletCount = 8;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int SMGBulletCount = 60;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    int CurrentBulletCount = 0;
 
public: // ======================================================================PlayerState
    UPROPERTY(EditAnywhere, BlueprintReadWrite) 
    bool isItem = false// 아이템 확인 변수 추가
    bool isAim = false;
 
public: // ======================================================================CameraShake
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> RifleFireCameraShakeClass;
 
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> PistolFireCameraShakeClass;
 
    UPROPERTY(EditAnywhere)
    TSubclassOf<class UCameraShakeBase> SMGFireCameraShakeClass;
 
public: // ======================================================================Widget
 
    UPROPERTY()
    class UUserWidget* HUDWidget;
 
    UPROPERTY(EditAnywhere, Category = "Widget")
    TSubclassOf<class UUserWidget> HUDClass;
};
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
// 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" // 새로 추가 사운드 추가할 때 소리 재생 시 필수
#include "Engine/DamageEvents.h" // 아이템 습득 시 추가
#include "Blueprint/UserWidget.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);
    }
    
    InitFindWeaponMesh();
    InitIsWeaponMap();
    InitFindInput();
    InitFindWidget();
}
 
// 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);
        }
 
    }
 
    //SM_Weapon->bHiddenInGame = true;
    //SM_Weapon->IsVisible();
    SM_RifleWeapon->SetVisibility(false);
    SM_PistolWeapon->SetVisibility(false);
 
    /*if (!HUDWidget && HUDClass)
    {
        HUDWidget = CreateWidget(GetWorld()->GetFirstLocalPlayerFromController(), HUDClass);
    }*/
}
 
// 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); // 아이템 감지 관련
    EnhancedInputComponent->BindAction(RifleChangeAction, ETriggerEvent::Started, this, &APlayerCharacterControl::RifleMode);
    EnhancedInputComponent->BindAction(PistolChangeAction, ETriggerEvent::Started, this, &APlayerCharacterControl::PistolMode);
}
 
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() // 조준 상태 애니메이션 연결
{
    if (IsWeaponMap.Num() > 0)
    {
        if (!HUDWidget && HUDClass)
        {
            HUDWidget = CreateWidget(GetWorld()->GetFirstPlayerController(), HUDClass);
        }
        
        if (HUDWidget)
        {
            HUDWidget->AddToViewport();
        }
        if (IsWeaponMap[EWeaponType::Rifle] && IsWeaponMap.Contains(EWeaponType::Rifle) && currentWeapon == EWeaponType::Rifle) // 라이플 소유 및 장비 여부 확인
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
            isAim = true// 조준 가능
            animInstance->isAim = true;
            CurrentBulletCount = RifleBulletCount; // 추가
            // 무기 상태를 AnimInstance 전달 필요
        }
        else if (IsWeaponMap[EWeaponType::Pistol] && IsWeaponMap.Contains(EWeaponType::Pistol) && currentWeapon == EWeaponType::Pistol) // 권총 소유 및 장비 여부 확인
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
            isAim = true// 조준 가능
            animInstance->isAim = true;
            CurrentBulletCount = PistolBulletCount;
        }
        else if (IsWeaponMap[EWeaponType::SMG] && IsWeaponMap.Contains(EWeaponType::SMG) && currentWeapon == EWeaponType::SMG) // SMG 소유 및 장비 여부 확인
        {
            OnAim_BP();
            UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
            isAim = true// 조준 가능
            animInstance->isAim = true;
            CurrentBulletCount = SMGBulletCount;
        }
    }
    
        //if (IsWeaponMap[EWeaponType::Rifle] && IsWeaponMap.Contains(EWeaponType::Rifle) && currentWeapon == EWeaponType::Rifle) // 아이템 습득 확인 // 이렇게 해도 되지만 일일이 추가해야하기에 비효율적
    //{
    //    //OnAim_BP();
    //    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
    //    isAim = true; // 조준 가능
    //    animInstance->isAim = true;
    //}
    //else if (IsWeaponMap[EWeaponType::Pistol] && IsWeaponMap.Contains(EWeaponType::Pistol) && currentWeapon == EWeaponType::Pistol) //am10
    //{
    //    //OnAim_BP();
    //    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance()); // 플레이어에게 값 조정으로 변화하는거 체크용
    //    isAim = true; // 조준 가능
    //    animInstance->isAim = true;
    //}
}
 
void APlayerCharacterControl::OffAim() // 비조준 상태 애니메이션 연결
{
    if (HUDWidget)
    {
        HUDWidget->RemoveFromViewport();
    }
    OffAim_BP();
    UPlayerAnimInstance* animInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
    isAim = false// 조준 불가능
    animInstance->isAim = false;
}
 
void APlayerCharacterControl::Fire() // 사격 상태 애니메이션 연결
{
    if (isAim)
    {
        if (currentWeapon == EWeaponType::Rifle && RifleBulletCount > 0)
        {
            CurrentBulletCount--;
            RifleBulletCount = CurrentBulletCount; // 줄어든 장탄 수를 저장
                        
            if (RifleFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(RifleFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::Pistol && PistolBulletCount > 0)
        {
            CurrentBulletCount--;
            PistolBulletCount = CurrentBulletCount;
            
            if (PistolFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(PistolFireCameraShakeClass);
            }
        }
 
        else if (currentWeapon == EWeaponType::SMG && SMGBulletCount > 0)
        {
            //CurrentBulletCount = SMGBulletCount;
            CurrentBulletCount--;
            SMGBulletCount = CurrentBulletCount;
            
            if (SMGFireCameraShakeClass)
            {
                GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(SMGFireCameraShakeClass);
            }
        }
 
        OnFire_BP();
        FHitResult Hit;
        FVector StartTrace = Camera->GetComponentLocation();
        FVector EndTrace = StartTrace + (Camera->GetForwardVector() * 1000);
        DrawDebugLine(GetWorld(), StartTrace, EndTrace, FColor::Green, false5.0f);
        if (GetWorld()->LineTraceSingleByChannel(Hit, StartTrace, EndTrace, ECC_GameTraceChannel6))
        {
            Hit.GetActor()->Destroy();
        }
 
        //OnFire_BP(); // BP 동시실행 중요중요중요
        UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
        if (currentWeapon == EWeaponType::Rifle)
        {
            if (RifleFireMontage)
            {
                AnimInstance->Montage_Play(RifleFireMontage, 0.233333f);
            }
            FTimerHandle FireSoundDelayHandle; // 추가
            GetWorld()->GetTimerManager().SetTimer(FireSoundDelayHandle, this,
                &APlayerCharacterControl::WeaponFireSound,
                0.2f,
                false
            );
        }
        
        else if (currentWeapon == EWeaponType::Pistol)
        {
            if (PistolFireMontage)
            {
                AnimInstance->Montage_Play(PistolFireMontage);
            }
            FTimerHandle FireSoundDelayHandle; // 추가
            GetWorld()->GetTimerManager().SetTimer(FireSoundDelayHandle, this,
                &APlayerCharacterControl::WeaponFireSound,
                0.2f,
                false
            );
        }
    
    }
}
 
void APlayerCharacterControl::Operate() // 아이템 습득 관리
{
    if (!isItem) // 반대 연산자
    {
        return;
    }
    
    Operate_BP();
 
    FVector StartLocation = GetMesh()->GetSocketLocation("hand_r");
    FVector EndLocation = StartLocation;
    float SphereRadius = 70.0f;
    FHitResult HitResult;
    FCollisionQueryParams Params;
    Params.AddIgnoredActor(this); // 자기자신을 무시
    bool isHit = GetWorld()->SweepSingleByChannel(HitResult, StartLocation, EndLocation, FQuat::Identity, ECC_GameTraceChannel9, FCollisionShape::MakeSphere(SphereRadius), Params);
    /*DrawDebugSphere(GetWorld(), StartLocation, SphereRadius, 12, FColor::Red, false, 1.0f);*/
    if (isHit) // 습득한 아이템과 map의 아이템 일치 여부 확인
    {
        FString name = HitResult.GetActor()->GetName();
 
        name = name.LeftChop(1);
        name = name.LeftChop(name.Len()-name.Find(TEXT("_C_"))); 
        /*if (name.Find(TEXT("BP_")) == 0)
        {
            name = name.RightChop(3);
        }*/
        /*name = name.RightChop(name.Len()-name.Find(TEXT("BP_")));
        name = name.RightChop(3);*/
        /*
        * 만약, name에 BP_가 존재한다면,  -> if
        * 앞에서부터 3개를 잘라라  -> RightChop
        * 
        */
        GEngine->AddOnScreenDebugMessage(-110.0f, FColor::Red, FString::Printf(TEXT("%s"), *name));
 
        if (name==TEXT("BP_Pistol")) // 이름 같은지 체크
        {
            IsWeaponMap[EWeaponType::Pistol] = true;
            //currentWeapon = EWeaponType::Pistol;
        }
        else if(name == TEXT("SMG"))
        {
            IsWeaponMap[EWeaponType::SMG] = true;
            //currentWeapon = EWeaponType::SMG;
        }
        else if (name == TEXT("Rifle"))
        {
            IsWeaponMap[EWeaponType::Rifle] = true;
            //currentWeapon = EWeaponType::Rifle;
        }
        
        else if (name == TEXT("BP_RifleBullet"))
        {
            RifleBulletCount += 30;
            if (RifleBulletCount >= 200)
            {
                RifleBulletCount = 200;
            }
        }
        else if (name == TEXT("BP_PistolBullet"))
        {
            PistolBulletCount += 8;
            if (RifleBulletCount >= 120)
            {
                RifleBulletCount = 120;
            }
        }
        else if (name == TEXT("BP_SMGBullet"))
        {
            SMGBulletCount += 30;
            if (SMGBulletCount >= 200)
            {
                SMGBulletCount = 200;
            }
        }
 
        FTimerHandle HitDelayHandle;
        GetWorld()->GetTimerManager().SetTimer(HitDelayHandle,
            FTimerDelegate::CreateLambda([HitResult]()
                {
                    HitResult.GetActor()->Destroy();
                }),
            0.5f,
            false
        );
    }
 
    /*UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
    if (OperateMontage)
    {
        AnimInstance->Montage_Play(OperateMontage, 1.0f);
    }*/
}
 
void APlayerCharacterControl::RifleMode() // 라이플 장비
{
    if (IsWeaponMap.Num() > 0 && currentWeapon == EWeaponType::Pistol)
    {
 
        /*if (IsWeaponMap.Contains(EWeaponType::Rifle) && IsWeaponMap.Num() > 0 && IsWeaponMap[EWeaponType::Rifle] == true)
        {}*/
            if (WeaponChangeInMontage)
            {
                UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
                float InMontageDuration = AnimInstance->Montage_Play(WeaponChangeInMontage); // 애니메이션 순차 재생을 위해 무기 넣는 애니메이션 재생 후 무기 꺼내는 애니메이션 진행하도록 함
 
                FTimerHandle HideHandle;
                GetWorld()->GetTimerManager().SetTimer(HideHandle,
                    FTimerDelegate::CreateLambda([this]()
                        {
                            if (WeaponChangeSound)
                            {
                                UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                                //SM_Weapon->bHiddenInGame = false;
                                /*SM_Weapon->IsVisible(); */ // 상태여부
                                SM_PistolWeapon->SetVisibility(false);
                            }
                            if (WeaponChangeOutMontage)
                            {
                                UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
                                float OutMontageDuration = AnimInstance->Montage_Play(WeaponChangeOutMontage);
 
                                FTimerHandle ShowHandle;
                                GetWorld()->GetTimerManager().SetTimer(ShowHandle,
                                    FTimerDelegate::CreateLambda([this]()
                                        {
                                            if (WeaponChangeSound)
                                            {
                                                UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                                                //SM_Weapon->bHiddenInGame = false;
                                                /*SM_Weapon->IsVisible(); */ // 상태여부
                                                SM_RifleWeapon->SetVisibility(true);
                                                UPlayerAnimInstance* playeranimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
                                                playeranimInstance->WeaponTypeCount = 1;
                                                currentWeapon = EWeaponType::Rifle;
                                            }
                                        }),
                                    OutMontageDuration,
                                    false
                                );
                            }
 
                        }),
                    InMontageDuration,
                    false
                );
            }
            else if (IsWeaponMap.Contains(EWeaponType::Rifle) && IsWeaponMap.Num() > 0 && IsWeaponMap[EWeaponType::Rifle] == true)
            {
                if (WeaponChangeOutMontage)
                {
                    UAnimInstance* AnimInstace = GetMesh()->GetAnimInstance();
                    AnimInstace->Montage_Play(WeaponChangeOutMontage);
                }
                FTimerHandle HitDelayHandle;
                GetWorld()->GetTimerManager().SetTimer(HitDelayHandle,
                    FTimerDelegate::CreateLambda([this]()
                        {
                            if (WeaponChangeSound)
                            {
                                UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                                SM_PistolWeapon->SetVisibility(true);
                                UPlayerAnimInstance* playeranimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
                                playeranimInstance->WeaponTypeCount = 1;
                                currentWeapon = EWeaponType::Rifle;
                            }
                        }),
                    0.5f,
                    false
                );
            }
            else // 총이 없는 경우
            {
                UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
            }
        
    }
}
 
void APlayerCharacterControl::PistolMode() // 권총 장비
{
    if (IsWeaponMap.Num() > 0 && currentWeapon == EWeaponType::Rifle)
    {
        if (WeaponChangeInMontage) // 무기 집어넣는 동작을 먼저 해야하기에 먼저 삽입
        {
            UAnimInstance* AnimInstace = GetMesh()->GetAnimInstance();
            float InMontageDuration = AnimInstace->Montage_Play(WeaponChangeInMontage);
 
            FTimerHandle HideHandle;
            GetWorld()->GetTimerManager().SetTimer(HideHandle,
                FTimerDelegate::CreateLambda([this]()
                    {
                        if (WeaponChangeSound)
                        {
                            UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                            SM_RifleWeapon->SetVisibility(false);
                        }
 
                        if (WeaponChangeOutMontage) // 무기 집어넣었으니 무기를 꺼내는 동작 삽입
                        {
                            UAnimInstance* AnimInstace = GetMesh()->GetAnimInstance();
                            float OutMontageDuration = AnimInstace->Montage_Play(WeaponChangeOutMontage);
 
                            FTimerHandle ShowHandle;
                            GetWorld()->GetTimerManager().SetTimer(ShowHandle,
                                FTimerDelegate::CreateLambda([this]()
                                    {
                                        if (WeaponChangeSound)
                                        {
                                            UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                                            SM_PistolWeapon->SetVisibility(true);
                                            UPlayerAnimInstance* playeranimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
                                            playeranimInstance->WeaponTypeCount = 2;
                                            currentWeapon = EWeaponType::Pistol;
                                        }
                                    }),
                                OutMontageDuration,
                                false
                            );
                        }
                    }),
                InMontageDuration,
                false
            );
        }
    }
 
    else if (IsWeaponMap.Contains(EWeaponType::Pistol) && IsWeaponMap.Num() > 0 && IsWeaponMap[EWeaponType::Pistol] == true)
    {
        if (WeaponChangeOutMontage)
        {
            UAnimInstance* AnimInstace = GetMesh()->GetAnimInstance();
            AnimInstace->Montage_Play(WeaponChangeOutMontage);
        }
        FTimerHandle HitDelayHandle;
        GetWorld()->GetTimerManager().SetTimer(HitDelayHandle,
            FTimerDelegate::CreateLambda([this]()
                {
                    if (WeaponChangeSound)
                    {
                        UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
                        SM_PistolWeapon->SetVisibility(true);
                        UPlayerAnimInstance* playeranimInstance = Cast<UPlayerAnimInstance>(GetMesh()->GetAnimInstance());
                        playeranimInstance->WeaponTypeCount = 2;
                        currentWeapon = EWeaponType::Pistol;
                    }
                }),
            0.5f,
            false
        );
    }
    else//총이 없는경우++
    {
        UGameplayStatics::PlaySound2D(this, WeaponChangeSound);
    }
}
 
void APlayerCharacterControl::WeaponFireSound() // 사격 소리
{
    if (RifleSound)
    {
        UGameplayStatics::PlaySound2D(this, RifleSound);
 
        //FTimerHandle HitSoundDelayHandle; // 타이머를 제어하기 위한 핸들 변수 (타이머 취소, 타이머 상태 확인) 선언
        //GetWorld()->GetTimerManager().SetTimer(HitSoundDelayHandle, // Get world() 현재 객체가 속한 월드를 가져옴 / GetTimerManager() 타이머를 관리하는 시스템 / SetTimer() 타이머를 설정하고 일정 시간이 지나면 특정 작업을 실행
        //    FTimerDelegate::CreateLambda([this]() { // FTimerDelegate::CreateLambda() 람다변수를 타이머에 연결 가능한 델리게이트로 변환 / [this] 현재 클래스의 멤버 변수 함수에 접근하기 위한 캡쳐 / [=] 모든 외부에 있는 변수 값을 사용함 / () 안에는 내가 원하는 매개변수 삽입가능 / 오류 발생으로 코드 수정
        //        UGameplayStatics::PlaySound2D(this, HitSound);
        //    }),
        //    1.0f, // 1초 뒤
        //    false // 반복 x
        //    ); //소리가 2번 재생되므로 주석처리
        // 람다함수 간단한 함수를 선언 없이 즉석해서 만드는 방식 / [캡쳐](매개변수){실행코드};
    }
}
 
void APlayerCharacterControl::InitFindWeaponMesh()
{
    SM_RifleWeapon = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RifleWeaponMesh")); // 무기 소켓 추가
    SM_RifleWeapon->SetupAttachment(GetMesh());
    static ConstructorHelpers::FObjectFinder<UStaticMesh>rifleWeaponMesh(TEXT("/Script/Engine.StaticMesh'/Game/Asset/FPS_Weapon_Bundle/Weapons/Meshes/AR4/SM_AR4.SM_AR4'")); // 무기 SM 가져옴
    if (rifleWeaponMesh.Succeeded()) // SM 체크
    {
        SM_RifleWeapon->SetStaticMesh(rifleWeaponMesh.Object);
 
    }
    SM_RifleWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("RifleWeapon_Socket")); // 소켓에 SM 장착
 
    SM_PistolWeapon = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PistolWeaponMesh"));
    SM_PistolWeapon->SetupAttachment(GetMesh());
    static ConstructorHelpers::FObjectFinder<UStaticMesh> pistolweaponMesh(TEXT("/Script/Engine.StaticMesh'/Game/ATPS/Assets/Weapons/Pistol/Mesh/StaticMesh.StaticMesh'"));
    if (pistolweaponMesh.Succeeded())
    {
        SM_PistolWeapon->SetStaticMesh(pistolweaponMesh.Object);
    }
    SM_PistolWeapon->AttachToComponent(GetMesh(), FAttachmentTransformRules::KeepRelativeTransform, TEXT("PistolWeapon_Socket"));
}
 
void APlayerCharacterControl::InitIsWeaponMap()
{
    IsWeaponMap.Add(EWeaponType::Pistol, false); // 아이템 확인
    IsWeaponMap.Add(EWeaponType::SMG, false);
    IsWeaponMap.Add(EWeaponType::Rifle, false);
}
 
void APlayerCharacterControl::InitFindInput()
{
    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;
    }
 
    static ConstructorHelpers::FObjectFinder<UInputAction>InputRifleChange(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_RifleChange.IA_RifleChange'")); // IA_RifleChange 지정
    if (InputRifleChange.Object != nullptr)
    {
        RifleChangeAction = InputRifleChange.Object;
    }
 
 
    static ConstructorHelpers::FObjectFinder<UInputAction>InputPistolChange(TEXT("/Script/EnhancedInput.InputAction'/Game/Input/IA_PistolChange.IA_PistolChange'")); // IA_PistolChange 지정
    if (InputPistolChange.Object != nullptr)
    {
        PistolChangeAction = InputPistolChange.Object;
    }
}
 
void APlayerCharacterControl::InitFindWidget()
{
    static ConstructorHelpers::FClassFinder<UUserWidget>HUD(TEXT("WidgetBlueprint'/Game/UMG/WB_HUD.WB_HUD_C'"));
    
    if (HUD.Succeeded())
    {
        HUDClass = HUD.Class;
    }
}
 
cs