3 minute read

‘이득우의 언리얼 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의 레벨 증가

무기를 들면 더 긴 공격 범위 구현

  • 무기에 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