60、游戏菜单、音频设置与存档功能实现指南

游戏菜单、音频设置与存档功能实现指南

1. 菜单与物品栏互斥显示

为了避免菜单和物品栏同时显示,需要进行如下操作:
1. 打开 MenuManager 脚本。
2. 添加变量:

internal var iMode = false; // track inventory mode
  1. Update 函数顶部添加:
if (iMode) return;          // the inventory screen is open
  1. OnGUI 函数顶部也添加相同代码。
  2. 保存脚本。
  3. 点击播放,测试当物品栏界面打开时,菜单是否不会出现。

接着,要防止在菜单模式开启时打开物品栏:
1. 打开 InventoryManager 脚本。
2. 在 ToggleMode 函数顶部添加:

// if there is a menu open, inventory is not allowed
if(controlCenter.GetComponent(MenuManager).menuMode) return;
2. 音频设置
2.1 音效(Sound FX)

音效通过 ActionObject 标签的对象的 Audio Source 组件播放。操作步骤如下:
1. 打开 MenuManager 脚本。
2. 在 UpdateSettings 函数中, gameManager.NewSettings(playerSettings) 行下方添加:

//Update Audio volumes
var gos = GameObject.FindGameObjectsWithTag ("ActionObject");
for (var go in gos) {
   print (go);
   if (go.activeSelf)  {          // if it is active
      go.audio.volume = fXVolume; // adjust the volume
      }
   else { // wasn't active, so turn it on long enough to adjust sound
      go.SetActive(true);
      go.audio.volume = fXVolume; // adjust the volume
      go.SetActive(false);
   }
}
  1. 保存脚本。
  2. 点击播放,若控制台报错,检查报错行上方的对象是否需要 Audio Source 组件。
  3. 打开控制台查看问题对象,修复问题后注释掉 print 语句。
  4. 保存脚本,使用有音效的对象测试音效调整。
2.2 环境音效(Ambient Sound)

在调整环境音效音量前,需先创建环境音效:
1. 从指定文件夹导入 AmbientSounds.unitypackage
2. 将新预制体拖入层级视图,检查其 Audio Source 组件。
3. 点击播放,测试音效。
4. 可将预制体用于其他音效,如拖到瀑布处,命名为 Ambient Falls ,加载瀑布音频剪辑,将 Rolloff Mode 改为 Logarithmic Rolloff

调整环境音效代码:
1. 打开 MenuManager 脚本。
2. 在 UpdateSettings 函数的 //Update Audio volumes 部分添加:

gos = GameObject.FindGameObjectsWithTag ("Ambient");
for (go in gos) go.audio.volume = ambVolume;
  1. 添加音乐代码:
gos = GameObject.FindGameObjectsWithTag ("Music");
for (go in gos) go.audio.volume = musicVolume;
  1. 创建新标签 Music
  2. 保存脚本,测试环境音量调整。
2.3 音乐(Music)

添加音乐步骤如下:
1. 导入 TempleMusic.unity 包。
2. 将新预制体放入层级视图。
3. 点击播放,测试音效,若有失真,将 Doppler Level 设为 0。
4. 停止播放模式,将 Max Distance 设为 30,更新预制体。
5. 选择 Control Center ,将 Music Volume 默认值调至 0.2,保存场景和项目。

2.4 语音音量(Voice Volume)

设置语音音量步骤:
1. 创建新标签 Voice
2. 打开 MenuManager 脚本。
3. 在 UpdateSettings 函数底部添加:

gos = GameObject.FindGameObjectsWithTag ("Voice");
for (go in gos) go.audio.volume = voiceVolume;

//Update FX Audio volumes 部分添加:

if(go.GetComponent(CharacterID)) go.audio.volume = voiceVolume; // readjust character's volume
  1. 保存脚本、场景和项目。
3. 最终关卡菜单设置

最终关卡菜单设置步骤:
1. 在主关卡场景中,复制 Control Center 对象的 MenuManager 组件。
2. 打开最终关卡场景,将 MenuManager 脚本添加到 Control Center2 对象。
3. 使用 Paste Component Values 转移设置。
4. 导入 MenuManager_F 脚本,加载到脚本参数中。
5. 打开 MenuManager_F 脚本,搜索 “###” 定位新添加内容。
6. 取消 TreeEffects FinalTask InventoryManager 脚本中特定注释行的注释。
7. 保存脚本。
8. 为相关对象添加标签。
9. 点击播放,测试最终场景的菜单可访问性。

4. 关卡存档与加载
4.1 存档方式选择

