Unity UI 从零到精通 (第30天): Canvas、布局与C#交互实战

news2025/4/26 18:13:07

Langchain系列文章目录

01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘

PyTorch系列文章目录

Python系列文章目录

C#系列文章目录

01-C#与游戏开发的初次见面:从零开始的Unity之旅
02-C#入门:从变量与数据类型开始你的游戏开发之旅
03-C#运算符与表达式:从入门到游戏伤害计算实践
04-从零开始学C#:用if-else和switch打造智能游戏逻辑
05-掌握C#循环:for、while、break与continue详解及游戏案例
06-玩转C#函数:参数、返回值与游戏中的攻击逻辑封装
07-Unity游戏开发入门:用C#控制游戏对象移动
08-C#面向对象编程基础:类的定义、属性与字段详解
09-C#封装与访问修饰符:保护数据安全的利器
10-如何用C#继承提升游戏开发效率?Enemy与Boss案例解析
11-C#多态性入门:从零到游戏开发实战
12-C#接口王者之路:从入门到Unity游戏开发实战 (IAttackable案例详解)
13-C#静态成员揭秘:共享数据与方法的利器
14-Unity 面向对象实战:掌握组件化设计与脚本通信,构建玩家敌人交互
15-C#入门 Day15:彻底搞懂数组!从基础到游戏子弹管理实战
16-C# List 从入门到实战:掌握动态数组,轻松管理游戏敌人列表 (含代码示例)
17-C# 字典 (Dictionary) 完全指南:从入门到游戏属性表实战 (Day 17)
18-C#游戏开发【第18天】 | 深入理解队列(Queue)与栈(Stack):从基础到任务队列实战
19-【C# 进阶】深入理解枚举 Flags 属性:游戏开发中多状态组合的利器
20-C#结构体(Struct)深度解析:轻量数据容器与游戏开发应用 (Day 20)
21-Unity数据持久化进阶:告别硬编码,用ScriptableObject优雅管理游戏配置!(Day 21)
22-Unity C# 健壮性编程:告别崩溃!掌握异常处理与调试的 4 大核心技巧 (Day 22)
23-C#代码解耦利器:委托与事件(Delegate & Event)从入门到实践 (Day 23)
24-Unity脚本通信终极指南:从0到1精通UnityEvent与事件解耦(Day 24)
25-精通C# Lambda与LINQ:Unity数据处理效率提升10倍的秘诀! (Day 25)
26-# Unity C#进阶:掌握泛型编程,告别重复代码,编写优雅复用的通用组件!(Day26)
27-Unity协程从入门到精通:告别卡顿,用Coroutine优雅处理异步与时序任务 (Day 27)
28-搞定玩家控制!Unity输入系统、物理引擎、碰撞检测实战指南 (Day 28)
29-# Unity动画控制核心:Animator状态机与C#脚本实战指南 (Day 29)
30-Unity UI 从零到精通 (第30天): Canvas、布局与C#交互实战 (Day 30)


文章目录

  • Langchain系列文章目录
  • PyTorch系列文章目录
  • Python系列文章目录
  • C#系列文章目录
  • 前言
  • 一、UI 系统的基石:Canvas
    • 1.1 Canvas 组件概述
      • 1.1.1 Canvas 的作用
      • 1.1.2 Render Mode (渲染模式)
    • 1.2 Canvas Scaler 组件
      • 1.2.1 UI Scale Mode (缩放模式)
  • 二、核心 UI 元素详解
    • 2.1 Text (文本)
      • 2.1.1 常用属性
      • 2.1.2 应用场景
    • 2.2 Image (图像)
      • 2.2.1 常用属性
      • 2.2.2 应用场景
    • 2.3 Button (按钮)
      • 2.3.1 核心构成
      • 2.3.2 Button 组件属性
      • 2.3.3 应用场景
    • 2.4 Slider (滑动条)
      • 2.4.1 核心构成
      • 2.4.2 Slider 组件属性
      • 2.4.3 应用场景
    • 2.5 InputField (输入字段)
      • 2.5.1 核心构成
      • 2.5.2 InputField 组件属性
      • 2.5.3 应用场景
  • 三、精准布局:RectTransform 系统
    • 3.1 RectTransform 核心概念
      • 3.1.1 Anchors (锚点)
      • 3.1.2 Pivot (轴心)
    • 3.2 响应式布局技巧
  • 四、响应交互:UI 事件处理
    • 4.1 Button 的 OnClick 事件
      • 4.1.1 在 Inspector 中设置
      • 4.1.2 通过 C# 脚本添加监听
    • 4.2 其他 UI 事件
  • 五、动态更新:C# 脚本控制 UI
    • 5.1 获取 UI 组件引用
      • 5.1.1 公共变量与 Inspector 赋值 (推荐)
      • 5.1.2 代码查找 (GetComponent/Find)
    • 5.2 更新 UI 内容
      • 5.2.1 更新文本 (Text / TextMeshPro)
      • 5.2.2 更新血条 (Slider / Image Fill)
      • 5.2.3 控制按钮交互性
  • 六、实践:制作游戏主界面
    • 6.1 场景设置
    • 6.2 创建 UI 管理脚本
    • 6.3 关联脚本与 UI
    • 6.4 运行测试
  • 七、总结


