Skip to content

02 搭好场地,让碰撞有回应 ​

先让人物推得动球,再用服务端碰撞事件只识别球体撞方块。

这课要看到的结果是:你把球推向方块后,输出窗口出现一条命中日志。它还不会在画面上弹字,也不会自动把球搬回去。

开始前检查 ​

从第 01 课工程继续。先按场景搭建指南完成地面、出生点、球体和方块的属性设置,再进入代码步骤。

text
World
├── 大地板
├── 出生点
├── 球体   WorldUnit,动态(Dynamic)
└── 方块   WorldUnit,运动学(Kinematic)

编辑器场景层级树与视口

“球体”“方块”必须是 World 的直接子节点,名称不要有多余空格;模型容器不能代替本例的 WorldUnit。

在右侧属性面板中,核对两者的物理关键开关:

  • 球体:物理类型设为 动态(Dynamic),开启物理、开启碰撞、开启碰撞事件,质量建议设为 1。本教程球体资源的原点位于模型底部,Y=0 即正确落位;只有换用原点不同的素材时才需要调整 Y,使球体刚好贴住地板表面。
  • 方块:物理类型设为 运动学(Kinematic),开启碰撞与碰撞事件。

1. 先只验证场景 ​

文件:沿用两端 main.lua,这一步不修改脚本。操作:试玩,控制蛋仔从球后方推它,确认球能向方块移动;随后停止试玩。

预期:人物站得住、球推得动、球和方块初始不重叠。

球推不动时的速查清单:

  1. 物理类型:必须是 动态(Dynamic);若误选为静态(Static),球体就如大地板一样固定在世界中无法被推动。
  2. 高度 Y:本教程球体原点位于模型底部,Y=0 为正确落位,不会半埋;只有换用原点不同的素材导致球体陷入地板时,才调整 Y 使其刚好贴住地板表面。
  3. 质量 Mass:质量过大(如默认 10)会导致推球极为吃力,建议改为 1。

2. 给碰撞连接一段代码 ​

文件:server/main.lua。操作:整文件替换成下面内容。原来的第一句话被本课的“就绪”日志取代,不要把两课代码追加在一起。client/main.lua 暂时保持第 01 课内容即可;下载包中的客户端只替换了提示文字。

lua
-- @runtime server
-- ============================================================================
-- 模块职责:负责服务端推球与方块的基础碰撞监听
-- 核心目标:
--   1. 在 World 根节点下安全检索「球体」与「方块」两个 WorldUnit 实体
--   2. 挂接方块的 OnCollisionEnter 碰撞事件,核实触碰对象必须为球体
--   3. 服务端关闭时自动注销事件连接,防止内存泄漏
-- ============================================================================

-- 1. 获取场景根节点服务 World
local World = game:GetService("World")

-- 2. 检索场景中的关键物体(注意名称需与编辑器层级树完全一致)
local ball = World:FindFirstChild("球体")
local cube = World:FindFirstChild("方块")

-- 3. 安全校验:确认物体存在且具备物理/碰撞所需的基础能力(WorldUnit)
if not ball or not ball:IsA("WorldUnit") then
    print("[推球][ERROR] World 下需要名为「球体」的 WorldUnit")
    return
end
if not cube or not cube:IsA("WorldUnit") then
    print("[推球][ERROR] World 下需要名为「方块」的 WorldUnit")
    return
end

-- 4. 监听方块的碰撞进入事件(OnCollisionEnter)
--    当任意物体碰上方块时触发此回调;otherUnit 为撞击方块的外部实体
local collisionConnection = cube.OnCollisionEnter:Connect(function(otherUnit)
    -- 严格过滤:只有当撞过来的物体确为目标「球体」时才处理(排除玩家或地面碰撞)
    if otherUnit ~= ball then return end
    print("[推球][命中] 球体碰到了方块")
end)

print("[推球][就绪] 已找到球体和方块,等待碰撞")

-- 5. 优雅退出:绑定服务端关闭生命周期,主动注销连接释放资源
game:BindToClose(function()
    collisionConnection:Disconnect()
end)

预期:保存同步并试玩后,先出现 [推球][就绪]。这一句说明球和方块已经找到,监听已经建立。

3. 推球并核对碰撞 ​

文件:无需继续修改。操作:把球推向方块,打开输出窗口查找:

text
[推球][命中] 球体碰到了方块

球只撞地面时不应该出现这句。人物直接撞方块也不应该出现。想再测一次,可以停止并重新试玩,让场景回到编辑时的起始状态;下一课才添加自动复位。

没有“就绪”:先看 ERROR 指出的对象,核对名称→父节点→类型。已经“就绪”但没有“命中”:核对物理和事件开关,再确认球确实碰到方块。短时间出现多条碰撞日志是下一课要处理的问题。

刚才的代码是什么意思 ​

写法对应本课发生的事
local ball = ...把找到的球暂存为名叫 ball 的变量,后面重复使用
game:GetService("World")取得场景根节点
FindFirstChild("球体")按名字寻找直接子节点,没找到时返回 nil
if not ball没找到球就进入错误处理
ball:IsA("WorldUnit")确认对象拥有本例需要的物理能力
Connect(function(otherUnit) ... end)注册一段回调,等碰撞发生时运行;otherUnit 是撞过来的对象
otherUnit ~= ball撞过来的不是球,就不处理

末尾保存并断开 Connection,是让这段监听在服务端关闭时收尾。本课只需保留它;增加更多监听后,我们会统一整理。

自己改一处 ​

文件:server/main.lua。操作:只把查找球的字符串改成“找不到的球”,保存同步并重新试玩。

预测:会不会还出现“就绪”?提示:沿着代码从查找到第一个 return 读一遍。答案:应出现球体缺失的 ERROR,不出现“就绪”和命中日志;return 已让脚本提前结束。把名字恢复“球体”,再重新试玩验证恢复。

保存进度 ​

API:World、Unit、WorldUnit、Connection。深读:事件与计时。