[Unreal] 04.탱크 게임

본 문서는 어소트락 언리얼엔진 게임프로그래머 양성과정의 강의를 토대로 필기한 내용입니다

탱크게임

  • w,s 누르면 -> 탱크 이동
  • a,d 누르면 -> 탱크 좌우 회전
  • 마우스 좌우회전 -> 헤드 좌우 회전
  • 마우스 상하회전 -> 포신 상하 회전
  • 마우스 왼쪽클릭 -> 불렛 발사
  • 스킬 마우스 1번 -> 불렛 3발 동시 발사
  • 일정 시간에 한 번씩 -> 불렛 5발 발사

메쉬 & 카메라

C++ 클래스로 만들기 위해 Mesh를 직접 생성하고 상속 구조의 경우 상대 위치와 상대 스케일을 통해 배치를 한다
필요에 따라 스태틱 메쉬를 여러 개 만들어 두고 Pivot 변경도 가능하다

PlayerPawn.h

#pragma once

#include "../GameInfo.h"
#include "GameFramework/Pawn.h"
#include "PlayerPawn.generated.h"

UCLASS()
class STUDY_240601_API APlayerPawn : public APawn
{
	GENERATED_BODY()

public:
	APlayerPawn();

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mBodyMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mHeadMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mBarrelMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USpringArmComponent> mArm;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UCameraComponent> mCamera;

    ...

};

PlayerPawn.cpp

#include "PlayerPawn.h"
#include "../Input/TankInputData.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "../Skill/ShieldActor.h"

APlayerPawn::APlayerPawn()
{
	PrimaryActorTick.bCanEverTick = true;

	mBodyMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BodyMesh"));
	SetRootComponent(mBodyMesh);

	static ConstructorHelpers::FObjectFinder<UStaticMesh>BodyMeshAsset(TEXT("/Script/Engine.StaticMesh'/Game/Test/Body.Body'"));
	if (BodyMeshAsset.Succeeded()) mBodyMesh->SetStaticMesh(BodyMeshAsset.Object);

	// Create the HeadMesh component and attach it to the BodyMesh
	mHeadMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("HeadMesh"));
	mHeadMesh->SetupAttachment(mBodyMesh);

	// Load the Head Mesh
	static ConstructorHelpers::FObjectFinder<UStaticMesh> HeadMeshAsset(TEXT("/Script/Engine.StaticMesh'/Game/Test/Head.Head'"));
	if (HeadMeshAsset.Succeeded())
	{
		mHeadMesh->SetStaticMesh(HeadMeshAsset.Object);
	}

	// Create the BarrelMesh component and attach it to the HeadMesh
	mBarrelMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BarrelMesh"));
	mBarrelMesh->SetupAttachment(mHeadMesh);

	// Load the Barrel Mesh
	static ConstructorHelpers::FObjectFinder<UStaticMesh> BarrelMeshAsset(TEXT("/Script/Engine.StaticMesh'/Game/Test/Barrel.Barrel'"));
	if (BarrelMeshAsset.Succeeded())
	{
		mBarrelMesh->SetStaticMesh(BarrelMeshAsset.Object);
	}

	mMuzzle = CreateDefaultSubobject<USceneComponent>(TEXT("Muzzle"));
	mMuzzle->SetupAttachment(mBarrelMesh);

	// 메쉬 위치 및 스케일 조정
	mBodyMesh->SetWorldScale3D(FVector(2.0, 2.0, 1.0));
	mHeadMesh->SetRelativeScale3D(FVector(0.5, 0.5, 1.0));
	mHeadMesh->SetRelativeLocation(FVector(0.0, 0.0, 100));
	mBarrelMesh->SetRelativeScale3D(FVector(2.0, 0.25, 0.25));
	mBarrelMesh->SetRelativeLocation(FVector(50, 0, 0));
	mMuzzle->SetRelativeLocation(FVector(105.0, 0, 0));

	// 스프링암 & 카메라
	mArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("Arm"));
	mCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));

	mArm->SetupAttachment(mBodyMesh); // SpringArm은 RootComponent의 자식으로 붙여주고
	mCamera->SetupAttachment(mArm); // Camera는 SpringArm의 자식으로 붙여준다

	mArm->TargetArmLength = 500.f;
	mArm->SetRelativeLocation(FVector(0.0, 0.0, 80.0));
	mArm->SetRelativeRotation(FRotator(-30.0f, 0.0, 0.0));
}

게임모드

DefaultGameMode.cpp

#include "DefaultGameMode.h"
#include "../Player/PlayerCharacter.h"
#include "../Player/PlayerPawn.h"

ADefaultGameMode::ADefaultGameMode()
{
	DefaultPawnClass = nullptr; // DefaultPawn이 없음으로 처리
}

입력

탱크의 InputData를 새로 만들고 MappingContext도 다시 매핑하여 사용

TankInputData.h

#pragma once

#include "../GameInfo.h"
#include "InputMappingContext.h"
#include "InputAction.h"
#include "UObject/NoExportTypes.h"
#include "TankInputData.generated.h"

UCLASS()
class STUDY_240601_API UTankInputData : public UObject
{
	GENERATED_BODY()

public:
	UTankInputData();

public:
	UInputMappingContext* mTankContext = nullptr; // 선언 및 초기화

public:
	UInputAction* mMove = nullptr;
	UInputAction* mAttack = nullptr;
	UInputAction* mShield = nullptr;
	UInputAction* mRotation = nullptr;
};