前言

欢迎来到“C# for Unity 从入门到精通”系列专栏的第 30 天!用户界面(UI)是游戏与玩家沟通的桥梁,无论是显示信息、提供操作选项,还是营造氛围,都离不开精心设计的 UI。今天,我们将深入探讨 Unity 中 UI 开发的核心机制,学习如何构建用户界面元素,并通过 C# 脚本让它们响应玩家的操作,动态地展示游戏状态。本篇将重点介绍 Canvas 系统、核心 UI 控件、强大的 RectTransform 布局系统以及事件处理机制,最后通过一个实战案例,带你亲手打造一个包含血条、得分和暂停按钮的基础游戏主界面。无论你是 UI 新手还是希望巩固基础的开发者,本文都将为你提供清晰、实用的指引。

一、UI 系统的基石:Canvas

在 Unity 中,所有 UI 元素都必须存在于一个 Canvas(画布) 组件之下。可以把 Canvas 理解为绘制所有 UI 元素的“画板”。

1.1 Canvas 组件概述

当你创建一个 UI 元素时(如通过 GameObject -> UI -> Text),如果场景中没有 Canvas,Unity 会自动为你创建一个。

1.1.1 Canvas 的作用

  • 容纳 UI 元素: 它是场景中所有 UI 元素的根对象。
  • 渲染控制: 决定 UI 如何绘制到屏幕上(Render Mode)。
  • 缩放控制: 通过 Canvas Scaler 组件管理 UI 元素在不同分辨率下的缩放行为。

1.1.2 Render Mode (渲染模式)

Canvas 主要有三种渲染模式:

  1. Screen Space - Overlay: 这是最常用的模式。UI 会被渲染在屏幕的最顶层,覆盖所有场景摄像机看到的内容。适合大多数游戏 HUD(Heads-Up Display)和菜单。
  2. Screen Space - Camera: UI 会被渲染在指定的摄像机前方特定距离处。可以实现 UI 被 3D 物体遮挡的效果,常用于需要与 3D 世界融合的 UI。
  3. World Space: UI 就像场景中的普通 3D 对象一样存在于世界空间中。常用于制作游戏世界中的交互式面板(如 VR 界面、角色头顶血条等)。

对于初学者和大多数 2D/常规 3D 游戏的主界面,Screen Space - Overlay 是最常用的选择。

1.2 Canvas Scaler 组件

附加在 Canvas 上的 Canvas Scaler 组件至关重要,它控制着 UI 元素如何适应不同的屏幕分辨率。

1.2.1 UI Scale Mode (缩放模式)

  • Constant Pixel Size: UI 元素保持固定的像素大小,屏幕越大,UI 看起来越小。
  • Scale With Screen Size: 这是最常用的模式。你可以设定一个参考分辨率(Reference Resolution),UI 元素会根据当前屏幕分辨率相对于参考分辨率进行缩放。可以设置 Match 滑块来控制是优先匹配宽度还是高度,或者取一个中间值。
  • Constant Physical Size: 根据屏幕的物理尺寸(DPI)来缩放,确保 UI 在不同设备上看起来物理大小一致。

对于跨平台游戏,强烈推荐使用 Scale With Screen Size 来保证 UI 在不同设备上的显示一致性。

二、核心 UI 元素详解

Unity 提供了丰富的内置 UI 元素,我们来了解几个最核心的。

2.1 Text (文本)

用于在屏幕上显示文字信息。

2.1.1 常用属性

  • Text: 输入要显示的文本内容。
  • Font: 设置字体。
  • Font Style: 粗体、斜体等。
  • Font Size: 字体大小。
  • Alignment: 文本对齐方式(水平、垂直)。
  • Color: 文本颜色。
  • Rich Text: 是否启用富文本标签(如 <b> 加粗, <color=red> 变色)。

2.1.2 应用场景

显示得分、玩家名称、对话内容、提示信息等。

2.2 Image (图像)

用于显示图片、图标或作为背景。