游戏存档可选择 PlayerPrefs 或文本文件。 PlayerPrefs 以整数、浮点数或字符串格式保存数据,但只能保存一个文件,且在某些平台可能保存到注册表的隐蔽位置。文本文件可存储多个存档,便于检查和在不同计算机间移动。

4.2 文本文件读写测试
  1. 保存最终关卡场景,打开主关卡场景。
  2. 创建新脚本 SaveLoad ,添加代码:
import System.IO;
internal var filePath : String;
internal var filename= "SavedGame";
internal var extension = ".txt";

function Start () {
   filePath = Application.dataPath + "/";
   print (filePath + filename+ extension);
}
  1. 保存脚本。
  2. 创建空游戏对象 SystemIO ,将 SaveLoad 脚本拖到其上。
  3. 点击播放,控制台显示文件路径和名称。
  4. 添加写文件函数:
function WriteFile(filename : String) {
   var sWrite: StreamWriter = new StreamWriter(filePath + filename + extension);
   sWrite.WriteLine("This is a test");
   sWrite.WriteLine("We are in level " + Application.loadedLevel);
   sWrite.WriteLine("This will be some data");
   sWrite.Flush();
   sWrite.Close();
}
  1. 保存脚本。
  2. 打开 MenuManager 脚本,在 SaveGame 函数中, yield 行上方添加:
GameObject.Find("SystemIO").SendMessage( "WriteFile", "MyNewSavedGame");
  1. 保存脚本,点击播放,打开主菜单点击保存按钮,查看新创建的文本文件。

为了让玩家知道游戏正在保存,进行如下操作:
1. 在 MenuManager SaveGame 函数中修改 yield 行:

saving = true;
yield new WaitForSeconds(2);
saving = false;
  1. 删除或注释 print ("saving") 行。
  2. 添加变量:
internal var saving = false; // flag for message for save function
  1. OnGui 函数结束括号前添加:
// saving message
if (saving) GUI.Label( Rect (20,20,250,100), "Saving game");
  1. 保存脚本,修改 SaveLoad 脚本内容,点击播放测试。
2.3 读取文件
  1. 打开 LoadSave 脚本,添加读文件函数:
function ReadFile(fileName : String) {
   if (!File.Exists(filePath + fileName + extension)) return;// in case there is no file yet
   var sRead = new File.OpenText(filePath + fileName + extension);
   var input = ""; 
   while (true) {
      input = sRead.ReadLine();
      if (input == null) break;
      print ("Content: "+ input);
   }
   sRead.Close();
}
  1. 保存文件。
  2. 打开 MenuManager 脚本,在 LoadGame 函数中替换 print ("loading") 行:
GameObject.Find("SystemIO").SendMessage( "ReadFile", "MyNewSavedGame");
  1. 删除 yield 行,保存文件。
  2. 点击播放,打开主菜单点击加载游戏,查看控制台输出。
  3. 打开 LoadSave 脚本,修改扩展名和内容,再次测试。
2.4 保存游戏数据

当读写机制正常后,添加真实游戏数据:
1. 打开 LoadSave 脚本。
2. 删除 WriteFile 函数中的 sWrite.WriteLine 行,替换为:

// level
var level = Application.loadedLevel; // the current level number
sWrite.WriteLine(level);

//First Person Controller transforms
var fpc = GameObject.Find("First Person Controller");
sWrite.WriteLine(fpc.transform.position);
sWrite.WriteLine(fpc.transform.localEulerAngles);

//Player Settings
var ps = GameObject.Find("Control Center").GetComponent(MenuManager).playerSettings;
sWrite.WriteLine(ps[0]); //walkSpeed
sWrite.WriteLine(ps[1]); //turnSpeed
sWrite.WriteLine(ps[2]); // useText
sWrite.WriteLine(ps[3]); //objectDescriptions
sWrite.WriteLine(ps[4]); //fXVolume
sWrite.WriteLine(ps[5]); //ambVolume
sWrite.WriteLine(ps[6]); //musicVolume
sWrite.WriteLine(ps[7]); // voiceVolume
sWrite.WriteLine(ps[8]); // mo color
  1. 引入 actionObject 数组:
//Action Objects- get the list generated by the GameManager on Awake
var ao = GameObject.Find("Control Center").GetComponent(GameManager).actionObjects;
  1. 添加循环保存动作对象状态:
