UE4开发C++沙盒游戏教程笔记(二十一)(对应教程集数 64)完结

UE4开发C++沙盒游戏教程笔记(二十一)(对应教程集数 64)完结

63. 保存存档与项目完结

继续补充保存数据的逻辑。

在 PlayerState 里面添加一个保存角色生命值和饥饿度的方法。

SlAiPlayerState.h

public:

	// 保存血量和饥饿值到指定数据
	void SaveState(float& HPVal, float& HungerVal);

SlAiPlayerState.cpp

void ASlAiPlayerState::SaveState(float& HPVal, float& HungerVal)
{
	HPVal = HP;
	HungerVal = Hunger;
}

敌人类里面添加一个获取敌人目前生命值的方法,方便后面存进敌人血量数组。

SlAiEnemyCharacter.h

public:

	// 获取血量,保存游戏时调用
	float GetHP();

SlAiEnemyCharacter.cpp

float ASlAiEnemyCharacter::GetHP()
{
	return HP;
}

然后就是背包管理器添加一个保存背包内容器的物品种类与数量的方法。

SlAiPackageManager.h

public:

	// 保存存档数据
	void SaveData(TArray<int32>& InputIndex, TArray<int32>& InputNum, TArray<int32>& NormalIndex, TArray<int32>& NormalNum, TArray<int32>& ShortcutIndex, TArray<int32>& ShortcutNum);

SlAiPackageManager.cpp

void SlAiPackageManager::SaveData(TArray<int32>& InputIndex, TArray<int32>& InputNum, TArray<int32>& NormalIndex, TArray<int32>& NormalNum, TArray<int32>& ShortcutIndex, TArray<int32>& ShortcutNum)
{
	for (int i = 0; i < InputContainerList.Num(); ++i) {
		InputIndex.Add(InputContainerList[i]->GetIndex());
		InputNum.Add(InputContainerList[i]->GetNum());
	}
	for (int i = 0; i < NormalContainerList.Num(); ++i) {
		NormalIndex.Add(NormalContainerList[i]->GetIndex());
		NormalNum.Add(NormalContainerList[i]->GetNum());
	}
	for (int i = 0; i < ShortcutContainerList.Num(); ++i) {
		ShortcutIndex.Add(ShortcutContainerList[i]->GetIndex());
		ShortcutNum.Add(ShortcutContainerList[i]->GetNum());
	}
}

给数据处理类添加一个新增存档名的方法。

SlAiDataHandle.h

public:

	// 添加新存档名
	void AddNewRecord();

SlAiDataHandle.cpp

void SlAiDataHandle::AddNewRecord()
{
	// 将现在的存档名添加到数组
	RecordDataList.Add(RecordName);
	// 更新 json 数据
	SlAiSingleton<SlAiJsonHandle>::Get()->UpdateRecordData(GetEnumValueAsString<ECultureTeam>(FString("ECultureTeam"), CurrentCulture), MusicVolume, SoundVolume, &RecordDataList);
}

在 Content/Res/ConfigData/RecordData.json 里面把之前测试添加的(如果有的话)RecordData 的对象去掉,只留下默认的 “0”: “Default”

在 GameMode 里添加一个保存游戏方法,就是实例化一个存档类,然后把游戏的各种数据保存到里面。

SlAiGameMode.h

public:

	// 保存游戏
	void SaveGame();

SlAiGameMode.cpp

