탱크 전쟁은 플레이어가 탱크를 몰고 다니며 맵에 배치된 터렛을 모두 파괴하여 승리하는 게임이다.
먼저 플레이어와 적의 탱크를 생성하기 위해 기반이 되는 C++ Class를 생성해준다.

탱크를 만들때 대표적인 요소는
벽이나 바닥, 적과 충돌을 일으키는 Capsule과
몸체가되는 Base와 발사체가 나가는 Turret,
Turret 부분에서 발사체를 생성해 발사하는 Spawn Point로
나눴고 각각 컴포넌트들을 만들어 SpawnPoint->Turret->Base->Capsule->Root 순으로 부착해주었다.

UStaticMeshComponent나 USceneComponent는 이미 PawnClass에 추가되어 있지만 CapsuleComponent는 추가되어 있지 않아 CapsuleComponent를 사용하기 위해 UCapsuleComponent를 전방 선언해주었다.
전방선언
전방선언은 한 헤더파일의 전부가 필요한 것이 아닌 극히 일부분만 필요할 때 파일이 커지는 것을 최소화하기 해당 선언 앞에 class를 붙여 사용한다.

BasePawn을 기반으로 각각 탱크와 터렛을 생성해 각각 스태틱 메쉬를 입혀주고 캡슐과 발사체 생성 위치를 설정해주었다. 발사체 생성 위치가 너무 가까우면 콜리전 오류를 발생할 수도 있다고 한다.

탱크와 터렛을 맵에 배치해주었다.

전에 만든 BasePawn을 파생하여 Tank의 역할만을 넣을 새로운 c++클래스를 생성하였다.
생성한 Tank의 public 영역에 생성자를 추가해주고 privat 영역에는 SpringArm와 Camera를 선언하고 SpringArm은 RootComponent에 Camera는 SpringArm에 부착해주었다.

탱크를 조종하기 위해 프로젝트 세팅의 입력에서 각각 키세팅을 해주었다.

탱크를 움직이고 조정하기 위해 Speed와 TurnRate 변수와 Move, Turn 함수를 만들어 SetupPlayerInputComponent 함수에서 바인드해주고 움직입이 일정하도록 UGameplayStatics::GetWorldDeltaSeconds를 사용해 DeltaTime을 적용 AddActorLocalRotation을 사용해 작동하도록 만들어주었다.

포탑의 터렛부분을 마우스를 따라 회전시키기 위해서 마우스 커서에 구체를 띄우는 작업을 하기위해 GetHitResultUnderCursor를 사용하여 커서의 위치를 HitResult에 가져오고 DrawDebugSphere를 사용하여 해당 위치에 구체를 생성하였다.

캡처를 사용하여 마우스 커서는 보이지 않지만 구체가 마우스 커서를 따라다니는 모습이다.

포탑이 목표방향으로 회전하는 것은 Tank와 Turret 둘 다 사용하는 기능이므로 BasePawn에서 (커서의 벡터위치 - 현재 포탑의 위치)를 사용하여 포탑이 바라봐야하는 벡터 ToTarget을 구하고 FMeth::RInterpTo를 사용하여 TurretMesh의 매끄러운 움직임이 가능하도록 만들었다.

C++ 클래스 Tower를 생성하고 Tank를 인식하는 거리 FireRange를 생성 초기화해준 뒤 Tower에서 Tank의 거리를 구해 인식 범위 안에 들어오면 Tank를 바라보도록 설정해 주었다.

발사 기능은 Tank와 Turret 둘 다 가지는 기능이기에 먼저 BasePawn에 Fire()함수를 만들어주고 함수가 작동하는지 확인하기 위해서 Fire()이 실행되면 Projectile이 생성되는 위치에 디버그 구체를 그려준다.

탱크의 경우는 플레리어가 마우스 왼쪽 버튼을 눌렀을때 발사가 실행되기 만들기 위해서 BindAction을 사용하여 IE_Pressed(<- 키를 눌렀을 때, 자매품 키를 뗐을 때 : IE_Released)로 설정하여 Fire을 호출할 수 있도록 만들어 주었다.

타워는 일정 시간이 지날 때마다 범위 내에 탱크가 있으면 발사를 하도록 만들고 싶어 GetWorldTimerManager의 SetTimer를 Beginplay에 배치함으로써 발사 시간인 FireRate를 확인하고 CheckFireCondition을 실행하도록 만들어주었다. Tank의 위치를 확인하기 전에 필수적으로 사용하던 If(Tank)를 발사 전에 사격 범위를 확인하는 InFireRange 함수에 묶어서 코드가 간결해질 수 있도록 리팩터링 해주었다.

