在这里插入图片描述

网罗开发 (小红书、快手、视频号同名)

  大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。

图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG

我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。

展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。


引言

很多开发者第一次做鸿蒙游戏动画时,思路通常是这样的:

角色移动
↓
播放动画

角色攻击
↓
播放动画

角色死亡
↓
播放动画

于是代码慢慢变成:

if (state == "idle") {
  playIdle()
}

if (state == "run") {
  playRun()
}

if (state == "attack") {
  playAttack()
}

游戏初期没什么问题。但随着项目变大:

待机
跑步
攻击
受伤
死亡
技能1
技能2
技能3
Buff动画
特效动画
UI动画

你会发现:

动画越来越多
状态越来越乱
切换越来越复杂

最终出现经典问题:

动画和业务逻辑耦合在一起。

例如:

attack() {

  playAttack()

  enemy.hp -= 100

}

看起来很自然。但实际上:

动画
业务
状态

已经混在一起了。大型游戏最终都会发现:

动画不是特效,而是一个独立系统。

一、动画真正解决的是什么?

很多人认为:

动画 = 好看

实际上:

动画 = 状态变化可视化

例如,角色攻击:

Store变化
↓
enemy.hp减少

玩家看不到。所以需要:

攻击动作
伤害飘字
受击效果

告诉玩家:

世界发生了变化

本质上:

动画是状态变化的翻译器。

二、动画不应该属于业务逻辑

错误写法:

attackEnemy() {

  playAttack()

  enemy.hp -= 100

}

问题:

BattleSystem
依赖动画

耦合严重。

正确结构:

BattleSystem
      ↓
Store变化
      ↓
AnimationSystem
      ↓
播放动画

例如:

battle.attack(store)

修改:

store.enemyHp -= 100

然后:

AnimationSystem

监听变化。

三、动画系统在架构中的位置

前面我们一直讲:

Input
 ↓
System
 ↓
Store
 ↓
HUD
 ↓
UI

动画应该插在哪里?答案:

Input
 ↓
System
 ↓
Store
 ↓
AnimationSystem
 ↓
HUD/UI

因为:

动画来自状态变化

而不是:

按钮点击

四、动画状态机(Animation State Machine)

大型游戏不会这样写:

playIdle()
playRun()
playAttack()

而是:

AnimationStateMachine

管理状态,例如:

Idle
Run
Attack
Hit
Dead

关系:

Idle
 ↓
Run
 ↓
Attack
 ↓
Idle

形成:

状态机

五、定义动画状态

enum AnimationState {

  Idle,

  Run,

  Attack,

  Hit,

  Dead

}

角色保存:

class CharacterStore {

  animation =
    AnimationState.Idle

}

动画状态成为:

Store的一部分

六、动画状态机 Demo

实现:

class AnimationSystem {

  update(
    store: CharacterStore
  ) {

    if (store.hp <= 0) {

      store.animation =
        AnimationState.Dead

      return
    }

  }

}

作用:

状态决定动画

而不是:

动画决定状态

七、为什么状态驱动动画更重要?

错误思路:

攻击
↓
播放动画
↓
修改状态

容易不同步。

正确:

攻击
↓
修改状态
↓
动画自动切换

例如:

store.isAttacking = true

动画系统读取:

isAttacking

自动播放:

Attack Animation

八、动画事件系统

很多动画会触发:

攻击命中
技能爆炸
伤害计算

例如:

挥刀
↓
第8帧
↓
真正造成伤害

不能:

点击就扣血

否则:

动画和表现错位

推荐:

Animation Event

机制。

定义:

class AnimationEvent {

  frame: number

  callback: Function

}

例如:

frame = 8

触发:

battle.hitEnemy()

九、技能动画系统

很多技能:

抬手
施法
释放
结束

并不是一个动作,例如:

FireBall

流程:

Cast
 ↓
Launch
 ↓
Impact

动画系统负责:

阶段切换

而:

BattleSystem

负责:

伤害计算

十、特效系统拆分

很多项目喜欢:

playEffect()

到处调用,最后:

特效失控

推荐独立:

EffectSystem

结构:

AnimationSystem
      ↓
EffectSystem

例如:

攻击动画

触发:

刀光特效

动画和特效分离。

十一、UI 动画也应该系统化

例如:

获得金币
升级
任务完成

很多开发者直接写:

.animate()

在页面里。

项目大了之后:

无法统一管理

推荐:

UIAnimationSystem

负责:

Toast
弹窗
奖励动画

形成:

GameAnimationSystem

UIAnimationSystem

双系统。

十二、AISystem 与动画系统协同

前面讲过:

AISystem

驱动 NPC,例如:

NPC决定攻击

执行:

store.state = Attack

然后:

AnimationSystem

自动切换:

Attack Animation

结构:

AISystem
 ↓
Store
 ↓
AnimationSystem

非常清晰。

十三、动画性能优化

很多项目掉帧并不是:

AI

导致,而是:

动画太多

例如:

100个敌人
100个动画

同时更新。

优化方案:

LOD动画

即:

远距离
简化动画

近距离
完整动画

例如:

if (distance > 500) {

  skipAnimation()

}

大型游戏非常常见。

十四、未来架构:Animation Graph

再往后发展,行为树管理:

AI决策

状态机管理:

动画状态

最终演化:

Animation Graph

结构:

Idle
 ↓
Run
 ↓
Attack
 ↓
Skill
 ↓
Dead

节点可视化连接,这也是现代游戏引擎普遍采用的方案。

十五、一个关键认知升级

初学者认为:

动画 = 播放图片

进阶开发者认为:

动画 = 状态切换

而大型项目最终理解:

动画系统的本质,是把 Store 中的状态变化翻译成玩家能够感知的视觉反馈。

所以真正驱动动画的不是:

按钮

也不是:

页面

而是:

Store

总结

鸿蒙游戏动画系统推荐架构:

Input
 ↓
System
 ↓
Store
 ↓
AnimationSystem
 ↓
EffectSystem
 ↓
HUD/UI

核心模块:

AnimationStateMachine
AnimationEvent
EffectSystem
UIAnimationSystem

核心原则:

状态驱动动画
动画不驱动状态

如果用一句话总结:

优秀的动画系统从来不是“播放动作”,而是让 Store 中的状态变化,以最自然的方式呈现在玩家眼前。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