2.2.1 常用属性

  • Source Image: 指定要显示的 Sprite(精灵)或 Texture(纹理)。
  • Color: 可以给图像叠加一层颜色(Tint),默认为白色(不改变原色)。
  • Material: 可以指定用于渲染图像的材质,实现特殊效果。
  • Image Type:
    • Simple: 简单拉伸或缩放图像。
    • Sliced: 九宫格切片模式。非常适合制作背景面板、按钮等需要保持边框不变,中间区域拉伸的 UI。需要在 Sprite Editor 中设置好九宫格边距。
    • Tiled: 平铺模式。适合制作重复纹理背景。
    • Filled: 填充模式。可以通过 Fill Amount (0 到 1) 控制图像按特定方式(如水平、垂直、径向)显示多少比例。非常适合制作血条、进度条、技能冷却图标。

2.2.2 应用场景

游戏 Logo、按钮图标、角色头像、血条/魔法条(使用 Filled 模式)、背景图。

2.3 Button (按钮)

最常用的交互元素,用于响应用户的点击操作。

2.3.1 核心构成

一个典型的 Button 由以下部分组成:

  • Image 组件: 显示按钮的背景或图标。
  • Button 组件: 处理交互逻辑,包含点击事件(OnClick)。
  • (可选) Text 组件: 作为 Button 的子对象,显示按钮上的文字。

2.3.2 Button 组件属性

  • Interactable: 是否可交互。取消勾选后按钮变灰且无法点击。
  • Transition: 按钮在不同状态(正常、高亮、按下、禁用)下的视觉反馈方式(颜色变化、精灵切换、动画)。
  • OnClick (): 最重要的部分。这是一个事件列表,你可以将其他脚本中的公共方法拖拽到这里,当按钮被点击时,这些方法就会被调用。

2.3.3 应用场景

暂停游戏、确认选择、攻击、跳跃、打开菜单等。

2.4 Slider (滑动条)

允许用户在一个范围内拖动选择一个数值。

2.4.1 核心构成

  • Background Image: 滑动条的背景轨道。
  • Fill Area / Fill Image: 显示当前值的填充区域。
  • Handle Slide Area / Handle Image: 用户拖动的滑块。

2.4.2 Slider 组件属性

  • Interactable: 是否可交互。
  • Direction: 滑动方向(从左到右、从右到左等)。
  • Min Value / Max Value: 滑动条的最小值和最大值。
  • Value: 当前的数值。
  • On Value Changed (Single): 当滑动条的值发生改变时触发的事件。可以链接方法来响应数值变化。

2.4.3 应用场景

调节音量、设置灵敏度、显示血量/魔法值(通常是程序控制 Value,禁用 Interactable)。

2.5 InputField (输入字段)

允许用户输入文本。

2.5.1 核心构成

  • Image 组件: 输入框的背景。
  • Text Component: 显示用户输入的文本。
  • Placeholder (Text): 当输入框为空时显示的提示文字。

2.5.2 InputField 组件属性

  • Text: 当前输入的文本内容。
  • Character Limit: 限制输入的最大字符数。
  • Content Type: 限制输入类型(标准、整数、数字、密码等)。
  • Placeholder: 关联 Placeholder 的 Text 组件。
  • On Value Changed (String): 输入内容每次改变时触发的事件。
  • On End Edit (String): 结束编辑(例如按下 Enter 或点击输入框外部)时触发的事件。

2.5.3 应用场景

输入玩家昵称、聊天信息、搜索内容等。

三、精准布局:RectTransform 系统

与场景中的普通 3D 对象使用 Transform 组件不同,UI 元素使用 RectTransform 组件来控制其位置、大小、旋转和缩放,并且提供了强大的锚点(Anchors)和轴心(Pivot)系统来实现灵活的布局。

3.1 RectTransform 核心概念

RectTransform 定义了一个矩形区域,所有 UI 元素的布局都是基于这个矩形。

3.1.1 Anchors (锚点)

锚点决定了 UI 元素相对于其父级 RectTransform 的对齐方式和拉伸行为。

  • 是什么: 锚点是 UI 元素上的四个小三角形标记(可以在 Scene 视图中看到),它们定义了一个参考框。
  • 作用: 当父容器大小改变时,UI 元素会根据其锚点的位置来调整自己的位置和大小,以保持与锚点定义的参考框之间的相对关系。
  • 锚点模式:
    • 固定模式 (Anchors Together): 四个锚点合并在一起。UI 元素的位置是相对于这个锚点的位置定义的,大小是固定的像素值。适用于图标、固定大小的按钮等。
    • 拉伸模式 (Anchors Separated): 锚点分散开。UI 元素会随着父容器的尺寸变化而拉伸,其边缘与对应锚点之间保持固定的边距。适用于需要填满父容器或随屏幕拉伸的背景、面板等。
  • 预设 (Presets): Unity 提供了一个方便的锚点预设窗口(点击 Inspector 中 RectTransform 左上角的方形图标),可以快速设置常用的对齐和拉伸模式(如左上角对齐、水平居中、垂直拉伸、完全拉伸等)。
