ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Phaser 4 Sprite 动画完整指南:从 AnimationManager 到 AnimationState 的实战全解

Phaser 4 Sprite 动画完整指南:从 AnimationManager 到 AnimationState 的实战全解 Phaser 4 Sprite 动画完整指南从 AnimationManager 到 AnimationState 的实战全解【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser本篇技术指南围绕 Phaser 4 的 Sprite 动画系统展开系统讲解动画系统的全局管理器AnimationManager与精灵级播放状态AnimationState的双层架构、精灵表Spritesheet与图集Atlas两种帧生成方式、播放/暂停/链式/反向/混音等全部控制手段以及动画事件与帧回调的完整事件流。读完本篇你将能独立完成从资源加载、动画定义、播放控制到帧级逻辑接入的完整工作流并理解其底层实现原理。Phaser 的动画系统由两个核心对象构成全局单例AnimationManager负责动画的创建与存储与每个精灵自带的AnimationState负责单个精灵的播放控制。这套系统支持精灵表、图集、Aseprite 导出等多种帧来源并提供链式播放、混音过渡、随机起始帧、时间缩放、JSON 导入导出等丰富能力是 2D 游戏角色动作、特效反馈、场景动画的通用基础设施。Quick Start5 步跑通一个精灵动画一个完整的精灵动画流程分为「加载资源 → 定义动画 → 播放动画」三个阶段全部在 Scene 的生命周期方法内完成// 1. 在 preload 中加载精灵表spritesheet this.load.spritesheet(explosion, explosion.png, { frameWidth: 64, frameHeight: 64 }); // 2. 在 create 中定义一个全局动画 this.anims.create({ key: explode, frames: this.anims.generateFrameNumbers(explosion, { start: 0, end: 11 }), frameRate: 24, repeat: 0 }); // 3. 创建精灵并播放 const sprite this.add.sprite(400, 300, explosion); sprite.play(explode);this.load.spritesheet将一张包含多帧的图片按frameWidth/frameHeight切成若干帧generateFrameNumbers从该精灵表中取出第 011 帧共 12 帧生成帧序列sprite.play(explode)实际上是sprite.anims.play()的简写见下文「Sprite 简写方法」。核心概念AnimationManager 与 AnimationState 的双层架构动画系统在架构上分为「定义」与「播放」两个层面理解二者的分工是掌握整个系统的前提。维度AnimationManagerAnimationState访问方式this.animsScene 内或this.game.animssprite.anims作用范围全局 —— 跨所有 Scene 共享每个精灵独立实例职责创建 / 存储动画定义生成帧序列控制单个游戏对象上的播放类Phaser.Animations.AnimationManagerPhaser.Animations.AnimationStateAnimationManager由 Game 实例持有是全局单例在其上注册的动画在所有 Scene 中均可用AnimationState则挂载在每个 Sprite 上只负责该对象自身的播放。二者的实例化与职责划分可以在源码中直接印证AnimationManager.js 的类注释明确说明它是「由 Game 实例拥有的全局系统跨 Scene 持久存在」第 19-37 行AnimationState.js 则描述为「挂载在拥有该组件的游戏对象如 Sprite上的实例」并通过parent.scene.sys.anims引用全局管理器第 45-68 行。一个Animation是由若干AnimationFrame对象按顺序组成的序列附带帧率、循环、延迟等时序数据。它通过this.anims.create(config)全局或sprite.anims.create(config)局部于单个精灵创建。从源码看Animation在初始化时会通过getFrames()解析帧配置、链接前后帧、计算每帧的归一化进度progress见 Animation.js 第 395-504 行每个AnimationFrame持有纹理引用、索引、前后帧链接与进度数据见 AnimationFrame.js。局部动画 vs 全局动画当调用sprite.anims.play(key)时播放器会先在精灵的局部动画表中查找该 key找不到再回退到全局 AnimationManager。因此局部动画可用于「只属于这个精灵」的个性化表现全局动画则适合所有精灵共享的通用动作。// 全局动画 —— 所有精灵可用 this.anims.create({ key: walk, frames: player_walk, frameRate: 12, repeat: -1 }); // 局部动画 —— 只存在于当前精灵 sprite.anims.create({ key: walk, frames: npc_walk, frameRate: 10, repeat: -1 }); // 这里播放的是局部版本因为局部优先级更高 sprite.play(walk);常见模式从精灵表与图集生成帧精灵表动画generateFrameNumbers精灵表是「一张大图、均匀切帧」的纹理帧以数字索引标识使用generateFrameNumbers生成this.load.spritesheet(dude, dude.png, { frameWidth: 32, frameHeight: 48 }); // 全部帧第 07 帧 this.anims.create({ key: run, frames: this.anims.generateFrameNumbers(dude, { start: 0, end: 7 }), frameRate: 10, repeat: -1 }); // 自定义帧序列不连续时使用 frames 数组 this.anims.create({ key: idle, frames: this.anims.generateFrameNumbers(dude, { frames: [0, 1, 2, 1] }), frameRate: 6, repeat: -1 });generateFrameNumbers配置项start默认0—— 起始帧索引end默认-1表示最后一张—— 结束帧索引first—— 在序列开头额外插入的单个帧frames—— 显式帧索引数组覆盖 start/end从源码看当end -1时实现会用texture.frameTotal - 2计算末帧注释明确说明「-1 是因为__BASE帧不需要出现在结果中且帧从 0 开始编号」见 AnimationManager.js 第 770-779 行。若某帧在纹理中不存在会打印警告并跳过同一函数第 782-794 行。图集动画generateFrameNames纹理图集Texture Atlas的帧以字符串命名如ruby_0001使用generateFrameNames生成this.load.atlas(gems, gems.png, gems.json); this.anims.create({ key: ruby_sparkle, frames: this.anims.generateFrameNames(gems, { prefix: ruby_, start: 1, end: 6, zeroPad: 4 // 生成 ruby_0001 ~ ruby_0006 }), frameRate: 12, repeat: -1 });generateFrameNames配置项prefix—— 帧编号前追加的前缀suffix—— 帧编号后追加的后缀start/end—— 数字范围zeroPad—— 数字左侧补零到指定位数frames—— 显式帧编号数组覆盖 start/end如果以generateFrameNames(key)且不传 config则返回图集中的所有帧源码见 AnimationManager.js 第 658-669 行未传 config 时直接调用texture.getFrameNames()遍历全部帧。补零逻辑由Pad(frames[i], zeroPad, 0, 1)实现第 679 行。字符串作为 frames直接传一个纹理 key 字符串作为frames将使用该纹理的全部帧默认按数字排序设置sortFrames: false可关闭排序this.anims.create({ key: walk, frames: player_walk, frameRate: 12, repeat: -1 });源码中Animation.getFrames()对字符串类型的frames会取出纹理的所有帧名并通过SortByDigits按数字值排序受sortFrames控制见 Animation.js 第 406-432 行。Yoyo 与循环this.anims.create({ key: pulse, frames: this.anims.generateFrameNumbers(orb, { start: 0, end: 5 }), frameRate: 10, yoyo: true, // 正向播放完后反向播放 repeat: -1, // -1 无限循环 repeatDelay: 500 // 每次循环周期之间的暂停毫秒数 });当yoyo为 true 时动画播放到末尾后反向回到起点正反两个方向合起来算一次播放。yoyo 的底层逻辑在 Animation.js 的nextFrame/handleYoyoFrame中实现第 546-621 行帧到达序列末尾时先判断 yoyo再判断 repeatCounter最后才进入 complete。链式播放Chainingsprite.play(attack); sprite.chain(idle); // attack 播完后播 idle sprite.chain([fall, land, idle]); // 一次链入多个 sprite.anims.chain(); // 清空链式队列链式播放是逐精灵的每个精灵独立维护一个nextAnimsQueue队列见 AnimationState.js 第 153-159 行动画在animationcomplete或animationstop时从队列头部弹出下一个动画继续播放第 1339 行、1377 行。repeat: -1的动画永远不会触发 complete因此需要用stop()来触发后续链。反向播放// 从最后一帧播到第一帧 sprite.playReverse(walk); // 播放中途反向 sprite.anims.reverse();playReverse会设置forward false且inReverse true对应 AnimationState.js 第 954 行起的playReverse实现reverse()方法则在播放中途切换方向。播放变体sprite.play(walk, true); // ignoreIfPlaying true已在播放则跳过 sprite.anims.playAfterDelay(walk, 1000); // 延迟 1 秒后播放 sprite.anims.playAfterRepeat(walk, 2); // 当前动画再重复 2 次后播放动画混音Mixing混音为「两个特定动画之间」添加过渡延迟设置于全局的 AnimationManager 上this.anims.addMix(idle, walk, 200); this.anims.addMix(walk, idle, 300); sprite.play(idle); sprite.play(walk); // 自动应用 200ms 混音延迟 this.anims.removeMix(idle, walk); // 移除指定配对 this.anims.removeMix(idle); // 移除所有与 idle 相关的混音混音只在使用sprite.play()时生效playAfterDelay/playAfterRepeat会绕过混音。混音以「A → B 的延迟」形式存储在AnimationManager的mixesMap 中源码见 AnimationManager.js 第 172-280 行addMix/removeMix/getMix自 3.50.0 引入。从AnimationState.play的实现看播放 B 时若存在 mix 值会改写为playAfterDelay(key, mix)见 AnimationState.js 第 881 行。暂停、恢复与停止sprite.anims.pause(); // 暂停单个精灵 sprite.anims.resume(); // 恢复单个精灵 this.anims.pauseAll(); // 全局暂停所有动画 this.anims.resumeAll(); // 全局恢复所有动画 sprite.anims.stop(); // 立即停止 sprite.anims.stopAfterDelay(2000); // 2 秒后停止 sprite.anims.stopAfterRepeat(1); // 再重复 1 次后停止 sprite.anims.stopOnFrame(frame); // 到达指定帧时停止所有 stop 类方法触发的是animationstop而非animationcomplete且会触发链式队列中后续动画的播放。pauseAll/resumeAll通过设置管理器的paused标志并广播PAUSE_ALL/RESUME_ALL事件实现源码见 AnimationManager.js 第 867-877 行与 1011-1021 行。动画事件与帧级回调动画事件是接入「动作结束时销毁」「某帧触发攻击判定」等游戏逻辑的标准方式sprite.on(animationcomplete, (anim, frame, gameObject, frameKey) { console.log(completed:, anim.key); }); // 带 key 的完成事件 —— 只在指定动画完成时触发 sprite.on(animationcomplete-explode, (anim, frame, gameObject, frameKey) { gameObject.destroy(); });可用事件animationstart、animationcomplete、animationcomplete-{key}、animationupdate、animationstop、animationrepeat、animationrestart。所有回调签名一致(animation, frame, gameObject, frameKey)。事件的完整生命周期在源码的事件常量注释中有明确记载见 ANIMATION_START_EVENT.js 的「animation event flow」animationstart—— 延迟结束后、第一次更新前animationupdate—— 每次换帧animationrepeat—— 每次循环周期animationcomplete—— 自然结束有限次循环animationcomplete-{key}—— 同上但事件名带动画 key 后缀手动停止时触发animationstop而非 complete播放中途重启则触发animationrestart。值得注意事件文档提示若动画播放速度超过游戏帧率一帧游戏帧内可能触发多次animationupdate见 ANIMATION_UPDATE_EVENT.js实现判定逻辑时应注意这一点。通过 animationupdate 实现帧级回调sprite.on(animationupdate, (anim, frame, gameObject, frameKey) { if (anim.key attack frame.index 4) { this.checkHit(gameObject); // 在第 5 帧index 4触发攻击判定 } });frame.index从 1 开始编号见 AnimationFrame.js 第 65 行的index属性及 Animation.js 第 402 行的初始化var index 1因此frame.index 4对应序列中第 4 帧下标 3。单帧时长控制可以为单个帧单独指定duration毫秒它会叠加在基础 msPerFrame 之上this.anims.create({ key: combo, frames: [ { key: fighter, frame: punch1, duration: 50 }, { key: fighter, frame: kick, duration: 200 }, // 保持更久 { key: fighter, frame: recover, duration: 100 } ], frameRate: 24 });帧级 duration 存储在AnimationFrame.duration中AnimationFrame.js 第 120-128 行由Animation.getFrames在构建时读取Animation.js 第 465 行并参与getFirstTick/getNextTick的下一次换帧时间计算第 360-366、514-519 行。可见性控制、随机起始帧与时间缩放// 可见性控制 this.anims.create({ key: appear, frames: sparkle, frameRate: 12, showOnStart: true, // 动画开始延迟结束后时设置 sprite.visible true hideOnComplete: true, // 动画完成时设置 sprite.visible false showBeforeDelay: true // 延迟期间也立即显示第一帧 }); // 随机起始帧 —— 每个精灵从不同帧开始 this.anims.create({ key: ambient, frames: fire, frameRate: 10, repeat: -1, randomFrame: true }); // 时间缩放 —— 单精灵或全局速度控制 sprite.anims.timeScale 2; // 2 倍速 sprite.play({ key: walk, timeScale: 0.5 }); // 通过配置半速播放 this.anims.globalTimeScale 0.5; // 影响所有动画randomFrame在播放时通过Between(0, totalFrames - 1)随机选取起始帧见 AnimationState.js 第 626-628 行timeScale与globalTimeScale共同作用于时间累加器this.accumulator delta * this.timeScale * this.animationManager.globalTimeScale第 1507 行globalTimeScale默认 1AnimationManager.js 第 83 行。错峰播放Staggered Playbackconst enemies this.add.group({ key: enemy, repeat: 9 }); this.anims.staggerPlay(walk, enemies.getChildren(), 100); // 每个精灵比前一个晚 100ms 开始传 staggerFirst: false 可跳过第一个精灵的延迟staggerPlay的底层实现为对每个子对象调用playAfterDelay(key, time)时间按stagger * i递增负值则反向递减源码见 AnimationManager.js 第 944-969 行。JSON 导出与导入// 导出全部全局动画为 JSON const data this.anims.toJSON(); // 从 JSON 导入传 true 表示先清空现有动画 this.anims.fromJSON(data); this.anims.fromJSON(data, true); // 创建前先检查避免重复创建警告 if (!this.anims.exists(walk)) { this.anims.create({ key: walk, frames: player_walk, frameRate: 12, repeat: -1 }); }toJSON输出的对象包含anims数组与globalTimeScaleAnimationManager.js 第 1034-1054 行fromJSON接受字符串或对象支持「多动画数组」与「单动画对象」两种结构并在clearCurrentAnimations为 true 时先执行anims.clear()第 557-593 行。exists直接查询内部 Map第 324-327 行。运行时修改动画帧const anim this.anims.get(walk); // 在末尾追加帧 anim.addFrame(this.anims.generateFrameNumbers(player, { start: 8, end: 10 })); // 在指定索引插入帧 anim.addFrameAt(this.anims.generateFrameNumbers(player, { frames: [5] }), 2); // 移除指定帧对象 const frame anim.frames[3]; anim.removeFrame(frame); // 按索引移除帧 anim.removeFrameAt(0);增删帧后updateFrameSequence会重建所有帧的index、isFirst/isLast、progress以及前后帧链接Animation.js 第 838-885 行因此运行时修改是安全的。注意这些是全局操作所有正在使用该动画的精灵都会受影响。Aseprite 支持this.load.aseprite(paladin, paladin.png, paladin.json); // 在 create 中 this.anims.createFromAseprite(paladin); // 创建全部标签动画 this.anims.createFromAseprite(paladin, [walk]); // 只创建指定标签 sprite.play(walk); // 按标签名播放createFromAseprite读取 Aseprite JSON 中meta.frameTags定义的标签每个标签生成一个动画标签名即动画 key区分大小写支持direction: forward / reverse / pingpong其中 pingpong 会映射为yoyo: true帧级duration会被完整保留AnimationManager.js 第 406-496 行自 3.50.0 引入。若加载的 JSON 数据不存在会打印警告并返回空数组第 414-417 行。配置参考AnimationConfig 与 PlayAnimationConfigAnimationConfig用于this.anims.create()属性类型默认值说明keystring--动画唯一标识framesstring 或 AnimationFrame[][]纹理 key 字符串使用全部帧或帧配置对象数组sortFramesbooleantrue使用字符串 key 时按数字排序defaultTextureKeystringnull帧未单独指定纹理时的回退纹理 keyframeRatenumber24播放帧率帧/秒duration为 null 时生效durationnumbernull动画总时长毫秒设置后由此推导 frameRateskipMissedFramesbooleantrue掉帧滞后时跳过帧delaynumber0播放开始前的延迟毫秒repeatnumber0首次播放后重复次数-1 无限repeatDelaynumber0每次重复前的延迟毫秒yoyobooleanfalse重复前反向播放回起点showBeforeDelaybooleanfalse延迟期间立即显示第一帧showOnStartbooleanfalse动画开始时设置 visibletruehideOnCompletebooleanfalse动画完成时设置 visiblefalserandomFramebooleanfalse从随机帧开始播放PlayAnimationConfig用于sprite.play()包含 AnimationConfig 的全部时序属性另外追加属性类型默认值说明keystring 或 Animation--要播放的动画 key 或实例startFramenumber0播放起始帧索引timeScalenumber1本次播放的速度倍率PlayAnimationConfig 中的值只对本次播放实例生效覆盖动画定义中的对应值。startFrame超出总帧数时会被重置为 0AnimationState.js 第 619-623 行。Duration 与 FrameRate 的优先级两者都为 null默认 24fps。只设置durationframeRate 按总帧数 / (duration / 1000)计算。设置了frameRate即使同时设置了 durationframeRate 优先duration 按(总帧数 / frameRate) * 1000推导。这条规则的实现位于 Animation.js 的calculateDuration第 253-279 行并在其类注释中写明「如果设置了frameRate它将覆盖duration」。事件总览精灵事件流animationstart—— 延迟结束后、第一次更新前animationupdate—— 每次换帧animationrepeat—— 每次循环animationcomplete—— 自然结束有限循环animationcomplete-{key}—— 同上事件名带动画 key手动停止触发animationstop而非 complete播放中途重启触发animationrestart。所有回调签名(animation, frame, gameObject, frameKey)。AnimationManager 事件监听于this.animsaddanimation、removeanimation、pauseall、resumeall。这些事件由 AnimationManager.js 中的add/create第 307、535 行、remove第 991 行、pauseAll/resumeAll第 873、1017 行分别派发事件常量定义于 events/index.js。API 速查表AnimationManagerthis.anims方法说明create(config)创建并注册全局动画remove(key)按 key 移除全局动画get(key)按 key 获取 Animation 实例exists(key)检查 key 是否已注册generateFrameNumbers(key, config)从精灵表生成帧数组generateFrameNames(key, config)从图集生成帧数组play(key, children)在多个游戏对象上播放动画staggerPlay(key, children, stagger)在多个对象上错峰播放pauseAll()/resumeAll()全局暂停 / 恢复所有动画addMix(animA, animB, delay)设置两个动画间的过渡延迟removeMix(animA, animB?)移除混音配对getMix(animA, animB)获取两个动画间的混音延迟createFromAseprite(key, tags?, target?)从 Aseprite JSON 创建动画toJSON()导出全部动画为 JSONfromJSON(data, clear?)从 JSON 加载动画传true先清空AnimationStatesprite.anims方法说明play(key, ignoreIfPlaying?)播放动画playReverse(key, ignoreIfPlaying?)反向播放动画playAfterDelay(key, delay)延迟指定毫秒后播放playAfterRepeat(key, repeatCount?)当前动画重复 N 次后播放chain(key)排队在当前动画之后播放的动画stop()立即停止stopAfterDelay(delay)延迟指定毫秒后停止stopAfterRepeat(repeatCount?)再重复 N 次后停止stopOnFrame(frame)到达指定帧时停止pause(atFrame?)暂停播放resume(fromFrame?)恢复播放restart(includeDelay?, resetRepeats?)从头重启reverse()播放中途反向getName()获取当前动画 keygetFrameName()获取当前帧 keygetProgress()获取 0-1 的播放进度setProgress(value)设置 0-1 的播放进度setRepeat(value)播放中修改重复次数getTotalFrames()获取总帧数create(config)在当前精灵上创建局部动画exists(key)检查局部动画是否存在get(key)按 key 获取局部动画关键属性isPlaying、hasStarted、currentAnim、currentFrame、forward、inReverse、timeScale。Sprite 简写方法sprite.play()、sprite.playReverse()、sprite.chain()、sprite.stop()均为对sprite.anims.*的包装日常编码可直接使用简写形式。易踩坑清单Gotchas动画默认是全局的。this.anims.create()注册的动画跨 Scene 共享。不要在每一个 Scene 中重复创建 —— 重复创建会打印警告并返回已存在的实例见 AnimationManager.js 第 519-544 行create的 key 查重逻辑与第 294-310 行add的警告。repeat: -1永远不会触发animationcomplete。结束无限循环动画请用stop()并监听animationstop。frameRate优先于duration。两者都设置时 frameRate 生效要精确控制总时长请只设置durationframeRate 留空。单帧duration是叠加的。它加在基础 msPerFrame 之上而非替换。play()会停止当前动画触发animationstop。希望「已在播放则跳过」时使用play(key, true)。混音只对play()生效。playAfterDelay/playAfterRepeat会绕过混音。局部动画覆盖全局动画。同一 key 在精灵局部 Map 中优先。Sprite 简写方法sprite.play()、playReverse()、chain()、stop()都只是sprite.anims.*的包装。链式动画在 stop 后也会触发。若不想停止后继续播放后续动画先调用sprite.anims.chain()清空队列。generateFrameNumbers的end-1表示最后一帧。__BASE帧会被自动排除。源码文件地图文件职责src/animations/AnimationManager.js全局单例 —— create/remove/get、generateFrame*、混音、错峰播放src/animations/Animation.js动画定义 —— 帧序列、时序、yoyo/重复逻辑src/animations/AnimationState.js精灵级组件 —— play/stop/pause/chain/事件src/animations/AnimationFrame.js单帧数据 —— textureKey、textureFrame、duration、progresssrc/animations/events/index.js全部动画事件常量src/animations/typedefs/Animation.jsAnimationConfig 类型定义src/animations/typedefs/PlayAnimationConfig.jsPlayAnimationConfig 类型定义src/animations/typedefs/GenerateFrameNumbers.jsgenerateFrameNumbers 配置类型src/animations/typedefs/GenerateFrameNames.jsgenerateFrameNames 配置类型对应测试位于 tests/animations/Animation.test.js、AnimationFrame.test.js、AnimationManager.test.js、AnimationState.test.js覆盖了动画状态机的播放、暂停、链式、事件派发等核心行为可作为理解各 API 语义的补充示例。类型声明可查阅 types/phaser.d.ts 中Phaser.Animations命名空间。【免费下载链接】phaserPhaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.项目地址: https://gitcode.com/gh_mirrors/ph/phaser创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表