for (var x : int = 0; x < ao.length; x++) { 
   print (ao[x]); 
   if (ao[x].activeSelf == true) {
       sWrite.WriteLine(ao[x].GetComponent(Interactor).currentState);
   }
   else { 
      ao[x].SetActive(true);  
      sWrite.WriteLine(ao[x].GetComponent(Interactor).currentState);
      ao[x].SetActive(false); 
   }
}
  1. 处理库存对象:
//Inventory Objects- get the list generated by the GameManager on Awake
var io = GameObject.Find("Control Center").GetComponent(GameManager).inventoryObjects;

复制上述循环代码,将 ao 改为 io ,并移除 x 变量前的 var
6. 添加判断,若在最终关卡则停止保存数据:

if (Application.loadedLevelName == "FinalLevel") {
   sWrite.Flush();
   sWrite.Close();
   return; // don't save any more data
}
  1. 保存对话数组内容:
//save dialogue array contents
var dm : DialogueManager = GameObject.Find("Dialogue Manager").
GetComponent(DialogueManager);
var tempArray = new String[dm.topics_1.length];
tempArray = dm.topics_1;
for(var e : String in tempArray) sWrite.WriteLine(e);
tempArray = new String[dm.topics_2.length];
tempArray = dm.topics_2;
for(e in tempArray) sWrite.WriteLine(e);
tempArray = new String[dm.replies_1.length];
tempArray = dm.replies_1;
for(e in tempArray) sWrite.WriteLine(e);
tempArray = new String[dm.replies_2.length];
tempArray = dm. replies_2;
for(e in tempArray) sWrite.WriteLine(e);

以下是音频设置和存档功能的流程图:

graph TD;
    A[开始] --> B[菜单与物品栏互斥设置]
    B --> C[音频设置]
    C --> C1[音效设置]
    C --> C2[环境音效设置]
    C --> C3[音乐设置]
    C --> C4[语音音量设置]
    C --> D[最终关卡菜单设置]
    D --> E[关卡存档与加载]
    E --> E1[存档方式选择]
    E1 --> E2[文本文件读写测试]
    E2 --> E3[读取文件]
    E3 --> E4[保存游戏数据]
    E4 --> F[结束]

通过以上步骤,你可以实现游戏中菜单、音频和存档功能的设置,为玩家提供更好的游戏体验。

游戏菜单、音频设置与存档功能实现指南(续)

5. 迷宫配置保存

由于玩家可能在迷宫中保存游戏,因此保存迷宫的当前配置非常重要。在 SaveLoad 脚本的 WriteFile 函数中,在保存对话数组内容之后,添加保存迷宫配置的代码。假设迷宫配置由一个 MazeManager 脚本管理,并且有一个 GetCurrentConfiguration 方法返回迷宫的当前状态。

// 保存迷宫配置
var mazeManager = GameObject.Find("Maze Manager").GetComponent<MazeManager>();
var mazeConfig = mazeManager.GetCurrentConfiguration();
sWrite.WriteLine(mazeConfig);
6. 数据读取与恢复

在实现了数据保存之后,需要实现数据的读取与恢复功能,以便在加载游戏时能够将游戏状态恢复到保存时的状态。

6.1 修改 ReadFile 函数

打开 SaveLoad 脚本,修改 ReadFile 函数,使其能够读取并解析保存的数据。