父容器大小变化
Yes
No
UI元素位置相对锚点不变, 大小不变
锚点固定?
UI元素边缘与锚点间距不变, 大小随父容器拉伸
父容器
UI元素

(上图展示了锚点在父容器尺寸变化时的影响)

3.1.2 Pivot (轴心)

轴心是 UI 元素自身的旋转和定位的参考点。

  • 是什么: Pivot 是一个在 UI 元素矩形内的归一化坐标点(X, Y 范围都是 0 到 1)。(0, 0) 代表左下角,(0.5, 0.5) 代表中心,(1, 1) 代表右上角。
  • 作用:
    • 定位: RectTransform 的 Position (X, Y, Z) 值实际上是其 Pivot 点相对于锚点的位置。
    • 旋转/缩放: UI 元素的旋转和缩放都是围绕其 Pivot 点进行的。
  • 默认值: 通常 UI 元素的 Pivot 默认在中心 (0.5, 0.5)。

类比: 想象在一块木板(UI 元素)上钉一颗图钉(Pivot),然后用四根橡皮筋(到锚点的距离)把它固定在一个框架(父容器)里。改变框架大小时,橡皮筋会拉伸,木板会移动,但它始终围绕图钉点旋转和定位。

3.2 响应式布局技巧

结合使用 Anchors 和 Pivot 可以创建适应不同屏幕尺寸的 UI 布局。

  • 固定位置和大小: 将锚点合并,设置好 Pivot 和 Position (通常 Pivot 设为对应角或边,如左上角 Pivot 为 (0, 1))。
  • 相对父容器边缘: 将锚点吸附到父容器的某个边缘或角落,然后设置相对该锚点的偏移量。
  • 百分比布局/拉伸: 分开锚点,让元素占据父容器的一部分区域。

熟练掌握 RectTransform 是实现专业、适配性强的 UI 的关键。

四、响应交互:UI 事件处理

让 UI 动起来的关键在于处理用户的输入事件,最常见的就是按钮点击。

4.1 Button 的 OnClick 事件

这是最常用的 UI 事件。当用户点击一个 Button 时,其 OnClick 事件列表中的所有注册方法都会被触发。

4.1.1 在 Inspector 中设置

  1. 选中你的 Button GameObject。
  2. 在 Inspector 中找到 Button 组件。
  3. 找到 OnClick () 列表。
  4. 点击右下角的 + 号添加一个新的事件监听槽。
  5. 将包含公共方法(必须是 public)的脚本所在的 GameObject 拖拽到事件槽的 None (Object) 区域。
  6. 点击右侧的下拉菜单 (No Function),选择你想要调用的脚本及其公共方法。

优点: 直观、无需编码。
缺点: 耦合度高,不适合动态添加或移除监听。

4.1.2 通过 C# 脚本添加监听

可以在代码中动态地为 Button 添加点击事件监听器。

using UnityEngine;
using UnityEngine.UI; // 必须引入 UI 命名空间

public class ButtonHandler : MonoBehaviour
{
    public Button myButton; // 在 Inspector 中拖拽按钮赋值

    void Start()
    {
        // 检查按钮是否已赋值
        if (myButton != null)
        {
            // 添加监听器,使用 Lambda 表达式
            myButton.onClick.AddListener(() => {
                Debug.Log("按钮被点击了!");
                MyButtonClickAction(); // 调用另一个方法
            });

            // 或者直接添加一个已存在的方法
            // myButton.onClick.AddListener(MyButtonClickAction);
        }
        else
        {
            Debug.LogError("myButton 没有在 Inspector 中赋值!");
        }
    }

    // 必须是 public 方法才能在 Inspector 中直接绑定,但 AddListener 可以绑定 private 方法
    private void MyButtonClickAction()
    {
        Debug.Log("执行按钮点击后的具体逻辑。");
        // 在这里执行暂停游戏、打开商店等操作
    }

    // 如果需要移除监听(例如在对象销毁时)
    void OnDestroy()
    {
        if (myButton != null)
        {
            // 移除所有监听器
            // myButton.onClick.RemoveAllListeners();

            // 或移除特定监听器(如果需要精确控制)
             myButton.onClick.RemoveListener(MyButtonClickAction); // 注意:如果是用 Lambda 添加的,移除会比较麻烦
        }
    }
}

优点: 灵活,解耦,适合在运行时动态管理事件。
缺点: 需要编写代码。

4.2 其他 UI 事件

  • Slider: OnValueChanged - 滑动条数值改变时触发。
  • InputField: OnValueChanged - 输入内容改变时触发;OnEndEdit - 结束编辑时触发。
  • Toggle: OnValueChanged - 切换开关状态时触发。

这些事件的设置方式与 Button 的 OnClick 类似,都可以在 Inspector 中或通过代码添加监听。

五、动态更新:C# 脚本控制 UI

