Skip to content

把参数和资源引用集中到配置 ​

把常量和资源 URI 放入 data 配置,建立清晰、可维护的模块依赖。

什么时候查这篇 ​

开始反复修改分值、时长或资源 URI,希望一处修改即可生效时,使用本专题。按当前需求选择小节即可,不必按专题编号顺序读完。

  • 开始前:会创建模块并 require;特效实验另需编辑器中已配置的 CoinEffect/GemEffect。
  • 安装与示例范围:先安装 data/items.lua,只读取配置即可完成第一步。特效模块另存 server/item_effects.lua,调用方显式传入道具键和位置,不放进 data。
  • 本次要看到:把 Coin.Score 改为 3 后读取结果也为 3;未知道具键有提示,连续特效不会被旧延时提前关闭。

如果还没有完成可运行的小游戏,先回到主线:09 课。语法卡住时查Lua 速查,运行结果不符时查按现象排错。

你会学到什么 ​

  • data/ 目录适合放什么,不适合放什么。
  • 如何用配置表集中管理道具、分数、资源 URI。
  • official://、custom:// 与不同资源字段的对应关系。
  • 如何给配置表加一层轻量校验,尽早发现拼写和类型错误。

data/ 目录的定位 ​

默认生成的 Lua 工程通常先只有 client/、server/、common/。data/ 有两种常见来源:

  • 在蛋仔 VS Code 插件中点击导出数据,生成或更新 data/UINodes.lua、data/Prefab.lua 等编辑器导出表。
  • 你手动创建 data/items.lua、data/round_config.lua 等项目配置表。

无论是哪种来源,data/ 放的都应该是静态配置:道具表、常量表、关卡参数、资源 URI 映射、UI 节点名映射。它可以被 client 和 server 读取,但自己不应该依赖任何运行端逻辑。

依赖方向保持成:

text
client -> common / data
server -> common / data
common -> data
data   -> 无运行时依赖

这条规则很重要:data/items.lua 不应该 game:GetService(...),不应该创建 Unit,不应该监听事件,也不应该 require("server.xxx") 或 require("client.xxx")。它只返回一份普通 Lua table。

最小示例:道具配置表 ​

运行端:data
文件:data/items.lua

示例类别:共享定义文件。 按上方路径保存,再由入口 require;不要当作 main.lua 替换文件。

lua
return {
    Coin = {
        DisplayName = "金币",
        Score = 1,
        EffectUnitName = "CoinEffect",
    },
    Gem = {
        DisplayName = "宝石",
        Score = 5,
        EffectUnitName = "GemEffect",
    },
}

EffectUnitName 是项目自己的配置键,保存编辑器中已配置 EffectUnit 的对象名。当前 Meta 没有提供可编辑的 Lua 侧特效资源选择字段,因此 Lua 不写入特效资源 ID,只通过公开的对象树查询接口找到并控制编辑器对象。

读取配置 ​

运行端:server
文件:server/main.lua

示例类别:配套入口;运行端:server。 先创建它 require 的所有文件;按本小节指定入口整段替换,不与已有主线入口重复安装。

lua
local Items = require("data.items")

for key, item in pairs(Items) do
    print("[SE Lua Guide] 道具:", key, item.DisplayName, item.Score, item.EffectUnitName)
end

local coin = Items.Coin
if coin then
    print("[SE Lua Guide] 金币分数:", coin.Score)
end

require 路径要写完整目录前缀:require("data.items")。不要写成 require("items"),否则多人协作或目录变复杂后很容易加载错。

资源引用与字段 ​

资源 URI 不只是“一个值”。它要和使用它的字段对应起来。

引用格式常见用途常见字段
official://preset/103489官方模型 / 组合 Unit 预设;103489 是本教程配置使用的示例AssetService:LoadUnitAsset / LoadUnitAssetAsync 的第一个参数
official://mesh/59268官方网格资源WorldUnit.RenderMeshId / PhysicsMeshId、RenderUnit.RenderMeshId
official://image/10066官方 UI 图片EUIImage.Image、EUIButton.NormalImage / PressImage / DisableImage
custom://...用户生产内容仅在目标公开 API 明确接受该 scheme 时使用

