news 2026/10/26 10:31:49

Cocos Creator抓娃娃机物理交互实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cocos Creator抓娃娃机物理交互实现指南

简介:这是一份基于Cocos Creator引擎与TypeScript开发的抓娃娃小游戏完整示例,面向游戏开发初学者及Cocos Creator进阶学习者,帮助理解2D物理交互、动画控制、UI事件响应与单机逻辑架构等核心实践问题。资源包共992个文件,涵盖225个PNG图像资源(含角色、背景、UI素材)、43个Prefab预制体(定义爪子、娃娃、场景节点等可复用对象)、321个meta元数据文件(支撑Cocos资源管理)、289个JSON配置(如动画参数、关卡数据),以及6个TS脚本文件(承载主控逻辑、碰撞判定与状态管理),整体压缩包仅7.72MB,轻量易学。已有1121人学习下载。读者可直接运行调试,深入掌握Cocos Creator中Anim动画系统驱动爪子运动、物理刚体模拟抓取反馈、TS强类型定义游戏实体、本地存档与后端接口预留设计等关键能力,是理论结合实操的优质入门级项目范例。

1. 抓娃娃机逻辑跑通只要 3 分钟:Cocos Creator + TypeScript 实现物理抓取、奖品掉落、状态反馈全链路

你有没有试过在 Cocos Creator 里写一个「看起来像真机器」的抓娃娃 Demo?不是只放个动画按钮,而是让爪子真的受力下落、碰到娃娃会反弹、夹住后悬停 1.2 秒再判定是否成功、失败时娃娃弹跳复位、成功时从出口滑出——这些细节,90% 的新手卡在物理刚体配置和状态机切换上,反复改RigidBody2D的gravityScale和linearDamping却始终手感发飘。这个项目就是为解决这个问题而生:它用纯 TypeScript 在 Cocos Creator 3.8+ 环境下实现了完整抓娃娃机交互闭环,包含爪子运动控制、碰撞检测、奖品物理模拟、状态提示(“夹住了!”/“差一点!”)、以及可扩展的奖品池管理。适合刚学完 Cocos 官方物理文档、想拿一个有真实反馈的小项目练手的开发者;也适合需要快速嵌入小游戏模块的中台团队——它不依赖任何第三方插件,所有逻辑集中在ClawMachineController.ts和PrizeItem.ts两个脚本里,删掉资源文件夹就能直接塞进你的主工程。


2. 工程结构与核心模块拆解:从场景搭建到脚本职责划分

2.1 场景层级与关键节点命名规范

整个 Demo 基于 Cocos Creator 3.8.2 构建(兼容 3.7+),使用 2D 物理系统(BuiltinPhysics2D),场景结构极简但职责清晰:

Canvas ├── Background (Sprite) ├── MachineBody (Sprite + BoxCollider2D) ├── ClawArm (Node) │ ├── ClawBase (Sprite + RigidBody2D: Static) │ └── ClawJaw (Sprite + RigidBody2D: Dynamic, gravityScale=0) ├── PrizeArea (Node) │ ├── PrizePool (Node) // 所有奖品预制体实例的父节点 │ └── ExitChute (Sprite + BoxCollider2D: Trigger) ├── UI │ ├── BtnStart (Button) │ ├── TxtStatus (Label) │ └── PanelResult (Node + Sprite) └── GameController (Empty Node + ClawMachineController.ts)

提示:ClawJaw必须设为Dynamic刚体且gravityScale = 0,否则下落过程会被重力持续加速,失去“电机驱动”的可控感;ClawBase设为Static是为了固定旋转轴心,避免爪臂整体漂移。

2.2 核心脚本职责与通信方式

项目仅含 3 个自定义脚本,全部使用 TypeScript 编写,无任何any类型,类型定义完整:

脚本名所属节点核心职责通信方式
ClawMachineController.tsGameController主控状态机:空闲→启动→下落→夹紧→提升→判定→复位;监听按钮、管理爪子运动、触发碰撞逻辑、更新 UI直接引用ClawJaw、PrizePool、TxtStatus节点;通过emit('claw:grabbed')发布事件
PrizeItem.ts每个奖品预制体根节点奖品个体行为:响应碰撞、播放掉落动画、进入出口后销毁、失败时复位监听claw:grabbed事件;调用this.node.emit('prize:reset')触发自身复位
ExitChuteTrigger.tsExitChute节点出口区域触发器:检测奖品进入,触发成功逻辑onTriggerEnter2D回调中判断otherCollider.node.name === 'PrizeItem'