游戏过程中,UI 常常需要根据游戏状态动态变化,例如更新血条、显示分数、改变按钮可用性等。这需要通过 C# 脚本来实现。

5.1 获取 UI 组件引用

首先,你的 C# 脚本需要获取到要控制的 UI 元素的引用。

5.1.1 公共变量与 Inspector 赋值 (推荐)

在脚本中声明 public 变量,然后在 Unity 编辑器的 Inspector 窗口中将对应的 UI GameObject 拖拽到变量槽中。

using UnityEngine;
using UnityEngine.UI; // 引入 UI 命名空间
using TMPro; // 如果使用 TextMeshPro,需要引入这个

public class UIManager : MonoBehaviour
{
    // 在 Inspector 中拖拽对应的 UI 元素到这里
    public Slider healthSlider;
    public Image healthFillImage; // 如果使用 Image 做血条
    public Text scoreText;
    // public TextMeshProUGUI scoreText_TMP; // 如果使用 TextMeshPro
    public Button pauseButton;

    // ... 其他代码 ...
}

优点: 直观,易于管理,性能较好(查找只在编辑时或 Start 时进行一次)。

5.1.2 代码查找 (GetComponent/Find)

StartAwake 方法中使用 GetComponentFindObjectOfType 等方法查找。

using UnityEngine;
using UnityEngine.UI;

public class UIManager : MonoBehaviour
{
    private Slider healthSlider;
    private Text scoreText;

    void Awake()
    {
        // 假设 Slider 是当前 GameObject 的子对象
        healthSlider = GetComponentInChildren<Slider>();

        // 假设 Score Text 有一个独特的 Tag "ScoreText"
        GameObject scoreTextObject = GameObject.FindGameObjectWithTag("ScoreText");
        if (scoreTextObject != null)
        {
            scoreText = scoreTextObject.GetComponent<Text>();
        }

        if (healthSlider == null || scoreText == null)
        {
            Debug.LogError("未能找到必要的 UI 组件!");
        }
    }
     // ... 其他代码 ...
}

优点: 可以在脚本中动态查找,不依赖 Inspector 设置。
缺点: Find 操作相对耗时,不建议在 Update 中频繁调用;如果场景结构改变,查找可能会失败。

5.2 更新 UI 内容

获取到引用后,就可以通过修改组件的属性来更新 UI 了。

5.2.1 更新文本 (Text / TextMeshPro)

public void UpdateScore(int newScore)
{
    if (scoreText != null)
    {
        scoreText.text = "得分: " + newScore.ToString();
        // 对于 TextMeshPro:
        // scoreText_TMP.text = $"得分: {newScore}";
    }
}

5.2.2 更新血条 (Slider / Image Fill)

假设有一个 UpdateHealth 方法,接收当前生命值和最大生命值。

public void UpdateHealth(float currentHealth, float maxHealth)
{
    // 使用 Slider
    if (healthSlider != null)
    {
        // Slider 的 value 通常是 0 到 1 或根据 Min/Max Value 设定
        // 如果 Min=0, Max=maxHealth,则直接赋值
        // healthSlider.value = currentHealth;

        // 更通用的做法是计算比例,假设 Slider 的 Min=0, Max=1
        if (maxHealth > 0) // 防止除以零
        {
            healthSlider.value = currentHealth / maxHealth;
        }
        else
        {
             healthSlider.value = 0;
        }
    }

    // 使用 Image 的 Filled 模式
    if (healthFillImage != null && healthFillImage.type == Image.Type.Filled)
    {
         if (maxHealth > 0)
        {
            healthFillImage.fillAmount = currentHealth / maxHealth; // fillAmount 范围是 0 到 1
        }
         else
        {
             healthFillImage.fillAmount = 0;
        }
    }
}

5.2.3 控制按钮交互性

public void SetPauseButtonInteractable(bool interactable)
{
    if (pauseButton != null)
    {
        pauseButton.interactable = interactable; // 控制按钮是否可点击
    }
}

六、实践:制作游戏主界面

现在,我们将运用所学知识,制作一个包含血条、得分显示和暂停按钮的简单游戏主界面。

6.1 场景设置

  1. 创建一个新的 Unity 场景。
  2. 在 Hierarchy 窗口右键 -> UI -> Canvas,创建一个 Canvas。确保其 Render Mode 为 Screen Space - Overlay,并配置 Canvas Scaler 使用 Scale With Screen Size,设置一个参考分辨率(如 1920x1080)。
  3. 选中 Canvas,在 Hierarchy 窗口右键 -> UI -> Slider,创建一个血条 Slider。调整其 RectTransform 的 Anchors 和 Pivot,将其放置在屏幕左上角或合适位置。可以去掉 Handle,只保留 Background 和 Fill。设置 Slider 的 Min Value = 0, Max Value = 1。
  4. 选中 Canvas,右键 -> UI -> Text (或 TextMeshPro - Text),创建一个得分 Text。调整 RectTransform,放置在屏幕右上角。修改 Text 内容为 “得分: 0”。
  5. 选中 Canvas,右键 -> UI -> Button (或 TextMeshPro - Button),创建一个暂停 Button。调整 RectTransform,放置在屏幕右上角得分下方或其他合适位置。修改 Button 子对象的 Text 内容为 “暂停”。

