Skip to content

第 1 章:脚本工程、目录结构与运行入口

建立可运行的 SE Lua 工程,理解 client、server、common 与 data 目录边界。

你会学到什么

  • 如何从编辑器生成 Lua 脚本工程。
  • 默认生成的 clientservercommon 三个目录分别负责什么。
  • 什么时候会出现 data/EggyAPI.lua,以及它们和 VS Code 插件“导出数据 / 导出 API”的关系。
  • official.codemaker.vscode 这类目录为什么不要随便改。
  • 如何写最小脚本,确认 client 和 server 都在运行。
  • 为什么 main.lua 应该只做入口组装。
  • 世界编辑器的 Lua 运行环境与原点版有哪些差异(库裁剪、表键、math 库)。

从编辑器生成脚本工程

  1. 在编辑器左上角选择 LUA - 蛋仔开发助手
  2. 在弹窗左下角选择 生成工程
  3. 选择本地路径,生成成功后用 VS Code 打开。
  4. 在 VS Code 里确认默认源码目录只有 clientservercommon

如果你还没有在 VS Code 插件里点过其它按钮,先不要疑惑为什么没有 data/EggyAPI.lua

动作生成或更新什么用途
生成工程client/server/common/脚本入口和双端共享模块
导出数据data/,以及 UINodes.luaPrefab.lua 等导出表UI 节点、预设、资源索引等编辑器数据
导出 APIEggyAPI.lua给 VS Code / Lua 语言服务使用的 API 提示与类型辅助

不同编辑器版本生成的辅助目录可能略有差异,但第一轮主线代码先围绕 client/server/common 组织;需要 UI、Prefab 或资源导出时,再通过“导出数据”获得 data/

推荐工程结构

text
LuaSource_<project_name>/
  .codemaker/              -- 官方编程助手规则、技能、工具
  .vscode/                 -- VS Code 工作区配置
  EggyAPI.lua              -- 点击“导出 API”后生成,供 VS Code 提示使用
  data/                    -- 点击“导出数据”后生成;也可手动放自己的静态配置
    CustomAsset.lua        -- 编辑器导出的自定义资产
    OfficialAsset.lua      -- 编辑器导出的官方资产索引
    Prefab.lua             -- 编辑器导出的预设资源
    UINodes.lua            -- UI 编辑器导出的节点表
    items.lua              -- 你自己写的静态配置
  common/
    remote_events.lua      -- 双端共享事件定义
  client/
    main.lua               -- 客户端入口
  server/
    main.lua               -- 服务端入口
  official/                -- 若存在,视为官方脚本目录
  log.txt                  -- 某些版本/导出流程生成的试玩日志;实际位置以当前工具为准

目录职责

目录运行端能引用谁不能引用谁典型内容
data/双端可 require必要时引用其它纯 data 模块common/client/server纯配置、导出表、资源 URI
common/双端可 requiredataclient/server工具函数、RemoteEvent 定义、常量
client/clientdata/common/clientserverUI、输入、本地表现、相机
server/serverdata/common/serverclient计分、校验、存储、回合、跨服
official/由官方生成不建议引用或修改不修改官方脚本或辅助内容

datacommon 的区别很重要:

  • data 可能来自“导出数据”,也可以由你手动创建配置表;它只放“数据”:table、数字、字符串、资源 URI,不要在顶层获取 Service、绑定事件或启动逻辑。
  • common 可以放双端工具,也可以集中定义 RemoteEvent,但仍不能依赖 clientserver

EggyAPI.lua 不属于玩法代码,不要在业务脚本里 require("EggyAPI")。它的作用是让编辑器和 VS Code 更懂当前 SDK。

require 的规则

require 必须带目录前缀,并且只能引用当前目录规则允许的模块。下面四条是分别放在合适运行端的写法,不是让你把它们全部复制到同一个文件:

  • 双端都可用:require("data.items")require("common.remote_events")
  • 只放客户端:require("client.hud")
  • 只放服务端:require("server.round_manager")

错误写法:

