« 华丽的桌游:星际争霸 | 返回首页 | 《银河竞逐》第二扩充《叛军对帝国》入手 »

游戏动作感设计初探

最近两年似乎大家一致的想把网络游戏向所谓动作感、打击感的这个方面推进。我想是因为,已经有太多人厌倦了打木桩吧。

我们在三年前就一直在考虑这个问题,但似乎走向了歧路。一个劲的考虑如何解决网络同步问题,怎样在一定网络延迟下保持公平,怎样避免作弊……

结果,尝试了多次后的结论就是,我们依然没有找到正确的方法解决以上问题,暂时把这一块搁置起来。最近,公司又有许多同事热切希望给游戏加入更多的操作感。我们又重拾这个课题。这次,换了个角度看这个问题:假设我们完全不考虑网络这个因素,就算做单机游戏,我们其实也没有太多动作游戏的设计实现经验。那么首先要解决的是如何把游戏的操作手感做出来,之后才应该考虑怎样在网络环境下实现它们。

为此,我纠结了很久。花了很长时间思考,以及编写了大量的代码印证,总算感觉自己的思路稍微清晰了一点。做一下记录,不一定正确,只是写出来或许可以给其他同僚提供一些思路。在内部讨论的过程中,这些也被广泛的质疑,许多人都不太认同。所以,一家之言,辜枉看之。


首先,所谓好的操作感打击感,和显示引擎的关系并不那么密切。如果指望引入物理碰撞系统、布娃娃系统、更好的动作融合模块,就可以一劳永逸的解决问题。让人物格斗以真实物理公式去运作,就能够给玩家爽快的体验,我认为不是正确的解决之道。

