场景(Scene)间跳转

注册场景

  1. 打开文件->生成设置
  2. 将所有的场景拖入
  3. 可以将场景调序,后续调用时可以用序号跳转

注意下图中的**Tutorial**错误的拼写为 **Turorial**

注册场景展示

跳转场景

初步规划主菜单各个按钮的交互

  • 开始游戏
    • 单人游戏->跳转到Game
    • 组队游戏->跳转到TeamLobby
  • 教程->跳转到Tutorial
  • 实验室->跳转到Laboratory
  • 设置->直接在主菜单生成子菜单
  • 退出游戏->弹窗询问是否真的要退出->关闭游戏
  1. 在父组件Canvas上挂个脚本MainMenuUIInterace
  2. 编写方法:如下图
    代码示范
  3. 按照下图顺序完成按钮点击设置:按钮->鼠标点击->选择相应函数->你写的脚本名称->你写的函数名称
  4. 一定记得把Canvas挂在图中蓝色箭头指向的位置
    按钮点击设置
  5. 以此类推完成所有按钮的点击设置

次级菜单设置

Start Game子菜单

  1. 在Canvas下创建空对象命名为StartSubmenu,在StartSubmenu下创建空对象命名为ContentPanel,添加Vertical Layout Group组件,准备将单人游戏组队游戏的按钮放在一个空对象下
  2. 使用之前创建好的“Button预制体”,同Day3->场景(Scene)间跳转->跳转场景,预先创建好鼠标点击事件
  3. 取消勾选StartSubmenu,因为一开始StartSubmenu是隐藏的
    StartSubmenu隐藏

编写StartSubmenu逻辑

最终要实现的逻辑

  • 点击Start Game->弹出两个子菜单
  • 点击非子菜单内容->子菜单隐藏
  • 点击Single Player->跳转到Game
  • 点击Multiplayer->跳转到TeamLobby

  1. MainMenuUIInterace脚本中新增方法OnSignlePlayerClickOnMultiplayerClick,与Day3->场景(Scene)间跳转->跳转场景一样添加场景跳转
  2. 按照下方代码所示方法获取组件(注意注释)
  3. 修改OnStartClick方法,去掉原本的场景跳转,修改为点击StartGame后显示StartSubmenu
  4. 为了实现点击非子菜单内容,将子菜单隐藏的功能,可以在StartSubmenu添加一个覆盖全屏的按钮CloseZone,当点击到此按钮时进行隐藏StartSubmenu
  5. 覆盖全屏的按钮添加Layout Element组件,选择Ignore Layout,从而不影响剩余按钮的排版,并且为此按钮添加方法
  6. 为新添加的按钮添加翻译
    最终结构如图所示
    最终结构
    代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MainMenuUIInteract : MonoBehaviour
{
private GameObject startSubmenu; // 开始游戏子菜单
private GameObject SettingsSubmenu;
private void Awake()
{
Transform subTrans = transform.Find("StartSubmenu"); // 尝试寻找
{
startSubmenu = subTrans.gameObject; // 找到子菜单则赋值
}
else
{
Debug.LogError("StartSubmenu 子物体未找到!");
}

Transform settingsTrans = transform.Find("SettingsSubmenu");
if (settingsTrans != null)
{
SettingsSubmenu = settingsTrans.gameObject;
}
else
{
Debug.LogError("SettingsSubmenu 子物体未找到!");
}
}
public void OnStartGameClicked()
{
if (startSubmenu != null)
{
startSubmenu.SetActive(true);
}
}
public void OnSinglePlayerClicked()
{
SceneManager.LoadScene("Game");
}
public void OnMultiplayerClicked()
{
SceneManager.LoadScene("TeamLobby");
}
public void OnTurorialClicked()
{
SceneManager.LoadScene("Tutorial");
}
public void OnLaboratoryClicked()
{
SceneManager.LoadScene("Laboratory");
}
public void OnSettingsClicked()
{
if (SettingsSubmenu != null)
{
SettingsSubmenu.SetActive(true);
}
}

public void OnExitGameClicked()
{
Debug.Log("执行退出游戏命令!(提示:在 Unity 编辑器中点击只会打印这行字,打包成 exe 后才会真正关闭窗口)");
// 缺 弹窗询问是否真的关闭
Application.Quit();
}
public void OnCloseZoneClicked()
{
if (startSubmenu != null)
{
startSubmenu.SetActive(false);
}
}
}

设置面板

26/3/23 Update:意识到设置面板在任何场景都可能使用,所以应当抽离出来作为单独的部分,用单独的Canvas_Settings_Menu作为设置面板的画布

规划

  • 需要Barrier用于屏蔽点击非设置内容时的点击事件(尤其是防止打开设置面板后依旧能点开始游戏等问题)
  • 设置面板内需要分为两个区域:选项卡(Tab)和选项内容(Content)
    • 选项卡:语言控制音效图像
    • 选项内容:具体内容
      具体层级如下图所示
      设置面板层级

Barrier设置

子物体不会大于父物体,所以Canvas_Settings_Menu需要占据全屏才可以使Barrier占据全屏

  1. 创建空物体Canvas_Settings_Menu并创建一个按钮作为子物体,命名为Barrier
  2. Canvas_Settings_Menu拉伸至全屏
  3. 删除按钮的Text(TMP)子物体,调整Image组件,将透明度调为0。
  4. Barrier拉伸至全屏

SettingsPanel切换

