본문 바로가기

공부/이득우의 언리얼 프로그래밍

[Study] Part 2 - 게임플로우 다듬기 (14/15)

728x90
반응형

 

 

  • 정리
    • 게임플로우의 마무리
      1. 죽는 상태에 대한 플레이어와 NPC의 설정
      2. 전체 게임 구성 요소의 이해
      3. 캐릭터 데이터 조정과 다양한 아이템 설정 및 구현

 

게임 구성 요소의 분류

 

  • 세 개의 레이어를 기준으로 게임을 구성하는 다양한 기능의 구현
  • 효과적으로 관리하기 위해 정한 규칙
    • 상위 레이어에 있는 객체들은 하위 레이어에 있는 객체들을 자유롭게 참조 가능
    • 하위 레이어에 있는 객체들은 상위 레이어에 있는 객체들을 참고하기 위해서는 인터페이스를 거치도록 설정
  • 게임 플로우를 위해 보강할 내용
    • 죽었을 때의 NPC와 플레이어의 처리
    • 이동 속도의 적용
    • 포션/스크롤 아이템의 추가 구현과 캐릭터의 적용
    • 스탯 기능 및 UI의 기능의 보강

 

  • 죽었을 때의 NPC와 플레이어의 처리
// 죽었을 때
void AABCharacterNonPlayer::SetDead()
{
	Super::SetDead();

	// 죽으면 AI를 중단시켜준다.
	AABAIController* ABAIController = Cast<AABAIController>(GetController());
	if (ABAIController)
	{
		ABAIController->StopAI();
	}

	FTimerHandle DeadTimerHandle;
	GetWorld()->GetTimerManager().SetTimer(DeadTimerHandle, FTimerDelegate::CreateLambda(
		[&]()
		{
			Destroy();
		}
	), DeadEventDelayTime, false);
}
  • ABCharacterNonPlayer.cpp

 

  • NPC가 죽었을 때는 ABAIController에 있는 StopAI함수를 호출해주도록 SetDead함수를 수정해준다.
// Player가 죽었을때 동작하는 함수
virtual void SetDead() override;
  • ABCharacterPlayer.h
void AABCharacterPlayer::BeginPlay()
{
	Super::BeginPlay();

	// 입력을 켜주는 기능 추가
	APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if (PlayerController)
	{
		EnableInput(PlayerController);
	}

	SetChangeCharacterControl(CurrentCharacterControlType);
}

// 죽었을 때 (ABCharacterBase)
// 죽었으니 움직임 제한시켜주면서 죽는 애니메이션 재생 + 콜리전 기능 제한 등의 기능이 구현되어 있다.
void AABCharacterPlayer::SetDead()
{
	Super::SetDead();

	// + 입력을 끄는 기능 추가
	APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if (PlayerController)
	{
		DisableInput(PlayerController);
	}
}
  • ABCharacterPlayer.cpp

 

  • 플레이어의 경우 SetDead 함수가 없었기 때문에 추가로 오버라이드 해주고, 죽게 되면 입력을 끄는 기능을 추가해준다.
  • 추가 안전장치 기능으로 BeginPlay가 실행될 때 입력을 받는 기능을 추가해주도록 하자.

 

  • 포션/스크롤 아이템의 추가 구현과 캐릭터의 적용 + 이동 속도의 적용
#pragma once

#include "ABWeaponItemData.h"
#include "ABScrollItemData.h"
#include "ABPotionItemData.h"
  • ABItems.h

 

  • ABItemData들을 관리하기 쉽게 다음과 같은 헤더파일을 만들어주고, ItemData가 필요한 곳에 헤더파일을 추가해주자.
  • 기존에는 Weapon만 구현을 했는데, PotionScroll도 함께 구현해주자.
  • 앞으로 ItemData들을 추가할 때는 ABItems.h를 추가해서 구현할 수 있도록

 

 // 생성자 ...
	
	// UPrimaryDataAsset 내부의 생성될 에셋의 아이디를 직접 지정해줄 수 있는 GetPrimaryAssetId() 함수를 오버라이드
	FPrimaryAssetId GetPrimaryAssetId() const override
	{
		// 두가지 정보를 바탕으로 유일한 식별자 아이디값을 만들어 낼 수 있다.
		return FPrimaryAssetId("ABItemData", GetFName());
	}
  • ItemData.cpp

 

  • IteamData에 공통적으로 GetPrimaryAssetId()와 생성자를 구현해준다. 생성자에서는 ItemType을 설정하도록 하고, GetPrimaryAssetId()을 통해 에셋의 아이디를 직접 지정해주자.
  • 이후, 아이템 타입에 맞게 Potionfloat HealAmount를, ScrollFABCharacterStat BaseStat 변수들을 선언해주고 아이템 별로 Data Asset의 값들을 설정해준다.

 

설정한 Data Asset들

 