void ASlAiGameMode::SaveGame()
{
	// 如果存档名是 Default,不进行保存
	if (SlAiDataHandle::Get()->RecordName.Equals(FString("Default"))) return;

	// 创建一个新的存档
	USlAiSaveGame* NewRecord = Cast<USlAiSaveGame>(UGameplayStatics::CreateSaveGameObject(USlAiSaveGame::StaticClass()));

	// 对存档进行赋值
	// 设置玩家位置和生命值
	NewRecord->PlayerLocation = SPCharacter->GetActorLocation();
	SPState->SaveState(NewRecord->PlayerHP, NewRecord->PlayerHunger);

	// 循环设置敌人
	for (TActorIterator<ASlAiEnemyCharacter> EnemyIt(GetWorld()); EnemyIt; ++EnemyIt) {
		NewRecord->EnemyLocation.Add((*EnemyIt)->GetActorLocation());
		NewRecord->EnemyHP.Add((*EnemyIt)->GetHP());
	}

	// 循环设置岩石
	for (TActorIterator<ASlAiResourceRock> RockIt(GetWorld()); RockIt; ++RockIt) {
		NewRecord->ResourceRock.Add((*RockIt)->GetActorLocation());
	}

	// 循环设置树木
	for (TActorIterator<ASlAiResourceTree> TreeIt(GetWorld()); TreeIt; ++TreeIt) {
		NewRecord->ResourceTree.Add((*TreeIt)->GetActorLocation());
	}
	
	// 循环设置拾取物品石头
	for (TActorIterator<ASlAiPickupStone> StoneIt(GetWorld()); StoneIt; ++StoneIt) {
		NewRecord->PickupStone.Add((*StoneIt)->GetActorLocation());
	}

	// 循环设置拾取物品木头
	for (TActorIterator<ASlAiPickupWood> WoodIt(GetWorld()); WoodIt; ++WoodIt) {
		NewRecord->PickupWood.Add((*WoodIt)->GetActorLocation());
	}

	// 获取背包数据
	SlAiPackageManager::Get()->SaveData(NewRecord->InputIndex, NewRecord->InputNum, NewRecord->NormalIndex, NewRecord->NormalNum, NewRecord->ShortcutIndex, NewRecord->ShortcutNum);

	// 查看是否已经有存档存在
	if (UGameplayStatics::DoesSaveGameExist(SlAiDataHandle::Get()->RecordName, 0)) {
		// 有的话先删除
		UGameplayStatics::DeleteGameInSlot(SlAiDataHandle::Get()->RecordName, 0);
	}
	// 保存存档
	UGameplayStatics::SaveGameToSlot(NewRecord, SlAiDataHandle::Get()->RecordName, 0);

	// 查看 json 是否已经有这个存档
	bool IsRecordExist = false;
	for (TArray<FString>::TIterator It(SlAiDataHandle::Get()->RecordDataList); It; ++It) {
		// 只要有一个相同,就跳出
		if ((*It).Equals(SlAiDataHandle::Get()->RecordName)) {
			IsRecordExist = true;
			break;
		}
	}
	// 如果存档不存在,让数据管理类添加存档到 json
	if (!IsRecordExist) SlAiDataHandle::Get()->AddNewRecord();
}

最后到 HUD 类,绑定暂停菜单界面中的 “保存游戏” 按钮的委托到这个 GameMode 的方法。

SlAiGameHUD.cpp

#include "SSlAiMiniMapWidget.h"
// 引入头文件
#include "SSlAiGameMenuWidget.h"

void ASlAiGameHUD::BeginPlay()
{
	
	// 保存游戏事件绑定
	GameHUDWidget->GameMenuWidget->SaveGameDele.BindUObject(GM, &ASlAiGameMode::SaveGame);
}

编译后打开项目,在 Project Settings 里把默认地图和游玩地图设置回 MenuMap。用 Standalone 模式运行游戏,新建一个存档,然后在场景中作出一些游玩过程的改变,存档后退出。再次运行游戏并且读取该存档,可以发现场景大体上还是上一次保存时的状态。

如果大家发现明明对敌人造成了伤害后存档了,再次读档时敌人的血量又满了。这个跟老师代码的逻辑不太一样 = =💧。笔者发现是因为 SlAiGameMode 的载入方法在执行顺序上先于敌人的 BeginPlay() 方法,导致载入的数据被敌人的 BeginPlay() 方法覆盖掉了。

并且如果载入时报错导致引擎崩溃,且崩溃来源是 GameMode 的载入方法中的一处调用 LoadHP() 的代码,这个问题的原因同上。读者可以作如下修改:

ASlAiEnemyCharacter::ASlAiEnemyCharacter()
{

	// 移到此处
	SAssignNew(HPBarWidget, SSlAiEnemyHPWidget);
	
	// 移到此处
	HP = 200.f;
}

void ASlAiEnemyCharacter::BeginPlay()
{
	
	// 设置血条 Widget(此处代码移到构造函数)
	//SAssignNew(HPBarWidget, SSlAiEnemyHPWidget);
	
	// 设置初始生命值(此处代码移到构造函数)
	//HP = 200.f;
}

至此,课程已经结束啦!希望这个系列的笔记能够帮到你,在此也感谢梁迪老师的高质量课程。: )

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值