游戏开发中古老的思想是认为,游戏是程序和数据来构成的,程序加载数据,并根据当前游戏的各种“状态”来调用对应的代码分支,由对应的代码分支来控制数据的使用,重要的数据之一就是动画。具体表现为,在游戏开发中对于动画会大量的使用状态机。
我们先看古老游戏的动画系统,在后面我们再讨论虚幻4的动画……
一个古老的游戏动画库伪码大概是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Class 动画数据
{
void
创建(动画数据文件路径)
void
释放()
void
播放()
void
绘制()
...
}
Class 动画播放器
//状态机
{
void
创建(动画数据句柄)
void
释放()
状态码 获得当前动画状态()
void
切换动画状态(状态码)
void
更新动画()
void
绘制()
void
绑定回调(...)
...
}
|
然后我们在实际使用的时候,找个游戏做例子,比如:
老板说:我们抄一个《炸弹人》
根据需求,我们的主角需要如下动画资源:
角色向上行走动画
角色向下行走动画
角色向左行走动画
角色向右行走动画(可由镜像获得 )
角色死亡动画
这已经把动画素材的需求量减到最小了,至少需要这么多资源
然后我们来实现主角类
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Class 主角
{
void
创建()
{
动画播放器句柄 = 动画播放器.创建(主角的动画数据句柄)
}
void
更新()
void
绘制()
动画播放器句柄
...
}
|
在更新函数中,我们使用状态机来控制分支代码,分支结构有各种各样的写法:函数指针组,if-else,switch-case,状态模式,等等……
用最傻的写法switch-case实现主角::更新()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
//需要我们添加一些状态码来记录角色状态,这也是一个状态机——角色的状态机
Class 主角
{
角色朝向枚举
{
上
下
左
右
}
角色动作枚举
{
站立
行走
死亡
}
角色当前动作状态码
角色当前朝向状态码
}
void
主角::更新()
{
switch
(角色当前动作状态码)
case
站立:
站立的处理输入()
//可能会触发从站立到移动切换、埋雷
//站立状态没有动画刷新
case
移动:
移动的处理输入()
//可能会触发从移动到站立切换、埋雷
移动的逻辑刷新()
//比如:处理角色位移(可能有碰撞)
移动的刷新动画数据()
//根据当前角色当前朝向状态码刷新移动动画
case
死亡:
//死亡状态不接受输入
//死亡状态没有逻辑刷新
死亡的刷新动画数据()
//刷新死亡动画
}
void
主角::绘制()
{
动画播放器句柄.绘制()
}
|
最后,我们在游戏主循环,刷新角色
1
2
3
4
5
6
7
8
|
游戏循环(
true
)
{
主角句柄.更新()
...
主角句柄.绘制()
...
}
|
然后我们就可以执行游戏来测试了,代码似乎也算清晰可用。
然而,完全不是那么回事
角色的状态很复杂,而且角色状态和动画状态都不是完全对应
每个状态下都要处理,输入,逻辑更新,动画更新,绘制,这些都用函数封装,这些不同层次的代码,混在一起显得很乱,别人不好读,要脑补很多东西
没有使用OO的特点,每个角色状态实际上是不同的对象来驱动,用函数既没有类,函数也含有副作用,这几乎没有实现复用
以上代码,对于简单的游戏可以,但是随着游戏需求越来越复杂,最后代码会变得一团乱麻
也许你会想到,我们重构代码来解决这个问题,然而真的能做到吗?我们下篇再讲——
本文转自 老G 51CTO博客,原文链接:http://blog.51cto.com/goldlion/1767606,如需转载请自行联系原作者