lua
local Config = require("items")          -- 找不到 data/items.lua
local Hud = require("server.hud")        -- client 不能 require server
local Service = require("GameAPI")       -- game、Enums、Data 类型都是全局,不 require

全局对象直接使用:gameEnumsVector3ColorQuaternionCFrameRemoteEvent 等都不需要 require。如果看到旧规则要求 math.Vector3(...),以当前教程和运行时验证为准:正文统一使用全局 Vector3(...)

Lua 运行环境(与原点版的差异)

世界编辑器的脚本同样运行在安全沙盒中,出于安全考虑裁剪了部分标准库。这部分裁剪与原点版(帧同步)一致;但在语言特性与 math 库上,世界编辑器不做原点版那样的定制。

库的裁剪(与原点版共通)

以下标准库被移除,脚本中不能使用:

  • io
  • os
  • package
  • debug

全局变量、函数和标准库按白名单管理。下面只列本教程会直接用到的常用入口,不宣称是运行包的完整清单;具体可用范围仍以目标运行包和当前开发助手提示为准。旧 Lua 教程里出现、但目标环境没有提供的库或函数不能直接照搬。

全局变量/函数限制
_VERSION
error / assert
ipairs / pairs / next
pcall / xpcall
tostring / tonumber / type字符串转数字失败时 tonumber 返回 nil,使用前要准备默认值
select
coroutine协程标准库;入门异步优先使用第 6 章的 Task 服务
require仅限加载脚本工程目录下的其他 Lua 模块
setmetatable不可使用 __mode__gc
getmetatable仅可获取 table 的 metatable
traceback等价于 debug.traceback
print
string / table / utf8 / math保留的标准库入口;具体成员仍以目标运行包为准

例如第 11 章会用 tonumber(value) or 0 把可能的字符串输入规整成数字;第 6 章则优先用 Task:Spawn / Task:Wait 讲协程,不要求新手直接操作 coroutine

main.lua 应该做什么

client/main.luaserver/main.lua 会自动执行,但它们不应该越来越长。推荐原则:

  • main.lua 负责 require 模块。
  • main.lua 负责调用 Init() / Start()
  • 玩法细节放进 Manager 或模块。
  • UI 模块不要在 require 顶层自动绑定事件,应显式调用 Init()

本章先用下面的 TestCommonUtil 完成最小入口。到第 19 章再拆 Manager,第 22 章会给出包含 RoundManager.Init(...)Start()Destroy() 的完整模块,避免你现在复制一个依赖尚未创建文件的半成品入口。

最小示例:确认双端都在运行

common/TestCommonUtil.lua

运行端:common

lua
local TestCommonUtil = {}

function TestCommonUtil.PrintCurrentSide()
    local RunService = game:GetService("RunService")
    if RunService:IsClient() then
        print("[SE Lua Guide] 这里是客户端 client")
    elseif RunService:IsServer() then
        print("[SE Lua Guide] 这里是服务端 server")
    else
        print("[SE Lua Guide] 当前不是 client/server 试玩运行域")
    end
end

return TestCommonUtil

client/main.lua

运行端:client

lua
local TestCommonUtil = require("common.TestCommonUtil")

print("[SE Lua Guide] Hello, SE Client!")
TestCommonUtil.PrintCurrentSide()

server/main.lua

运行端:server

lua
local TestCommonUtil = require("common.TestCommonUtil")

print("[SE Lua Guide] Hello, SE Server!")
TestCommonUtil.PrintCurrentSide()

期望结果

运行游戏后,在编辑器输出窗口中应该看到类似日志。部分版本或导出流程也会生成 log.txt;没有这个文件不代表脚本没运行,应以当前编辑器输出和工具实际提供的日志入口为准。

text
[SE Lua Guide] Hello, SE Server!
[SE Lua Guide] 这里是服务端 server
[SE Lua Guide] Hello, SE Client!
[SE Lua Guide] 这里是客户端 client

日志顺序可能不同,这是正常的。客户端和服务端不是同一条顺序执行的脚本。

