角色动作练习:jumping/crouch
这周研究了一下跳跃动作与下蹲动作,并简单学习了 Lua 语言和 Love2d 框架。并在实践中逐一验证自己的想法。
¶跳跃
几乎没有哪一款 platform game 是不能跳跃的。它被视为游戏角色的基本动作之一。但是不同的游戏对跳跃的实现方式也是不一样的。
在 2D 游戏中,最简单的跳跃动作只需要展现跃起的那个动作即可(也就是只有一帧),如最早期的玛丽兄弟、Maplestory 等。这在表现上不会产生太多不适,所以大部分游戏都这么做。但是我想如果能表现得更丰富一些就好了,如果能把角色起跳、腾空、下落、着陆全都展示出来应该会很不错吧。于是我准备了一些素材进行尝试:
- 起跳
- 腾空
- 下落
- 着陆
- 演示
把这些动画片段通过有限状态机连接起来,很容易就得到想要的效果:
但是在测试中发现,把所有动作都展示出来并不是很好的主意。当角色需要连跳的时候,每次都来一遍「着陆」再「起跳」动作非常不流畅。于是作了如下修改:
- 「站立/Idle」时跳起,先切换到「起跳/prepareJump」动作,随后「腾空/Jumping」;
- 「走动/Walking」或「跑动/Running」时跳起,直接「腾空/Jumping」;
- 落地时速度超过一定阈值才会切换到「着陆/Landed」动作,一般连跳时不会触发。
经过这几个修订后,效果比较令人满意。
¶蹲下
我暂还没想好做「蹲下」有什么用,并不是所有的游戏都有这个设定。但是如果要做冒险类的游戏,蹲下搞不好可以捡回一条命。
- 蹲下
- 站起
- 演示
关于「蹲下/Crouch」这个动作,可以有很多展开,例如可以接上一段 Sliding Attack 之类的攻击动作,或者来一个 Higher Jumping 等等。此外,蹲下也意味着 hitbox 的面积减小。如果使用物理引擎的话,需要适应调整角色的碰撞检测框——一种简单的做法是将角色的碰撞框一分为二,当蹲下的时候,上部的碰撞框失效即可。
¶动作状态机
在上一篇 角色动作练习:idle/walk/run 的演示中,我使用了简单的 if/elseif/else 来实现动作的切换。但是当我加入更多的动作的时候,这种方式已经明显难以维护了。所以我引入了一套简单的「动作状态机」来实现人物控制和动作切换。
¶状态
所谓状态,就是角色动作的一些简单划分,例如「待命/Idle」,「走/Walking」,「跑/Running」,「跳/Jumping」等。每个状态提供三个接口:
state = {}
state.enter = function(owner)
-- do something
end
state.update = function(owner)
-- do something
end
state.exit = function(owner)
-- do something
end
每个状态都是单例,状态类本身并不储状态量,而是间接使用 owner 。当切换至一个新状态时,会触发旧状态的 exit 事件,以及新状态的 enter 事件。之后的每一帧都会触发当前状态的 update 事件。可以将大部分自动切换的状态转换逻辑至于 update 中。
¶状态机
每个角色拥有一个或多个「状态机」实例,「状态机」用于维护状态间的切换。对于角色控制,一个「状态机」或许不太够用,考虑这样的场景:
玩家在「待命」、「走动」、「跑动」时按下「space」键时,切换到「跳起」。
这部分的逻辑基本是相同的,如果把它们分散在每个状态的代码中,就不得不 Copy/Paste 。基于 DRY 原则,如果我们使用另一个状态机(或者使用层次状态机),将角色的「环境状态」也提炼出来,那样会更加灵活方便。只要增加两个状态:「在地上/OnTheGround」和「在空中/InTheAir」即可。于是规则就变成了:
玩家「在地上」时按下「space」键时,动作状态切换到「跳起」。
¶To be continue
由于现在还没有开始地图编辑器的探索之旅,现在现在角色还处在虚空世界中,无法接收「事件/Event」,一个完整的「状态机」除了能够自己进行状态切换以外,还能处理外界发送的信息进行状态切换。这部分的内容将在之后慢慢摸索。
关于人物动作,我还想了解一下「飞檐走壁/Wall Jumping」的做法,可能是对忍者游戏的痴迷吧,Haha
Update 20150202
《大型多人在线游戏开发》(2.7 使用并行状态机来创建可信角色)章节中提到了分层状态机及跨层阻止(Cross-Layer Blocking),值得一看。