6.2 创建 UI 管理脚本

  1. 在 Project 窗口创建一个新的 C# 脚本,命名为 GameUIManager
  2. 将以下代码复制到 GameUIManager.cs 中:
using UnityEngine;
using UnityEngine.UI; // 引入 UI 命名空间
using TMPro; // 如果使用 TextMeshPro,取消注释这行并替换 Text 相关代码

public class GameUIManager : MonoBehaviour
{
    [Header("UI References")]
    public Slider healthSlider;        // 血条 Slider
    public Text scoreText;             // 得分 Text
    // public TextMeshProUGUI scoreText; // 如果使用 TextMeshPro
    public Button pauseButton;         // 暂停 Button

    [Header("Game State Sim")]
    public float maxHealth = 100f;    // 模拟最大生命值
    public float currentHealth = 100f; // 模拟当前生命值
    public int currentScore = 0;       // 模拟当前得分

    private bool isPaused = false;    // 模拟游戏暂停状态

    void Start()
    {
        // 初始化 UI
        UpdateHealthUI(currentHealth, maxHealth);
        UpdateScoreUI(currentScore);

        // 为暂停按钮添加点击事件监听
        if (pauseButton != null)
        {
            pauseButton.onClick.AddListener(TogglePause);
        }
        else
        {
             Debug.LogError("Pause Button not assigned in the Inspector!");
        }

        // ---- 模拟游戏状态变化 (仅用于演示) ----
        InvokeRepeating("SimulateDamage", 2.0f, 1.5f); // 每1.5秒扣血
        InvokeRepeating("SimulateScoreIncrease", 1.0f, 1.0f); // 每1秒加分
        // ---- 模拟结束 ----
    }

    // 公共方法:更新血条显示
    public void UpdateHealthUI(float health, float max)
    {
        currentHealth = health; // 更新内部记录
        maxHealth = max;       // 更新内部记录

        if (healthSlider != null)
        {
            if (maxHealth > 0)
            {
                healthSlider.value = currentHealth / maxHealth;
            }
            else
            {
                 healthSlider.value = 0;
            }
             Debug.Log($"Health UI Updated: {currentHealth}/{maxHealth}");
        }
    }

    // 公共方法:更新得分显示
    public void UpdateScoreUI(int score)
    {
        currentScore = score; // 更新内部记录
        if (scoreText != null)
        {
            scoreText.text = "得分: " + currentScore.ToString();
            // scoreText.text = $"得分: {currentScore}"; // TextMeshPro 写法
             Debug.Log($"Score UI Updated: {currentScore}");
        }
    }

    // 暂停按钮点击时调用的方法
    public void TogglePause()
    {
        isPaused = !isPaused;
        if (isPaused)
        {
            Time.timeScale = 0f; // 暂停游戏时间
            Debug.Log("游戏已暂停");
            // 可以在这里显示暂停菜单等
        }
        else
        {
            Time.timeScale = 1f; // 恢复游戏时间
            Debug.Log("游戏已恢复");
            // 隐藏暂停菜单
        }
        // 可以在此更新暂停按钮的文本,如 "恢复" / "暂停"
    }


    // ---- 模拟游戏状态变化的方法 (仅用于演示) ----
    void SimulateDamage()
    {
        if (!isPaused)
        {
            UpdateHealthUI(Mathf.Max(0, currentHealth - 10), maxHealth); // 每次减少10点生命值
        }
    }

    void SimulateScoreIncrease()
    {
         if (!isPaused)
        {
            UpdateScoreUI(currentScore + 5); // 每次增加5分
        }
    }
     // ---- 模拟结束 ----

    // 确保在脚本销毁时移除监听器(好习惯)
    void OnDestroy()
    {
        if (pauseButton != null)
        {
            pauseButton.onClick.RemoveListener(TogglePause);
        }
    }
}

6.3 关联脚本与 UI

  1. 在 Hierarchy 窗口创建一个空的 GameObject,命名为 UIManager
  2. GameUIManager.cs 脚本拖拽附加到 UIManager GameObject 上。
  3. 选中 UIManager GameObject,在 Inspector 中找到 GameUIManager 组件。
  4. 将场景中创建的 SliderTextButton 分别拖拽到脚本对应的 Health SliderScore TextPause Button 公共变量槽中。

6.4 运行测试