表中的数字 ID 只用于说明 URI 形态,不代表所有项目都应使用同一资源。在编辑器中右键你实际选择的资源,选择复制引用 ID,即可得到对应 URI。新手阶段建议把这些 URI 全部集中在 data/ 配置里,业务代码只读取配置,不到处硬编码。

official://audio/...、official://animation/... 也是 AssetService 文档列出的公开 scheme,但具体应该交给哪个播放 API,要到音效和动画专题按该成员签名确认。本专题不把“URI scheme 存在”推导成任意对象都有对应可写字段。

特效是当前教程的受控用法:先在编辑器中完成 EffectUnit 的资源配置并设置稳定名称,Lua 配置表只保存这个对象名或公开路径。不要因为看见别的资源使用 URI,就猜测 EffectUnit 也有可由教程写入的资源选择字段。

UI 图片也使用字符串 URI:EUI 节点的图片字段(Image、NormalImage、PressImage 等)写成 official://image/... 字符串,不是裸数字。让 EUI 显示状态并响应按钮已把 UI 节点和图片 URI 的规则单独讲过。

配置只声明,加载由对应专题负责 ​

把参数和资源引用集中到配置只负责把资源 URI 管好;真正“创建模型”“播放特效”“加载自定义资产”属于运行时代码。

  • 播放特效:读取 EffectUnitName,定位编辑器中已配置的 EffectUnit,再调用公开播放控制接口。
  • 创建完整模型:读取 PresetUri,优先用 AssetService:LoadUnitAsset 或 LoadUnitAssetAsync。
  • 创建单个网格 Unit:读取 RenderMeshId,写入 WorldUnit.RenderMeshId。

如果一个道具既要播放特效,又要生成模型,可以这样扩展配置:

示例类别:共享定义文件。 按上方路径保存,再由入口 require;不要当作 main.lua 替换文件。

lua
return {
    Coin = {
        DisplayName = "金币",
        Score = 1,
        EffectUnitName = "CoinEffect",
        PresetUri = "official://preset/103489",
    },
}

不要在 data/items.lua 里直接调用 AssetService:LoadUnitAsset。配置表只说明“用哪个资源”,不执行“如何创建”。

进阶:给配置加轻量校验 ​

配置表的错误通常很小:字段拼错、分数写成字符串、URI 少了前缀。但这些小错误会在运行时变成很难读的错误日志。可以在服务端启动时先校验一次。

示例类别:配套入口或接入片段;运行端:server。 先创建它 require 的所有文件;按本小节指定入口整段替换,不与已有主线入口重复安装。

lua
local Items = require("data.items")