C++ Class Projectile을 만들어주고 StaticMeshComponent를 RootComponent로 설정해준 뒤 TSubclassOf를 사용하여 정의해주었다.
TSubclassOf
새 객체를 만드는 데 사용되는 보라색 핀 클래스 변수이지만 편집기의 세부 정보 패널은 UDamage에서 파생된 클래스만 나열한다.엔진의 모든 클래스를 해당 포인터를 대체할 수 있는 항목으로 나열하는 대신 속성에 대한 선택 항목으로 입력한다.
BasePawn의 Fire함수에서 Ator를 스폰하도록 만들어 주었다.

Projectile의 매끄러운 발사를 위해 UProjectileMovementComponent를 사용하여 MaxSpeed와 InitialSpeed를 지정해 주었다.

Projectile이 발사되는 모습이다.

타격 이벤트를 구현하기 위해 OnHit이라는 함수를 만들고 OnComponentHit 델리게이트에 바인딩하여 ProjectileMesh가 다른 컴포넌트와 충돌하면 Log가 출력되도록 만들어 주었다.

먼저 새로운 C++ Class HealthComponent를 만들고 MaxHealth와 Health를 생성, 타격 이벤트와 마찬가지로 DamageTaken 함수를 만들어 OnTakeAnyDamage에 바인딩 해주었다.

Projectile에서의 원활한 함수 사용을 위해 BasePawn의 Fire함수에서 Projectile이 발사되면 발사한 Actor를 자신의 Owner로 설정한다.

UGameplayStatics::ApplyDamage(); 함수를 사용해 Projectile을 맞은 Actor에게 대미지를 입히거나 구조물에 맞은 뒤 Destroy()로 인해 사라진다.

발사체를 맞은 Actor는 자신의 Health 컴포넌트의 DamageTaken으로 이동하여 체력을 조정한다.
Pawn의 죽음과 게임의 규칙을 정하기 위해서 C++ Class ToonTanksGameMode를 만들고 블루프린트로 상속받아 게임 모드로 설정 DefaultPawn을 BP_Tank로 설정해 주었다.
Tank나 Tower의 죽음을 표현하기 위해서는 위의 게임 모드 말고도 몇 가지의 과정이 더 필요하다.
Damage를 조정하는 Health(Component)에서 Health가 0 이하가 되면 ToonTanksGameMode에 생성할 함수 ActorDied가 실행되고 ActorDied가 실행되면
Pawn에서 함수 HandleDestruction이 실행되게 만든다.

먼저 가장 나중에 실행되고 Tank와 Tower 둘 다 실행되어야 될 기능을 넣을 함수인 HandleDestruction을 BasePawn에 만든다.

Tank에서 HandleDestruction을 생성하고 BasePawn에서 정의된 기능을 Super로 가져와준 뒤 Tank가 죽어도 카메라가 남을 수 있게 바로 파괴가 아닌
Tank를 숨기는 SetActorHiddenInGame(true); 함수와
Tank의 Tick을 멈추는 SetActorTickEnabled(true); 함수를 true로 실행해준다.

Tower에서는 바로 사라질 수 있도록 해주었다.

ToonTanksGameMode에서 ActorDied를 생성하여 ActorDied가 받은 Actor가 Tank인 경우 Tank의 HandleDestruction을 실행하고 Input과 마우스 커서가 작동하지 않도록 만들어주었고 Tank가 아닌 경우 Tower 캐스팅에 성공하면 캐스팅 받은 Tower에서 HandleDestruction을 실행하도록 만들어 주었다.

마지막으로 Health(Component)의 BeginPlay에서 ToonTanksGameMode를 선언 캐스팅해 주어 DamageTaken에서 Health가 0 이하가 되면 ToonTanksGameMode의 ActorDied를 실행하도록 만들어 주었다.
C++ class ToonTanksPlyerController를 생성하고 ToonTanksGameMode에서 기본 PlyerController를 ToonTanksPlyerController로 변경했다.

ToonTanksPlyerController에서는 ToonTanksGameMode에서 Input과 마우스 커서를 없애줬던 부분을 SetPlyerEnabledState 함수를 만들어 다뤄었다.

ToonTanksGameMode에서 ToonTanksPlyerController를 가져와 false만 넘겨주도록 만들었다.

게임시작 전 약간의 시간을 주기 위해 ToonTanksGameMode에 HandleGameStart 함수를 만들고 BeginPlay에서 게임이 시작되면 바로 실행되도록 만들어 주었다.
게임이 시작하면 StartDelay동안 Pawn이 작동하지 않도록 SetPlyerEnabledState에 false를 전달한 뒤 SetTimer를 사용하기 위해 PlayerEnableTimerDelegate를 선언 정의하고 SetTimer에 전달하여 사용하였다.
게임 시작 전 메세지를 위젯으로 출력한다.

위젯을 생성하기 전에 ToonTanksGameMode에 StartGame 함수를 만들고 컴파일 해준다.!

