[개요]

  • 이동 가능한 탱크 생성
  • WASD 이동과 마우스 클릭 액션
  • 적 포탑 생성
  • 발사체 생성
  • 체력, 대미지, 파괴
  • 이펙트 추가
  • 승/패 UI 출력

 

[프로젝트 설정]

  • 프로젝트를 다운로드 받아서 맞는 버전으로 포팅

 

[126. Using the Mouse Cursor]

 

if (PlayerControllerRef)
{
    FHitResult HitResult;
    // Line tracing과 충돌이 검출될 채널, 추적 복잡성 여부, 충돌체
    PlayerControllerRef->GetHitResultUnderCursor(ECollisionChannel::ECC_Visibility, false, HitResult);
    DrawDebugSphere(GetWorld(), HitResult.ImpactPoint, 25.f, 12, FColor::Red, false);
}

 

마우스 커서가 위치한곳에 Line tracing을 해서 최초로 충돌하는 오브젝트의 정보를 레퍼런스로 반환받는다.

2번째 인자인 bTraceComplex는 메시의 단순 콜리전, 복합 콜리전과 관계가 있는 것으로 보인다.

아주 정교한 위치가 필요한 경우가 아니라면 false를 사용해 주는 것이 연산량이 적을 것이다.

 

[127. Rotating the Turret]

 

FVector를 FRotator로 변환시킬 수 있다.

 

FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
TurretMesh->SetWorldRotation(LookAtRotation);

 

회전보간을 통해서 한번에 스냅되는게 아닌 조금 더 자연스러운 포탑 회전을 만들 수 있다.

void ABasePawn::RotateTurret(FVector LookAtTarget)
{
    FVector ToTarget = LookAtTarget - TurretMesh->GetComponentLocation();
    FRotator LookAtRotation = FRotator(0.f, ToTarget.Rotation().Yaw, 0.f);
        TurretMesh->SetWorldRotation(
            FMath::RInterpTo(TurretMesh->GetComponentRotation(),
            LookAtRotation, 
            UGameplayStatics::GetWorldDeltaSeconds(this),
        	25.f)
    );
}

 

[130. Timer]

 

타이머 핸들에 함수를 바인딩 시켜서 일정 시간 뒤에 실행시키거나 일정 시간마다 반복 실행 시킬 수 있다.

 

[132. Spawning The Projectile]

 

SpawnActor는 새로운 액터를 월드에 생성시켜준다. 템플릿에는 어떤 클래스를 만들어 줄 것인지를 지정하고 첫 번째 인자인 UClass에는 블루프린트도 들어갈 수 있기때문에 의미가 다르다.

 

예를 들어 C++ 클래스를 블루프린트로 파생시켜서 만들고 코드상에서는 스태틱 메시를 따로 지정하지 않는다면 C++ 클래스를 SpawnActor 하게될 시 보이지 않는 액터가 생성이 될 것이다. 하지만 스태틱 메시를 포함한 다른 값들이 지정된 블루프린트를 SpawnActor 한다면 정상적으로 잘 보일것이다.

 

[133. Projectile Movement Component]

 

발사체를 이동시키는 방법은 크게 3가지가 있다.

  • 매 틱마다 Transform을 변화시키는 것 (Set Location & Rotation)
  • 물리를 적용시켜서 운동량을 변화시키는 것 (Add Impulse)
  • 발사체 전용 Movement Component 사용

 

언리얼에는 발사체를 위한 Movement 컴포넌트가 따로 존재한다.

발사체에 컴포넌트를 붙여주고 속도 설정만 해주면 된다.

 

[134. Hit Events]

 

어떤 Primitive Component와 충돌 이벤트(Hit, OnOverlap)가 발생했을 때 바인딩 시킨 메소드들을 호출할 수 있다.

 

ProjectileMesh->OnComponentHit.AddDynamic(this, &AProjectile::OnHit);


void AProjectile::OnHit(UPrimitiveComponent* HitComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, FVector NormalImpulse, const FHitResult& Hit)
{
    // do something
}

 

매개변수 형식이 미리 지정되어 있으므로 반드시 위의 매개변수 형식을 지켜야 한다.

이벤트 발생시 자동으로 호출해준다.

 

만약 코드 작성 직후 작동하지 않는다면 에디터를 끄고 IDE에서 빌드를 다시 해주고 실행하면 된다.

 

[135. Health Component]

 

체력이나 그외 공통적인 부분은 액터 컴포넌트로 따로 만들어서 관리하는 것이 재사용성에도 좋고 코드가 길어지지 않아서 가독성에도 좋다.

 

[136. Applying Damage]

 

투사체를 생성할 때 단순히 생성만으로 끝나면 누구의 투사체가 데미지를 주었는지를 알 수가 없다.

SpawnActor의 반환값은 AActor이므로 소유자를 설정해서 처리해야한다.

 

auto Projectile = GetWorld()->SpawnActor<AProjectile>(ProjectileClass, Transform);
Projectile->SetOwner(this);

 

ApplyDamage의 매개변수중에 데미지를 입힌 주체의 Controller를 요구하는데 이때 Owner가 필요하다.

 

