ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Cocos Creator跑酷动画实战:状态机与动作切换源码解析

Cocos Creator跑酷动画实战:状态机与动作切换源码解析 简介本资源是Cocos Creator实战教程第二篇的配套源码包面向正在学习2D游戏开发、希望掌握跑酷类游戏动画与动作系统的初中级开发者。内容围绕角色动画创建、动画控制器状态切换、关键帧事件触发以及行为树驱动的寻路、追逐、躲避等动态行为展开并涉及精灵图集管理、对象池、性能分析工具等优化思路。压缩包共46个文件约6.98MB包含4个anim动画文件、4个js脚本、4个json配置、2个fire场景、4个png与2个jpg贴图以及meta、plist、ts声明等工程辅助文件结构完整可直接导入编辑器运行调试。目前已有132人学习。通过这份源码读者能对照实现角色跑步、跳跃、攻击等动画切换与行为逻辑理解动画控制器与行为树的协作方式并借鉴资源打包与性能优化做法为同类跑酷项目提供可复用的工程参考。1. 天天酷跑式跑酷动画在 Cocos Creator 里到底难在哪跑酷游戏看着简单真动手做才发现坑全在动画和动作衔接上。天天酷跑这类横版跑酷玩家 90% 的时间盯着角色角色只要有一帧滑步、一次落地穿地、一次二段跳动作卡住手感立刻崩。Cocos Creator 实战教程里讲动画、动作相关的部分核心就三件事帧动画怎么切、状态机怎么管、物理位移怎么和动画对齐。这套源码打包的价值不在于代码多长而在于它把「跑、跳、二段跳、滑铲、受击、死亡」这几个动作的切换逻辑和参数配置摊开给你看。适合已经会用 Cocos Creator 拖控件、但对 Animation 组件和状态切换没系统认知的开发者也适合想拿一套能跑的跑酷骨架改自己项目的熟手。下面按「先立住原理、再动手复现、最后避坑」的顺序拆。2. 跑酷动画的底层帧动画、骨骼动画与状态机怎么选2.1 帧动画和骨骼动画在跑酷里的分工天天酷跑这类 2D 横版跑酷角色动作主流是帧动画SpriteFrame 序列不是骨骼动画。原因很直接帧动画每一帧是美术画好的动作表现力可控切换成本低Cocos Creator 里用 Animation 组件挂 SpriteFrame 轨道就能播。骨骼动画Spine / DragonBones适合动作多、需要换装的场景但跑酷角色动作数量固定帧动画更省事。不过帧动画有个硬伤动作之间的过渡帧要美术补否则跑切跳会「闪」。常见做法是给关键动作加 2 到 3 帧过渡或者在代码里用交叉淡化掩盖。我一般会在 Animation 组件上给每个动作单独建 Clip跑是run、跳是jump、二段跳是jump2、滑铲是slide然后用状态机控制播放哪个 Clip。选型判断标准维度帧动画骨骼动画动作数量少而固定多且需组合换装需求无有内存占用帧数×单帧大小骨骼贴图过渡控制需补帧或淡化骨骼插值天然平滑Cocos 接入Animation SpriteFrameSpine/DragonBones 组件跑酷选帧动画是因为动作集合封闭且美术能直接控制每一帧的形变落地反馈更「实」。2.2 状态机跑酷动作切换的中枢动作切换不能靠 if-else 堆。跑酷角色状态至少包括Idle、Run、Jump、DoubleJump、Slide、Hurt、Dead。每个状态有进入条件、退出条件、可播放的 Clip、是否可被打断。用状态机管逻辑才不炸。Cocos Creator 里可以手写一个轻量状态机也可以用cc.StateMachine这类第三方。我倾向手写因为跑酷状态少手写可控。核心结构是当前状态 转移表。转移表用对象描述比如Run - Jump条件是「按下跳跃且在地面」。// 轻量状态机状态定义与转移 const State { IDLE: idle, RUN: run, JUMP: jump, DOUBLE_JUMP: doubleJump, SLIDE: slide, HURT: hurt, DEAD: dead }; // 转移表from - [{ to, condition }] const transitions { [State.IDLE]: [{ to: State.RUN, condition: (ctx) ctx.isMoving }], [State.RUN]: [ { to: State.JUMP, condition: (ctx) ctx.jumpPressed ctx.onGround }, { to: State.SLIDE, condition: (ctx) ctx.slidePressed ctx.onGround }, { to: State.HURT, condition: (ctx) ctx.hurt } ], [State.JUMP]: [ { to: State.DOUBLE_JUMP, condition: (ctx) ctx.jumpPressed !ctx.onGround ctx.canDoubleJump }, { to: State.RUN, condition: (ctx) ctx.onGround } ], [State.DOUBLE_JUMP]: [ { to: State.RUN, condition: (ctx) ctx.onGround } ], [State.SLIDE]: [ { to: State.RUN, condition: (ctx) !ctx.slidePressed || ctx.slideTimeUp } ], [State.HURT]: [ { to: State.RUN, condition: (ctx) ctx.hurtTimeUp }, { to: State.DEAD, condition: (ctx) ctx.hp 0 } ] };这段代码的关键在condition是函数运行时拿上下文判断。ctx里放onGround、jumpPressed、canDoubleJump这些标志由角色控制器每帧更新。参数说明canDoubleJump必须在落地时重置为 true否则二段跳只能用一次slideTimeUp是滑铲计时器防止按住不放一直滑。转移表的好处是加状态只改数据不改逻辑分支。2.3 动画事件与物理位移的对齐跑酷最容易被忽略的是「动画和位移不同步」。角色跑动时动画在播但位移是代码加的两者速度不匹配就滑步。解决办法有两种一是让位移速度等于动画每帧位移量×帧率二是用动画事件在特定帧触发位移变化。Cocos Creator 的 Animation 组件支持添加事件帧Event Frame。比如跳跃动画在起跳帧触发onJumpStart在落地帧触发onLand。代码里监听这些事件在onJumpStart里施加向上的速度在onLand里重置canDoubleJump和onGround。// 监听动画事件对齐物理与动作 const anim this.node.getComponent(cc.Animation); anim.on(jump_start, () { // 起跳帧施加向上速度 this.body.linearVelocity cc.v2(this.body.linearVelocity.x, this.jumpSpeed); this.onGround false; }, this); anim.on(land, () { // 落地帧重置二段跳与地面标志 this.canDoubleJump true; this.onGround true; this.body.linearVelocity cc.v2(this.body.linearVelocity.x, 0); }, this);参数说明jumpSpeed要和跳跃动画的时长匹配跳太高动画播完人还在空中会显得飘跳太低动画没播完就落地会穿地。我一般用「跳跃高度 初速度² / (2×重力)」反推初速度再让动画时长约等于滞空时间。重力在物理系统里设Cocos Creator 2.x 默认是cc.director.getPhysicsManager().gravity3.x 在物理系统配置里改。3. 用 Cocos Creator 把跑酷动作系统跑起来的最小步骤3.1 角色节点与 Animation 组件的搭建先在场景里建角色节点挂 Sprite 和 Animation 组件。Sprite 的 SpriteFrame 先随便给一张Animation 组件负责切 Clip。然后在资源管理器里建 Animation Clip 文件每个动作一个run.anim、jump.anim、jump2.anim、slide.anim、hurt.anim、dead.anim。每个 Clip 里加一条 SpriteFrame 轨道把美术给的序列帧拖进去。注意帧率跑酷跑动一般 12 到 15 帧每秒跳跃 10 到 12 帧滑铲 12 帧左右。帧率太高动作会「快进」太低会卡顿。Cocos Creator 的 Animation Clip 默认采样率是 60要手动改成你想要的帧率或者在 Clip 的sample属性里设。// 播放指定动作 Clip并处理循环与速度 playAction(state) { const clipName this.stateToClip[state]; // 状态到 Clip 名的映射 const clip this.anim.getClips().find(c c.name clipName); if (!clip) return; // 跑动循环跳跃/滑铲不循环 clip.wrapMode (state State.RUN || state State.IDLE) ? cc.WrapMode.Loop : cc.WrapMode.Normal; this.anim.play(clipName); }逻辑说明stateToClip是状态到 Clip 名的映射表比如Run - run。wrapMode控制循环跑和待机循环跳跃和滑铲播一次就停。参数说明cc.WrapMode.Loop是循环cc.WrapMode.Normal是播完停在最后一帧。如果跳跃动画播完还停在空中说明状态没切回 Run检查onGround是否在落地事件里正确置位。3.2 输入处理与状态切换的接线输入用cc.systemEvent监听键盘或者用触摸事件。跑酷一般两个操作跳跃和滑铲。跳跃按下触发jumpPressed滑铲按下触发slidePressed。这些标志每帧传给状态机。// 输入监听跳跃与滑铲 onLoad() { cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this); cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this); } onKeyDown(event) { switch (event.keyCode) { case cc.macro.KEY.space: this.ctx.jumpPressed true; break; case cc.macro.KEY.down: this.ctx.slidePressed true; break; } } onKeyUp(event) { switch (event.keyCode) { case cc.macro.KEY.space: this.ctx.jumpPressed false; break; case cc.macro.KEY.down: this.ctx.slidePressed false; break; } }逻辑说明jumpPressed在按下时置 true松开置 false。状态机里判断jumpPressed onGround才切 Jump避免空中按跳跃直接切。参数说明cc.macro.KEY.space是空格键cc.macro.KEY.down是下方向键。如果要支持触摸把 KEY_DOWN 换成 TOUCH_START判断触摸区域是上半屏还是下半屏。3.3 二段跳与滑铲的边界处理二段跳的边界是「只能跳一次」。实现上canDoubleJump初始 true第一次跳后置 false落地事件里重置 true。滑铲的边界是「滑铲时不能跳」状态机里 Slide 状态不接受 Jump 转移即可。// 每帧更新上下文驱动状态机 update(dt) { // 更新地面检测 this.ctx.onGround this.checkGround(); // 更新滑铲计时 if (this.currentState State.SLIDE) { this.slideTimer dt; this.ctx.slideTimeUp this.slideTimer this.slideDuration; } else { this.slideTimer 0; this.ctx.slideTimeUp false; } // 更新受伤计时 if (this.currentState State.HURT) { this.hurtTimer dt; this.ctx.hurtTimeUp this.hurtTimer this.hurtDuration; } // 跑状态机 this.stateMachine.tick(this.ctx); }逻辑说明checkGround()用射线或碰撞检测判断是否在地面。slideDuration是滑铲持续时间一般 0.5 到 0.8 秒。hurtDuration是受击硬直一般 0.3 到 0.5 秒。参数说明slideTimer和hurtTimer在状态切换时要清零否则下次进状态计时会接着上次的走。这个坑我踩过滑铲第二次直接秒结束查了半天才发现计时器没重置。4. 源码打包里那些让你翻车的动画与动作坑4.1 动作切换闪帧现象是跑切跳时角色闪一下白现象角色从跑切到跳画面闪一帧白或者闪一帧其他动作的帧。原因是 Animation 组件在切换 Clip 时默认会先停当前 Clip 再播新 Clip中间有一帧空档SpriteFrame 被清空或显示默认图。解决在切换前先anim.stop()再anim.play()或者给 Sprite 设一个默认 SpriteFrame 兜底。更稳的做法是用anim.crossFade()做交叉淡化但跑酷动作切换快淡化反而糊我一般直接 stop play 并确保 SpriteFrame 不为空。4.2 二段跳失效现象是空中按第二下没反应现象第一次跳正常空中再按跳跃没反应。原因通常是canDoubleJump没在落地时重置或者状态机里 DoubleJump 的转移条件写成了onGround。检查两点落地事件里有没有this.canDoubleJump true转移表里Jump - DoubleJump的条件是不是jumpPressed !onGround canDoubleJump。还有一个隐蔽原因jumpPressed在按下时置 true但状态机每帧 tick如果同一帧内先切了 Jump 又判断 DoubleJump会误触发。解决是在状态切换后把jumpPressed消费掉置 false。4.3 滑铲穿地现象是滑铲时角色陷进地面现象滑铲动作播放时角色碰撞体下移陷进地面。原因是滑铲动画里角色蹲下Sprite 的锚点或碰撞体没跟着调。解决给滑铲单独设一个碰撞体或者用动画事件在滑铲开始时缩小碰撞体、结束时恢复。Cocos Creator 里可以在 Animation Clip 里加一条碰撞体尺寸的轨道但更简单的是代码里在进入 Slide 状态时改cc.BoxCollider的 size。4.4 动画事件不触发现象是监听的回调没执行现象Animation 组件上加了事件帧代码里anim.on(jump_start, ...)没反应。原因有三个事件帧的名字和监听的名字不一致事件帧加在了错误的 Clip 上Animation 组件没在onLoad里注册监听。排查顺序先看 Clip 里事件帧的 Function 名再看代码里on的第一个参数是否完全一致大小写敏感最后确认监听注册在play之前。我习惯在onLoad里统一注册所有动画事件避免播到一半才注册漏掉。4.5 帧动画内存爆现象是动作多了之后加载慢、内存高现象动作 Clip 一多游戏启动加载慢运行时内存高。原因是每个 SpriteFrame 都是独立纹理帧数多纹理就多。解决把同一角色的序列帧打成图集AtlasCocos Creator 里用自动图集或 TexturePacker。图集能把多张帧合并成一张纹理减少 DrawCall 和内存碎片。参数上图集尺寸别超过 2048×2048否则低端机可能不支持。另外不常用的动作死亡、受击可以延迟加载别一开始全塞进内存。5. 让跑酷动作更跟手的三个进阶技巧5.1 用输入缓冲让跳跃不丢帧跑酷玩家经常在落地前几帧按跳跃如果状态机严格判断onGround这一下就丢了手感很「粘」。解决办法是输入缓冲按下跳跃时记录时间戳如果在落地后 100 到 150 毫秒内仍然执行跳跃。实现上在ctx里加jumpBufferTime落地时检查当前时间 - jumpBufferTime bufferWindow。// 输入缓冲落地前按跳落地后仍生效 onKeyDown(event) { if (event.keyCode cc.macro.KEY.space) { this.ctx.jumpPressed true; this.ctx.jumpBufferTime performance.now(); // 记录按下时刻 } } // 落地事件里检查缓冲 anim.on(land, () { this.canDoubleJump true; this.onGround true; const now performance.now(); if (now - this.ctx.jumpBufferTime 150) { // 缓冲窗口内立即起跳 this.ctx.jumpPressed true; } });参数说明150是缓冲窗口毫秒数太小没效果太大角色会「自动跳」。我一般设 120 到 150实测这个区间最跟手。这个技巧在动作游戏里叫「跳跃缓冲」是手感调优的后悔药。5.2 用 Coyote Time 让离地后还能跳和输入缓冲对称的是 Coyote Time角色刚离开平台边缘的几帧内仍然允许跳跃。跑酷里角色跑过平台边缘玩家按跳如果严格判断onGround就跳不了体验很差。实现上在onGround变 false 时记录时间之后 80 到 120 毫秒内仍视为可跳。// Coyote Time离地后短暂仍可跳 update(dt) { const wasOnGround this.ctx.onGround; this.ctx.onGround this.checkGround(); if (wasOnGround !this.ctx.onGround) { this.coyoteTimer 0; // 刚离地开始计时 } if (!this.ctx.onGround) { this.coyoteTimer dt; } this.ctx.canJump this.ctx.onGround || this.coyoteTimer 0.1; // 100ms 内可跳 }参数说明0.1是 Coyote 窗口秒数对应 100 毫秒。这个值和输入缓冲配合跑酷手感会明显「顺」。注意 Coyote Time 只对第一次跳生效二段跳不适用否则空中能连跳。5.3 用动画速度缩放匹配位移速度跑动动画的播放速度和角色实际位移速度要匹配。如果角色加速了动画还按原速播就滑步。解决办法是动态调 Animation 的speed。Cocos Creator 的 AnimationState 有speed属性可以按当前速度 / 基准速度缩放。// 根据位移速度缩放跑动动画速度 updateRunAnimation() { const baseSpeed 300; // 基准速度对应动画原速 const currentSpeed Math.abs(this.body.linearVelocity.x); const scale currentSpeed / baseSpeed; const state this.anim.getAnimationState(run); if (state) { state.speed cc.misc.clampf(scale, 0.5, 2.0); // 限制缩放范围 } }参数说明baseSpeed是动画设计时的基准速度问美术要。clampf把缩放限制在 0.5 到 2.0防止速度极端时动画鬼畜。这个技巧在加速道具、减速陷阱场景里特别有用。我一般会在角色控制器里每帧调一次开销很小。这三个技巧——输入缓冲、Coyote Time、动画速度缩放——是跑酷手感调优里性价比最高的。源码打包里如果没带自己加上去手感提升立竿见影。我自己的习惯是先把状态机和动画事件跑通再逐个加这三个每加一个实测一次别一次全上否则出问题不知道是谁的锅。希望帮到你。本文还有配套的精品资源点击获取
返回列表