local function StartsWith(text, prefix)
    return type(text) == "string" and string.sub(text, 1, #prefix) == prefix
end

local function ValidateItemConfig()
    for key, item in pairs(Items) do
        assert(type(item.DisplayName) == "string", key .. ".DisplayName 必须是 string")
        assert(type(item.Score) == "number", key .. ".Score 必须是 number")
        assert(type(item.EffectUnitName) == "string" and item.EffectUnitName ~= "",
            key .. ".EffectUnitName 必须是非空对象名")
        if item.PresetUri ~= nil then
            assert(StartsWith(item.PresetUri, "official://preset/") or StartsWith(item.PresetUri, "map://"),
                key .. ".PresetUri 必须是已确认的 Unit 资产 URI")
        end
    end
end

ValidateItemConfig()

校验只在启动时跑一次,不要在每次拾取道具时重复跑。

从配置驱动特效播放 ​

这是独立模块文件 server/item_effects.lua。入口先 require("server.item_effects"),再在已经判定成功的命中回调里调用 ItemEffects.Play("Coin", position);这里的 position 来自该回调计算的命中位置。只安装模块不会自动播放特效。

运行端:server

示例类别:完整模块文件。 保存为 server/item_effects.lua;由对应运行端入口 require 并调用,单独放置文件不会自动执行功能。

lua
local Items = require("data.items")
local World = game:GetService("World")
local Task = game:GetService("Task")

local ItemEffects = {}
local effectGenerations = {}

function ItemEffects.Play(itemKey, position)
    if not World or not Task then
        print("[SE Lua Guide][ERROR] World 或 Task 服务获取失败")
        return false
    end

    local item = Items[itemKey]
    if not item then
        print("[SE Lua Guide][WARN] 未找到道具配置:", itemKey)
        return false
    end

    local effect = World:FindFirstChild(item.EffectUnitName, true)
    if not effect or not effect:IsA("EffectUnit") then
        print("[SE Lua Guide][WARN] 未找到已配置的特效对象:", item.EffectUnitName)
        return false
    end

    local effectName = item.EffectUnitName
    effectGenerations[effectName] = (effectGenerations[effectName] or 0) + 1
    local generation = effectGenerations[effectName]

    effect:SetVisible(false)
    effect:SetPosition(position)
    effect:SetDuration(1)
    effect:SetVisible(true)
    Task:Delay(1, function()
        -- 同一对象连续播放时,旧回调不能关闭更新一轮的表现。
        if effectGenerations[effectName] == generation and effect.Parent then
            effect:SetVisible(false)
        end
    end)
    print("[SE Lua Guide] 播放", item.DisplayName, "特效")
    return true
end

return ItemEffects

这里的重点不是特效 API,而是“代码不再猜测未公开资源字段”。以后美术替换特效资源时,在编辑器中更新同名对象;只有对象名变化时才修改配置表。

常见错误 ​

错误:在 data 中写运行时逻辑 ​

data/ 不调用 Service、不创建 Unit、不监听事件。它只返回静态 table。

错误:把项目配置键当成引擎属性 ​

EffectUnitName 只是本教程的项目配置键,用来查找对象,不代表引擎存在同名属性。真正写入引擎对象时,只能使用 Meta 公开的字段,例如 RenderMeshId。

错误:资源 URI 散落在各处 ​

动画 URI、preset URI、mesh URI 等作者可见资源引用应集中到配置表,方便替换、校验和审查;由编辑器管理的 EffectUnit 资源则保留在编辑器配置中。

错误:把 preset 当成 mesh ​

official://preset/... 用 AssetService:LoadUnitAsset 或 LoadUnitAssetAsync 加载;official://mesh/... 才写给 RenderMeshId。两者不是同一种资源。

错误:require 路径不带目录前缀 ​

错误写法是省略目录的 require("items");本教程统一写成 require("data.items")。

练习任务 ​

  1. 给配置表新增一个道具,定义 DisplayName、Score、EffectUnitName。
  2. 把用事件和计时器组织动作示例里的特效对象名改成从配置表读取。
  3. 给配置表新增 PresetUri 字段,并在加载官方预设并放进场景的资产加载示例中读取它。

本专题验收标准 ​

  • [ ] 我知道 data/ 只放静态配置,不写运行时逻辑。
  • [ ] 我能用 require("data.xxx") 读取配置表。
  • [ ] 我知道 preset / mesh 等公开资源 URI 应写到对应字段;EffectUnit 的资源选择当前由编辑器配置。
  • [ ] 我知道资源 URI 应集中到配置表,并能写简单校验。

本专题产物 ​

  • 一个 data/items.lua 或 data/round_config.lua 配置表。
  • 一段启动校验日志,能指出缺失字段、错误 URI scheme、错误 EffectUnitName 类型或空配置。
  • 一次从配置读取对象名并交给运行时代码使用的改造,例如让用事件和计时器组织动作通过 EffectUnitName 查找特效对象。

本专题 API 对照 ​

把结果带回小游戏 ​

先确认本页“本次要看到”的现象,再把选中的功能接到已有模块。保留原有入口和清理逻辑,只迁入需要的部分;不要把多个试验入口拼在一起。

返回主线对应步骤,或去专题导航选择下一项能力。新的代码尚未完成目标地图实测时,记录为待验证,不把编译通过当作行为通过。