안선생의 개발 블로그

[UE5 멀티플레이 게임 만들기] 11. Skill DataAsset 본문

언리얼/멀티플레이 게임 만들어보자

[UE5 멀티플레이 게임 만들기] 11. Skill DataAsset

안선생 2024. 1. 21. 21:30

자 이번에는 DataAsset을 이용하여 스킬정보를 관리해 보려고 해요.

 

DataAsset은 주로 데이터의 관리와 재사용성을 향상시키기 위해 사용해요.

 

DataAsset을 이용하면 나중에 보기도 편하고 관리하기도 편하기때문에 데이터에셋으로 관리할게요.

 

먼저 데이터에셋 클래스를 만들어볼게요

 

DataAsset클릭 후 다음

 

생성 해줄게요.

 

 

그리고 헤더파일에 아래와 같이 작성해 줄게요

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

#pragma once

#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "SkillIInfoEnum.h"
#include "SkillInfo.generated.h"

class UAnimMontage;

USTRUCT(BlueprintType)
struct FJHSkillInfo
{
	GENERATED_BODY()

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	ESkillInput SkillInput;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	TObjectPtr<UTexture2D> SkillIcon = nullptr;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FText SkillContext = FText();

};

/**
 * 
 */
UCLASS()
class JH_MULTI_RPG_API USkillInfo : public UDataAsset
{
	GENERATED_BODY()
public:

	/**스킬 정보 / TMap을 사용하여 시간복잡도를 줄임*/
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "SkillInfo")
	TMap<ESkillName,FJHSkillInfo> SkillInformation;

	FJHSkillInfo FindSkillInfo(const ESkillName& SkillName, bool bLogNotFound = false) const;
};

스킬에 필요한 정보를 갖는 구조체를 만들어줄게요

 

그리고 스킬을 찾을 때 필요한 이름과 구조체를 갖고 있는 TMap을 만들어 줄게요.

 

여기서 설정된 변수가 데이터 에셋에 넣을 수 있는 정보들이에요

 

아래 함수는 이름을 통해 스킬 정보를 얻어 올 수 있는 함수예요

 

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


#include "Skill/SkillInfo.h"

FJHSkillInfo USkillInfo::FindSkillInfo(const ESkillName& SkillName, bool bLogNotFound) const
{
	const FJHSkillInfo* Info = SkillInformation.Find(SkillName);
	if (Info)
	{
		return *Info;
	}
	
	if (bLogNotFound)
	{
		UE_LOG(LogTemp,Error,TEXT("Not SkillInfo"))
	}

	return FJHSkillInfo();
}

 

TMap에서 Find함수를 이용해서 Key같이 같으면 SkillInformation을 갖고 오는 함수예요

 

없으면 로그 출력하고 빈 정보를 갖고 와요

 

이렇게 하고 컴파일해 줄게요

 

 

완료되면 우클릭->기타 -> 데이터 에셋 클릭해 줄게요

 

 

그러면 저희가 만든 데이터 에셋이 있을 거예요.

만들어줄게요

 

그런 다음에 위와 같이 8개의 정보를 생성해 줄게요.

 

 

자 이제 데이터 에셋을 채웠으니 이 에셋으로 HUD 스킬칸을 채워줘야겠죠.

 

다시 C++코드로 가서 SkillComponet로 갈게요.

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

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Skill/SkillIInfoEnum.h"
#include "SkillComponent.generated.h"

class ASkills;
class USkillInfo;

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class JH_MULTI_RPG_API USkillComponent : public UActorComponent
{
	GENERATED_BODY()
private:
	friend class AJHCharacter;
public:	
	USkillComponent();
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

	void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const;

	UFUNCTION(BlueprintCallable)
	const TArray<ESkillName>& GetActivatableSkillNames() const;
protected:
	virtual void BeginPlay() override;

	UFUNCTION(Server,Reliable)
	void ServerSkill(ACharacter* Character,const ESkillInput& SkillInput);

	UFUNCTION(NetMulticast,Reliable)
	void MultiSkill(ACharacter* Character, ASkills* Skill);

private:
	UPROPERTY()
	TArray<TObjectPtr<ASkills>> ActivatableSkills;

	UPROPERTY(Replicated)
	TArray<ESkillName> ActivatableSkillNames;

	UPROPERTY(EditDefaultsOnly)
	TObjectPtr<USkillInfo> SkillInfo;

	UPROPERTY(EditDefaultsOnly)
	TArray<TSubclassOf<ASkills>> StartSkillsClass;
};

 