TankInputData.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "TankInputData.h"

UTankInputData::UTankInputData()
{
	static ConstructorHelpers::FObjectFinder<UInputMappingContext>Context(TEXT("/Script/EnhancedInput.InputMappingContext'/Game/Test/Input/IMC_Tank.IMC_Tank'"));

	if (Context.Succeeded()) mTankContext = Context.Object;

	static ConstructorHelpers::FObjectFinder<UInputAction>Move(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Move.IA_Move'"));

	if (Move.Succeeded()) mMove = Move.Object;

	static ConstructorHelpers::FObjectFinder<UInputAction>Rotation(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Rotation.IA_Rotation'"));

	if (Rotation.Succeeded()) mRotation = Rotation.Object;
}

이동

  • bUseControllerRotationYaw : true 일때, 폰의 Yaw가 컨트롤러 Yaw 로테이션과 매칭된다

PlayerPawn.h

#pragma once

#include "../GameInfo.h"
#include "InputActionValue.h"
#include "GameFramework/Pawn.h"
#include "PlayerPawn.generated.h"

UCLASS()
class STUDY_240601_API APlayerPawn : public APawn
{
	GENERATED_BODY()

public:
	// Sets default values for this pawn's properties
	APlayerPawn();

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mBodyMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mHeadMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UStaticMeshComponent> mBarrelMesh;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USceneComponent> mMuzzle; // 공격 지점 씬 컴포넌트(빈 게임 오브젝트의 개념)

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UFloatingPawnMovement> mMovement; // 이동을 위해 PlayerPawn 클래스에 FloatingPawnMovement 컴포넌트 추가

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<USpringArmComponent> mArm;

	UPROPERTY(VisibleAnywhere)
	TObjectPtr<UCameraComponent> mCamera;


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:
	void OnMove(const FInputActionValue& InputValue);
	void OnRotation(const FInputActionValue& InputValue);
};

회전

공격

Muzzle을 씬 컴포넌트로 하나 만들어서 캐논의 자식으로 상속 -> 캐논 움직임에 따라 함께 움직인다

Bullet.h

  • UStaticMeshComponent : 불렛의 메쉬를 위한
  • UProjectileMovementComponent : 투사체 발사를 위함
  • Collision Event
    • OnProjectileStop()
    • Hit Result 구조체
      • 부딪힌 물체의 종류, 벡터, 크기, 속도 등 저장하는 구조체
  • 델리게이트에 등록할 함수는 반드시 UFUNCTION()을 붙여주어야 한다
  • Unreal -> 컴포넌트가 아닌 액터를 제거 해야한다

이펙트

  • 종류
    • 케스케이드 시스템
      • UParticleSystem
    • 나이아가라 시스템
  • 호출방식
    • 동기화 방식 : 여러 작업이 동시에 실행될 때, 한 작업이 끝날 때까지 다른 작업이 시작되지 않는 것
    • 비동기화 방식 : 여러 작업을 동시에 처리하면서 작업이 완료되기 전에 다른 작업을 수행할 수 있는 것
  • 참조
    • LoadObject : 로드가 안되어 있는 오브젝트를 레퍼런싱할 때 사용
    • FindObject : 로드가 되어있는 오브젝트를 레퍼런싱할 때 사용
  • 파티클 매니저
    • 미리 로드해 놓고 필요할 때마다 재생하는 방식, 싱글톤으로 구현 하는 기법 사용 가능

사운드

  • 사운드 큐 : 사운드 매니저
  • 플레이어 컨트롤러 BeginPlay()에서 보통 실행 시킴
  • 게임모드에 만드는 것이 아님 (데디케이드 서버 개념 나중에 확인)
    • 클라이언트에서 UI, 사운드 등을 띄우는 것이지 서버에 띄우면 안된다…

충돌

  • BP : CollisionComponent
  • 프로젝트 세팅 : [엔진] - [콜리전]
    • Ignore, Overlap, Block으로 구성
    • 열거형으로 선언되어 있어서 기본 셋팅 외에 1 ~ 18 채널까지 사용 가능
    • 오브젝트 채널
      • 물체에 지정
    • 트레이스 채널
      • 물체가 아닌 것에 지정 가능
      • 마우스 클릭, 카메라 등
    • 프리셋
      • 프로파일
        • 원하는 오브젝트가 사용하는 프로파일을 지정할 수 있다
        • 콜리전 켜짐 : No Collision, Query Only(RackDoll), Physics Only, Collision Enabled로 구성
        • 오브젝트 타입 : 채널 지정 가능

AI

  • PawnClass로 생성
  • 골자는 Player의 것과 같을 수 있다 -> 나중에 상속구조로 설계할 수도 있음
  • 데미지 함수
    • GetActor() : 충돌된 액터를 얻어온다
    • TakeDamage() : Actor 클래스에 가상함수로 선언된 함수. Actor를 상속받은 클래스에서 재정의하여 사용 가능
      • DamageAmount : 데미지 양
      • DamageEvent : 데미지 타입
      • EventInstigator : 데미지를 주는 컨트롤러
      • DamageCauser : 데미지를 주는 액터

아이템

  • OnComponentBeginOverlap : 두 컴포넌트가 Overlap 시작 시 호출
  • OnComponentEndOverlap : 두 컴포넌트가 Overlap 끝날 시 호출

© 2023 Jinsoo Lee. All rights reserved.

Powered by Hydejack v9.1.6