点击 Unity 编辑器的 Play 按钮。你应该能看到:

  • 血条初始为满。
  • 得分初始为 0。
  • 血条会随着时间(模拟伤害)减少。
  • 得分会随着时间(模拟得分)增加。
  • 点击“暂停”按钮,游戏时间停止(血条和得分不再变化),再次点击则恢复。

恭喜你!你已经成功构建了一个基础的游戏主界面,并实现了动态更新和交互响应。

七、总结

今天,我们深入学习了 Unity UI 开发的核心知识与实践:

  1. Canvas: UI 系统的根基,理解其 Render Mode 和 Canvas Scaler 对于 UI 显示至关重要。
  2. 核心 UI 元素: 掌握了 Text, Image, Button, Slider, InputField 等常用组件的属性和应用场景,特别是 Image 的 Sliced 和 Filled 模式,以及 Button 的 OnClick 事件。
  3. RectTransform: 学习了 Anchors 和 Pivot 的概念,它们是实现精准、响应式 UI 布局的关键。
  4. UI 事件处理: 了解了如何在 Inspector 中或通过 C# 脚本为 Button 等元素添加事件监听,以响应用户交互。
  5. C# 脚本动态更新: 学会了通过 C# 脚本获取 UI 组件引用,并动态修改其属性来更新界面内容(如血条、分数)。
  6. 实践应用: 通过构建一个包含血条、得分和暂停按钮的游戏主界面,将理论知识应用于实际开发。

掌握 UI 开发是 Unity 游戏开发中不可或缺的一环。希望通过今天的学习,你能更有信心地构建和控制你的游戏界面。在后续的学习中,我们还会接触到更高级的 UI 技术和优化技巧。继续加油!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2335933.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电视盒子 刷armbian

参考 中兴电视盒子中兴B860AV3.2-M刷Armbian新手级教程-CSDN博客 1.刷安卓9 带root版本 a. 下载安卓线刷包 链接&#xff1a;https://pan.baidu.com/s/1hz87_ld2lJea0gYjeoHQ8A?pwdd7as 提取码&#xff1a;d7as b.拆机短接 3.安装usbburning工具 使用方法 &#xff0c;…

AI应用开发之扣子第一课-夸夸机器人

首先&#xff0c;进入官网&#xff1a;点击跳转至扣子。 1.创建智能体 登录进网站后&#xff0c;点击左上角&#xff0b;图标&#xff0c;创建智能体&#xff0c;输入智能体名称、功能介绍 2.输入智能体提示词 在“人设与回复逻辑”输入以下内容&#xff1a; # 角色 你是一…

【计算机网络实践】(十二)大学校园网综合项目设计

本系列包含&#xff1a; &#xff08;一&#xff09;以太网帧分析与网际互联协议报文结构分析 &#xff08;二&#xff09;地址解析协议分析与传输控制协议特性分析 &#xff08;三&#xff09;交换机的基本操作、配置、 虚拟局域网配置和应用 &#xff08;四&#xff09;交…

uniapp小程序位置授权弹框与隐私协议耦合(合而为一)(只在真机上有用,模拟器会分开弹 )

注意&#xff1a; 只在真机上有用&#xff0c;模拟器会分开弹 效果图&#xff1a; 模拟器效果图&#xff08;授权框跟隐私政策会分开弹&#xff0c;先弹隐私政策&#xff0c;同意再弹授权弹框&#xff09;&#xff1a; manifest-template.json配置&#xff08; "__usePr…

【星闪模组开发板WS8204SLEBLEModule】星闪数据收发测试

目录 开发板简介 串口设置 主从模式设置 AT命令数据发送 透传模式数据发送 结语 本文首发于《电子产品世界》论坛&#xff1a;【星闪模组开发板WS8204SLE&BLEModule】星闪数据收发测试-电子产品世界论坛https://forum.eepw.com.cn/thread/392011/1 感谢eepw论坛和成…

基础知识:Dify 错误排查

Case1:Dify 卡在管理员界面 查看容器状态 docker compose ps 可以看到有个容器异常:docker_db_1 的状态是 Restarting(表示一直在重启) 解决方案 参考:https://github.com/langgenius/dify/issues/5731

spring cloud微服务断路器详解及主流断路器框架对比

微服务断路器详解 1. 核心概念 定义&#xff1a;断路器模式通过快速失败机制防止故障扩散&#xff0c;当服务调用出现异常或超时时&#xff0c;自动切换到降级逻辑&#xff0c;避免级联故障。核心功能&#xff1a; 熔断&#xff1a;在故障阈值&#xff08;如错误率&#xff09…

(小白0基础) 微调deepseek-8b模型参数详解以及全流程——训练篇

