게임의 완성
‘이득우의 언리얼 c++ 게임 개발의 정석’ 책을 참고하여 작성한 포스트입니다.
게임의 완성을 위해 시작, 종료, 미션 달성, 데이터 저장 로드 등을 구현해 본다.
게임 데이터의 저장과 로딩
- SaveGame 언리얼 오브젝트를 상속받은 클래스를 설계하고, 언리얼이 제공하는 세이브게임 시스템에 넘겨주면 게임 데이터의 저장과 로딩을 간편하게 구현할 수 있다.
- 에디터에서 저장하는 경우, 프로젝트의 Saved/SaveGames 폴더에 게임 데이터가 저장된다.
- 처음에는 세이브된 게임 데이터가 없으므로 PlayerState 클래스의 InitPlayerData 에 기본 세이브 데이터를 생성하는 로직을 구현한다.
void AABPlayerState::InitPlayerData()
{
auto ABSaveGame = Cast<UABSaveGame>(UGameplayStatics::LoadGameFromSlot(SaveSlotName, 0));
if (nullptr == ABSaveGame)
{
ABSaveGame = GetMutableDefault<UABSaveGame>();
}
SetPlayerName(ABSaveGame->PlayerName);
SetCharacterLevel(ABSaveGame->Level);
GameScore = 0;
GameHighScore = ABSaveGame->HighScore;
Exp = ABSaveGame->Exp;
}
UGameplayStatics::LoadGameFromSlot
- Load the contents from a given slot.
static USaveGame * LoadGameFromSlot
(
const FString & SlotName,
// For some platforms, master user index to identify the user doing the loading.
const int32 UserIndex
)
GetMutableDefault
- Gets the mutable default object of a class.
- returns Class default object (CDO)
template<class T>
T * GetMutableDefault
(
UClass* Class
)
데이터 저장 기능
- 최초 플레이어 데이터 생성 후 저장하고, 이후 경험치 변동 시 저장하는 로직으로 해본다.
- 언리얼 오브젝트 생성 시 NewObject 명령을 사용하며, 이렇게 생성된 오브젝트를 더이상 사용하지 않으면 가비지 컬렉터가 탐지해 자동으로 소멸시킨다.
- 액터 생성 작업도 언리얼 오브젝트 생성의 한 종류이다. 하지만 액터는 생성 시 고려할 점이 많아 SpawnActor를 통해 생성한다.
void AABPlayerState::SavePlayerData()
{
UABSaveGame* NewPlayerData = NewObject<UABSaveGame>();
NewPlayerData->PlayerName = GetPlayerName();
NewPlayerData->Level = CharacterLevel;
NewPlayerData->Exp = Exp;
NewPlayerData->HighScore = GameHighScore;
if (!UGameplayStatics::SaveGameToSlot(NewPlayerData, SaveSlotName, 0))
{
ABLOG(Error, TEXT("SaveGame Error!"));
}
}
UGameplayStatics::SaveGameToSlot
- Save the contents of the SaveGameObject to a platform-specific save slot/file.
- 성공하면 true 반환
static bool SaveGameToSlot
(
USaveGame * SaveGameObject,
const FString & SlotName,
const int32 UserIndex
)
UABSaveGame* NewPlayerData = NewObject();
template<class T>
T * NewObject
(
UObject * Outer,
const UClass * Class,
FName Name,
EObjectFlags Flags,
UObject * Template,
bool bCopyTransientsFromClassDefaults,
FObjectInstancingGraph * InInstanceGraph,
UPackage * ExternalPackage
)
전투 시스템의 설계
- 다음과 같은 요소들을 추가해 보자.
- 게임 진행 중 hp 회복이 불가능하고 레벨업 할 때만 회복한다
- 이미 구현된 사항
- 무기를 들 때 더 긴 공격 범위를 가짐
- 무기에 공격력 증가치가 랜덤으로 부여됨
- 현재 게임 스코어가 높을수록 생성되는 NPC의 레벨 증가
- 게임 진행 중 hp 회복이 불가능하고 레벨업 할 때만 회복한다
무기를 들면 더 긴 공격 범위 구현
- 무기에 AttackRange 속성을 추가하고, 무기가 없는 경우는 character의 AttackRange를, 무기가 있는 경우는 무기의 AttackRange를 이용하도록 구현한다.
- 무기 AttackRange 속성 키워드에 EditAnywhere, BlueprintReadWrite을 지정해 무기 블루프린트에서도 AttackRange를 다르게 설정할 수 있도록 한다.
- 새로운 무기 습득 시 기존에 있는 무기가 있는 경우 기존 무기를 없애고 새로운 무기를 습득하는 로직도 구현한다.
- AttackRange 최종 결정은 캐릭터 클래스에서 해야겠죠.
void AABCharacter::SetWeapon(AABWeapon* NewWeapon)
{
ABCHECK( nullptr != NewWeapon);
if (nullptr != CurrentWeapon)
{
CurrentWeapon->DetachFromActor(FDetachmentTransformRules::KeepWorldTransform);
CurrentWeapon->Destroy();
CurrentWeapon = nullptr;
}
...
}
AActor::DetachFromActor
- Detaches the RootComponent of this Actor from any SceneComponent it is currently attached to.
void DetachFromActor
(
// How to handle transforms when detaching.
const FDetachmentTransformRules & DetachmentRules
)
무기에 공격력 증가를 랜덤 수치로 구현
- 무기 생성시 BeginPlay() 에서 무기 공격력을 랜덤으로 정해 준다.
- character 클래스에서 무기 공격력을 불러와 최종 데미지를 계산하면 된다.
AABWeapon::AABWeapon()
{
...
AttackDamageMax = 10.0f;
AttackDamageMin = -2.5f;
AttackModifierMax = 1.25f;
AttackModifierMin = 0.85f;
}
...
void AABWeapon::BeginPlay()
{
Super::BeginPlay();
AttackDamage = FMath::RandRange(AttackDamageMin, AttackDamageMax);
AttackModifier = FMath::RandRange(AttackModifierMin, AttackModifierMax);
ABLOG(Warning, TEXT("Weapon Damage : %f, Modifier : %f"), AttackDamage, AttackModifier);
}
NPC의 레벨 조정 기능 구현
- 캐릭터의 스탯은 LOADING state에서 초기화하게 구현 했었다.
- 초기화 시에 GameMode로 부터 현재 게임 스코어를 받아 NPC의 레벨값을 정하는 로직을 구현해본다.
void AABCharacter::SetCharacterState(ECharacterState NewState)
{
...
switch (CurrentState)
{
case ECharacterState::LOADING:
{
if(bIsPlayer)
{
...
}
else
{
auto ABGameMode = Cast<AABGameMode>(GetWorld()->GetAuthGameMode());
ABCHECK(nullptr != ABGameMode);
int32 TargetLevel = FMath::CeilToInt(((float)ABGameMode->GetScore() * 0.8f));
int32 FinalLevel = FMath::Clamp<int32>(TargetLevel, 1, 20);
ABLOG(Warning, TEXT("New NPC Level : %d"), FinalLevel);
CharacterStat->SetNewLevel(FinalLevel);
}
...
}
}
}
GetWorld()->GetAuthGameMode()
- 게임 실행 중에 게임 모드의 포인터를 가져오는 함수.
- 멀티 플레이 게임에서 게임 모드는 게임을 관리하는 방장 역할을 하며 겡미에서 중요한 데이터를 인증하는 권한을 가진다.
```cpp /** - Returns the current Game Mode instance, which is always valid during gameplay on the server.
- This will only return a valid pointer on the server. Will always return null on a client. / AGameModeBase GetAuthGameMode() const { return AuthorityGameMode; } ```
타이틀 화면의 제작
- 새로운 레벨을 만들어 (공백 레벨 등) 타이틀 화면을 만들어 보자.
- 타이틀 레벨에서 사용할 게임 모드와 UI를 띄울 플레이어 컨트롤러를 제작해 준다.
- 플레이어 컨트롤러에 UI 클래스 값을 에디터에서 설정할 수 있도록 위젯 클래스 속성을 추가하고 EditDefaultOnly 키워드를 지정한다.
- 플레이어 컨트롤러는 게임 시작 시 UI 인스턴스를 생성하고, 이를 뷰포트에 띄운 후, 입력은 UI에만 전달되도록 구현한다.
Leave a comment