Defold GUI/UI 开发:节点体系、布局自适应与 HUD 实战

深入 Defold GUI 系统:GUI 节点体系、文本字体与图集、自适应布局与锚点、动画特效,以及 HUD、菜单与弹窗的完整实现方法。

Defold 的 UI 层由独立的 GUI 系统承载:.gui 资源描述界面结构,.gui_script 驱动逻辑与动画。GUI 与游戏世界(Game Object 体系)相对独立,但有清晰的联动方式。(Defold GUI docs) 本文从节点体系讲到自适应布局与 HUD 实战,帮助你搭建从主菜单到游戏内 HUD 的完整界面。

前置建议:先掌握 Defold Lua 脚本系统深入 中的消息模型,GUI 脚本同样依赖 on_message 通信。

一、GUI 组件与节点体系

1. GUI 组件与 GUI 脚本

一个 UI 通常由两部分组成:

  • GUI 组件(.gui 资源):声明节点树,放在 Game Object 上
  • GUI 脚本(.gui_script):绑定在该组件上,写逻辑与动画

编辑器操作:右键 → New → GUI 创建 .gui,拖到 Game Object 上作为 Component;再创建 .gui_script 并在 Properties 中绑定到 GUI 组件。

2. 节点类型

GUI 编辑器可创建以下节点:

  • Box:矩形色块,可做按钮底、进度条背景
  • Text:文本节点
  • Texture:图片节点(引用图集内图片)
  • Pie:扇形(血量环、冷却圈)
  • Slider / Template 等高级节点用于列表与滑动条
-- 运行时动态创建 Box 节点
local node = gui.new_box(vmath.vector3(100, 100, 0), vmath.vector3(120, 60, 0))
gui.set_color(node, vmath.vector4(1, 0.3, 0.3, 1))

3. 节点层级与变换

在 GUI 编辑器的 Outline 中可拖动节点建立父子关系。父节点变换会影响子节点,这与 Game Object 的父子机制一致。节点的 Pivot(锚点)决定旋转/缩放中心:

  • PIVOT_CENTER:中心
  • PIVOT_NW/N/NE/W/E/SW/S/SE:九宫格锚点

Size Mode 决定节点大小来源:Auto(随内容)、Manual(手动设置)。

4. 节点查找与通用操作

GUI 脚本通过节点 id 或路径获取节点:

function init(self)
    self.hp_bar = gui.get_node("hp_bar")
    self.title = gui.get_node("menu/title")   -- 斜杠寻子节点
end

-- 常用节点操作
gui.set_position(self.hp_bar, vmath.vector3(10, 20, 0))
gui.set_scale(self.title, vmath.vector3(1.2, 1.2, 1))
gui.set_enabled(self.hp_bar, false)          -- 显隐

二、文本与字体

1. 创建字体与文字节点

字体资源由 game.project 的 [fonts] 段引用 TTF 文件生成,编辑器里右键 → New → Font 选择字体文件即可。文字节点属性:

  • Text:显示内容
  • Font:绑定的字体资源
  • Size:字号
  • Color:颜色
  • Leading / Tracking:行距 / 字距
  • Alignment:左/中/右对齐
  • Line Break:自动换行

2. 动态修改文本

function set_score(self, value)
    gui.set_text(gui.get_node("score_text"), tostring(value))
end

中文需要字体支持对应字形(TTF 内含中文子集或大字体文件);大字体体积大,可考虑拆分字体、按需加载。

3. 文本与图集的配合

按钮一般由「背景图(Texture)+ 文字(Text)」两个节点组合,文字叠在图上。把图片放入图集可减少 Draw Call,参见 编辑器与资源管线。

三、布局与自适应

1. 屏幕适配策略

移动与 Web 上横竖屏、不同分辨率的差异巨大。Defold 的相机与 GUI 坐标系基于 game.project 中的 [display] 设置:

[display]
width = 1280
height = 720

