61、游戏开发:保存、加载与跨关卡数据传递

游戏开发:保存、加载与跨关卡数据传递

1. 保存迷宫墙的Z旋转

在游戏开发中,保存迷宫墙的Z旋转是一项重要的操作。以下是具体的代码实现:

// maze walls find them, save their local z rotation
var walls : Component[] = GameObject.Find("MazeWalls").GetComponentsInChildren(Transform);
for (var wall : Component in walls)
   if (wall.gameObject.GetComponent(MeshRenderer)) sWrite.WriteLine(wall.gameObject.transform.localEulerAngles.z);

操作步骤如下:
1. 找到名为 “MazeWalls” 的游戏对象,并获取其所有子对象的 Transform 组件。
2. 遍历这些组件,对于具有 MeshRenderer 组件的游戏对象,将其本地 Z 旋转写入文件。
3. 保存脚本。

2. 测试保存功能

完成代码编写后,需要进行测试。具体步骤如下:
1. 点击 “Play” 按钮,打开主菜单,保存游戏。
2. 如果遇到 “NullReferenceException” 错误,检查控制台中列出的最后一个对象。若该对象有 “ActionObject” 标签但没有 “Interactor” 脚本,则会抛出错误。停止 “Play” 模式,将对象标签设置为 “Untagged”,或者完成对象的设置。
3. 注释掉 “SaveGame” 代码中的打印语句,并保存脚本。
4. 再次点击 “Play”,在主关卡中测试保存功能,但暂时不要尝试加载。

3. 加载保存的数据

在加载保存的数据时,需要将数据转换为正确的类型,并重新分配给原始变量。具体步骤如下:
1. 打开 “SaveLoad” 脚本。
2. 创建一个处理变换的函数:

function ProcessTransforms (object :GameObject, theValue : String, transform : String) {
   //strip off parentheses
   theValue = theValue.Substring(1,theValue.length -2);
   //split the string into an array using the commas
   var readString : String[] = theValue.Split(","[0]);
   // feed the new elements into a Vector3
   var nt : Vector3 =  
Vector3(parseFloat(readString[0]),parseFloat(readString[1]),parseFloat(readString[2]));
   if (transform == "position") object.transform.position = nt;
   else object.transform.localEulerAngles = nt;
}
  1. 在 “ReadFile” 函数中,添加以下代码来检查关卡:
// level, if the level is different than the present level, load it
var level = parseInt( sRead.ReadLine());
if (level != Application.loadedLevel) {
   // more here later
   Application.LoadLevel(level);
}
  1. 处理第一人称控制器的变换:
// First Person Controller transforms
var fpc = GameObject.Find("First Person Controller");
ProcessTransforms(fpc,sRead.ReadLine(),"position");
ProcessTransforms(fpc,sRead.ReadLine(),"rotation");
  1. 加载玩家设置数组:
// read Settings into an array
var ps = new String[9];
for (var i : int; i<9; i++) {
   ps[i] = sRead.ReadLine();
}
var controlCenter : GameObject = GameObject.Find("Control Center");
controlCenter.GetComponent(GameManager).NewSettings(ps);     // update array in game manager
controlCenter.GetComponent(MenuManager).playerSettings = ps; // update the playerSettins array
controlCenter.GetComponent(MenuManager). UpdateControls();   // update GUI controls
  1. 处理动作对象和库存对象:
//Process action objects- get the list generated by the GameManager on Awake
var ao = GameObject.Find("Control Center").GetComponent(GameManager).actionObjects;
for (var x : int = 0; x < ao.length; x++) { // iterate through the array of action objects
   // process it into the save's state
   ao[x].SetActive(true);                     // activate it
   ao[x].SendMessage("ProcessObject",parseInt(sRead.ReadLine()));
}

//Process inventory objects- get the list generated by the GameManager on Awake
var io = GameObject.Find("Control Center").GetComponent(GameManager).inventoryObjects;
for (x = 0; x < io.length; x++) { // iterate through the array of inventory objects
   // process it into the save's state
   io[x].SetActive(true); // activate it
   io[x].SendMessage("ProcessObject",parseInt(sRead.ReadLine()));
}
  1. 检查关卡,若玩家处于最终关卡,则停止读取数据:
// stop reading/loading data here if player is in FinalLevel
if (Application.loadedLevelName == "FinalLevel") {
   sRead.Close();
   return; // don't read any more data
}
  1. 加载对话数组:
//load dialogue array contents
var dm : DialogueManager = GameObject.Find("Dialogue Manager").GetComponent(DialogueManager);
for(i = 0; i < dm.topics_1.length; i++) dm.topics_1[i] = sRead.ReadLine();
for(i = 0; i < dm.topics_2.length; i++) dm.topics_2[i] = sRead.ReadLine();
for(i = 0; i < dm.replies_1.length; i++) dm.replies_1[i] = sRead.ReadLine();
for(i = 0; i < dm.replies_2.length; i++) dm.replies_2[i] = sRead.ReadLine();
  1. 恢复迷宫墙的旋转:
// maze walls find them, update their local z rotation
var walls : Component[] = GameObject.Find("MazeWalls").GetComponentsInChildren(Transform);
for (var wall : Component in walls)
   if (wall.gameObject.GetComponent(MeshRenderer)) wall.gameObject.transform.localEulerAngles.z = parseFloat(sRead.ReadLine());
  1. 删除 “ReadFile” 函数中读取测试内容并打印到控制台的原始代码,确保保留 “sRead.Close()” 行。
  2. 保存脚本。
4. 优化加载过程

在加载过程中,可能会遇到一些问题,如对象播放动画和音效。为了解决这些问题,可以进行以下优化:
1. 在 “readFile” 函数中,在调用 “ao[x].SendMessage(“ProcessObject”” 之前,添加以下代码:

ao[x].GetComponent(Interactor).loading = true; // turn on the loading flag
  1. 保存脚本。
  2. 打开 “Interactor” 脚本。
  3. 在 “//Misc vars” 部分添加新变量:
internal var loading = false; // flag for alternate processing of object state
  1. 在 “ProcessObject” 函数中,将两个 “ProcessAudio (currentSound)” 语句修改为:
if (!loading) ProcessAudio (currentSound);
  1. 在 “ProcessObject” 函数的末尾,关闭加载标志:
loading = false;
  1. 保存脚本。
  2. 点击 “Play” 进行测试,确保加载时不再播放音效。
5. 优化动画播放

为了让动画剪辑立即结束,可以将 “normalizedTime” 设置为 0。具体操作如下:
1. 在 “ProcessObject” 函数的 “if (animates)” 部分,将 “aniObject.animation.Play(currentAnimationClip.name)” 语句修改为:

if (loading) {
    // set the animate time to 0, play the animation, then set it back to normal
    aniObject.animation[currentAnimationClip.name].normalizedTime = 0.0;
    aniObject.animation.Play(currentAnimationClip.name);
    aniObject.animation[currentAnimationClip.name].normalizedTime = 1.0;
}
else { // else it is not loading so process as usual
   aniObject.animation.Play(currentAnimationClip.name);
}
  1. 保存脚本。
  2. 点击 “Play” 进行测试,确保打开的箱子盖子在加载时能立即打开。
6. 优化库存对象显示

为了确保库存对象的图标在保存和加载后正常显示,需要在 “Interactor” 脚本的 “Handle2D” 函数末尾添加以下代码:

if(previousState == 0 && currentState == 0) gameObject.SetActive(false);

保存脚本后,点击 “Play” 进行测试,打开库存界面,确保图标正常显示。

7. 管理非动作对象

在完成加载和保存功能后,需要对非动作对象进行管理。以下是初步测试的步骤:
1. 每次获得动作对象或完成任务时保存游戏。
2. 开始新游戏,然后加载保存的游戏。
3. 记录需要特别关注的对象。
4. 保存游戏后立即重命名,并添加有意义的名称。
5. 重复上述步骤,直到完成游戏,手动处理出现的问题。

经过测试,可能会发现以下问题需要解决:
- 处理岩石和水晶。
- 保存收集金色袖套后浮动托盘的位置。
- 找出 “KeyAtLock” 在加载后不可见的原因。
- 保存和加载玩家在寺庙内时地形的 Y 位置。
- 保存和加载寺庙阻挡器的碰撞器状态。
- 保存和加载玩家在隧道内时的雾状态。

8. 解决岩石和水晶问题

对于岩石和水晶问题,可以通过修改 “Rockfall” 脚本解决。具体步骤如下:
1. 打开 “Rockfall” 脚本。
2. 将 “function OnTriggerEnter () {” 行修改为:

function OnTriggerEnter (object : Collider) {
   if( object == prize.collider) Destroy(this.gameObject); //prevent rock fall
}
  1. 保存脚本,触发岩石坠落,留下水晶,保存游戏,开始新游戏,然后加载保存的游戏。

为了让水晶记住其位置,可以添加保存变换的功能。具体步骤如下:
1. 打开 “Interactor” 脚本,添加以下变量:

var saveTransforms : boolean = false; // flag to save current transform for save/load
  1. 保存脚本,将水晶对象的新参数设置为 “true”。
  2. 打开 “SaveLoad” 脚本。
  3. 在 “WriteFile” 函数中,在两个 “sWrite.WriteLine(ao[x].GetComponent(Interactor).currentState)” 行之后添加以下代码:
if (ao[x].GetComponent(Interactor).saveTransforms == true){
    sWrite.WriteLine(ao[x].transform.position);
    sWrite.WriteLine(ao[x].transform.localEulerAngles);
}
  1. 在 “ReadFile” 函数中,在 “ao[x].SendMessage(“ProcessObject”, parseInt(sRead.ReadLine()))” 行之后添加以下代码:
if (ao[x].GetComponent(Interactor).saveTransforms == true){
   ProcessTransforms(ao[x],sRead.ReadLine(),"position");
   ProcessTransforms(ao[x],sRead.ReadLine(),"rotation");
}
  1. 保存脚本,将水晶的 “Save Transform” 参数设置为 “true”。
  2. 点击 “Play” 进行测试,再次触发岩石坠落,保存,重启,然后加载。
  3. 将 “TrayCloth Floating” 对象的 “Save Transforms” 参数也设置为 “true”。
9. 解决 KeyAtLock 问题

“KeyAtLock” 问题是由于 Unity 保存动画剪辑的方式导致的。为了解决这个问题,可以进行以下操作:
1. 复制 “key insert_copy” 剪辑,并将其命名为 “key inserted”。
2. 选择 “KeyAtLock Group”,将其 “Animations” 数组增加 1。
3. 将新剪辑加载到新元素中。
4. 选择 “KeyAtLock”,将新剪辑加载到 “Animation Clip Element 2” 中。
5. 打开动画编辑器,加载新剪辑,打开录制按钮。
6. 在左下角,将 “Show: All” 切换为 “Show: Animated”。
7. 选择关键材质的 “Color.a” 轨道,将第 0 帧的值改为 1。
8. 将第 26 帧的关键帧移动到第 1 帧。
9. 选择 “Transform” 轨道,删除第 15 帧的关键帧。
10. 将指示器移动到第 0 帧,点击 “Add Keyframe”。
11. 在第 1 帧再次点击 “Add Keyframe”。
12. 删除剩余的关键帧。

10. 保存和加载地形与寺庙阻挡器

地形和寺庙阻挡器只需保存一个值,可以将它们作为杂项对象添加。具体步骤如下:
1. 打开 “SaveLoad” 脚本。
2. 在 “WriteFile” 函数中,在 “sWrite.Flush()” 行之前添加以下代码:

// Misc single values
sWrite.WriteLine(GameObject.Find("Terrain").transform.position.y);
sWrite.WriteLine(GameObject.Find("TempleBlocker").collider.enabled);
  1. 在 “ReadFile” 函数中,在 “sRead.Close()” 行之前添加以下代码:
// Misc single values
GameObject.Find("Terrain").transform.position.y = parseFloat(sRead.ReadLine());
GameObject.Find("TempleBlocker").collider.enabled = parseBool(sRead.ReadLine());
  1. 从 “MenuManager” 脚本中复制 “parseBool()” 函数,并粘贴到 “SaveLoad” 脚本中。
  2. 保存脚本。
  3. 点击 “Play” 进行测试,从寺庙内部足够远的地方保存游戏,确保地形下降。
11. 解决隧道状态问题

隧道状态问题涉及雾状态和玩家掉落最后一个洞时的情况。由于它依赖于多个变量,可以将新状态发送到一个函数进行处理。具体步骤如下:
1. 打开 “SaveLoad” 脚本。
2. 在 “WriteFile” 函数中,在 “sWrite.Flush()” 行之前添加以下代码:

sWrite.WriteLine(GameObject.Find("Control Center").GetComponent(FogManager).currentState);
  1. 在 “ReadFile” 函数中,在 “sRead.Close()” 行之前添加以下代码:
GameObject.Find("Control Center").GetComponent(FogManager).InTheDark(parseBool(sRead.ReadLine()));
  1. 保存脚本。
  2. 点击 “Play” 进行测试,在隧道内使用光源保存游戏。
12. 跨关卡数据传递

在测试加载和保存功能时,可能会发现从不同关卡加载保存的游戏会出现问题。为了解决这个问题,可以创建一个简单的 “快递服务” 来传递数据。具体步骤如下:
1. 保存最终关卡场景,加载最终关卡场景。
2. 从章节的 “Assets” 文件夹中导入 “SaveLoad_F” 脚本。
3. 将 “SaveLoad_F” 脚本添加到 “SystemIO_F” 对象中。
4. 保存场景。

创建快递预制体的步骤如下:
1. 加载主关卡场景。
2. 创建一个空的游戏对象,命名为 “Courier”。
3. 创建一个新脚本,命名为 “CourierBag”,添加以下代码:

var fileName : String;

function Awake () {
   DontDestroyOnLoad(gameObject); // so it will persist to the next level
}

function Start () {
   Destroy(gameObject, 1); //allow the info 1 second to be harvested before killing courier
}
  1. 保存脚本,将其拖到 “Courier” 对象上。
  2. 创建一个名为 “Courier” 的新预制体,并将 “Courier” 对象加载到其中。
  3. 从层次结构视图中删除 “Courier” 对象。

在 “SaveLoad” 和 “SaveLoad_F” 脚本中进行以下修改:
1. 打开 “SaveLoad” 脚本。
2. 添加以下变量:

var courier : GameObject; // the prefab
  1. 在 “ReadFile” 函数的顶部附近,将 “// more here later” 行修改为:
// instantiate Courier to carry data to new level
var newCourier : GameObject =  Instantiate(courier);
// load the fileName into it
newCourier.GetComponent(CourierBag).fileName = fileName;
  1. 在 “Start” 函数中,添加以下代码:
var tempCourier : CourierBag = GameObject.FindObjectOfType(CourierBag);
if(tempCourier) {
   yield;
   if(loadSavedGame) ReadFile(tempCourier.fileName); // load the saved game
}
  1. 保存脚本。
  2. 将 “Courier” 预制体加载到 “SaveLoad” 组件的 “Courier” 参数中。
  3. 对最终关卡中的 “SaveLoad_F” 脚本重复上述步骤。
  4. 点击 “Play” 进行测试,在主关卡保存游戏,玩到最终关卡,在触发最终序列之前加载保存的游戏。
13. 添加开始菜单

现在大部分游戏功能已经实现,可以添加开始菜单。具体步骤如下:
1. 从本章的 “Book Assets” 文件夹中加载 “StartMenu.unitypackage” 文件。
2. 从 “Scenes” 文件夹中打开 “StartScene”。
3. 选择主摄像机,在 “Start Menu” 组件中,将 “Simple Text” 的字体设置为 “Arial”。

流程图

graph LR
    A[保存迷宫墙Z旋转] --> B[测试保存功能]
    B --> C[加载保存的数据]
    C --> D[优化加载过程]
    D --> E[优化动画播放]
    E --> F[优化库存对象显示]
    F --> G[管理非动作对象]
    G --> H[解决岩石和水晶问题]
    H --> I[解决KeyAtLock问题]
    I --> J[保存和加载地形与寺庙阻挡器]
    J --> K[解决隧道状态问题]
    K --> L[跨关卡数据传递]
    L --> M[添加开始菜单]

总结

通过以上步骤,可以实现游戏的保存、加载和跨关卡数据传递功能,并解决在开发过程中遇到的各种问题。在实际开发中,需要根据具体情况进行调整和优化,以确保游戏的稳定性和用户体验。

游戏开发:保存、加载与跨关卡数据传递(续)

14. 总结与回顾

在前面的内容中,我们已经详细介绍了游戏开发中保存、加载和跨关卡数据传递的各个步骤。下面通过一个表格来回顾一下主要的操作和对应的脚本文件:
| 操作内容 | 涉及脚本文件 | 关键代码或步骤 |
| — | — | — |
| 保存迷宫墙的Z旋转 | 相关脚本 | 查找 “MazeWalls” 并遍历子对象,保存Z旋转到文件 |
| 测试保存功能 | 相关脚本 | 点击 “Play”,处理可能的错误,注释打印语句 |
| 加载保存的数据 | SaveLoad 脚本 | 创建处理变换函数,检查关卡,处理控制器变换、玩家设置等 |
| 优化加载过程 | Interactor 脚本、SaveLoad 脚本 | 设置加载标志,修改音效处理逻辑 |
| 优化动画播放 | Interactor 脚本 | 设置动画的 normalizedTime |
| 优化库存对象显示 | Interactor 脚本 | 在 Handle2D 函数末尾添加代码 |
| 管理非动作对象 | 多个脚本 | 测试并记录问题,针对性解决 |
| 解决岩石和水晶问题 | Rockfall 脚本、Interactor 脚本、SaveLoad 脚本 | 修改触发函数,添加保存变换标志和代码 |
| 解决 KeyAtLock 问题 | 动画编辑器、相关脚本 | 复制剪辑,修改动画关键帧 |
| 保存和加载地形与寺庙阻挡器 | SaveLoad 脚本 | 添加保存和读取单个值的代码 |
| 解决隧道状态问题 | SaveLoad 脚本 | 添加保存和处理隧道状态的代码 |
| 跨关卡数据传递 | SaveLoad 脚本、SaveLoad_F 脚本、CourierBag 脚本 | 创建快递预制体,修改脚本传递文件名 |
| 添加开始菜单 | 相关脚本 | 加载包,打开场景,设置字体 |

15. 常见问题及解决方案

在游戏开发过程中,可能会遇到一些常见问题,下面为大家总结并提供相应的解决方案:
- NullReferenceException 错误
- 原因 :对象有 “ActionObject” 标签但没有 “Interactor” 脚本。
- 解决方案 :停止 “Play” 模式,将对象标签设置为 “Untagged”,或者完成对象的设置。
- 加载时播放音效和动画问题
- 原因 :默认加载时对象会正常播放动画和音效。
- 解决方案 :设置加载标志,修改音效和动画处理逻辑,如在 Interactor 脚本中设置加载标志,修改 ProcessAudio 和动画播放代码。
- 库存对象图标显示问题
- 原因 :没有代码处理已停用对象的激活状态。
- 解决方案 :在 Interactor 脚本的 Handle2D 函数末尾添加代码,根据状态设置对象的激活状态。
- 不同关卡加载保存游戏问题
- 原因 :从不同关卡加载时,数据传递和处理存在问题。
- 解决方案 :创建快递预制体,在脚本中传递文件名,实现跨关卡数据传递。

16. 进一步优化建议

虽然我们已经解决了大部分问题,但在实际开发中,还可以进行一些进一步的优化:
- 性能优化 :在保存和加载大量数据时,可能会影响游戏性能。可以考虑使用更高效的数据存储格式,如二进制文件,减少数据的读写时间。
- 多存档支持 :目前只支持一个保存文件,可以扩展代码,实现多个存档的功能,让玩家可以有更多的选择。
- 用户界面优化 :可以添加更友好的用户界面,如加载进度条,让玩家在加载游戏时能更直观地了解加载进度。

17. 流程图回顾与拓展

下面再次展示之前的流程图,它清晰地展示了整个游戏开发过程的主要步骤:

graph LR
    A[保存迷宫墙Z旋转] --> B[测试保存功能]
    B --> C[加载保存的数据]
    C --> D[优化加载过程]
    D --> E[优化动画播放]
    E --> F[优化库存对象显示]
    F --> G[管理非动作对象]
    G --> H[解决岩石和水晶问题]
    H --> I[解决KeyAtLock问题]
    I --> J[保存和加载地形与寺庙阻挡器]
    J --> K[解决隧道状态问题]
    K --> L[跨关卡数据传递]
    L --> M[添加开始菜单]

如果要进行进一步的拓展,可以在这个流程图的基础上添加性能优化、多存档支持等步骤,形成一个更完善的开发流程。

18. 总结

通过一系列的操作和优化,我们成功实现了游戏的保存、加载和跨关卡数据传递功能,并且解决了开发过程中遇到的各种问题。在实际开发中,我们需要不断地测试和优化,根据游戏的具体需求和玩家的反馈,对代码进行调整和改进,以提供更好的游戏体验。希望本文能为游戏开发者在处理保存、加载和数据传递方面提供一些有用的参考和指导。

流程图(拓展设想)

graph LR
    A[保存迷宫墙Z旋转] --> B[测试保存功能]
    B --> C[加载保存的数据]
    C --> D[优化加载过程]
    D --> E[优化动画播放]
    E --> F[优化库存对象显示]
    F --> G[管理非动作对象]
    G --> H[解决岩石和水晶问题]
    H --> I[解决KeyAtLock问题]
    I --> J[保存和加载地形与寺庙阻挡器]
    J --> K[解决隧道状态问题]
    K --> L[跨关卡数据传递]
    L --> M[添加开始菜单]
    M --> N[性能优化]
    N --> O[多存档支持]
    O --> P[用户界面优化]

总结

整个游戏开发过程涵盖了多个方面,从基本的保存和加载功能实现,到解决各种问题和进行优化,每一个步骤都至关重要。通过合理的设计和代码实现,我们能够创建出具有良好保存、加载和数据传递功能的游戏。在未来的开发中,开发者可以根据具体项目的特点,灵活运用这些方法和技巧,不断提升游戏的质量和用户体验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值