游戏菜单、音频设置与存档功能实现指南
1. 菜单与物品栏互斥显示
为了避免菜单和物品栏同时显示,需要进行如下操作:
1. 打开 MenuManager 脚本。
2. 添加变量:
internal var iMode = false; // track inventory mode
- 在
Update函数顶部添加:
if (iMode) return; // the inventory screen is open
- 在
OnGUI函数顶部也添加相同代码。 - 保存脚本。
- 点击播放,测试当物品栏界面打开时,菜单是否不会出现。
接着,要防止在菜单模式开启时打开物品栏:
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);
}
}
- 保存脚本。
- 点击播放,若控制台报错,检查报错行上方的对象是否需要
Audio Source组件。 - 打开控制台查看问题对象,修复问题后注释掉
print语句。 - 保存脚本,使用有音效的对象测试音效调整。
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;
- 添加音乐代码:
gos = GameObject.FindGameObjectsWithTag ("Music");
for (go in gos) go.audio.volume = musicVolume;
- 创建新标签
Music。 - 保存脚本,测试环境音量调整。
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
- 保存脚本、场景和项目。
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 文本文件读写测试
- 保存最终关卡场景,打开主关卡场景。
- 创建新脚本
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);
}
- 保存脚本。
- 创建空游戏对象
SystemIO,将SaveLoad脚本拖到其上。 - 点击播放,控制台显示文件路径和名称。
- 添加写文件函数:
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();
}
- 保存脚本。
- 打开
MenuManager脚本,在SaveGame函数中,yield行上方添加:
GameObject.Find("SystemIO").SendMessage( "WriteFile", "MyNewSavedGame");
- 保存脚本,点击播放,打开主菜单点击保存按钮,查看新创建的文本文件。
为了让玩家知道游戏正在保存,进行如下操作:
1. 在 MenuManager 的 SaveGame 函数中修改 yield 行:
saving = true;
yield new WaitForSeconds(2);
saving = false;
- 删除或注释
print ("saving")行。 - 添加变量:
internal var saving = false; // flag for message for save function
- 在
OnGui函数结束括号前添加:
// saving message
if (saving) GUI.Label( Rect (20,20,250,100), "Saving game");
- 保存脚本,修改
SaveLoad脚本内容,点击播放测试。
2.3 读取文件
- 打开
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();
}
- 保存文件。
- 打开
MenuManager脚本,在LoadGame函数中替换print ("loading")行:
GameObject.Find("SystemIO").SendMessage( "ReadFile", "MyNewSavedGame");
- 删除
yield行,保存文件。 - 点击播放,打开主菜单点击加载游戏,查看控制台输出。
- 打开
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
- 引入
actionObject数组:
//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++) {
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);
}
}
- 处理库存对象:
//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
}
- 保存对话数组内容:
//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 测试步骤
- 点击播放,进入游戏。
- 在游戏中进行一些操作,例如移动角色、打开物品栏、调整音频设置等。
- 打开主菜单,点击保存按钮,保存游戏。
- 关闭游戏,重新打开。
- 打开主菜单,点击加载游戏按钮,检查游戏状态是否恢复到保存时的状态。
- 检查音频设置是否正确,迷宫配置是否正确。
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
通过实现这些功能,玩家可以在游戏中方便地进行菜单操作、调整音频设置,并能够保存和加载游戏进度,从而获得更好的游戏体验。
超级会员免费看
11

被折叠的 条评论
为什么被折叠?