从最强调操作感的格斗类游戏来看,玩家的对抗运算,从来都不是建立在所谓物理碰撞等这些系统之上的。从格斗游戏的开山之作街头霸王,大学里,我们寝室间最为流行的真侍魂,到我最喜爱的 VF ,都有简单且严格的游戏规则。设计人员在那些基本规则上建立起平衡的游戏。前段时间我在 google reader 上[读到一篇文章《從快打旋風談格鬥戲》,深以为然。

第二,做为需要操作感的游戏,一定是主要由游戏内在逻辑去驱动动画,而不是由动画驱动逻辑。所以不应该过于依赖画面最终的表现来做任何判定和处理。在以往的回合制游戏中,由于不强调操作,而偏向画面的动作表现,往往在动画数据中插入大量信息(比如关键帧反馈)。这是因为,当我们之关心每个动作的结果时,这样做能减少动作控制和外界的耦合。但,一旦我们希望提供更多的细节控制,即游戏设计的基本元素细于单个完整的格斗动作,再这样做就本末倒置了。

设计人员必定需要把握拆分后的动作片段的细节,才可以掌握最终游戏的平衡。而不是任由美术人员做出华丽的招数,然后根据招数的表现来左右最终的打击判定。

当游戏从 2d 过度到 3d 后,表现力变的更强、灵活度更高,我们的制作人员反而有点束手无策了。不知道该怎么控制 3d 人物动画,还转而去追求所谓物理碰撞等等原本属于渲染的枝节技术,我十分怀疑是否走对了路?当游戏中的动作画面是一帧帧画出来的时候,我们反而可以做出强烈的打击感,这是件没道理的事。看过铁拳、VF 等等 3d 格斗游戏,我们知道,一定是我们没找对方法,而不是 3d 的错。


关于动画动作控制。我曾经在 blog 上探讨过一次。实践过程中,由于设计依旧不够简洁,我们在互动性强的游戏中,去掉了引导动作的设计(因为本来就要做仔细的控制)。复杂的动作片段组合,用有限状态机来实现最好。状态机使得分片的代码可以分开来写,减少了耦合。并且,状态机是用纯数据表达的。如果条件成熟,还可以设计一种 DSL 来描述。不过在研究阶段,我们并没有完全肯定最终怎样表达最好,暂时我是用 lua 的表来描述的。这块数据可以根据游戏的具体动作设计来独立修改,能够交给非专业程序设计人员协同维护。这也使得单一程序源文件不至于过大。(流程数据化后,让数据和算法分离)

状态机的好处还在于,提高了动作控制模块的健壮性。让一些非法的状态切换很早就可以检测出来。减少把代码写成一锅粥的可能性。

比较难处理的是动作中的互动元素。即攻击者和受击者的动作配合问题。这部分逻辑,由攻击者对象管理或受击者对象管理都不自然。比较容易想到的是提到第三个独立模块去处理。另外,3d 或所谓 2.5D (我不喜欢这个不严谨的词) 游戏中,不同于格斗游戏的是,存在多打一的情况。不确定性更多。这些难点,我经过一些思考后,得到的方案是这样:

先抽象出“冲突(Clash)”这个最小战斗单元。一个“冲突”被定义成一对一的一个招式。一个冲突中一定存在一个且只有一个攻击者和一个防御者。冲突过程中,双方总是趋向于面对面,即处于一个一维空间(如果有跳跃的设定,则是 2D 空间)。在这个冲突空间中,双方能做的主要动作是前进和后退。这个前进和后退都可以是主动完成或被动完成的。冲突本身会因为招式,迫使双方保持在合适的距离。因为只有距离合理,才能更真切的表达出打击感。因为保持距离的需求,大多数情况下会逼迫防守者后退。冲突模块同时会不断的督促参与者调整自己的面向,保持正确的冲突空间。

每场局部战斗,在时间轴上都由多场冲突构成。在动作表现上,每个对象只能同时存在于一个冲突中(逻辑上,它还是可以受到多方面而来的伤害)。只有退出一个冲突,才能进入下一个冲突。每个冲突中,只要有一方宣布退出,那么冲突就结束。

最终,我们实现了一个冲突管理器,称为战场。以冲突的固定心跳频率(略低于游戏逻辑)来更新。每次更新时,会根据每个对象实体发出的消息请求以及优先级,来最终配对,构成一场场冲突,逐个处理。

细节不再展开。下面再谈另一个问题。

动作游戏中,我们不再简单的处理人物的位置。允许他们重合站在一个点上。因为位置对动作性非常重要。但是我个人比较排斥所谓堵门的设定。我希望更自然的处理游戏世界中碰撞的问题。

当然,我不会采用任何的所谓物理系统,(物理碰撞系统可以加入游戏,但不是在这里加入)。作为游戏设计者和实现者,我认为无此必要,甚至会画蛇添足。它让我不太容易把握游戏的平衡。我简单的把规则定为:允许对象的坐标重合,但是如有可能,它们都趋向于保持距离。结合上面提过的设定,再加另一条原则:游戏世界的每个对象都尽力完成自己应该做的事情,让这个世界趋向合理。比如:我被人击中会硬直一段时间,或是需要后退。这并不是攻击方强加给我的,而是通过冲突模块建议给我,以及动作控制的状态机建议给我的。然后我尽量按照建议来行动。作为游戏对象,一切事情都是主动完成,不分主动被动。

对象一个游戏对象,它的行动来源可以来至自身的状态机运转、来至于战场模块,来至于环境障碍,来至于环境的位置管理器。最终在一起决策,做出合理的行为。

至于 PC 和 NPC 的区别,仅在于外部对状态机的输入是 AI 还是控制器。


在实现这些东西的时候,我发现虽然我把事情分成了许多小模块逐个实现。但是我一开始并不能把握所有的需求(应该是因为我没有此类游戏的制作经验)。所以无法一开始就设计好全部接口。也不容易分工给不同的同事一起来做实现。

所以我选择了一种方案,每个模块间都采用消息通讯的机制。并且异步的处理消息。每个模块都认为外部有可能发生一些不合理的事情,而被允许过滤掉一些消息。同时也会认为自己提交的请求会被拒绝受理。这样,单独编写模块会相对容易。这个思路来至于 Erlang 对我的影响。

异步处理的必要性还在于,有许多事件虽然先后到达,但逻辑上需要相互决策。当我们解开小模块间的耦合,就失去了调整模块运作的先后次序的机会。这样,我们只好再把逻辑处理划分成时间片,让每个时间片的事件可以放在一起做一个协调。同时对象需要根据不断的状态变化,对自己的行为做一个反馈。(所以,我在实现冲突中被击打者后退的动作时,并没有由冲突模块准确发送应该后退多少,而是简单的发送一个“后退”消息,由持续不断的反馈机制来做到最终需要的效果)

除了消息传递这个基本机制,我还给整个系统增加了另一个基本元素。被称为探测器(Detector) ,这个东西用来取代直接向对象索取数据。比如,我可以创建出一个对象位置的探测器,从中拿到对象在世界中的坐标。这个间接层我还没有找到足够的理由说明它和直接获得对象属性的区别和好处。但是直觉暂时告诉我,是有必要的。因为在现实中,我们了解到外部的一切都来至于我们的感官感受到的东西。也就是说,我们看到的东西仅仅是外部世界在视网膜上的图象投影,并不是外部世界的本质。Detector 就是模拟的这一点。

在模块划分上,对于这些不确定的需求,我倾向于按功能划分模块,而不是对象的实体。倾向于用非侵入式的方式扩展功能,而不是在一个类上不段的增加代码。比如,消息通讯对象就不是放在需要收发消息的对象内部的,而是独立出来,由对象自己去从通讯模块中找到属于自己的接口。因为我不希望有一个类不断的增加成员数据,庞大到不可收拾的地步。而且,在执行次序上,我更希望按功能去统一处理:形象点说,我希望处理完所有对象的视觉信号后,再处理所有对象的行动。把对象的视觉模块放在一起管理,可以更方便的做到这点。


写了好多,似乎还是谈的太泛泛。一是思考了很久,想的东西太多,写的程序也很多,不是几千字能写完的。二是,的确也没有考虑全所有的细节。

如果明天还有兴致,我会继续写。

Comments

画面真实感和流畅性最关键
一直以来我也在研究一些动画相关的问题,可能和你的方向不大一样,我一直认为原始动画信息里面应该提供除了矩阵之外更高层次的信息,比如关键帧中每块骨头的“松紧”程度,主要是为了计算IK时能够提供一些具体的信息让计算出来的IK结果看上去更自然。 这几年做体育游戏做了很多,对动画的理解也是一言难尽,有兴趣的话找机会详谈:)
时间片内的状态机吗? 那么如果一个时间片结束,新的时间片内状态机的状态是回到初始状态吧? 也就是容许丢弃一些消息?
不一定打击感重要啊 画面真实感更重要
嗯,看前面就想评论太泛了,让人看了没多少收获,不过在最后发现你自己已经发现了.
唉,《功夫小子》真的这么没名气啊,伤心。 请云风看看《功夫小子》是不是达到你想要的效果?
拳皇如果能把他做的很好时尚或者网络化。那是相当不错。易操作性也要考虑
那个应该是C9。因为工作原因接触过,说是3D版本的龙与地下城都过于牵强。韩国游戏如果不在游戏性本身上做出努力,一直寄希望于华丽的特效(这个游戏动作本身的僵硬就不提了),所谓的细节,是不可能有大的改观的。想想AION吧。
韩国现在有个网游D9似乎就在动作方面很出彩,据我同学说很有3d版本龙与地下城的感觉,我目测了下感觉很不错。 云风的这篇文章很有内容,学习
几点: 1. 要在TCP协议上做动作游戏,很难,基本没戏。 2. 动作和打击结果是两码事,所以动作的计算可以下放到客户端,或者想DNF这样的小队宿主机 3. 没必要让每个参与的客户端所显示的内容严格一致。也不现实。 4. 所以个人认为:打击感是一个抢主动权的问题。参与打击的多客户端进行争权计算。获权者为主动,失权者从动。然后在每个客户端根据权利分配播放画面 done
说道状态机,正好遇到个问题。现在要为游戏里的角色做一个状态机,可能有多个当前态并存,状态可能会对玩家的行为做一些限制。一种方案是,为玩家的每种行为设置一个bool开关,每增加一个当前态,就根据配置去设置对应的行为开关,每减少一个当前态,就遍历剩余的所有状态来刷新所有的开关。另一种方案是,为玩家的每种行为设置一个消息,当玩家发起某种操作时,会把操作消息发给所有当前态,只要有一个状态拒绝这个操作,那么这个操作就无法执行。从数据化的角度看,前一种更好,因为前一种的状态仅仅是开关的集合,而后一种要为不同的状态写逻辑。但是从灵活的角度看,后一种更灵活(因为状态可以包括逻辑而不仅仅是数据,所以可以对玩家的操作进行响应)。云风怎么看这个问题?
互动的东西我比较倾向于做一个场景提供动作来代替用角色来执行,角色只进行Next递归和数据保存,场景调用角色的Next递归来取得动作意向再根据自身的逻辑决定动作的执行。早期的格斗游戏之所以有提前输入的效果,应该就是这样造成的后果。
打击感啊流畅度啊个人感觉到现在还是很少有游戏超越暗黑2的。格斗类的游戏和网游相似度太小了吧,我倒是蛮喜欢波斯王子四的格斗设计的,虽然多了也腻…
倩女幽魂是用你们引擎开发的吗?感觉很难玩啊,操作之类都感觉没有那种很流畅的感觉
这么多格斗游戏,我最喜欢街霸,拳皇什么的根本比不了。 为啥呢? 街霸中一共有6个扭,拳、脚各分 轻、中、重 三种,每种的速度,力量,效果均不同。 所以,街霸中的“组合招数”都是自己捉摸出来的,轻、中、重三种各种组合,真是乐趣无穷阿! 相比拳皇中的组合招数,都是定死的,有啥意思。 轻、中、重 三种配合,自然会产生动感,所以现在仍然喜欢街霸。
云风的一些意见深有同感 对于强调操作性的游戏,一套好的消息管理机制是必须的.云风提出的"探测器(Detector)" 个人感觉这东西蛮有用,例如开发足球游戏,不同战术可以有不同的传球路线,当玩家按传球键后,AI根据玩家战术或所按传球方向确定最应该把传给谁,然后确定传给谁,然后再通过球员的传球能力计算出传球的位置,根据一系列逻辑规则计算出最好的传球路线,模拟出真实的足球比赛,所以感觉云风提出"探测器(Detector)" 就能比较好的解决这问题,而使用对象属性就不太适合了. 不过云风对于物理系统似乎较排斥之,其实不同的游戏还是有不同的需求的,对于足球游戏来说,是越真实越好玩,所以例如像判断球员铲球或铲人是否犯规,从球员的高度或跳跃能力与位置关系判断球员是否能头顶到球等等,这时候就得引入物理系统设计好的碰撞检测了!
wow中有朝向的概念,正面攻击可被格挡,后面的攻击无法格挡。是不是应该在消息里面加入攻击朝向的参数,最后计算时综合各种朝向的攻击及其力量大小来计算移动的方向和距离。
对游戏开发比较感兴趣,老大的思路很有借鉴作用,很多我也看不懂,不过还是继续关注
很高兴您也对动作游戏(或者严谨一点说是动作偏向的战斗)设计比较感兴趣,您文章中前面两点认识我非常赞同,我的一些制作经验也确实验证了这两点,可以说以此为思路制作出的战斗系统完全是另外一种表现情形和游戏体验。不过后面的技术实现思考我个人认为您有点想当然(当然也有可能是我没看懂……我主要还是从策划的角度去考虑的)。 有机会可以交流一下这方面的经验,我参与过刀剑OL的战斗系统设计,现在完美做战斗系统设计方面的策划。如果您有兴趣可以给我写信:charmelf@163.com PS:貌似我们是校友……
从外部来看,确实是B的功能。但是因为B的功能是比较复杂的,所以B又把自己的工作代理给了C/C1/C2/C3,把B做了分解。 但是确实觉得这种封装意义不大。如果C的接口需要变化,B还得跟着改一把。
如果C提供的服务对外部代码来说看起来象是B本身的功能,就把C的方法封装起来。 也可以无理由的访问到C,但C的变化通常会影响到B。。。
占个沙发。顺便问题个问题: 两个对象,B包含C。 外部代码需要使用C提供的一个服务,是直接从B获取C,然后调用C的方法好呢,还是B再把C的方法再封装起来好呢? 封装的话,好像比较符合一般设计的原则。但是如果B还含有类似的C1/C2/C3,好像这种封装没做任何功能,只是传递了一下参数而已,意义不大。 你的看法呢?封装有的时候,会意味着需要把接口塞到一个类里面去了,这个类的方法越来越多,却大多数没有什么作用——这是我在设计接口的过程中遇到一个常见的问题。

Post a comment

非这个主题相关的留言请到:留言本