ApplyDamage & TakeDamage 추가 정보 : https://erikanes.tistory.com/352

 

[137. The Game Mode Class]

 

게임 모드는 게임의 규칙이나 승리 조건을 처리할 수 있다.

GameMode는 GameModeBase를 상속하고 멀티플레이에 관한 기능 등 좀더 다양한 기능이 있다.

 

[138. Handling Pawn Death]

 

죽음에 대한 처리는 게임모드에 의해 처리되도록 한다.

 

Tank->DisableInput(TankPlayerController); // 입력 비활성화
TankPlayerController->bShowMouseCursor = false; // 마우스 커서 표시 비활성화

 

[139. Custom Player Controller]

 

임의의 플레이어 컨트롤러를 만들어서 게임모드에 오버라이딩 시킬 수 있다.

 

[140. Starting The Game]

 

TimerManager의 SetTimer 매개변수중에 FTimerDelegate 라는 것이 있다.

https://erikanes.tistory.com/353

위의 글을 참고하면 된다.

 

[143. Displaying Countdown Time]

 

UI 구성요소를 변수화 시켜서 그래프에 노출시킬 수 있다. 

 

 

타이머가 0초 미만으로 내려가면 더 이상 필요없는 위젯이므로 제거해준다.

 

[144. Winning And Losing]

 

TArray<AActor*> Towers;
UGameplayStatics::GetAllActorsOfClass(this, ATower::StaticClass(), Towers);

 

월드에 존재하는 특정 액터들을 TArray에 반환받을 수 있다.

태그, 클래스+태그, 인터페이스까지 총 4가지의 함수가 있다.

 

[145. Game Over HUD]

 

위젯은 AddToViewport를 통해서 화면에 출력될 수 있다.

 

 

위젯의 구성요소들은 변수로 변환이 되고 외부에서 접근이 가능하다.

 

[147. Smoke Trail]

 

UParticleSystem은 파티클 애셋, UParticleSystemComponent는 UParticleSystem을 관리하는 컴포넌트이다. 둘의 차이가 있다.

UParticleSystem은 UGamePlaystatics::SpawnEmitterAtLocation을 통해 월드에 스폰되거나 UParticleSystemComponent를 통해 제어될 수 있다.

 

[150. Camera Shake]

 

언리얼은 카메라 흔들림에 대한 클래스가 이미 만들어져있다.

여기서는 마티네 카메라 셰이크에서 파생된 블루프린트 클래스를 사용한다.

 

 

블루프린트에서 속성을 지정해주고 C++ 코드에서 해당 카메라 셰이크를 재생시 설정된 값으로 흔들린다.

카메라 셰이크를 C++ 코드로 만들어줄수도 있지만 단순히 값만 지정하는거라서 블루프린트로 만들고 관리하는것이 편해보인다.

 

if (HitCameraShakeClass)
{
    APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
    PlayerController->ClientPlayCameraShake(HitCameraShakeClass);
}

 

마티네 카메라 셰이크에는 카메라를 어떻게 흔들지에 대한 정보가 담길뿐이고 실제로 카메라를 흔드는것은 플레이어 컨트롤러이다. 

 

// 특정 플레이어의 카메라 흔들기
APlayerController::ClientPlayCameraShake

// 범위 내 거리에 따른 카메라 흔들기
UGameplayStatics::PlayWorldCameraShake

 

[151. Polish And Wrap-Up]

 

몇 가지 문제를 수정하고 마무리 한다.

 

1. 카메라 무빙

SpringArm의 CameraLag, CameraRotationLag을 활성화 시켜주면 포탑의 회전이 보간되어서 자연스러워진 것처럼 약간의 딜레이가 생기기 때문에 카메라의 움직임이 부드러워진다. 값을 낮출수록 딜레이가 더 길어진다.

 

 

2. 플레이어가 죽은뒤에도 계속 공격하는 타워

 

void ATank::HandleDestruction()
{
    Super::HandleDestruction();
    SetActorHiddenInGame(true);
    SetActorTickEnabled(false);
}

 

단순히 액터를 숨긴다고 실제로 월드에서 비활성화 되는것은 아니다. 눈에 안보일뿐이지 타워는 계속 플레이어를 인지한다.

추가로 콜리전까지 꺼주면 콜리전 이벤트에서 검출되지 않을것이다.

 

다만 이 실습에서는 콜리전 이벤트를 통한 공격이 이루어지는것이 아닌 타겟(플레이어)를 처음부터 설정해놓고 매 틱마다 추적하는 방식이기때문에 플레이어에게 생존여부를 알수있는 bool값을 추가해서 죽은 상태라면 타워가 플레이어를 추적하거나 발사하지 않는 방식을 사용해야한다.

 

OnComponentBeginOverlap같은 이벤트라면 콜리전을 없애는것 만으로도 해결이 될 것이라고 생각된다. 

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

[UE5 C++] Simple Shooter -完-  (0) 2022.10.12
[UE5 C++] Crypt Raider  (0) 2022.09.29
[UE5 C++] Obstacle Assalult  (0) 2022.09.28
[UE5 C++] Warehouse Wreckage  (0) 2022.09.28

+ Recent posts