GUI 节点坐标默认相对屏幕左下角(原点)。为了在不同分辨率下不变形,常用策略:

  • 锚点布局:用 Adjust Mode 让节点贴边(左下、顶部居中、右下等)
  • 留安全区:刘海边距用 Adjust Reference 保证避开刘海
  • 固定逻辑分辨率 + 缩放:[display] 中设 scale_mode = FIT,等比缩放适配

2. Adjust Mode 详解

GUI 节点属性 Adjust Mode 决定它相对哪个「基准」定位:

  • FIT:等比缩放整个 GUI,保持比例(默认)
  • ZOOM:等比但可能裁切
  • STRETCH:拉伸填满(可能变形,慎用)

配合 Pivot 设置,按钮「永远贴着右上角」等需求一行属性即可完成:

Pivot = NE
Adjust Mode = FIT
Position = (-10, -10, 0)   -- 距右上角 10px

3. 响应式布局代码示例

需要更精细的适配时,脚本读取屏幕尺寸计算:

function init(self)
    local width = gui.get_size()          -- 返回 x, y, z
    local half_w = width.x * 0.5
    gui.set_position(gui.get_node("center_panel"), vmath.vector3(half_w, 0, 0))
end

4. 多语言与本地化

文本内容集中管理:

local L = require "main.localization"

function init(self)
    gui.set_text(gui.get_node("start_btn"), L.get("start_game"))
end

切换语言时遍历语言表更新全部文本节点即可,无需改动布局。

四、动画与特效

1. gui.animate:属性补间

GUI 节点动画使用 gui.animate(),支持位置、缩放、旋转、颜色、透明度等属性的缓动:

-- 淡入
gui.animate(node, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1),
            gui.EASING_INOUTQUAD, 0.3, 0, nil, gui.PLAYBACK_ONCE_FORWARD)

-- 弹跳进入
gui.animate(node, gui.PROP_SCALE, vmath.vector3(1, 1, 1),
            gui.EASING_OUTBACK, 0.4)

-- 循环旋转
gui.animate(node, gui.PROP_ROTATION, vmath.vector3(0, 0, 360),
            gui.EASING_LINEAR, 2.0, 0, nil, gui.PLAYBACK_LOOP_FORWARD)

常用缓动:gui.EASING_INOUTQUAD(平滑)、gui.EASING_OUTBACK(弹性)、gui.EASING_LINEAR(线性)。

2. 动画回调

gui.animate 最后一个参数可传完成回调,用于「动画结束后切换场景」:

local function on_fade_done()
    msg.post("/main/main.collection", "proceed_to_next_level")
end

gui.animate(node, gui.PROP_COLOR, vmath.vector4(0, 0, 0, 1),
            gui.EASING_LINEAR, 1.0, 0, on_fade_done, gui.PLAYBACK_ONCE_FORWARD)

3. 序列与循环特效

组合多个 animate 实现复杂序列(命中闪白、宝箱开合、结算滚动):

function flash_white(self, node)
    gui.set_color(node, vmath.vector4(1, 1, 1, 1))
    gui.animate(node, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0),
                gui.EASING_INQUAD, 0.15)
end

五、HUD 与菜单实现

1. 游戏内 HUD 的常用结构

HUD 常驻屏幕,需要响应游戏事件。推荐结构:HUD 是一个独立 Game Object + GUI 组件,通过消息接收「血量、分数」数据:

-- hud.gui_script
function init(self)
    self.score_node = gui.get_node("score")
    self.hp_bar = gui.get_node("hp_bar")
end

function on_message(self, message_id, message, sender)
    if message_id == hash("update_hp") then
        -- 按比例缩宽血条
        local scale = message.hp / message.max_hp
        gui.set_scale(self.hp_bar, vmath.vector3(math.max(scale, 0.01), 1, 1))
    elseif message_id == hash("update_score") then
        gui.set_text(self.score_node, tostring(message.score))
    end
end

游戏内逻辑只需要 msg.post("hud", "update_hp", {...}),HUD 与玩法完全解耦。