如果只看到其中一端,按这个顺序排查:

  1. 确认文件名分别是 client/main.luaserver/main.lua,没有放反目录。
  2. 确认 require("common.TestCommonUtil") 的目录、文件名和大小写完全一致。
  3. 从日志中找到第一条错误;后续错误往往只是第一处加载失败的连带结果。
  4. 修复后重新试玩,不要只看旧日志判断新代码。

来源优先级

本系列会参考 Meta 契约、.codemaker 规则、published API 文档和真实运行验证。遇到冲突时按下面顺序处理:

  1. 当前 Meta 契约:se-lua/sdk/meta/meta_reference.txt 和 meta-spec 派生规则。
  2. 真实运行验证结果,用于验证行为,不新增未进 Meta 的作者 API。
  3. published/generated 文档、.codemaker 中仍然有效的工程规则。
  4. 与当前 API 体系和版本一致的外部正式官方文档,可用来交叉核对 Meta 描述,但不能替代 Meta 建立新能力。
  5. 旧手册或旧规则中的历史写法,只能帮助理解概念和迁移背景。

判断“正式官方文档”时还要看它属于哪套 API。若页面主要使用 LuaAPI / GameAPI / GlobalAPI,而当前工程使用 game:GetService(...) 和 Service / Unit / Data / Enum,那么两者不是可以逐成员互相覆盖的同一版契约。例如 .codemaker 的工程目录和 require("common.xxx") 规则仍然有效;但旧手册中的 math.Vector3(...) 已被当前 Meta 和运行时验证支持的全局 Vector3(...) 规范替代。

常见错误

错误:把运行逻辑写进 data

data/items.lua 只应该返回配置表。如果在里面 game:GetService()、绑定事件、创建 Unit,client 和 server require 它时都会产生副作用。data/ 即使是你手动创建的,也要遵守同样规则。

错误:client require server 模块

客户端不能直接引用服务端模块。需要跨端通信时,第 8 章会使用 RemoteEvent

错误:修改 official 目录

如果工程里有 official/,把它当作官方生成内容。项目逻辑写在 client/server/common/data,不要把自定义玩法塞进 official

错误:把 EggyAPI.lua 当运行时代码

EggyAPI.lua 是点击“导出 API”后生成给编辑器/VS Code 看的辅助文件。它不是玩法模块,不应被 client/main.luaserver/main.lua require。

错误:main.lua 堆满所有玩法

第一天能跑,第三天就难改。超过一个小功能后,就把计分、回合、UI、技能等拆成模块。

练习任务

  1. 如果工程里还没有 data/,先手动创建它,或在插件里点击“导出数据”生成它。
  2. 创建 data/round_config.lua,返回 { RoundSeconds = 60 }
  3. 在 client 和 server 中分别 require 并打印 RoundSeconds
  4. 创建 common/side.lua,封装 RunService:IsClient() / IsServer()
  5. 点击“导出 API”,确认根目录出现 EggyAPI.lua,但不要在运行时代码里 require 它。
  6. 在确认前五步通过后,临时把一个 require 路径写错,观察第一条加载错误;随后立即恢复正确路径并重新试玩,确保工程回到可运行状态。

本章验收标准

  • [ ] 我知道如何从编辑器生成脚本工程。
  • [ ] 我知道默认生成工程先只有 client/server/common
  • [ ] 我知道 data/ 来自“导出数据”或手动创建,EggyAPI.lua 来自“导出 API”。
  • [ ] 我知道 client/server/common/data 的职责和依赖方向。
  • [ ] 我知道 official.codemaker 不应作为玩法代码目录。
  • [ ] 我知道 require 要写目录前缀。
  • [ ] 我能通过日志确认 client 和 server 都在运行。

本章产物

本章产物是一个能启动的脚本工程骨架:client/main.luaserver/main.luacommon 模块都能被正确加载;如果你需要配置表或 UI 节点映射,还应能通过手动创建或“导出数据”得到 data/,并知道“导出 API”生成的 EggyAPI.lua 只服务于编辑器提示。

本章 API 对照

下一章预告

现在你已经能运行脚本了。下一章我们将学习如何在 Lua 中找到编辑器里摆放的对象,以及为什么位置、颜色、枚举不能用普通 table。