언리얼 5

230904

슬뷔 2023. 9. 4. 10:49

계속 빌드하다보면, 연결해놨던 것들이 (ex. Move, Rotation 등)깨진다

이런 점을 개선하기 위해, 정보도 하나의 에셋(리소스화시킴)으로서 활용되게 한다.

 

사용할 액션의 종류들을 묶어서 에셋화

 

global.h

#pragma once

// 자주 사용하는 헤더 배치
#include "EngineMinimal.h"

#include "enum.h"
#include "struct.h"

enum.h

#pragma once

// 게임 개발에 필요한 enum 값 정리
// 언리얼에서 사용하는 규칙이 있음
// enum은 앞에다 E, 구조체는 F, action으로 만들어진것은 A 를 붙여야한다

// 액션 하나에 이넘 타입 하나씩 대응
UENUM(BlueprintType)
enum class EInputActionType : uint8
{
	MOVE,
	ROTATION,
	JUMP,
	SPRINT_TOGGLE,
	ATTACK,
	FIRE,
	RELOAD,
};

struct.h 생성

#pragma once

// enum 값 타입에 대응하는 action 하나씩 구조체로 만들어서 배열에 저장하여 셋팅
#include "enum.h"
// input 액션의 타입을 알아야한다
#include "InputAction.h"

// 매크로
// 항상 제일 밑에 있어야 한다
#include "struct.generated.h"

USTRUCT(Atomic, BlueprintType)
struct FIAData
{
	// 매크로
	// 기본 구현들이 추가된다
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	EInputActionType Type;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TSoftObjectPtr<UInputAction>	Action;

};

 

* 한글 나오게 하는 방법

제어판 -> 국가 또는 지역 -> 관리자 옵션 -> 시스템 로캘 변경

 

* 데이터 에셋 만들기

c++ 클래스 -> system -> DataAsset 을 상속받는 IADataAsset 생성

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

#pragma once

#include "../Header/global.h"

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "IADataAsset.generated.h"

/**
 * 
 */
UCLASS()
class UNREAL_3TH_API UIADataAsset : public UDataAsset
{
	GENERATED_BODY()

public:
	// EditDefaultOnly -> 에셋 페이지에서만 수정
	// DisplayName -> 변수 설명정보
	UPROPERTY(EditDefaultsOnly, Category = "Input", meta = (DisplayName = "InputAction 세팅"))
	TArray<FIAData>							IADataArr;
	
};

Input -> 기타 -> 데이터 에셋 -> DA_PlayerSetting

 

* 오전 시간에 한 것 개념 정리

1. enum 값 추가

-> 타입 하나하나가 실제 인풋 액션과 연결을 시키려고 enum 을 여러 종류 추가

2. struct 

-> 구조체 하나 생성

3. IADataAsset

-> 구조체를 여러 가지 가질 수 있는 동적배열

4. Character_Base 에서 바인딩 코드 개선

ACharacter_Base.h

#pragma once

#include "../Header/global.h"

#include "InputMappingContext.h"

#include "CoreMinimal.h"
#include "GameFramework/Character.h"

#include "../System/IADataAsset.h"

#include "Character_Base.generated.h"

UCLASS()
class UNREAL_3TH_API ACharacter_Base : public ACharacter
{
	GENERATED_BODY()
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	UCameraComponent*						m_Cam;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	USpringArmComponent*					m_Arm;

private:
	UPROPERTY(EditAnywhere, Category = "Input")
	TSoftObjectPtr<UInputMappingContext>	InputMapping;

	UPROPERTY(EditAnywhere, Category = "Input")
	TSoftObjectPtr<UIADataAsset>			InputActionSetting;

public:
	// Sets default values for this character's properties
	ACharacter_Base();

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;

private:
	void Move(const FInputActionInstance& _Instance);
	void Rotation(const FInputActionInstance& _Instance);
	void SprintToggle(const FInputActionInstance& _Instance);
	void Jump(const FInputActionInstance& _Instance);
};

ACharacter_Base.cpp

// 컨트롤러가 빙의할때, 컨트롤러의 인풋 컴포넌트를 인자로 넣어주면서 빙의대상 액터에게 호출해주는 함수
void ACharacter_Base::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	UEnhancedInputComponent* InputCom = Cast<UEnhancedInputComponent>(PlayerInputComponent);

	if (nullptr == InputCom)
		return;

	// null 이 아니다 => 로딩은 안되어 있지만 경로는 문제 없다
	if (!InputActionSetting.IsNull())
	{
		// 로딩 되어있으면 된 것을 주고, 아니면 로딩해라
		UIADataAsset* pDA = InputActionSetting.LoadSynchronous();
		
		// 배열 형태로 
		for (int32 i = 0; i < pDA->IADataArr.Num(); ++i)
		{
			switch (pDA->IADataArr[i].Type)
			{
			case EInputActionType::MOVE:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Move);
				break;
			case EInputActionType::ROTATION:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Rotation);
				break;
			case EInputActionType::JUMP:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::Jump);
				break;
			case EInputActionType::SPRINT_TOGGLE:
				InputCom->BindAction(pDA->IADataArr[i].Action.LoadSynchronous(), ETriggerEvent::Triggered, this, &ACharacter_Base::SprintToggle);
				break;
			case EInputActionType::ATTACK:
				break;
			case EInputActionType::FIRE:
				break;
			case EInputActionType::RELOAD:
				break;
			default:
				break;
			}
		}
	}
}