2. 血条进度条的实现

进度条核心是「用 Box 节点宽度表达比例」:

function set_bar(self, node, ratio)
    local size = gui.get_size(node)
    gui.set_size(node, vmath.vector3(size.x * ratio, size.y, 0))
end

配合背景暗色 Box + 前景亮色 Box 叠层,即是最实用的血条。

3. 主菜单与场景切换

菜单通常用一个独立 Collection 承载。切换场景 = 卸载旧集合、加载新集合(参考 Collection 加载):

-- 主菜单点击「开始」
function on_message(self, message_id, message, sender)
    if message_id == hash("start_game") then
        msg.post("/menu_proxy", "unload")          -- 卸载菜单
        msg.post("/game_proxy", "load")            -- 加载游戏
    end
end

4. 弹窗与确认框

弹窗是「覆盖式」UI:一个半透明全屏 Box 遮罩 + 内容面板 + 按钮。显示/隐藏用 gui.set_enabled,动画入场用 gui.animate:

function show_pause(self)
    gui.set_enabled(gui.get_node("pause_root"), true)
    gui.animate(gui.get_node("pause_panel"), gui.PROP_COLOR,
                vmath.vector4(1, 1, 1, 1), gui.EASING_OUTBACK, 0.3)
end

六、输入处理与交互

1. GUI 接收输入

GUI 脚本默认不接收输入,需先获取:

function init(self)
    msg.post(".", "acquire_input")
end

2. 按钮点击检测

按钮交互基于「命中检测 + 输入回调」:

function on_input(self, action_id, action)
    if action_id == hash("touch") and action.pressed then
        local pos = gui.screen_to_node(gui.get_node("start_btn"), action.screen_pos)
        if gui.pick_node(gui.get_node("start_btn"), pos.x, pos.y) then
            msg.post("#", "start_game")
        end
    end
end

gui.pick_node 判断点是否落在节点内,是按钮检测的标准 API。

3. 输入与场景聚焦

多个 GUI 同时存在时(HUD + 弹窗),输入焦点容易乱。惯用方案:

  • 弹窗打开时,暂停 HUD 脚本输入:msg.post("hud#gui_script", "disable_input")
  • 或由全局输入脚本统一分发:先判断是否在弹窗内,是则只响应弹窗

七、GUI 性能与调试

1. 降低 Draw Call

  • 所有 UI 图片放同一张图集(含 9-slice 缩放按钮)
  • 减少动画对同一节点属性的高频率 gui.set_*(每帧设置最耗)
  • 静态界面用编辑器静态布局,避免运行时逐帧创建

2. 常见问题

  • 文字不显示:字体资源未绑定、或字库不含所需字符
  • 节点找不到:id 拼写或路径层级写错,gui.get_node 报 nil
  • 点击没反应:acquire_input 未调用,或节点被 disable/被遮挡
  • GUI 随窗口错位:Adjust Mode 与 Pivot 组合不当,检查基准设置

3. 调试技巧

  • GUI 编辑器支持直接预览与热重载,改属性即时可见(见 热更新与热重载)
  • gui.get_node 结果打印 id 排查层级错误
  • Profiler 中关注 gui 分区耗时,与物理、渲染对照定位瓶颈

八、总结

Defold GUI 系统的核心优势是「声明式节点 + 脚本驱动动画」:用 .gui 描述界面、用 Adjust Mode 解决自适应、用 gui.animate 与消息实现动效与数据绑定。把 HUD 做成独立对象并只通过消息喂数据,能让 UI 与玩法长期保持低耦合。

至此,脚本(脚本系统)、物理(物理引擎)、UI 三大块已齐备,下一步就是打包发布(跨平台发布)。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 跨平台发布:iOS/Android/Web/桌面打包、签名与 Store 上架
  2. Defold 编辑器与资源管线:从场景搭建到包体瘦身
  3. Defold 物理引擎:碰撞体、回调、关节与性能优化