function ReadFile(fileName : String) {
    if (!File.Exists(filePath + fileName + extension)) return;
    var sRead = new File.OpenText(filePath + fileName + extension);
    var input = "";

    // 读取关卡信息
    var level = int.Parse(sRead.ReadLine());
    Application.LoadLevel(level);

    // 读取第一人称控制器信息
    var fpc = GameObject.Find("First Person Controller");
    var positionStr = sRead.ReadLine();
    var position = ParseVector3(positionStr);
    fpc.transform.position = position;

    var rotationStr = sRead.ReadLine();
    var rotation = ParseVector3(rotationStr);
    fpc.transform.localEulerAngles = rotation;

    // 读取玩家设置
    var ps = GameObject.Find("Control Center").GetComponent(MenuManager).playerSettings;
    ps[0] = float.Parse(sRead.ReadLine()); // walkSpeed
    ps[1] = float.Parse(sRead.ReadLine()); // turnSpeed
    ps[2] = bool.Parse(sRead.ReadLine()); // useText
    ps[3] = bool.Parse(sRead.ReadLine()); // objectDescriptions
    ps[4] = float.Parse(sRead.ReadLine()); // fXVolume
    ps[5] = float.Parse(sRead.ReadLine()); // ambVolume
    ps[6] = float.Parse(sRead.ReadLine()); // musicVolume
    ps[7] = float.Parse(sRead.ReadLine()); // voiceVolume
    ps[8] = sRead.ReadLine(); // mo color

    // 读取动作对象状态
    var ao = GameObject.Find("Control Center").GetComponent(GameManager).actionObjects;
    for (var x = 0; x < ao.length; x++) {
        var stateStr = sRead.ReadLine();
        var state = int.Parse(stateStr);
        ao[x].GetComponent(Interactor).currentState = state;
    }

    // 读取库存对象状态
    var io = GameObject.Find("Control Center").GetComponent(GameManager).inventoryObjects;
    for (var x = 0; x < io.length; x++) {
        var stateStr = sRead.ReadLine();
        var state = int.Parse(stateStr);
        io[x].GetComponent(Interactor).currentState = state;
    }

    // 读取对话数组内容
    var dm = GameObject.Find("Dialogue Manager").GetComponent(DialogueManager);
    var tempArray = new String[dm.topics_1.length];
    for (var i = 0; i < tempArray.length; i++) {
        tempArray[i] = sRead.ReadLine();
    }
    dm.topics_1 = tempArray;

    tempArray = new String[dm.topics_2.length];
    for (var i = 0; i < tempArray.length; i++) {
        tempArray[i] = sRead.ReadLine();
    }
    dm.topics_2 = tempArray;

    tempArray = new String[dm.replies_1.length];
    for (var i = 0; i < tempArray.length; i++) {
        tempArray[i] = sRead.ReadLine();
    }
    dm.replies_1 = tempArray;

    tempArray = new String[dm.replies_2.length];
    for (var i = 0; i < tempArray.length; i++) {
        tempArray[i] = sRead.ReadLine();
    }
    dm.replies_2 = tempArray;

    // 读取迷宫配置
    var mazeManager = GameObject.Find("Maze Manager").GetComponent<MazeManager>();
    var mazeConfig = sRead.ReadLine();
    mazeManager.SetConfiguration(mazeConfig);

    sRead.Close();
}

function ParseVector3(str : String) : Vector3 {
    var parts = str.Trim('(', ')').Split(',');
    var x = float.Parse(parts[0]);
    var y = float.Parse(parts[1]);
    var z = float.Parse(parts[2]);
    return new Vector3(x, y, z);
}
6.2 辅助函数 ParseVector3

为了将保存的字符串格式的向量数据转换为 Vector3 类型,添加了 ParseVector3 辅助函数。

7. 错误处理与优化

在实际应用中,可能会遇到各种错误,例如文件不存在、数据格式错误等。因此,需要添加错误处理代码,以提高程序的健壮性。

7.1 异常捕获

ReadFile 函数中添加异常捕获代码,以处理可能出现的异常。

function ReadFile(fileName : String) {
    try {
        if (!File.Exists(filePath + fileName + extension)) return;
        var sRead = new File.OpenText(filePath + fileName + extension);
        var input = "";

        // 读取数据代码...

        sRead.Close();
    } catch (ex : Exception) {
        Debug.LogError("读取文件时发生错误: " + ex.Message);
    }
}
7.2 数据验证

在读取数据时,添加数据验证代码,确保读取的数据格式正确。例如,在解析向量数据时,检查字符串格式是否正确。

function ParseVector3(str : String) : Vector3 {
    if (str.StartsWith("(") && str.EndsWith(")")) {
        var parts = str.Trim('(', ')').Split(',');
        if (parts.Length == 3) {
            var x = float.Parse(parts[0]);
            var y = float.Parse(parts[1]);
            var z = float.Parse(parts[2]);
            return new Vector3(x, y, z);
        }
    }
    Debug.LogError("向量数据格式错误: " + str);
    return Vector3.zero;
}
8. 功能测试与总结

在完成了上述所有功能的实现之后,需要进行全面的测试,确保游戏的菜单、音频和存档功能正常工作。

8.1 测试步骤
  1. 点击播放,进入游戏。
  2. 在游戏中进行一些操作,例如移动角色、打开物品栏、调整音频设置等。
  3. 打开主菜单,点击保存按钮,保存游戏。
  4. 关闭游戏,重新打开。
  5. 打开主菜单,点击加载游戏按钮,检查游戏状态是否恢复到保存时的状态。
  6. 检查音频设置是否正确,迷宫配置是否正确。
8.2 总结

