[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 끝날 시 호출