主题
07 拓展与回顾
农场做完了:留下能继续改的方向,再把这一路学到的东西串一遍。
你将做出什么
地图能跑起来了:种下 → 长大 → 结果实 → 搬进放置区换分。 这一课不再加功能——留下可以继续改的方向(含两个可选练习),再把知识点串一遍。
1. 先回头看一眼:现在它是什么样
text
场景
├── 田地 每块地:互动组件(种)+ 种子
├── 放置区 预设 + 触发区域 + 服务器脚本对象
└── 果实 由种子长大后创建出来(动态 + 可抓举)
脚本
├── common/config.lua 数值(成长时间、突变率……)
├── common/remote_event.lua 两端共用的事件名
├── server/main.lua 种植 / 生长 / 结果实 / 突变
└── client/main.lua 头顶进度条 + 分数飘字2. 两个可选练习(原本可以单独成课的内容)
这两件事都不需要新知识,只是把学过的东西再用一遍——想练手就做,不做也不影响后面。
练习一:天气(让天空自己变)
思路:定时换一套天空球。天空是场景物件,服务端换完,客户端自然就看到了(不用通知谁)。
关键代码:
lua
-- 独立成一个模块: server/weather.lua
local Lighting = game:GetService("LightingService")
local TimerService = game:GetService("TimerService")
local skyList = {
Enums.PresetSkyTemplate.DreamGalaxy,
Enums.PresetSkyTemplate.AuroraBorealis,
}
local sky = nil
TimerService:CreateTimer(-1, 3, false, function() -- 每 3 秒换一次
if sky then sky:Destroy() end -- 旧的先销毁, 不然越堆越多
sky = game:CreateUnit("PresetSky", {
Parent = Lighting, -- 预置天空挂在 Lighting 下
Template = skyList[math.random(1, #skyList)], -- 模板从 Enums.PresetSkyTemplate 里取
})
end)lua
-- main.lua 里加载它(第 07 课学的 require)
local Weather = require("server.weather")练到的点:模块拆分(require)+ 计时器 + "场景物件不用通知"。
模块化与可复用系统:专题「可复用系统与管理器」 | 天空与光照:LightingService
练习二:一整片田(用代码复制)
思路:一块地做一次,用循环复制出 3×3——前面写的种植逻辑一行都不用改,因为它从事件里拿地块(interact.Parent),不是写死某一块。
关键代码:
lua
local World = game:GetService("World")
local function CreateFarm()
local base = World:FindFirstChild("地块1") -- 拿第 01 课那块地当模板
if base == nil then return end
for i = 1, 3 do
for k = 1, 3 do
if not (i == 1 and k == 1) then -- 原来那块留着
local plot = base:Clone() -- 只为其余八个位置复制
plot.Position = base.Position + Vector3.New(4 * (i - 1), 0, (k - 1) * 4)
plot.Parent = base.Parent -- 挂回去才出现在场景里
end
end
end
end
CreateFarm()练到的点:Clone() + Parent + 循环 + "名字与结构统一,代码才复用得上"。
场景树与查找:专题「找到场景对象并管理父子关系」
3. 还想再改,从哪儿下手
| 方向 | 怎么做 | 用到什么 |
|---|---|---|
| 更多作物 | 多种果实预设,种的时候按种类选一个;不同作物成长时间不同 | 预设 + config.lua |
| 第二档稀有度 | 再加一个特效:1% 出"彩虹",分数 ×10 | 第 06 课的掷骰逻辑 |
| 天气更丰富 | 白天 / 夜晚 × 晴 / 雨 / 雷暴,让天空和概率一起变 | 练习一 + Enums.PresetSkyTemplate |
| 看守与栅栏 | 加一个站桩的人形生物 + 能撞倒的物理栅栏 | 触发区域 + 物理组件 |
| 计分板 | 把第 05 课那行分数换成你自己的榜(本局榜、累计榜……) | RemoteEvent.sell |
| 存档 | 把"这一局"的分数留下来,下次进来还在 | 存档相关能力(要自己查) |
| 对象脚本版 | 把 farm.lua 里的逻辑拆到各个物件上(像放置区那样) | 第 05 课的物件脚本 |
改造时照旧守两条:
- 判定放服务端(客户端只说"我想做什么")
- 数据放对地方:跟物件走的进物件属性,本局分数记在玩家属性;需要跨对局保存时另接持久存储
4. 这个做法能撑多大:规模与边界
现在地图小、人也少,但有几件事值得提前知道,免得东西一多就出怪现象。
会有多少个"计时器"
| 东西 | 数量 | 说明 |
|---|---|---|
| 生长的计时器 | 每株作物一个 | 种下才创建,长完就结束(不是常驻) |
| 进度条的计时器 | 每个客户端各一个 | 挂在本地,用来走进度条——别人看不到 |
| 天气的计时器(练习一) | 一个 | 循环跑,全场共用 |
算一下:3×3 = 9 块地全种上 → 服务端 9 个生长计时器;你自己看到的进度条计时器最多同时几个(取决于你同时种几株)。这个量级完全没问题。
但要留个心:计时器不是"免费的背景音乐"。如果以后做成几百块地、几百个人一起种,就该换个思路(比如"每秒统一检查一遍"),而不是"每株各开一个计时器"。
三条边界(踩过才知道)
| 边界 | 会发生什么 | 怎么办 |
|---|---|---|
| 物件销毁了,计时器回调还会被调用 | 作物提前被销毁,生长回调仍可能运行 | 回调中先确认作物仍有效、地块仍处于生长状态;课程现有计数判断只决定何时收尾,不能代替生命周期检查 |
| 玩家退出,服务端计时器还在跑 | 他种的那株还在长,回调里却可能拿到已经失效的玩家 | 回调需要用到玩家时重新确认其有效性;第 05 课结算时对 GetPlayerFromCharacter 返回值的判空只保护那次结算 |
| 无限循环的计时器停不下来 | 练习一那种 -1 的天气循环会一直跑 | 需要停的时候查一下 TimerService 的停止接口(以官方文档为准),别让它在不需要时继续跑 |
这三条的共同点是:计时器只负责"到点叫我一声",叫醒之后该不该干活,得自己判断。
5. 以后想加人,要改这几处
课程一直说"判定写在服务端,以后加人不用重写"——具体落到地上是这 6 处:
| # | 改哪儿 | 怎么改 | 在第几课学过 |
|---|---|---|---|
| 1 | 让更多人能进来 | 摆多个出生点,或改出生点容量 | 01 课的出生点 |
| 2 | 地块要认主 | 给地块加 owner 属性,点击分支先判"有没有主" | 06 课的属性写法 |
| 3 | 只让主人种自己的地 | 上面那条判完再走种植流程 | 06 课的还原/状态 |
| 4 | 分算给搬果实的人 | 已经有了:果实身上的 持有者 → GetPlayerFromCharacter | 05 课 |
| 5 | 该谁知道的告诉谁 | 现在的 FireClient 只发给本人;需要别人也看到时,再加广播 | 04 课的通道 |
| 6 | 人走了要收尾 | 退出时释放他的地(清 owner、把互动装回去、状态复位),并通知别人 | 06 课的还原四步 |
没有一处需要推倒重来——都是"加判断、加通道、加清理",这也是前面坚持"判定在服务端、数据放对地方"换来的。
6. 知识点串一遍
这一路其实就围绕几件事在转:
| 主题 | 在哪学的 | 一句话 |
|---|---|---|
| 场景与层级 | 01 | 东西要挂在 World 下才"在场景里";能力靠挂子组件加上去 |
| 数据放哪儿 | 03 / 05 / 06 | 跟物件走的进属性(持有者、变异),跟人走的进玩家属性(score) |
| 意图与判定 | 02 / 05 | 点击只是"意图",算不算数由服务端说 |
| 预设与实例 | 03 | 做一份存成预设,用的时候取一份出来(CreateAsset 给的是数组) |
| 时间与节奏 | 03 / 04 | 到点做事用一次性计时器;分步变化用多次计时器;进度条本机自己走 |
| 界面挂哪儿 | 04 / 05 | 属于物件的挂场景 UI,属于玩家的挂屏幕 UI |
| 通知范围 | 04 / 05 | 场景物件不用通知;数据才要通知,而且要挑范围(给谁) |
| 复用与还原 | 05 / 06 | Parent = nil 是"收起来"不是"删掉";用完还原状态才能反复用 |
| 模块化 | 练习一 | 独立的一块放一个文件,用 require 加载;共享的放 common/ |
| 批量处理 | 练习二 | 用循环一次处理一整片(Clone + 遍历) |
| 概率 | 06 | 掷骰在服务端;数值进配置表;标记跟着物件走 |
7. 三个"如果"(自测)
能答上来,说明这一路的东西真的吃进去了:
- 如果不挂
Parent,创建出来的果实会怎样?(→ 02/03:不在场景里,看不到也抓不到) - 如果进度条改成服务端每秒同步,会有什么问题?(→ 04:费带宽、网络一卡就跳格)
- 如果一块地种完不还原状态,会怎样?(→ 06:只能种一次;互动按钮回不来)
保存进度
- 下一步:没有下一课了。挑上面任一方向继续;改乱了就用完整工程包覆盖。
- 通过标准:跑得通、改得动、说得清。
API:World、TimerService、RemoteEvent。