先预览最终结构

  1. UI->Panel创建画板,重命名为SettingsPanel
  2. SettingsPanel下创建两个空对象,分别重命名为BarSettings,在Bar下创建空对象并重命名为Content
  3. Bar添加以下组件
    • ToggleGroup组件,勾选当中的Allow Switch Off
    • Rect Mask 2D组件,取消勾选Scroll Rect中的Horizontal水平滚动选项
    • Scroll Rect组件,调整Scroll Sensitivity改变滚动灵敏度;将Content拖入Scroll Rect组件的Content内容选项
  4. Content下创建Toggle对象,重命名为Bar_Toggle,准备用它制作预制体,需要进行以下操作
    1. Bar_Toggle下的Toggle组件中,选择合适的配色,并且把Bar拖到下方的Group选项卡中
    2. 删去Bar_Toggle->Background下的Checkmark子物体
    3. Bar_Toggle->BackgroundRact Transform组件调整颜色覆盖区域
    4. Bar_Toggle->Label像之前一样添加自动切换字体功能,Text组件对齐方式改为居中,Ract Transform延伸至合适大小
    5. 保存预制体
  5. Content创建Vertical Layout Group组件和Content Size Fitter组件,将Content Size Fitter组件中的Vertical Fit选项设为Preferred Size
  6. 使用预制体添加自己需要的选项,例如下图
    ScrollRect
  7. 新建SettingsManager脚本挂在在Canvas_Settings_Menu上。参考以下代码实现面板的切换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Localization.Settings;
using TMPro;

public class SettingsManager : MonoBehaviour
{
// 【架构核心】单例模式,方便从任何地方使用此函数
public static SettingsManager Instance { get; private set; }
/*
注意命名规范
- 在C#编程中私有变量一般使用"小驼峰命名法"
- 但是Unity中是"大驼峰命名法"
顺带一提一般的编程规范
- 类、结构体、函数名、公开变量、属性等都要使用"大驼峰命名法"
- 常量名一般使用"全大写字母",单词之间用下划线分隔
- 局部变量、参数一般使用"小驼峰命名法"
- 私有变量一般使用"_ + 小驼峰命名法"
- 接口名一般使用"I + 大驼峰命名法"
*/
[Header("UI 引用")]
private GameObject _settingsPanel;
private GameObject _languagePanel;
private GameObject _controlsPanel; // 注意,这里是 controlsPanel,而不是 controlPanel,我在一开始没有留意到这个细节,后续有全部修改,但是文档上可能存在部分截图和文字依旧使用Control,一定要修改
private GameObject _audioPanel;
private GameObject _graphicsPanel; // 注意,这里是 graphicsPanel,而不是 graphicPanel,我在一开始没有留意到这个细节,后续有全部修改,但是文档上可能存在部分截图和文字依旧使用graphic,一定要修改

private void Awake()
{
// 1. 初始化单例,确保跨场景不被销毁
if (Instance != null && Instance != this)
{
// 如果已经有了实例,销毁新创建的实例
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);

_settingsPanel = transform.Find("SettingsPanel").gameObject;

Transform Settings = _settingsPanel.transform.Find("Settings");
_languagePanel = Settings.Find("LanguagePanel").gameObject;
_controlsPanel = Settings.Find("ControlsPanel").gameObject;
_audioPanel = Settings.Find("AudioPanel").gameObject;
_graphicsPanel = Settings.Find("GraphicsPanel").gameObject;

Transform Bar = _settingsPanel.transform.Find("Bar");
Toggle ToggleLanguage = Bar.Find("Content/Language").GetComponent<Toggle>();
Toggle ToggleControls = Bar.Find("Content/Controls").GetComponent<Toggle>();
Toggle ToggleAudio = Bar.Find("Content/Audio").GetComponent<Toggle>();
Toggle ToggleGraphics = Bar.Find("Content/Graphics").GetComponent<Toggle>();

ToggleLanguage.onValueChanged.AddListener((isOn) => { if (isOn) SwitchSettingsPage(_languagePanel); });
ToggleControls.onValueChanged.AddListener((isOn) => { if (isOn) SwitchSettingsPage(_controlsPanel); });
ToggleAudio.onValueChanged.AddListener((isOn) => { if (isOn) SwitchSettingsPage(_audioPanel); });
ToggleGraphics.onValueChanged.AddListener((isOn) => { if (isOn) SwitchSettingsPage(_graphicsPanel); });

// 默认初始化到第一页
ToggleLanguage.isOn = true;
SwitchSettingsPage(_languagePanel);

// 游戏启动时,默认隐藏整个设置菜单
CloseSettings();
}

// --- 供外部调用的核心方法 ---

/// <summary>
/// 打开设置界面
/// </summary>
public void OpenSettings()
{
foreach (Transform child in transform)
{
// 选择激活每一个子物体,否则关闭Canvas_Settings_Menu之后无法再次打开
child.gameObject.SetActive(true);
}
}

/// <summary>
/// 关闭设置界面
/// </summary>
public void CloseSettings()
{
foreach (Transform child in transform)
{
child.gameObject.SetActive(false);
}
}

// --- 内部 UI 逻辑 ---

private void SwitchSettingsPage(GameObject page)
{
_languagePanel.SetActive(false);
_controlsPanel.SetActive(false);
_audioPanel.SetActive(false);
_graphicsPanel.SetActive(false);

page.SetActive(true);
}
}

SettingsPanel内容

因为有许多设置内容,可能无法全部呈现,所以同样要设置为可滑动窗口,步骤与上面的Bar一致,我这里不再详细描述,而是提供两张图片。
注意组件的添加位置以及需要更改的内容
ContentPanel
ContentPanel