위젯 블루프린트를 생성한 뒤 캔버스 패널과 텍스트를 추가해 Get Ready!라는 글자를 화면에 띄운다. 가운데 꽃 모양은 앵커라는 것인데 캔버스 패널에서의 위젯의 위치는 앵커의 위치에 따라 상대적으로 나오고 설정해줄 수 있기 때문에 앵커를 캔버스 패널의 가운데로 설정해 주었다.
글자가 중앙에 위치할 수 있도록 중앙정렬, 그림자 설정에서 A를 1로 설정하여 글자가 잘 보이도록 만들어 주었다.

ToonTanksGameMode의 블루프린트에서 StartGame을 생성하고 Create Widget과 Add to Viewport를 이어주면 화면에 Widget이 생성된다.

시간마다 알맞은 글을 화면에 띄우기 위해 텍스트를 Display Text로 명명하고 변수 여부를 체크해준 뒤 그래프로 넘어와서 Countdown이라는 float변수를 만들어 빼기와 Switch를 이용해 Countdown이 줄어들때마다 다른 글자가 뜨도록 만들고 마지막엔 Remove from Parent를 사용하여 위젯 자체를 지워준다.
게임 시작 전 위젯을 설정해 주었으니 이제 게임이 끝났을 때의 위젯을 만들어 출력하려한다.
게임이 끝나는 조건은 Tank가 죽거나 Tower들이 모두 죽는 두 가지 경우가 있다.
Tank가 죽는 시점은 명확히 정해져 있지만 Tower는 한개가 아니기 때문에 월드에 존재하는 Tower들의 개수를 알아야한다.

위처럼 타워들의 개수를 알릴 TargetTowers변수와 처음에 월드에 타워의 개수를 알려주는 GetTargetTowerCount함수를 만들어 Tarry와 UGameplayStatics::GetAllActorsOfClass 함수를 이용해 HandleGameStart에서 타워의 개수를 반환해준다.

GameOver 함수를 만들고 Tank가 죽은 시점과 Tower가 전멸한 시점에서 bool 변수로 승패를 알린다.

WBP_StartGameWidget을 복사하여 WBP_EndGameWidget을 생성하고 사용하지 않은 그래프 부분의 내부를 삭제해준다.
GameMode 블루 프린트에서 승패에 따라 다른 메세지를 출력하도록 만든 모습이다.

발사체가 무언가에 맞았을때의 폭발효과를 주기 위해서 C++ Projectile.h에서 전방선언으로 UParticleSystem* HitPrticles;를 선언하고 Projectile 블루프린트에서 이펙트를 선택해준 뒤 Projectile.cpp의 OnHit함수에서 발사체가 목표에 도달하고 파괴하기 전에 파티클을 스폰해주는 함수인 UGameplayStatics::SpawnEmitterAtLocation을 사용하여 파티클을 스폰해주었다.

발사체가 날아갈 때 뒤에 연기가 따라가도록 효과를 주기위해 이번에는 UParticleSystemComponent를 사용하여 RootComponent에 부착해 주었다.

에디터의 Projectile 블루프린트에 들어가 ParticleSystemComponent의 디테일 패널에 템플릿에서 알맞은 효과를 선택해 주었다.

폰이 죽었을 때의 파티클도 추가하기 위해 BasePawn에 선언 및 만들어 두었던 HandleDestruction에 UGameplayStatics::SpawnEmitterAtLocation을 만들어 주었다.

Tank와 Tower의 블루프린트에서 각각 DeathParticle을 선택해 주었다.

Pawn이 죽을 때의 사운드를 만들어주기 위해서 BasePawn에 전방선언으로 함수 USoundBase* DeathSound; 를 만들어주고 BP_Tank와 BP_Tower에서 폭발 사운드를 선택해주었다.
함수 HandleDestruction에 UGameplayStatics::PlaySoundAtLocation 를 사용하여 소리가 재생되도록 만들어 주었다.

Projectile에 LaunchSound와 HitSound도 만들어 각각 소리가 발생해야하는 과정에 넣어주었다.

LegacyCameraShake를 기반으로 CameraShake를 만들어주고 진동과 위치 진동에서 수치를 조정해준다.
진동은 회전, 위치, Fov 세가지의 진동타입이 있는데 회전과 Fov는 멀미를 유발하기 쉽다고 하여 위치 진동에서 진폭과 주파수를 조정하였다.

TSubclassOf로 전방선언하는 UCameraShakeBase를 선언해주고 카메라가 흔들려야되는 알맞은 위치에
GetWorld()->GetFirstPlayerController()->ClientStartCameraShake()를 사용해 넣어준다.!

해당 블루프린트에 들어가 만들어 놓았던 CameraShake 블루프린트를 설정해준다.
카메라 렉과 Tower가 Tank가 죽고 나서도 Tank를 쏘는 경우들을 조정하여 War Tank를 마무리 하였다.