所有状态流转均通过this.state枚举管理,而非布尔变量堆砌,避免状态歧义:

// ClawMachineController.ts enum ClawState { IDLE = 'idle', DROPPING = 'dropping', GRIPPING = 'gripping', LIFTING = 'lifting', JUDGING = 'judging', RESET = 'reset' } @ccclass('ClawMachineController') export class ClawMachineController extends Component { @property({ type: Node }) clawJaw: Node = null; @property({ type: Node }) prizePool: Node = null; @property({ type: Label }) txtStatus: Label = null; private state: ClawState = ClawState.IDLE; private rigidBody: RigidBody2D = null; private gripTimer: number = 0; start() { this.rigidBody = this.clawJaw.getComponent(RigidBody2D); // 绑定按钮点击 this.node.parent.getChildByName('UI').getChildByName('BtnStart').getComponent(Button).clickEvents.push({ target: this.node, component: 'ClawMachineController', handler: 'onStartClick' }); } onStartClick() { if (this.state !== ClawState.IDLE) return; this.setState(ClawState.DROPPING); } setState(newState: ClawState) { this.state = newState; switch (newState) { case ClawState.DROPPING: this.startDropping(); break; case ClawState.GRIPPING: this.startGripping(); break; case ClawState.LIFTING: this.startLifting(); break; case ClawState.JUDGING: this.startJudging(); break; case ClawState.RESET: this.resetClaw(); break; } } }

这段代码的关键在于:状态变更必须走setState()统一入口,而不是散落在各处this.state = ...。我当年在一个线上项目里因为漏改某处状态赋值,导致爪子在GRIPPING状态下还能被再次点击启动,最终物理刚体叠加上千次力导致崩溃——从此养成了强制封装状态变更的习惯。

2.3 物理参数配置表:为什么这些数值能“手感对”

抓娃娃机的真实感,70% 来自物理参数的微调。本项目已验证有效的参数组合如下(基于Pixels per Unit = 100的默认设置):

参数项节点推荐值作用说明修改影响
gravityScaleClawJaw刚体0关闭重力,由代码控制线性速度下落设为1→ 爪子自由落体,无法精准停在目标高度
linearDampingClawJaw刚体5阻尼系数,抑制下落末段抖动<3→ 爪子触底反弹明显;>8→ 下落迟滞,像在糖浆里移动
angularDampingClawJaw刚体10抑制旋转抖动,防止爪子歪斜不设 → 夹取时易因碰撞扭矩发生不可控翻转
density所有PrizeItem刚体0.3奖品质量,影响碰撞响应强度>0.5→ 爪子夹住后易被拖拽偏移;<0.1→ 碰撞像打棉花,无反馈
frictionPrizeItem刚体0.4奖品与底板摩擦力0→ 奖品滑行过远,难进出口;>0.6→ 卡在角落不动

注意:所有奖品预制体必须启用Auto Simulation,否则onCollisionEnter2D不会触发;ClawJaw的碰撞体建议用PolygonCollider2D包裹爪形,比BoxCollider2D更准。


3. 核心交互逻辑实现:从按下按钮到奖品滑出的完整链路

3.1 爪子下落与夹紧:用LinearVelocity替代applyForce

很多新手习惯用applyForce给爪子加力下落,结果发现速度越来越快、停不准。本项目采用更可控的linearVelocity直接赋值法,配合Time.deltaTime实现匀速下落:

// ClawMachineController.ts private startDropping() { const targetY = -120; // 目标下落 Y 坐标(像素) const speed = 80; // 像素/秒 this.schedule(() => { const currentY = this.clawJaw.position.y; if (currentY <= targetY) { this.clawJaw.setPosition(this.clawJaw.position.x, targetY); this.setState(ClawState.GRIPPING); this.unscheduleAllCallbacks(); return; } // 计算本次位移:speed * deltaTime const deltaMove = speed * dt; this.clawJaw.setPosition( this.clawJaw.position.x, currentY - deltaMove ); }, 0, macro.REPEAT_FOREVER, 0); }