여기서 추가된 거는 스킬정보 멤버 변수와 활성화된 스킬 이름을  Replicated 하고 있어요 

Replicated 하면 서버에서 클라이언트로 전달해 줘요. 즉 서버에서 값이 바뀌면 클라이언트도 알 수 있어요.

 

함수

UFUNCTION(BlueprintCallable)
const TArray<ESkillName>& GetActivatableSkillNames() const;

추가해 줬어요. 이 함수는 활성화된 스킬을 찾을 수 있어요.

 

cpp

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


#include "Skill/SkillComponent.h"
#include "Skill/Skills.h"
#include "Skill/SkillInfo.h"
#include "Net/UnrealNetwork.h"

USkillComponent::USkillComponent()
{
	PrimaryComponentTick.bCanEverTick = false;
}

void USkillComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
{
	DOREPLIFETIME_CONDITION(USkillComponent, ActivatableSkillNames,COND_OwnerOnly);
	//DOREPLIFETIME(USkillComponent, ActivatableSkills);

}

const TArray<ESkillName>& USkillComponent::GetActivatableSkillNames() const
{
	return ActivatableSkillNames;
}

void USkillComponent::BeginPlay()
{
	Super::BeginPlay();
	
	/* 서버만 스킬을 가질 수 있습니다.**/
	if (GetOwner() && GetOwner()->HasAuthority())
	{
		for (int32 i = 0; i < StartSkillsClass.Num(); i++)
		{
			ActivatableSkills.Add(GetWorld()->SpawnActor<ASkills>(StartSkillsClass[i]));
			ActivatableSkills[i]->SetOwner(GetOwner());
			ActivatableSkillNames.Add(ActivatableSkills[i]->SkillName);
		}
	}
}

void USkillComponent::ServerSkill_Implementation(ACharacter* Character, const ESkillInput& SkillInput)
{
	for (ASkills* Skill : ActivatableSkills)
	{
		if (Skill->SkillInput == SkillInput)
		{		
			MultiSkill(Character, Skill);
		}
	}
}

void USkillComponent::MultiSkill_Implementation(ACharacter* Character, ASkills* Skill)
{
	/* 모든 클라이언트에서 실행**/
	/* 클라이언트는 SKill값을 알라면 복제를 해야됨
	   서버에서만 알고 있고 복제를 하면 서버에서 클라이언트로 알려줌*/
	
	if (Skill)
	{
		Skill->SkillExecute(Character);
	}

}

void USkillComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

}

cpp 에선 추가된 거는 

const TArray<ESkillName>& USkillComponent::GetActivatableSkillNames() const
{
	return ActivatableSkillNames;
}

이 함수는 아까 말한 데로 이 함수는 활성화된 스킬을 찾을 수 있어요.

 

자 이제 HUD에 보이게 해야겠죠 그럼 활성화된 스킬 정보를 가져와야 돼요

 

어떻게 할까요?

 

Widget에 있는 Image를 변경해 줘야겠죠.

 

Widget은 저번에 WidgetController 하나만 의존한다 했어요.

 

그 이유는 위젯은 앞으로 모듈식으로 엄청 많이 만들 텐데 그 위젯마다 필요한 정보를 가지고 오면 유지보수가 힘들어요.

 

만약에 캐릭터를 의존하는 위젯들이 있는데 캐릭터가 변경되면 위젯도 전부 변경해야 하는 일이 생겨요.. 

 

그렇지 않기 위해 WidgetController를 의존해서 위젯은 바꿀필요 없고 WidgetController 하나만 바꾸면 돼요

 

그러면 나중에 관리하기 편할 거 같아서 그렇게 했어요.

 

자 그럼 위젯에 값을 수정하려면 WidgetController에 필요한 정보를 가져와야겠죠

 

 

 

 

 

 

 

 

그거는 다음시간에 할게요!