​ 本篇参考bilibili如何在本地微调DeepSeek-R1-8b模型_哔哩哔哩_bilibili 上篇&#xff1a;(小白0基础) 租用AutoDL服务器进行deepseek-8b模型微调全流程(Xshell,XFTP) —— 准备篇 初始变量 max_seq_length 2048 dtype None load_in_4bit True单批次最大处理模型大小dy…

关于汽车辅助驾驶不同等级、技术对比、传感器差异及未来发展方向的详细分析

以下是关于汽车辅助驾驶不同等级、技术对比、传感器差异及未来发展方向的详细分析&#xff1a; 一、汽车辅助驾驶等级详解 根据SAE&#xff08;国际自动机工程师学会&#xff09;的标准&#xff0c;自动驾驶分为 L0到L5 六个等级&#xff1a; 1. L0&#xff08;无自动化&…

mongodb7日志特点介绍:日志分类、级别、关键字段(下)

#作者&#xff1a;任少近 上篇《mongodb7日志特点介绍&#xff1a;日志分类、级别、关键字段(上)》 链接: link 文章目录 4.日志会输出F/E/W/I四种情况5.日志关键字段6.日志量验证情况7.总结 4.日志会输出F/E/W/I四种情况 在MongoDB7中&#xff0c;日志输出按照严重性分为四种…

word中插入图片显示不完整,怎么处理让其显示完整?

在WORD里插入图片后&#xff0c;选择嵌入式发现插入的图片显示不正常&#xff0c;只能显示底部一部分&#xff0c;或者遮住文字。出现此故障的原因有可能是设置为固定值的文档行距小于图形的高度&#xff0c;从而导致插入的图形只显示出了一部分。 1.选中图片&#xff0c;然后点…

SAP S4HANA embedded analytics

SAP S4HANA embedded analytics

JavaWeb开发 Servlet底层 Servlet 过滤器 过滤器和拦截器 手写一个限制访问路径的拦截器

目录 万能图 过滤器自我理解 案例 实现Filter 接口 配置文件 web.xml 将过滤器映射到 servlet 用处 拦截器 手写案例 重写 preHandle() 方法 拦截处理 重写 postHandle() 方法 后处理 重写 afterHandle() 方法 完成处理 代码 如何配置拦截器 万能图 还是看一下这张…

【leetcode hot 100 72】编辑距离

解法一&#xff1a;递归 解法二&#xff1a;&#xff08;动态规划&#xff09;①定义&#xff1a;dp[i][j]为word1中前i个字符转化为word2中前j个字符所需操作数;dp[m1][n1] ②初始状态&#xff1a;dp[0][j]j(0变为j&#xff0c;需要j步)&#xff0c;dp[i][0]i(i变为0&#xff…

Java练习——day1(反射)

文章目录 练习1练习2练习3思考封装原则与反射合理使用反射“破坏”封装的场景 练习1 编写代码&#xff0c;通过反射获取String类的所有公共方法名称&#xff0c;并按字母顺序打印。 示例代码&#xff1a; import java.lang.reflect.Method; import java.util.Arrays;public …

Docker 安装 Elasticsearch 8.x

Docker 安装 Elasticsearch 8.x 前言一、准备工作二、设置容器的目录结构三、启动一个临时的容器来复制配置文件四、复制配置文件到本地目录五、删除临时容器六、创建并运行容器&#xff0c;挂载本地目录七、修改文件配置监听端口八、端口配置&#xff1a;Host 网络模式 vs Por…

Vue工程化开发脚手架Vue CLI

开发Vue有两种方式 核心包传统开发模式&#xff1a;基于html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xff08;例如&#xff1a;webpack&#xff09;的环境中开发Vue。 脚手架Vue CLI Vue CLl 是 Vue 官方…

开源智慧巡检——无人机油田AI视频监控的未来之力

油田巡检&#xff0c;关乎能源命脉&#xff0c;却常受困于广袤地形、高危环境和人工效率瓶颈。管道泄漏、设备故障、非法闯入——这些隐患稍有疏忽&#xff0c;便可能酿成大患。传统巡检已无法满足现代油田对安全与效率的需求&#xff0c;而无人机油田巡检系统正以智能化之力重…

Django从零搭建卖家中心登陆与注册实战

在电商系统开发中&#xff0c;卖家中心是一个重要的组成部分&#xff0c;而用户注册与登陆则是卖家中心的第一步。本文将详细介绍如何使用Django框架从零开始搭建一个功能完善的卖家注册页面&#xff0c;包括前端界面设计和后端逻辑实现。 一、项目概述 我们将创建一个名为sel…

MySQL表的使用(4)

首先回顾一下之前所学的增删查改&#xff0c;这些覆盖了平时使用的80% 我们上节课中学习到了MySQL的约束 其中Primary key 是主键约束&#xff0c;我们今天要学习的是外键约束 插入一个表 外键约束 父表 子表 这条记录中classid为5时候&#xff0c;不能插入&#xff1b; 删除…