主题
02 搭好场地,让碰撞有回应
先让人物推得动球,再用服务端碰撞事件只识别球体撞方块。
这课要看到的结果是:你把球推向方块后,输出窗口出现一条命中日志。它还不会在画面上弹字,也不会自动把球搬回去。
开始前检查
从第 01 课工程继续。先按场景搭建指南完成地面、出生点、球体和方块的属性设置,再进入代码步骤。
text
World
├── 大地板
├── 出生点
├── 球体 WorldUnit,动态(Dynamic)
└── 方块 WorldUnit,运动学(Kinematic)
“球体”“方块”必须是 World 的直接子节点,名称不要有多余空格;模型容器不能代替本例的 WorldUnit。
在右侧属性面板中,核对两者的物理关键开关:
- 球体:物理类型设为 动态(Dynamic),开启物理、开启碰撞、开启碰撞事件,质量建议设为 1。本教程球体资源的原点位于模型底部,Y=0 即正确落位;只有换用原点不同的素材时才需要调整 Y,使球体刚好贴住地板表面。
- 方块:物理类型设为 运动学(Kinematic),开启碰撞与碰撞事件。
1. 先只验证场景
文件:沿用两端 main.lua,这一步不修改脚本。操作:试玩,控制蛋仔从球后方推它,确认球能向方块移动;随后停止试玩。
预期:人物站得住、球推得动、球和方块初始不重叠。
球推不动时的速查清单:
- 物理类型:必须是 动态(Dynamic);若误选为静态(Static),球体就如大地板一样固定在世界中无法被推动。
- 高度 Y:本教程球体原点位于模型底部,Y=0 为正确落位,不会半埋;只有换用原点不同的素材导致球体陷入地板时,才调整 Y 使其刚好贴住地板表面。
- 质量 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 已让脚本提前结束。把名字恢复“球体”,再重新试玩验证恢复。
保存进度
- 下载第 02 课完整代码;恢复地图配置时仍需按搭建指南操作。
- 通过标准:人物能推动球,球撞方块有命中日志,撞地面与人物直接撞方块不误报。
- 下一课:每次命中只算一次。
API:World、Unit、WorldUnit、Connection。深读:事件与计时。
