主题
10 整理代码并交付小游戏
把上一课已经能玩的函数拆成模块,通过同一套操作检查重构前后行为,再交付完整工程。
这课不加新规则。你把第 09 课的函数整理到模块,让入口更容易读,并核对整理后仍是同一个推球游戏。
开始前检查
先用第 09 课完成一轮“触球→命中→个人得分→结算→点击重开”,记下结果。如果已有错误,先修复再整理。场景、HUD 和可选表现保持搭建指南中的配置。
1. 安装最终六文件工程
文件:下表全部六个 Lua 文件。操作:下载第 10 课完整工程,停止试玩,按同路径整文件替换或新增。两个 main.lua 必须替换,不能在原来的完整逻辑后面再追加模块调用。
| 文件 | 本课职责 | 相对第 09 课 |
|---|---|---|
server/main.lua | 调用回合模块初始化、开始、关闭清理 | 替换为短入口 |
server/round.lua | 原服务端的回合、归属、计分、协议和可选特效 | 新增,把已有函数移入 |
client/main.lua | 调用 HUD 模块初始化 | 替换为短入口 |
client/hud.lua | 原客户端的快照、显示、按钮和音效 | 新增,把已有函数移入 |
common/remote_events.lua | 两端共用通道名 | 保持 |
data/game_config.lua | 两端共用配置 | 保持 |
预期:保存同步后仍使用 20 秒回合、个人计分、结算等待和全场重开。方块仍在起点附近随机选择 Z 轴偏移,允许抽到 0 或重复位置。整理文件不应该改变任何一条规则。
模块化工程依赖全景图
text
client/main.lua ──require──> client/hud.lua (入口只做组装)
server/main.lua ──require──> server/round.lua (入口只做组装)
client/hud.lua ──require──> common/remote_events.lua (双端通道定义)
client/hud.lua ──require──> data/game_config.lua (数值配置)
server/round.lua ──require──> common/remote_events.lua (双端通道定义)
server/round.lua ──require──> data/game_config.lua (数值配置)运行时双端通过 common/remote_events.lua 里的 RemoteEvent 通道交换快照与请求:服务端 Snapshot:FireClient 下发,客户端 Request:FireServer 上报;hud.lua 只管显示与按钮,round.lua 只管回合、积分与球体复位。
2. 读懂服务端入口
文件:server/main.lua。操作:确认完整内容如下。
lua
-- @runtime server
-- ============================================================================
-- 模块职责:服务端主入口(Entry Point)
-- 核心目标:
-- 1. require 引入业务管理模块 server.round,保持入口脚本极简
-- 2. 调用 Round.Init() 初始化场景与状态机,失败时安全拦截
-- 3. 启动对局循环,并在服务器关闭或模块卸载时调用 Round.Destroy() 释放资源
-- ============================================================================
local Round = require("server.round")
-- 初始化场景与状态
if not Round.Init() then
print("[推球][ERROR] 初始化失败,请核对球体和方块")
return
end
-- 启动对局流程
Round.Start()
-- 绑定生命周期退出清理
game:BindToClose(function()
Round.Destroy()
end)
return {
Destroy = function()
Round.Destroy()
end,
}require 得到 Round 这个模块表;它还没有建立监听。Round.Init() 才查场景、准备状态并绑定事件,找不到球或方块时返回失败。Round.Start() 让第一局正式开始。
服务端关闭时调用 Round.Destroy(),断开碰撞、Heartbeat、请求和玩家离开监听,并让尚未结束的表现回调失效。
3. 读懂客户端入口
文件:client/main.lua。操作:确认完整内容如下。
lua
-- @runtime client
-- ============================================================================
-- 模块职责:客户端主入口(Entry Point)
-- 核心目标:
-- 1. require 引入界面业务模块 client.hud
-- 2. 执行 Hud.Init() 挂接 HUD 渲染、快照接收与音效播放
-- 3. 导出 Destroy 接口以供生命周期管理
-- ============================================================================
local Hud = require("client.hud")
Hud.Init()
return {
Destroy = function()
Hud.Destroy()
end,
}Hud.Init() 完成原来的“先监听、再请求、等待 UI、显示结果”。UI 暂未就绪仍会重试。本地玩家离开时统一清理;Hud.Destroy() 也可由加载它的上层逻辑主动调用,断开消息、玩家离开、Heartbeat 和按钮连接。入口返回 Destroy 便于主动卸载,它不会因 return 而自动调用。模块不会销毁引擎管理的根节点或编辑器中准备的 UI。
两个模块的 Init() 开头都会先调用自身 Destroy(),避免重复初始化时重复监听。在模块文件里搜索上一课的 onCubeTouched、updateHud,你会看到原来的逻辑只是换了位置。
同一次试玩内重新初始化回合模块,局号和修订号继续递增;同一球体、方块仍使用第一次记录的起点。这样客户端能接受新状态,也不会把已经随机移动的位置当成下一次安装的起点。只有停止试玩、开始新的 Lua 运行环境时,才从第 1 局重新开始。
4. 按同一套动作验收
文件:不改代码,使用最终检查点。操作:按下表逐项完成,不用“没有报错”替代实际操作。
| 操作 | 必须观察到 |
|---|---|
| 新一次试玩 | 只有一局开始,HUD显示当前局状态 |
| 人物触球后命中 | 本人积分加 1,球复位;方块的 Z 坐标在起点左右 3 范围内,允许回到起点或留在原位 |
| 球没有有效触碰者时命中 | 不给玩家加分,球仍复位 |
| 等到结束后再碰撞 | 不再加分,按钮出现 |
| 连点再来一局 | 全场只开始下一局一次,积分清零 |
| 连续完成三局 | 没有叠加的回调、旧归属或重复开局 |
| 缺失可选特效或关闭音效 | 规则与 HUD 继续工作 |
| 有真实第二客户端时中途加入 | 收到当前回合和自己的分数,重开后两端同局 |
多人项需要实际联机环境。若只有单客户端,先完成其余项,并把多人项明确留作待验证,不能用模拟玩家对象宣称通过。
自己改一处
文件:data/game_config.lua。操作:把每局时长改为 10,保持两个 main.lua 不动。
预测:模块化以后是否要改多个地方?提示:回合模块仍读取配置。答案:同步并重新试玩后,时长变为 10;HUD与结算读取同一服务端结果,无需另改客户端时间。最后恢复 20。
交付给下一个人
发送最终工程包以及你实际完成的地图/UI配置。本包对应本系列的 10 课主线;SE UGC Lua 开发系列的第 22 章另有独立工程。
说明包里有 Lua 文件和搭建步骤,没有已保存地图、UI 数据或资源文件。让对方在专用空白世界版地图按随包 SCENE.md 搭建,并按上表独立运行一次。能在新地图重建,才说明安装说明足够清楚。
你已经完成默认主线。接下来按需求选择存档与排行榜、物理与射线、角色系统或更多项目挑战。
API:Game、Connection、RemoteEvent、EUIManager。工程化深读:可复用系统。