// Potion 획득
void AABCharacterBase::DrinkPostion(UABItemData* InItemData)
{
	UABPotionItemData* PotionItemData = Cast<UABPotionItemData>(InItemData);
	if (PotionItemData)
	{
		Stat->HealHp(PotionItemData->HealAmount);
	}
}

// ...

// Scroll 획득
void AABCharacterBase::ReadScroll(UABItemData* InItemData)
{
	UABScrollItemData* ScrollItemData = Cast<UABScrollItemData>(InItemData);
	if (ScrollItemData)
	{
		Stat->AddBaseStat(ScrollItemData->BaseStat);
	}
}
  • ABCharacterBase.cpp

 

  • 로그만 찍었던 DrinkPotionReadScroll의 기능을 추가해준다.
  • Stat컴포넌트에서 Potion을 획득하면 MaxHp가 증가하도록 해주고, Scroll을 획득하면 데이터에 따라 BaseStat을 변경해주도록 로직을 구성하면 된다.

 

// 아이템 효과 함수 (포션)
FORCEINLINE void HealHp(float InHealAmount) { CurrentHp = FMath::Clamp(CurrentHp + InHealAmount, 0, GetTotalStat().MaxHp); OnHpChanged.Broadcast(CurrentHp); }

// 아이템 효과 함수 (스크롤)
FORCEINLINE void AddBaseStat(const FABCharacterStat& InAddBaseStat) { BaseStat = BaseStat + InAddBaseStat; OnStatChanged.Broadcast(GetBaseStat(), GetModifierStat()); }
  • ABCharacterStatComponent.h

 

  • 전처리를 통해 간단하게 구현하고, Stat이 변경되기때문에 델리게이트를 통해 Broadcast를 해주면 UI에도 반영이 된다.

 

  • 에디터에서 HpBarWidget의 레이아웃을 살짝 수정해주고, 클래스를 수정해준다.
public:
	//FORCEINLINE void SetMaxHp(float NewMaxHp) { MaxHp = NewMaxHp; }
	void UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat);
	void UpdateHpBar(float NewCurrentHp);
  • ABHpBarWidget.h
// NativeConstruct 함수가 호출될 때면 UI에 관련된 모든 기능들이 거의 초기화가 완료됐다고 보면 된다.
void UABHpBarWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// 아까 PbHpBar라고 Canvas에 설정함
	HpProgressBar = Cast<UProgressBar>(GetWidgetFromName(TEXT("PbHpBar")));
	ensure(HpProgressBar);

	// TextBlock
	HpStat = Cast<UTextBlock>(GetWidgetFromName(TEXT("TxtHpStat")));
	ensure(HpStat);

	IABCharacterWidgetInterface* CharacterWidget = Cast<IABCharacterWidgetInterface>(OwningActor);
	if (CharacterWidget)
	{
		CharacterWidget->SetupCharacterWidget(this);
	}
}

// Stat이 변경되면 HpBar와 변경된 Stat이 반영되도록
void UABHpBarWidget::UpdateStat(const FABCharacterStat& BaseStat, const FABCharacterStat& ModifierStat)
{
	MaxHp = (BaseStat + ModifierStat).MaxHp;

	if (HpProgressBar)
	{
		HpProgressBar->SetPercent(CurrentHp / MaxHp);
	}

	if (HpStat)
	{
		HpStat->SetText(FText::FromString(GetHpStatText()));
	}
}

// Stat이 변경되면 HpBar와 변경된 Stat이 반영되도록
void UABHpBarWidget::UpdateHpBar(float NewCurrentHp)
{
	CurrentHp = NewCurrentHp;

	ensure(MaxHp > 0.0f);
	if (HpProgressBar)
	{
		HpProgressBar->SetPercent(CurrentHp / MaxHp);
	}

	if (HpStat)
	{
		HpStat->SetText(FText::FromString(GetHpStatText()));
	}
}

// HpBar의 Text가 100/100 형태로 나올 수 있도록
FString UABHpBarWidget::GetHpStatText()
{
	return FString::Printf(TEXT("%.0f/%.0f"), CurrentHp, MaxHp);
}
  • ABHpBarWidget.cpp

 

  • UpdateStat함수를 추가해 HpBarStat이 변경되면 바로 적용될 수 있도록 해준다.
  • GetHpStatText를 통해 HpBarText의 형태가 정상적으로 나올 수 있게 변경해준다.

 

 

 

 

아이템을 획득하니 MaxHp가 증가하여 스텟이 변경되는 모습을 확인할 수 있다.

 

 

 

 

 

 

해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part2 -언리얼 게임 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
 

이득우의 언리얼 프로그래밍 Part2 - 언리얼 게임 프레임웍의 이해 | 이득우 - 인프런

청강문화산업대학교에서 언리얼 엔진, 게임 수학, UEFN 게임제작을 가르치고 있습니다. - 이득우의 언리얼 C++ 프로그래밍, 넥슨 코리아 공식 교육 교재 선정 2023 - 스마일게이트 언리얼 프로그래

www.inflearn.com

 

 

 

 

728x90