[Unreal] 03.C++ 스크립트로 변환
본 문서는 어소트락 언리얼엔진 게임프로그래머 양성과정의 강의를 토대로 필기한 내용입니다
C++로 스크립트 생성
스폰
PlayerCharacter.h
UCLASS()
class STUDY_240601_API APlayerCharacter : public ACharacter
{
...
protected:
// TObjectPtr은 객체의 메모리 주소를 저장함
UPROPERTY(VisibleAnywhere)
TObjectPtr<USpringArmComponent> mArm; // 언리얼 UObject용 포인터 선언
UPROPERTY(VisibleAnywhere)
TObjectPtr<UCameraComponent> mCamera;
// TSubclassOf는 UClass(클래스 타입) 정보를 저장한다
TSubclassOf<AActor> mBulletClass;
...
protected:
void OnAttack(const FInputActionValue& InputValue); // OnAttack 함수 선언
}
PlayerCharacter.cpp
APlayerCharacter::APlayerCharacter()
{
...
static ConstructorHelpers::FClassFinder<AActor>BulletClassAsset(TEXT("/Script/Engine.Blueprint'/Game/Test/BP_Bullet.BP_Bullet_C'")); // 경로 마지막에 _C 붙여주기
if (BulletClassAsset.Succeeded()) mBulletClass = BulletClassAsset.Class; // 불렛클래스에셋 생성을 성공하면 미리 만들어 둔 mBulletClass 클래스 타입에 클래스정보
}
GetWorld()- 전체 월드를 셀을 나눠놓고 그 셀에 소속된 부분을 만들어 낼 수 있다
- Level, GameMode, WorldSettings, WorldPartitian
- World안에 Level이 소속되있는 개념
- 스폰을 위해서는 클래스 정보가 필요함
TSubclassOf는 UClass(클래스 타입) 정보를 저장함TSubclassOf<AActor> mBulletClass;- 클래스 - 객체 관계를 잘 구분지어 생각하기
- 경로 마지막에 _C 붙여주기!
Input
UObject타입으로 클래스 생성
InputData.h
UCLASS()
class STUDY_240601_API UDefaultInputData : public UObject
{
...
public:
UInputAction* mMove = nullptr;
UInputAction* mAttack = nullptr;
};
InputData.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "InputData.h"
UDefaultInputData::UDefaultInputData()
{
...
static ConstructorHelpers::FObjectFinder<UInputAction>Attack(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Attack.IA_Attack'")); // 정적 변수로 선언 (프로그램과 생명주기를 함께함)
if (Attack.Succeeded()) mAttack = Attack.Object;
static ConstructorHelpers::FObjectFinder<UInputAction>Shield(TEXT("/Script/EnhancedInput.InputAction'/Game/Test/Input/IA_Shield.IA_Shield'"));
if (Shield.Succeeded()) mShield = Shield.Object;
}
스킬
Shield를 만들고 플레이어 기준 방어벽을 앞뒤양옆 네방향으로 세우고 시계방향으로 회전
- 생성할 오브젝트인 ShieldActor를 하나 만든다
SetLifeSpan(): 일정시간 뒤에 오브젝트 파괴하는 함수
ShieldActor.h
#pragma once
#include "../GameInfo.h"
#include "GameFramework/Actor.h"
#include "ShieldActor.generated.h"
UCLASS()
class STUDY_240601_API AShieldActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
AShieldActor();
protected:
UPROPERTY(VisibleAnywhere)
TObjectPtr<UStaticMeshComponent> mMesh; // 언리얼 UObject용 포인터 선언
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
ShieldActor.cpp
#include "ShieldActor.h"
// Sets default values
AShieldActor::AShieldActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
mMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
SetRootComponent(mMesh);
static ConstructorHelpers::FObjectFinder<UStaticMesh>MeshAsset(TEXT("/Script/Engine.StaticMesh'/Game/Test/Cube.Cube'"));
if (MeshAsset.Succeeded()) mMesh->SetStaticMesh(MeshAsset.Object);
mMesh->SetRelativeScale3D(FVector(0.2, 0.5, 0.5));
SetLifeSpan(5.f); // 일정 시간 뒤에 오브젝트 파괴하는 함수
}
// Called when the game starts or when spawned
void AShieldActor::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void AShieldActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
PlayerCharacter.h
UCLASS()
class STUDY_240601_API APlayerCharacter : public ACharacter
{
...
protected:
// 누적 시간 (쿨타임마다 초기화)
float mShieldTime = 0.f;
// 스킬 쿨타임
float mShieldCoolDown = 6.f;
// 스킬 사용가능 여부 불린 값
bool mShieldEnable = true;
protected:
void OnShield(const FInputActionValue& InputValue); // OnShield 함수 선언
PlayerCharacter.cpp
#include "../Skill/ShieldActor.h"
APlayerCharacter::APlayerCharacter()
{
mRotation = CreateDefaultSubobject<USceneComponent>(TEXT("Rotation"));
mRotationMovement = CreateDefaultSubobject<URotatingMovementComponent>(TEXT("RotationMovement"));
// Shield 회전
mRotation->SetupAttachment(RootComponent); // 로테이션을
mRotationMovement->SetUpdatedComponent(mRotation);
mRotationMovement->RotationRate.Yaw = 180.0;
}
// Called every frame
void APlayerCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// 실드 스킬이 사용 불가능 상태일 경우 CoolDown 계산
if (mShieldEnable == false)
{
mShieldTime += DeltaTime;
if (mShieldTime >= mShieldCoolDown)
{
mShieldEnable = true;
mShieldTime = 0;
}
}
}
void APlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
...
// Shield 함수
EnhancedInput->BindAction(InputData->mShield, ETriggerEvent::Started, this, &APlayerCharacter::OnShield);
}
void APlayerCharacter::OnShield(const FInputActionValue& InputValue)
{
// 실드 스킬을 사용할 수 있는지 판단하고 사용할 수 없을 경우 바로 함수를 빠져나간다
if (mShieldEnable == false) return;
// 사용 불가능 상태로 만듬
mShieldEnable = false;
// 월드 정보를 미리 만들고 부모에 상속시키는 방법
FVector Location[4];
Location[0] = GetActorLocation() + GetActorForwardVector() * 200.f;
Location[1] = GetActorLocation() + GetActorRightVector() * 200.f;
Location[2] = GetActorLocation() + GetActorForwardVector() * -200.f;
Location[3] = GetActorLocation() + GetActorRightVector() * -200.f;
for (int32 i = 0; i < 4; i++)
{
FRotator Rot = GetActorRotation() + FRotator(0.0, i * 90.0, 0.0);
// StaticClass() : UClass 정보를 얻어옴
AShieldActor* Shield = GetWorld()->SpawnActor<AShieldActor>(AShieldActor::StaticClass(), Location[i], Rot);
// Shield의 부모로 플레이어를 지정한다
// Shield->AttachToActor : 부모로 지정된 Actor의 RootComponent에 붙여준다
// Shield->AttachToComponent : 원하는 컴포넌트를 지정하여 붙여준다
// KeepWorldTransform : 월드 정보를 유지하며 붙여준다
// KeepRelativeTransform : 상대 정보를 유지하며 붙여준다
Shield->AttachToComponent(mRotation, FAttachmentTransformRules::KeepWorldTransform); // 월드 정보를 유지하면서 부모에 붙여주기
}
}
회전
입력액션
- Vector2D로 설정
- MouseX (마우스 횡이동), MouseY (마우스 종이동)
bUseControllerRotationYaw: true 일때, 폰의 Yaw가 컨트롤러 Yaw 로테이션과 매칭된다- 부모로부터 상속받은 회전값의 상속을 끊음
mArm->bInheritPitch = false;mArm->bInheritYaw = true;mArm->bInheritRoll = false;
실습
- w,s 누르면 -> 탱크 이동
- a,d 누르면 -> 탱크 좌우 회전
- 마우스 좌우회전 -> 헤드 좌우 회전
- 마우스 상하회전 -> 포신 상하 회전
- 마우스 왼쪽클릭 -> 불렛 발사
- 스킬 마우스 1번 -> 불렛 3발 동시 발사
- 일정 시간에 한 번씩 -> 불렛 5발 발사