这里dt是macro.REPEAT_FOREVER循环中每次执行的deltaTime,等价于cc.game.step的时间增量。不用applyForce的根本原因:力是加速度的积分,而我们只需要精确控制位置和速度;用linearVelocity反而容易因帧率波动导致位移跳变。

3.2 夹紧判定与碰撞过滤:只响应“爪子-奖品”有效碰撞

爪子夹紧时,需检测是否真正接触到奖品。但onCollisionEnter2D会同时触发爪子与机器外壳、底板等无效碰撞,必须过滤:

// ClawMachineController.ts onCollisionEnter2D(event: CollisionEvent2D) { const selfCollider = event.selfCollider; const otherCollider = event.otherCollider; // 只处理爪子(ClawJaw)与奖品(PrizeItem)的碰撞 if ( selfCollider.node.name === 'ClawJaw' && otherCollider.node.name === 'PrizeItem' && this.state === ClawState.GRIPPING ) { // 获取奖品脚本 const prizeScript = otherCollider.node.getComponent(PrizeItem); if (prizeScript && !prizeScript.isGrabbed) { prizeScript.onGrabbed(); // 触发奖品被抓逻辑 this.txtStatus.string = '夹住了!'; this.setState(ClawState.LIFTING); } } }

PrizeItem.ts中的isGrabbed是关键标志位,防止同一奖品被多次夹取:

// PrizeItem.ts @property({ type: Boolean }) isGrabbed: boolean = false; onGrabbed() { this.isGrabbed = true; this.node.getComponent(RigidBody2D).linearVelocity = v2(0, 0); // 立即停止运动 this.node.getComponent(RigidBody2D).gravityScale = 0; // 关闭重力,随爪子上升 }

3.3 提升与判定:用计时器替代帧数硬编码

提升阶段需保持爪子夹住奖品上升 1.2 秒,然后松开并判定是否进入出口。绝不能写for (let i = 0; i < 72; i++)这种帧数循环——不同设备帧率不同,会导致时长偏差。正确做法是用scheduleOnce配合dt累加:

// ClawMachineController.ts private startLifting() { const liftSpeed = 60; // 像素/秒 const liftDuration = 1.2; // 秒 let elapsed = 0; this.schedule(() => { elapsed += dt; const currentY = this.clawJaw.position.y; const newY = currentY + liftSpeed * dt; this.clawJaw.setPosition(this.clawJaw.position.x, newY); if (elapsed >= liftDuration) { this.clawJaw.setPosition(this.clawJaw.position.x, currentY); // 精确停在当前Y this.setState(ClawState.JUDGING); this.unscheduleAllCallbacks(); } }, 0, macro.REPEAT_FOREVER, 0); } private startJudging() { // 松开爪子:恢复奖品重力,允许其自由下落 const prizes = this.prizePool.children; for (const prize of prizes) { const prizeScript = prize.getComponent(PrizeItem); if (prizeScript?.isGrabbed) { prizeScript.release(); // PrizeItem.ts 中定义 prizeScript.isGrabbed = false; } } this.setState(ClawState.RESET); }

PrizeItem.release()内部会重置gravityScale并施加一个向下的初速度,模拟“松开瞬间的惯性下坠”。


4. 常见问题排查:5 个真实踩坑记录与血泪修复方案

4.1 现象:爪子下落时抖动严重,像在抽搐

原因:ClawJaw刚体的linearDamping过低(<3),且未关闭allowSleep,导致物理引擎频繁唤醒/休眠。
解决:将ClawJaw刚体的linearDamping设为5,allowSleep设为false;下落逻辑改用setPosition而非applyForce。

4.2 现象:点击开始按钮没反应,控制台无报错

原因:BtnStart按钮的clickEvents未正确绑定到GameController节点,或GameController脚本未挂载到场景节点。
解决:检查GameController节点是否在Canvas下;在start()中添加console.log('Claw controller initialized')确认脚本加载;手动在编辑器中重新拖拽Button.clickEvents的target到GameController节点。

