Roblox Studio · 第 20 课

模块脚本:把规则装进工具箱

用 ModuleScript 与 require 共享奖励计算,把规则和场景操作分开并写出小测试。

10–15 岁 60 分钟 ModuleScript / require 动手实践 测试与保存

Course overview

把奖励计算写一次,让不同脚本都能使用。

当两个机关都需要“每颗晶石值多少分”的规则,复制代码会产生一个新问题:改了这里,却忘记那里。ModuleScript 可以把共同规则集中起来。

这一课不做复杂背包系统。我们先写一个只计算、不改变场景的模块,再用 assert 检查它的边界。

01 · BEFORE

开始之前

理解函数返回值、table 与条件判断。新建 Lesson20 Baseplate。

02 · MAKE

你的作品

一个 RewardRules 模块与一个调用它的测试 Script。

03 · CHECK

完成标志

0、1、3 颗晶石分别得到 0、10、40 分;非法输入不进入奖励计算。

创建 ModuleScript 的官方 Studio 截图
图 20-1:官方创建模块截图:选择 ModuleScript,注意它与普通 Script 的对象类型不同。Roblox Creator Documentation · CC BY 4.0 · 原图未修改 · 许可说明

Learning route

先准备,再动手;看到结果后,记得保存。

每完成一小步,就测试一次。先说出你预计会看到什么,再用 Play 和 Output 检查,不用急着一次写完所有代码。

1

01 · 创建模块,不是普通 Script

在 ServerScriptService 建立 ModuleScript:RewardRules,再建立普通 Script:RewardDemo。模块不会自己启动,它要被 require 后使用。

2

02 · 模块返回一个装着函数的表

RewardRules 是我们返回给调用者的工具表。rewardFor 接收晶石数量,返回分数,不直接修改 Coins 或场景。

3

03 · require 取回模块的返回值

require 得到的不是模块实例本身,而是模块返回的值。这里返回 table,所以能够调用 Rules.rewardFor(3)。

4

04 · 有意制造一次错误

assert 是“这件事必须成立”的检查,不满足就抛出错误。这里用 pcall 包住故意传入 -1 的实验,能看到失败又不让整段测试突然结束。

5

05 · 把变化集中在一个地方

如果每颗晶石改为 20 分,你应只修改模块中的计算,并同步修改测试的预期。调用者仍然使用同一个函数名。

Step by step

01 · 创建模块,不是普通 Script

在 ServerScriptService 建立 ModuleScript:RewardRules,再建立普通 Script:RewardDemo。模块不会自己启动,它要被 require 后使用。

这里的奖励规则属于服务器,所以模块放在 ServerScriptService。ReplicatedStorage 可以存共享展示代码,但放在那里不等于客户端就变得可信。

  1. 确认两个对象类型分别是 ModuleScript 与 Script。
  2. 删掉模块默认的空表示例内容,替换成下一节的完整代码。
模块脚本位置与对象层级参考
图 20-2:官方模块层级参考:本课 RewardRules 与 RewardDemo 都放在 ServerScriptService,按名称连接。Roblox Creator Documentation · CC BY 4.0 · 原图未修改 · 许可说明

Step by step

02 · 模块返回一个装着函数的表

RewardRules 是我们返回给调用者的工具表。rewardFor 接收晶石数量,返回分数,不直接修改 Coins 或场景。

先验证数字、整数和范围,再计算。三颗的奖励是 3×10,再加 10 分完成奖励,所以是 40。注意 0 颗也应该是合法输入。

  1. 将代码放入 RewardRules。
  2. 找到 return RewardRules,它把工具表交给 require 的调用者。
  3. 读懂 math.floor(count)==count:它检查数量是否为整数。
ServerScriptService > RewardRules(ModuleScript)Luau
local RewardRules = {}

function RewardRules.rewardFor(count)
    assert(typeof(count) == "number", "count must be a number")
    assert(count == count and count >= 0 and count <= 3, "count out of range")
    assert(math.floor(count) == count, "count must be an integer")
    local reward = count * 10
    if count == 3 then reward += 10 end
    return reward
end

return RewardRules

Step by step

03 · require 取回模块的返回值

require 得到的不是模块实例本身,而是模块返回的值。这里返回 table,所以能够调用 Rules.rewardFor(3)。

同一执行环境中重复 require 同一模块通常会得到缓存结果,不会每次从头执行。但服务器与不同客户端不是共享一份运行内存,不要拿模块当跨网络通信工具。

  1. 将下方代码放进 RewardDemo。
  2. Play 后应看到 All reward checks passed。
  3. 若断言失败,Output 会指出对应行;先核对期望值与计算规则。
ServerScriptService > RewardDemo(Script)Luau
local Rules = require(script.Parent:WaitForChild("RewardRules"))
assert(Rules.rewardFor(0) == 0, "Zero crystals")
assert(Rules.rewardFor(1) == 10, "One crystal")
assert(Rules.rewardFor(3) == 40, "Completion bonus")
print("All reward checks passed")

local ok, message = pcall(function()
    return Rules.rewardFor(-1)
end)
print("Negative input accepted?", ok) -- 预期 false
if not ok then warn(message) end

Step by step

04 · 有意制造一次错误

assert 是“这件事必须成立”的检查,不满足就抛出错误。这里用 pcall 包住故意传入 -1 的实验,能看到失败又不让整段测试突然结束。

count == count 看起来奇怪,是为了拒绝特殊数字 NaN,因为 NaN 不等于自己。本课不需要背它的数学来源,只要知道外部输入不能直接相信。

  1. 把无效输入依次换成 1.5 和 "3",两者都应被拒绝。
  2. 不要为了让测试变绿就删掉断言;先决定规则应不应该允许这个值。
  3. 让故意失败的实验保持明确注释,避免误认成未修复错误。

Step by step

05 · 把变化集中在一个地方

如果每颗晶石改为 20 分,你应只修改模块中的计算,并同步修改测试的预期。调用者仍然使用同一个函数名。

模块划分不是越多越好。先把重复且稳定的规则抽出来,不用把每一行都拆成一个文件。

  1. 保持旧版本,修改单颗奖励为 20,再算出三个测试的预期值。
  2. 确认测试失败能指出规则变化,而不是无声通过。
  3. 恢复或保存你选择的奖励版本。

Homework

轮到你来改一改,做出自己的版本。

1 奖励规则表

列出 0、1、2、3 颗对应分数,补上缺少的 2 颗测试。

2 一个入口

另写一个 Demo Script 调用同一模块,不复制奖励公式。

3 选做:参数命名

把单颗奖励和完成奖励提为模块内部常量,让规则更容易读。

完成标准:先在 Playtest 中验证作品,再点击 Stop,保存为“姓名缩写_Lesson20”或对应 Homework 副本;不要覆盖上一课的可运行版本。

Exit questions

先自己回答,再展开看看。

ModuleScript 会像 Script 一样自己启动吗?

不会,它通常需要被 require。

模块最后 return 的是什么?

本课返回装有 rewardFor 函数的 table。

require 能让服务器与客户端共享同一份内存吗?

不能,它不是跨网络通信,执行环境彼此独立。

为什么计算函数不直接加 Coins?

让规则与场景操作分开,便于独立测试和复用。

Explore further

需要查资料时,从这里出发。

正文代码可以直接复制。先确认脚本类型、放置位置,以及这段代码是完整版本还是小实验;不要把同一脚本的多个版本叠加运行。界面参考图已标注来源,图中的对象名可能与本课不同,请以操作步骤为准。