1. 왼쪽 클릭을 했을 때, 총을 쏘는 애니메이션

class UNREAL_3TH_API ACharacter_Base : public ACharacter
{
	GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	bool						IsFire;
    
protected:
	UPROPERTY(EditAnywhere, Category = "Animation")
	TSoftObjectPtr<UAnimMontage>			FireMontage;
    
private:
	void Fire(const FInputActionInstance& _Instance);
}
ACharacter_Base::ACharacter_Base()
	: m_Cam(nullptr)
	, m_Arm(nullptr)
	, IsFire(false)
{
	// 발사 몽타주를 찾아서 참조한다.
	ConstructorHelpers::FObjectFinder<UAnimMontage> montage(TEXT("/Script/Engine.AnimMontage'/Game/BlueprintClass/Player/Animation/AM_Fire.AM_Fire'"));
	if (montage.Succeeded())
	{
		FireMontage = montage.Object;
	}
}

void ACharacter_Base::Fire(const FInputActionInstance& _Instance)
{
	// 기본 C++ 매크로 구문
	//__FUNCTION__; // 1 바이트
	// 몇번째 라인인지
	//__LINE__;

	// 2byte 로 관리
	//FString str = FString::Printf(TEXT("%s : {#d} : %s"), *FString(__FUNCTION__), __LINE__,TEXT("Fire!!"));

	// 오퍼레이터 앞에 * 을 붙여주면, 
	// UE_LOG : 가변 매크로
	//UE_LOG(Player, Log, TEXT("%s", *str));

    	bool bToggle = _Instance.GetValue().Get<bool>();

	LOG(Player, Error, TEXT("!! Fire !!"));

	if (!IsValid(GetMesh()->GetAnimInstance()) || FireMontage.IsNull())
	{		
		return;
	}
	
	if (bToggle)
	{
		GetMesh()->GetAnimInstance()->Montage_Play(FireMontage.LoadSynchronous());
		IsFire = true;
	}
	else
	{
		GetMesh()->GetAnimInstance()->Montage_Stop(0.3f, FireMontage.LoadSynchronous());
		IsFire = false;
	}
}
class UNREAL_3TH_API UAnimInstance_Base : public UAnimInstance
{
	GENERATED_BODY()
public:
	UPROPERTY(Editanywhere, BlueprintReadWrite, Category = "데이터")
	bool		IsFire;
    
	UPROPERTY(Editanywhere, BlueprintReadWrite, Category = "데이터")
	float		FireBlendWeight;
}
void UAnimInstance_Base::NativeUpdateAnimation(float _fDeltaTime)
{
	// 캐릭터가 총을 쏘는지 확인
	IsFire = Character->IsFire;

	if (IsFire)
	{
		FireBlendWeight = 1.f;
	}
	else
	{
		FireBlendWeight = 0.f;
	}
}

 

define.h 에 매크로 구현 

#pragma once

//Verbosity 로그 수준
#define LOG(Category, Verbosity, String)    UE_LOG(Category, Verbosity, \
                                            TEXT("%s"), *FString::Printf(TEXT("%s : {%d} : %s"), \
                                            *FString(__FUNCTION__), __LINE__, String));

1-1 총쏘면서 움직이는 애니메이션

 

* 걷다가 총쏘는 애니메이션으로 교체될 때 끊기는 현상 개선

ABP_Player_C

class UNREAL_3TH_API ACharacter_Base : public ACharacter
{
	GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	bool									IsFire;
}

//////////////////////////////////////////////////////////////////////////////////////////

class UNREAL_3TH_API UAnimInstance_Base : public UAnimInstance
{
	GENERATED_BODY()
public:
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	bool									IsFire;
    
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Component")
	float									FireBlendWeight;
}

* 총쏘는 모션을 하다가 땠을 때 ( 총 쏘는 모션 -> 걷거나 일반 모션으로 변경) 끊기는 현상 개선

=> visual studio 에서 강제로

 

2. 커스텀 카테고리 만들기 (define.h / define.cpp 생성)

define.h

// 나만의 커스텀 카테고리 추가를 위해 매크로구문 선언
DECLARE_LOG_CATEGORY_EXTERN(Player, Log, All);
define.cpp

#include "define.h"

// 만든 카테고리 선언
DEFINE_LOG_CATEGORY(Player);

3. 애니메이션 몽타주

-> 섹션을 나눠서 관리가 가능하다

-> 조합을 해서 섞어주는 역할

 

3-1 몽타주의 슬롯

 

3-2 ABP_Player_C -> 본 별로 레이어드 블렌딩

Blend Weights => 가중치

아래거는 영향을 0 받게 된다.

디테일 -> 레이어 설정 -> 인덱스 -> 본이름 추가

디테일 -> 레이어 설정  -> 메시 스페이스 회전 블렌드 true 로 변경 ==> 고유의 회전값을 사용하겠다는 의미