4.3 现象:奖品被夹住后,爪子提升时奖品脱离爪子飞出去

原因:PrizeItem的RigidBody2DgravityScale未在onGrabbed()中设为0,导致提升过程中重力持续向下拉。
解决:在PrizeItem.onGrabbed()中增加this.getComponent(RigidBody2D).gravityScale = 0;,并在release()中恢复为1。

4.4 现象:多个奖品堆叠时,爪子只夹到最上面一个,下面的没响应

原因:PrizeItem预制体的Collider2D层级(Layer)与ClawJaw不在同一物理层,或PhysicsSystem2D.instance.enable = false。
解决:确认PhysicsSystem2D.instance.enable为true;在Project Settings > Physics中确保ClawJaw和PrizeItem的 Collider 层级勾选了相互碰撞(Collision Matrix)。

4.5 现象:奖品滑入ExitChute后没触发成功逻辑,控制台报otherCollider.node is null

原因:ExitChuteTrigger.ts的onTriggerEnter2D回调中,otherCollider.node在某些帧已被销毁(如奖品刚进入就触发销毁逻辑)。
解决:在回调开头加防御性判断:

onTriggerEnter2D(event: TriggerEvent2D) { if (!event.otherCollider || !event.otherCollider.node) return; if (event.otherCollider.node.name !== 'PrizeItem') return; // 后续逻辑... }

5. 二次开发实战:3 种低成本扩展方案与验证技巧

5.1 方案一:接入真实奖品池(JSON 配置驱动)

当前奖品是静态预制体,实际项目需动态加载。只需两步改造:

第一步:定义奖品配置 JSON

// resources/prizes.json { "items": [ { "id": "bear_001", "prefabPath": "prefabs/PrizeBear", "weight": 85, "name": "毛绒熊" }, { "id": "robot_002", "prefabPath": "prefabs/PrizeRobot", "weight": 10, "name": "机械臂模型" } ] }

第二步:修改ClawMachineController.start()加载逻辑

start() { // ...原有初始化 this.loadPrizeConfig(); } private async loadPrizeConfig() { const config = await resources.load('prizes', JsonAsset); const prizeData = config.json.items; // 按 weight 权重随机选取一个奖品 const totalWeight = prizeData.reduce((sum, p) => sum + p.weight, 0); let rand = Math.random() * totalWeight; let selected: any = null; for (const item of prizeData) { rand -= item.weight; if (rand <= 0) { selected = item; break; } } if (selected) { const prefab = await resources.load(selected.prefabPath, Prefab); const node = instantiate(prefab); node.setParent(this.prizePool); node.setPosition(v3(0, 0, 0)); } }

验证技巧:在loadPrizeConfig()后加console.table(prizeData),运行时打开浏览器控制台,一眼看清加载了哪些奖品及权重分布。

5.2 方案二:增加“夹取力度”可视化反馈

用户需要感知操作强度。可在 UI 上加一个力度条,随按钮按压时长增长:

// ClawMachineController.ts @property({ type: ProgressBar }) pbPower: ProgressBar = null; private powerLevel: number = 0; private isHolding: boolean = false; onStartClick() { if (this.state !== ClawState.IDLE) return; this.isHolding = true; this.powerLevel = 0; this.pbPower.progress = 0; this.schedule(() => { if (!this.isHolding) return; this.powerLevel = Math.min(1, this.powerLevel + 0.02); this.pbPower.progress = this.powerLevel; }, 0.05); } // Button 的 press events 中绑定 onButtonPress,release events 中绑定 onButtonRelease onButtonRelease() { this.isHolding = false; this.unscheduleAllCallbacks(); // 根据 powerLevel 决定夹紧力度:powerLevel 越高,grip duration 越长 const gripDuration = 0.8 + (this.powerLevel * 0.6); // 0.8~1.4 秒 this.gripTimer = gripDuration; this.setState(ClawState.GRIPPING); }

血泪经验:力度条必须用ProgressBar而非Sprite.fillRange,后者在 WebGL 下存在渲染闪烁;schedule间隔设为0.05是为兼顾低端机帧率(20fps)与反馈灵敏度。

5.3 方案三:对接后台统计(埋点上报)

上线前必加埋点。在ClawMachineController中注入轻量上报方法:

// ClawMachineController.ts private reportEvent(eventType: string, data: Record<string, any> = {}) { // 使用原生 fetch,不引入 axios 等依赖 fetch('https://your-api.com/log', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ eventType, timestamp: Date.now(), scene: 'claw-machine', ...data }) }).catch(err => console.warn('Log failed:', err)); } // 在关键节点调用 setState(newState: ClawState) { this.reportEvent('state_change', { from: this.state, to: newState }); // ...原有逻辑 } onGrabbed() { this.reportEvent('prize_grabbed', { prizeId: this.node.name, gripDuration: this.gripTimer }); }

验证技巧:本地调试时把fetch替换为console.log,上线前全局搜索reportEvent确保无遗漏;所有上报字段必须小驼峰命名,与后台协议对齐。

从那以后我每次做交互类 Demo,都会在start()里加一行console.time('init'),在onLoad()结尾加console.timeEnd('init'),再配合performance.now()测关键路径耗时——不是为了炫技,是怕哪天用户反馈“点一下要等两秒”,而你连瓶颈在哪都不知道。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 10:31:42

电光调制器仿真:从物理链路到高频设计的MATLAB实践

简介&#xff1a;这份资源面向光学与通信方向的学习者及工程实践者&#xff0c;提供一套基于MATLAB的电光调制器性能仿真工具&#xff0c;用于解决调制特性曲线难以直观验证的问题。输入半波电压、偏置电压与调制电压后&#xff0c;即可输出电光调制特性曲线&#xff0c;并通过…

作者头像 李华
网站建设 2026/10/26 10:31:35

MATLAB卷积神经网络手写数字识别例程拆解与避坑

简介&#xff1a;面向深度学习入门者与MATLAB用户的卷积神经网络手写数字识别例程包&#xff0c;围绕手写数字任务给出从数据加载、网络搭建到训练测试的完整实现&#xff0c;帮助读者在MATLAB环境中理解卷积、池化、Softmax分类及反向传播等关键环节&#xff0c;适合课程实验、…

作者头像 李华
网站建设 2026/10/26 10:30:55

SamsungBIOS固件更新实战:SMBIOS校验、BitLocker挂起与批量运维

简介&#xff1a;面向需要排查三星笔记本 BIOS/UEFI 故障、寻找旧版固件或研究官方更新机制的用户&#xff0c;整理了一套围绕三星 BIOS 下载与恢复的工具和资料合集。三星长期隐藏 BIOS 下载入口&#xff0c;SW Update 自动更新也存在失败报告&#xff0c;旧版本更难获取&…

作者头像 李华
网站建设 2026/10/26 10:30:15

Python预测模型工程实战:从解压到二次开发的完整指南

简介&#xff1a;这份资源面向具备一定Python基础、希望系统入门预测建模的数据分析与机器学习学习者&#xff0c;围绕数据预处理、特征选择、模型选择、训练验证、正则化与调参、模型评估及部署等环节&#xff0c;梳理从原始数据到可用预测模型的完整知识脉络&#xff0c;帮助…

作者头像 李华
网站建设 2026/10/26 10:29:55

ONVIF Java SDK网络摄像头接入实战:发现、鉴权、取流与PTZ

简介&#xff1a;这份 ONVIF JAVA SDK 面向需要在 Java 项目中接入网络摄像头的开发者&#xff0c;适合已具备一定 Java 与网络编程基础、希望快速打通设备发现、媒体与 PTZ 等 ONVIF 能力的中级读者。它提供较完整的协议实现代码&#xff0c;Demo 侧重核心流程演示&#xff0c…

作者头像 李华
网站建设 2026/10/26 10:28:42

AI文献工具如何提升科研效率:8款工具全解析

1. 科研写作的AI革命&#xff1a;文献综述工具如何改变学术游戏规则作为一名在高校指导本科生科研多年的导师&#xff0c;我亲眼见证了学生们在文献综述环节消耗的时间占比——通常超过整个科研周期的40%。直到去年开始系统测试各类AI文献工具&#xff0c;才发现这个领域的技术…

作者头像 李华