Skip to content

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分算给搬果实的人已经有了:果实身上的 持有者 → GetPlayerFromCharacter05 课
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 / 06Parent = nil 是"收起来"不是"删掉";用完还原状态才能反复用
模块化练习一独立的一块放一个文件,用 require 加载;共享的放 common/
批量处理练习二用循环一次处理一整片(Clone + 遍历)
概率06掷骰在服务端;数值进配置表;标记跟着物件走

7. 三个"如果"(自测) ​

能答上来,说明这一路的东西真的吃进去了:

  1. 如果不挂 Parent,创建出来的果实会怎样?(→ 02/03:不在场景里,看不到也抓不到)
  2. 如果进度条改成服务端每秒同步,会有什么问题?(→ 04:费带宽、网络一卡就跳格)
  3. 如果一块地种完不还原状态,会怎样?(→ 06:只能种一次;互动按钮回不来)

保存进度 ​

  • 下一步:没有下一课了。挑上面任一方向继续;改乱了就用完整工程包覆盖。
  • 通过标准:跑得通、改得动、说得清。

API:World、TimerService、RemoteEvent。