通过以上步骤,我们实现了游戏中菜单、音频和存档功能的设置。具体步骤总结如下表:
| 功能模块 | 操作步骤 |
| ---- | ---- |
| 菜单与物品栏互斥显示 | 1. 打开 MenuManager 脚本,添加变量和判断逻辑;2. 打开 InventoryManager 脚本,添加菜单打开时禁止打开物品栏的逻辑 |
| 音频设置 | 1. 音效:打开 MenuManager 脚本,添加音效音量调整代码;2. 环境音效:导入预制体,添加调整代码;3. 音乐:导入音乐包,调整设置;4. 语音音量:创建标签,添加调整代码 |
| 最终关卡菜单设置 | 1. 复制组件,添加脚本,转移设置;2. 导入 MenuManager_F 脚本,取消注释特定行 |
| 关卡存档与加载 | 1. 选择存档方式;2. 进行文本文件读写测试;3. 实现读取文件和保存游戏数据功能;4. 实现数据读取与恢复功能;5. 添加错误处理和优化 |

以下是数据读取与恢复的流程图:

graph TD;
    A[开始读取文件] --> B[检查文件是否存在]
    B -- 存在 --> C[读取关卡信息]
    C --> D[加载关卡]
    D --> E[读取第一人称控制器信息]
    E --> F[设置控制器位置和旋转]
    F --> G[读取玩家设置]
    G --> H[设置玩家设置]
    H --> I[读取动作对象状态]
    I --> J[设置动作对象状态]
    J --> K[读取库存对象状态]
    K --> L[设置库存对象状态]
    L --> M[读取对话数组内容]
    M --> N[设置对话数组内容]
    N --> O[读取迷宫配置]
    O --> P[设置迷宫配置]
    P --> Q[结束读取文件]
    B -- 不存在 --> Q

通过实现这些功能,玩家可以在游戏中方便地进行菜单操作、调整音频设置,并能够保存和加载游戏进度,从而获得更好的游戏体验。

基于粒子群优化算法的p-Hub选址优化(Matlab代码实现)内容概要:本文介绍了基于粒子群优化算法(PSO)的p-Hub选址优化问题的研究实现,重点利用Matlab进行算法编程和仿真。p-Hub选址是物流交通网络中的关键问题,旨在通过确定最优的枢纽节点位置和非枢纽节点的分配方式,最小化网络总成本。文章详细阐述了粒子群算法的基本原理及其在解决组合优化问题中的适应性改进,结合p-Hub中转网络的特点构建数学模型,并通过Matlab代码实现算法流程,包括初始化、适应度计算、粒子更新收敛判断等环节。同时可能涉及对算法参数设置、收敛性能及不同规模案例的仿真结果分析,以验证方法的有效性和鲁棒性。; 适合人群:具备一定Matlab编程基础和优化算法理论知识的高校研究生、科研人员及从事物流网络规划、交通系统设计等相关领域的工程技术人员。; 使用场景及目标:①解决物流、航空、通信等网络中的枢纽选址路径优化问题;②学习并掌握粒子群算法在复杂组合优化问题中的建模实现方法;③为相关科研项目或实际工程应用提供算法支持代码参考。; 阅读建议:建议读者结合Matlab代码逐段理解算法实现逻辑,重点关注目标函数建模、粒子编码方式及约束处理策略,并尝试调整参数或拓展模型以加深对算法性能的理解。
内容概要:本文全面介绍了C#全栈开发的学习路径资源体系,涵盖从基础语法到企业级实战的完整知识链条。内容包括C#官方交互式教程、开发环境搭建(Visual Studio、VS Code、Mono等),以及针对不同应用场景(如控制台、桌面、Web后端、跨平台、游戏、AI)的进阶学习指南。通过多个实战案例——如Windows Forms记事本、WPF学生管理系统、.NET MAUI跨平台动物图鉴、ASP.NET Core实时聊天系统及Unity 3D游戏项目——帮助开发者掌握核心技术栈架构设计。同时列举了Stack Overflow、Power BI、王者荣耀后端等企业级应用案例,展示C#在高性能场景下的实际运用,并提供了高星开源项目(如SignalR、AutoMapper、Dapper)、生态工具链及一站式学习资源包,助力系统化学习工程实践。; 适合人群:具备一定编程基础,工作1-3年的研发人员,尤其是希望转型全栈或深耕C#技术栈的开发者; 使用场景及目标:①系统掌握C#在不同领域的应用技术栈;②通过真实项目理解分层架构、MVVM、实时通信、异步处理等核心设计思想;③对接企业级开发标准,提升工程能力和实战水平; 阅读建议:此资源以开发简化版Spring学习其原理和内核,不仅是代码编写实现也更注重内容上的需求分析和方案设计,所以在学习的过程要结合这些内容一起来实践,并调试对应的代码。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值