[96. Pause Menu #5]

 

버튼에 이벤트를 바인딩 시킨다.

 

이런 구조가 될 것이다.

 

[97. Pause Menu #6]

 

저장시 현재 레벨 이름도 같이 저장해서 로드할 때 해당 레벨로 변환시켜주면 일반적으로 알고있는 세이브/로드 같은 기능을 할 것이다.

 

GetWorld()->GetMapName(); // 현재 맵(레벨)이름을 가져옴. 레벨 스트리밍 접두사가 붙는다
GetWorld()->StreamingLevelPrefix; // 레벨 스트리밍 접두사를 가져온다

 

현재 맵(레벨) 이름을 받아오는것은 위처럼 하면 되지만 레벨 스트리밍 접두어가 붙기때문에 접두어를 제거하고 저장해야 한다.

 

레벨이 바뀌면 BeginPlay가 호출되므로 LoadGame을 이곳에서 해주면 된다.

 

[98. Finishing Up]

 

내용 없음.

 

[99. Removing All Unused Assets]

 

프로젝트를 완성시키고 배포시키기 전에 사용하지 않은 애셋들의 정리 등을 통해 프로젝트의 사이즈를 줄일 필요가 있다.

 

  1. 별개의 폴더를 만들고 완성한 프로젝트와 같은 이름의 빈 프로젝트를 해당 폴더에 만든다.
  2. Config 폴더를 덮어쓰기한다.
  3. Intermediate 폴더를 삭제한다.
  4. 소스코드를 붙여넣는다(C#까지)
  5. generate visual studio project file을 수행한다.
  6. 완성한 프로젝트를 열고 레벨을 migrate한다.

 

C++ 코드로 애셋을 로드하는것은 레벨을 migrate 할 때 잡아내지 못할 것이라 생각된다.

위와 같이 프로젝트의 크기를 줄일 때 해당 부분을 고려해야 할 것이다.

 

[100. Packaging the Game]

 

 

[정리]

 

  • 프로젝트를 완성하면 불필요한 리소스를 제거해서 크기를 줄여야 한다.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Level Changing and Saving the Game #1  (0) 2022.09.15
[UE C++] Combat #10  (0) 2022.09.15
[UE C++] Combat #9  (0) 2022.09.14
[UE C++] Combat #8  (0) 2022.09.14
[UE C++] Combat #7  (0) 2022.09.13

[89. Changing Levels in Game]

 

UGameplayStatics::OpenLevel을 이용해서 레벨을 바꿀 수 있다.

 

UGameplayStatics::OpenLevel(World, LevelName);

 

[90. Saving the Game]

 

레벨이 전환된다는 것은 모든 액터들이 새롭게 스폰되는것과 같다.

그래서 이동하기 전의 레벨에서 유지했던 정보들(체력, 무기 등등)은 모두 초기화가 되버린다.

그렇기 때문에 다음 레벨로 넘어가기 전에 현재 상태를 저장했다가 레벨로 넘어갔을 때 다시 갱신할 필요가 있다.

 

현재 상태를 세이브하기 위해서는 SaveGame 클래스가 필요하다.

SaveGame 클래스를 상속받는 클래스를 만들어서 세이브해야 할 정보들을 미리 선언해둔다.

 

// SaveGame 인스턴스 생성
UFirstSaveGame* SaveGameInstance = Cast<UFirstSaveGame>(UGameplayStatics::CreateSaveGameObject(UFirstSaveGame::StaticClass()));

// todo: 데이터 저장
// ex) SaveGameInstance->CharacterStats.Health = Health;

// 슬롯에 저장
UGameplayStatics::SaveGameToSlot(SaveGameInstance, SaveGameInstance->PlayerName, SaveGameInstance->UserIndex);

 

// SaveGame 인스턴스 생성
UFirstSaveGame* LoadGameInstance = Cast<UFirstSaveGame>(UGameplayStatics::CreateSaveGameObject(UFirstSaveGame::StaticClass()));

// 슬롯에서 불러옴
LoadGameInstance = Cast<UFirstSaveGame>(UGameplayStatics::LoadGameFromSlot(LoadGameInstance->PlayerName, LoadGameInstance->UserIndex));

// todo: 데이터 갱신

 

세이브, 로드 둘다 SaveGame 인스턴스를 만드는것 까지는 똑같다.

세이브는 인스턴스 생성 -> 데이터 저장 -> 슬롯에 인스턴스 저장

로드는 인스턴스 생성 -> 슬롯에서 인스턴스 로드 -> 데이터 갱신 순으로 이뤄진다.

 

한가지 고려 사항이 있다.

레벨을 이동하기 전에 저장한 위치는 레벨 이동 후에도 유효하리라는 보장이 없다. 맵이 다르기 때문이다.

 

 

그리고 사용시 반드시 알아야할 주의점이 몇가지 있다.

우선 저장하는 데이터들은 반드시 UPROPERTY 매크로가 붙어있어야 한다. 그렇지 않으면 데이터가 저장되지 않는다.

 

두번째로 장착 무기와 같은 포인터는 값이 아닌 주소가 저장되는 문제가 있다.

이 문제는 다음 장에서 해결해 보도록 한다.

 

[91. Saving the Weapon]

 

무기의 컨테이너 역할을 하는 새로운 액터를 만들어서 해결해 보려고 한다.

 

무기별로 블루프린트를 만들고 그 무기들을 담을 C++ 클래스를 블루프린트 클래스로 파생시킨다.

관리는 Map으로 할 것이다. Key는 무기의 이름, Value는 무기의 블루프린트이다.

 

UPROPERTY(EditDefaultsOnly, Category="SaveData")
TMap<FString, TSubclassOf<class AWeapon>> WeaponMap;

 

위의 구조가 구현의 핵심이다. 블루프린트로 관리하는 것이다.

 

Key 검색을 위해서 무기마다 이름을 지정해주고 무기 컨테이너에 각 블루프린트를 넣어준다.

ItemStorage가 4개의 무기 블루프린트를 가지고 Save/Load할 때 ItemStorage에 접근해서 저장할 때 장착하고 있던 무기의 이름과 ItemStorage의 이름과 비교해서 같은 것을 SpawnActor로 월드에 스폰시키고 캐릭터에 장착시켜주는 매커니즘으로 작동한다.

 

덧붙여서 Map의 Value에 접근할 때 STL의 map처럼 값의 존재 유무를 따지지 않고 []연산으로 접근 시 에러가 발생하므로 꼭 Contain으로 값의 존재 여부를 파악하고 사용하는 것이 좋다.

 

[92. Pause Menu #1]

 

Pause Menu 위젯 블루프린트를 만들고 키 바인딩을 해준다.

 

[93. Pause Menu #2]

 

UI에 키프레임 애니메이션을 적용시킬 수 있다.

 

[94. Pause Menu #3]

 

SetInputMode(const FInputModeBase &InData);
// FInputModeGameAndUI, FInputModeGameOnly, FInputModeUIOnly
bShowMouseCursor = true;

 

SetInputMode를 통해 입력 모드를 바꿀 수 있다.

입력 모드가 바뀌더라도 마우스 커서가 자동으로 보여지는것은 아니므로 따로 보여지게 설정하여야 한다.

 

 

InputModeUIOnly

 

기존에 InputComponent에 매핑시켰던 키(액션, 축)들은 모두 사용할 수 없다.

마우스 커서가 보이지 않지만 UI 조작이 가능하다.

만약 UI를 열고 닫는 키가 InputComponent에 매핑 되어있다면 UI를 여는것은 가능하지만 이후 조작이 안되기 때문에 닫는 것은 안된다.

 

InputModeGameOnly

 

위와 반대로 UI를 열수는 있지만 UI의 조작이 불가능하다.

(마우스 커서 true인 경우 확인 필요)

 

InputModeGameAndUI

 

UI와 게임 조작이 모두 가능하다.

 

[95. Pause Menu #4]

 

UI만 조작하고 싶어서 UIOnly를 사용하면 매핑된 키를 사용할 수 없는 문제가 있고 그걸 해결하려고 GameAndUI를 사용하면 캐릭터도 조작이 되는 문제가 있다.

해당 실습에서는 UI의 On/Off 여부에 따라 bool체크를 해서 조작을 일일이 막았는데 더 좋은 방법이 있는지는 찾아봐야 할 것 같다.

 

[정리]

 

  • 구조체를 만들때도 GENERATED_BODY() 매크로를 써줘야 한다.
  • 저장하는 데이터는 반드시 UPROPERTY로 관리되어야 한다.
  • 게임의 저장(현재 상태를 캡처에 가까움)은 SaveGame 인스턴스를 이용하면 된다.
  • 포인터는 값이 아닌 주소가 있으므로 바로 저장하는것은 문제가 생길 수 있다.
  • UI(UMG)에도 키 프레임 애니메이션을 적용시킬 수 있다.
  • 입력 모드 변경을 통해 조작을 제어할 수 있다.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Level Changing and Saving the Game #2 -完-  (0) 2022.09.16
[UE C++] Combat #10  (0) 2022.09.15
[UE C++] Combat #9  (0) 2022.09.14
[UE C++] Combat #8  (0) 2022.09.14
[UE C++] Combat #7  (0) 2022.09.13

[85. Refining Gameplay #1]

 

동시에 두 마리의 몬스터와 전투를 하다가 한쪽 몬스터가 먼저 죽었는데 그게 타게팅된 적이라면 남은 적은 자동으로 타게팅되지 않아서 체력바가 안나오는 문제가 있다.

타게팅을 플레이어가 아닌 적쪽에서 감지 범위에 들어올 때 설정해주기 때문이다.

그래서 이미 감지 범위 안에서 전투중이었다면 업데이트가 안 될 것이다.

 

GetOverlappingActors(TArary<AActor*> &OverlappingActors, TSubclassOf<AActor> ClassFilter = nullptr) const

 

위와 같은 좋은 함수가 있다.

겹침 판정을 주는 액터들을 TArray 레퍼런스에 넘겨주는데 두번째 인자에 해당하는 클래스만 넘겨준다.

TSubclassOf<AEnemy>를 넘겨주면 AEnemy만 컨테이너에 담아준다.

 

그것으로 최소거리인 액터를 구해서 타게팅을 바꾸고 체력바도 다시 표시해주면 될 것이다.

 

// Enemy.cpp
void AEnemy::BeginPlay()
{
    GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
    GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
}

 

덤으로 적의 메시와 캡슐 컴포넌트가 카메라의 충돌을 무시하게 하면 카메라가 더이상 적에게 밀려서 의도하지 않은 움직임을 보이지 않을것이다.

 

[86. Refining Gameplay #2]

 

기존의 Pickup 클래스는 오로지 코인 하나만 증가시키는 클래스였으나 이제는 체력을 회복시키는 물약도 사용할 수 있게 확장시킬 것이다.

어떤 아이템을 획득했을 때 코인의 보유량을 증가시키거나 체력을 회복시키는 등 각자의 기능이 다르기 때문에 기존과는 조금 다르게 기능 구현을 C++이 아닌 블루프린트에서 해줄 것이다.

 

UFUNCTION(BlueprintImplementableEvent)
void OnPickupBP(class AMain* Target);

 

해당 속성은 구현을 블루프린트에서 하겠다는 의미이다. C++에서 정의가 불가능하다.

 

void APickup::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    OnPickupBP(Main);
}

 

정의를 하지 않아도 코드에서 함수를 사용할 수 있다.

코인은 획득시 코인개수를 증가시키고 포션은 획득시 체력을 회복시키기 때문에 각자의 블루프린트 이벤트그래프에서 해당 기능을 구현해주면 된다.

 

[87. Weapon Trails]

 

공격하여 무기를 휘두를 때 아무런 이펙트가 없으면 밋밋하다. 공격 모션의 특정 구간에 궤적을 그려주면 조금 더 멋있을 것이다.

이것 역시 노티파이를 이용해서 구현할 수 있는데 기존과는 다르게 "노티파이 스테이트" 를 사용한다.

 

보라색으로 표시된 부분이 노티파이 스테이트인데 시작점과 끝점이 존재하고 그 구간만큼만 실행된다.

Trail의 경우 이름 그대로 흔적을 남겨준다.

애님 트레일이 들어있는 파티클을 정해준 스켈레탈 메시의 소켓 위치를 따라서 흔적을 그려준다.

 

 

[88. Spawn Volume Improved]

 

예전에 만들어둔 스폰 볼륨에 몬스터만 스폰 되는것이 아니라 포션이나 코인 등등 다른 오브젝트도 스폰이 가능하게 변경하면 좋을 것이다.

 

BeginPlay->SpawnOurActor 노드를 바로 연결시켰을 경우 초기화 순서로 인해 값이 제대로 안나올 수 있다.

아주 짧은 딜레이를 줘서 해결할 수 있다.

 

 

SpawnActor로 월드에 스폰시킨 액터는 컨트롤러가 없어서 중력 적용이나 이동을 전혀 하지 못하기 때문에 따로 붙여주어야 한다. (참고: https://blog.naver.com/luicypher/220757861973)

SpawnDefaultController로 기본 컨트롤러를 붙여주고 따로 만든 컨트롤러가 있다면 GetController와 Cast를 통해서 바꾸어주면 된다.

 

멤버 변수를 AActor 타입으로 바꿔주었기 때문에 몬스터가 아닌 코인이나 물약같은 Item 타입도 스폰시킬 수 있게 되었다.

 

추가로 에디터에서 스폰 볼륨을 클릭하기 어렵다면 Billboard 컴포넌트를 추가시켜서 쉽게 컨트롤 할 수 있다.

 

 

[정리]

 

  • BlueprintImplementableEvent(또는 BlueprintNativeEvent) 속성을 이용해서 함수의 다형성을 활용할 수 있다. 하나의 클래스를 사용하면서도 오버라이딩이 가능해진다.
  • SpawnActor로 생성된 액터는 컨트롤러가 없어서 따로 추가를 시켜주어야 한다.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Level Changing and Saving the Game #2 -完-  (0) 2022.09.16
[UE C++] Level Changing and Saving the Game #1  (0) 2022.09.15
[UE C++] Combat #9  (0) 2022.09.14
[UE C++] Combat #8  (0) 2022.09.14
[UE C++] Combat #7  (0) 2022.09.13

[81. Refining Pickups]

 

기존에 만든 폭발물과 동전 오브젝트는 단순히 Overlap 이벤트만 발생하면 사운드와 파티클이 발생한다.

 

Item 클래스의 겹침 이벤트에서 구현했던 부분을 Explosive와 Pickup 클래스로 빼주도록 하자.

 

[82. Refining Sprint]

 

Shift키를 눌러서 전력질주를 하면 추가적인 조작을 하지 않아도 전력질주 매커니즘이 작동하는 문제가 있다.

자동으로 이동하는 것은 아니지만 제자리에서 전력질주 애니메이션이 재생되고 스태미너도 감소된다.

 

이동 키가 눌렸는지를 판단해서 전력질주를 실행해야 하므로 bool 변수 두개를 만들고 MoveForward, MoveRight에서 처음에 항상 false로 설정하고 키가 눌렸으면 true로 바꿔준다.

스태미너 감소 및 캐릭터의 상태가 변경되는 곳에서 이동 키가 눌렸는지 체크하는 부분을 넣어주면 된다.

 

[83. Extending the Enemy Class #1]

 

기능이 따로 추가되거나 변경된것은 없고 Paragon: Minions 애셋을 받아서 Enemy를 상속받은 새로운 BP를 하나 더 만들었을 뿐이다.

 

[84. Extending the Enemy Class #2]

 

새로운 애셋이 추가되었다고 딱히 달라지는것은 없고 파생 블루프린트 클래스를 만들어서 거미를 만들었을 때처럼 속성을 정해주고 몽타주를 만들고 스테이트머신을 만들고 등등 하는것은 똑같다.

 

기존에 만들었던 폭발물을 이제는 플레이어가 아닌 적들에게도 피해를 입히도록 바꿔보고 싶다.

 

// Explosive.cpp
void AExplosive::OnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    Super::OnOverlapBegin(OverlappedComponent, OtherActor, OtherComp, OtherBodyIndex, bFromSweep, SweepResult);

    if (OtherActor)
    {
        // 캐스팅 실패시 nullptr 반환
        AMain* Main = Cast<AMain>(OtherActor);
        AEnemy* Enemy = Cast<AEnemy>(OtherActor);
        if (Main || Enemy)
        {
            if (OverlapParticles)
            {
                UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), OverlapParticles, GetActorLocation(), FRotator(0.f), true);
            }
            if (OverlapSound)
            {
                UGameplayStatics::PlaySound2D(this, OverlapSound);
            }

            UGameplayStatics::ApplyDamage(OtherActor, Damage, nullptr, this, DamageTypeClass);

            Destroy();
        }
    }
}

 

양쪽을 다 캐스팅해서 null이 아니라면 OtherActor의 TakeDamage를 호출할 수 있다.

폭발물은 컨트롤러가 없으므로 세번째 인자는 nullptr이다.

 

 

여기서 문제가 한가지 생긴다.

플레이어의 콜리전 볼륨은 캡슐 컴포넌트 하나라서 여태까지는 잘 몰랐지만 적의 경우 감지 범위도 콜리전 볼륨에 속하기 때문에 상당히 먼 거리에서도 충돌이 일어나서 터지게 된다.

이 부분은 콜리전 채널에 대한 반응을 조정함으로써 수정할 수 있다.

 

// Enemy.cpp
AEnemy::AEnemy()
{
    AgroSphere->SetCollisionResponseToChannel(ECollisionChannel::ECC_WorldDynamic, ECollisionResponse::ECR_Ignore);
}

 

감지 범위에 대한 WorldDynamic 충돌이 무시되었지만 ACharacter를 상속받은 플레이어, 적은 기본적으로 루트 컴포넌트가 캡슐이고 캡슐의 콜리전 오브젝트 타입은 Pawn이라서 정상적으로 폭발이 이뤄진다.

 

[정리]

 

  • 컴포넌트를 붙이는 메소드는 SetupAttachment와 AttachToComponent가 있는데 AttachToComponent는 생성자에서 사용하는것을 지양하길 권장한다.
  • 의도하지 않은 충돌을 피하고 싶다면 콜리전 채널을 잘 설정해야 한다.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Level Changing and Saving the Game #1  (0) 2022.09.15
[UE C++] Combat #10  (0) 2022.09.15
[UE C++] Combat #8  (0) 2022.09.14
[UE C++] Combat #7  (0) 2022.09.13
[UE C++] Combat #6  (0) 2022.09.13

[78. Enemy Health Bar]

 

전투 중 적의 체력을 표시해주는 체력바 같은 것은 매우 중요한 정보중 하나이다.

하지만 거리나 전투 유무와 관계없이 체력바가 매번 표시되는것은 너무 불필요한 정보들이다.

그래서 전투 중에만 체력바를 표시되게 만들면 좋을 것이다.

 

전투중(정확히는 나에게 적대중인 몬스터가 있는것)인 경우를 어떻게 판단하면 될까?

몬스터의 전투 범위 내에 들어갔을 때, 몬스터가 캐릭터의 SetCombatTarget에 접근해서 자기 자신으로 설정해주던 부분이 있었다. 그 부분을 활용하면 될 것이다.

대신 nullptr 여부를 따지는게 아니라 bool체크를 따로 할 것이다.

 

 

// Main.h
public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Combat")
    bool bHasCombatTarget;
    
// Main.cpp
AMain::AMain()
{
    bHasCombatTarget = false;
}

 

 

 

캐릭터의 HealthBar를 만들어준것과 같이 EnemyHealthBar를 만들고 Percent에 바인딩 시켜준다.

 

 

// MainPlayerController.h
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets")
    TSubclassOf<UUserWidget> WEnemyHealthBar;

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Widgets")
    UUserWidget* EnemyHealthBar;

    bool bEnemyHealthBarVisible;

    void DisplayEnemyHealthBar();
    void RemoveEnemyHealthBar();
}

// MainPlayerController.cpp
void AMainPlayerController::BeginPlay()
{
    if (WEnemyHealthBar)
    {
        EnemyHealthBar = CreateWidget<UUserWidget>(this, WEnemyHealthBar);
        if (EnemyHealthBar)
        {
            EnemyHealthBar->AddToViewport();
            EnemyHealthBar->SetVisibility(ESlateVisibility::Hidden);
        }
    }
}

void AMainPlayerController::DisplayEnemyHealthBar()
{
    if (EnemyHealthBar)
    {
        bEnemyHealthBarVisible = true;
        EnemyHealthBar->SetVisibility(ESlateVisibility::Visible);
    }
}

void AMainPlayerController::RemoveEnemyHealthBar()
{
    if (EnemyHealthBar)
    {
        bEnemyHealthBarVisible = false;
        EnemyHealthBar->SetVisibility(ESlateVisibility::Hidden);
    }
}

 

// Main.h
public:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Controller")
    class AMainPlayerController* MainPlayerController;

// Main.cpp
#include "MainPlayerController."

void AMain::BeginPlay()
{
    Super::BeginPlay();

    MainPlayerController = Cast<AMainPlayerController>(GetController());
}

 

// Enemy.cpp
void AEnemy::AgroSphereOnOverlapEnd(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    if (OtherActor)
    {
        AMain* Main = Cast<AMain>(OtherActor);
        if (Main)
        {
            if (Main->MainPlayerController)
            {
                Main->MainPlayerController->RemoveEnemyHealthBar();
            }
            // .. 생략
}

void AEnemy::CombatSphereOnOverlapBegin(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
    if (OtherActor && Alive())
    {
        AMain* Main = Cast<AMain>(OtherActor);
        if (Main)
        {
            Main->SetCombatTarget(this);
            if (Main->MainPlayerController)
            {
                Main->MainPlayerController->DisplayEnemyHealthBar();
            }
            // .. 생략
}

 

코드가 너무 길고 스파게티 코드가 되는듯한 느낌을 지울수가 없다.

아무리 실습이라고는 하지만 조심해야 할 코딩 스타일인것 같다.

 

아무튼 MainPlayerController에서 HUD를 그려줬던 것처럼 적의 체력바를 보여줄 것이다.

다만 On/Off가 되어야 하기 때문에 Main에서 PlayerController의 정보를 가지고 Enemy에서 접근할 것이다.

 

공격 범위 안에 들어오면 체력바가 켜지고 탐지 범위 밖으로 나가면 체력바가 꺼질 것이다.

 

 

다만 현재상태로 실행하면 체력바가 화면을 다 채워버릴 것이다.

그래서 적당한 크기로 조정하고 몬스터의 머리 위쪽으로 체력바를 표시하게 해 줄 것이다.

 

// Main.h
public:
    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Combat")
    FVector CombatTargetLocation;
    
    FORCEINLINE void SetHasCombatTarget(bool HasTarget) { bHasCombatTarget = HasTarget; }
    
// Main.cpp
void AMain::Tick(float DeltaTime)
{
    if (CombatTarget)
    {
        CombatTargetLocation = CombatTarget->GetActorLocation();
        if (MainPlayerController)
        {
            MainPlayerController->EnemyLocation = CombatTargetLocation;
        }
    }
}

 

// MainPlayerController.h
public:
    FVector EnemyLocation;
    
protected:
    virtual void Tick(float DeltaTime) override;

// MainPlayerController.cpp
void AMainPlayerController::BeginPlay()
{
    if (WEnemyHealthBar)
    {
        EnemyHealthBar = CreateWidget<UUserWidget>(this, WEnemyHealthBar);
        if (EnemyHealthBar)
        {
            EnemyHealthBar->AddToViewport();
            EnemyHealthBar->SetVisibility(ESlateVisibility::Hidden);
        }
        FVector2D Vec(0.f, 0.f);
        EnemyHealthBar->SetAlignmentInViewport(Vec); // 초기 위치 정렬
    }
}

void AMainPlayerController::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    if (EnemyHealthBar)
    {
        FVector2D PositionInViewport;
        // 월드 좌표를 스크린 좌표로 변환
        ProjectWorldLocationToScreen(EnemyLocation, OUT PositionInViewport);

        FVector2D SizeInViewport(200.f, 25.f);

        EnemyHealthBar->SetPositionInViewport(PositionInViewport);
        EnemyHealthBar->SetDesiredSizeInViewport(SizeInViewport);
    }
}

 

... 이제 코드는 이만 넣고 작업이나 하도록 하자.

이쯤 되니 코드가 너무 복잡하고 클래스 의존도가 커져서 기록할 의미가 크게 없는것 같다.

Main 클래스가 거의 슈퍼클래스급이 되었다.

 

적의 공격 범위나 감지 범위 여부에 따라 Main의 SetHasCombatTarget을 true/false로 잘 설정해야 한다.

 

적의 공격범위 안에 들어가면 체력이 잘 나오는 반면 공격범위에서 벗어나면 CombatTarget이 nullptr이 되버리기 때문에 체력바는 존재하지만 값을 가져오지 못하는 문제가 생긴다.

 

전투 영역을 벗어날 때 nullptr로 만드는 부분을 감지 범위를 벗어날 때로 옮겨주면 된다.

 

[79. Fine Tuning Character Death #1]

 

적의 죽음처리는 괜찮게 했지만 아직 캐릭터의 죽음 처리는 하지 않았다. 애니메이션이 한번 재생되고 말 뿐이다.

 

적의 죽음처리와 비슷하게 해주면 된다. 몽타주에 노티파이를 추가해주고 해당 노티파이가 호출되면 애니메이션 재생을 중지한다.

 

죽은 상태에서 공격이나 움직이는것은 부자연스러우므로 죽음 상태인지 체크하는 조건이 추가로 들어가야 할 것이다.

 

 

이동과 공격에 대한 부분은 죽음 여부로 감싸서 처리가 가능한데 점프는 상위 클래스의 기능을 바인딩 시켜서 사용한 것이라서 방법이 조금 다르다.

 

방법은 의외로 간단하다. 오버라이딩 해서 Super를 호출하는 부분을 감싸주면 된다.

또한 Tick 함수에서 스태미너를 체크하며 Sprint, Normal 상태로 변환하는 부분이 있는데 최상단에서 죽었는지 여부를 확인하고 return 시켜주면 간단히 해결된다.

 

마지막으로, 죽는 애니메이션이 재생되는 도중에 한번 더 공격을 받는다면 EndDeath가 호출되지 않아서 다시 처음부터 재생이 되는 문제가 있다.

이것 역시 죽음을 처리하는 Die 함수에서 상태를 확인해서 곧바로 return을 시켜주면 된다.

 

[80. Fine Tuning Character Death #2]

 

직전 챕터에서 마지막에 죽는 애니메이션이 연속으로 재생되는 경우를 방지했지만 애초에 죽은 상태의 플레이어를 더이상 공격하지 않게 막으면 된다.

 

몬스터가 유효한 대상을 가지고 있는지를 확인하는 불리언 값을 하나 추가해서 공격 범위 안에 들어오면 true, 감지 범위 밖으로 나가면 false로 설정하고 추가로 플레이어가 죽을 때 불리언값을 false로 만들어주면 될 것이다.

 

[정리]

 

  • 실습의 C++ 코딩 스타일을 따라하지 말자. 앞으로 전반적인 흐름이나 함수의 기능들을 정리하도록 하자.

'언리얼 엔진 > UE4 C++' 카테고리의 다른 글

[UE C++] Combat #10  (0) 2022.09.15
[UE C++] Combat #9  (0) 2022.09.14
[UE C++] Combat #7  (0) 2022.09.13
[UE C++] Combat #6  (0) 2022.09.13
[UE C++] Combat #5  (0) 2022.09.12

+ Recent posts