天地之灵

Seed Engine中的输入系统(一)、底层架构、历史

今天看了叛大的 KlayGE中输入系统的改进系列文章,觉得可以谈谈Seed Engine中的输入系统。
因为Seed Engine诞生之初,就定位为主要面向Android、iOS等移动设备(直到2012年6月才有了Flash平台的内部版本,才开始正式需求鼠标事件),所以Seed的输入模块除了对键盘做了简单支持(主要是出于调试目的)外,在很长的一段时间内,鼠标事件都是被wrap成为触摸事件。所有后续关于输入的工作都是针对触摸来做的。所以这篇文章主要重点讲触摸这个方面,尤其是手势识别的做法。
在Seed发展至今的期间,面临了大量复杂的需求,不断改进完善,目前还有很多不尽善尽美的地方。这篇文章会回顾一下这个过程,重点介绍一下当前的处理机制,再展望一下将来期望进行的改进工作。

Seed目前总计支持四种输入:键盘、鼠标、触摸、重力感应。
所有的输入会被响应的处理模块封装成一个struct,这个struct的头四个字节表明了触摸的类型。在单线程模式下,这个struct会被直接传递给应用。在多线程模式下,这个struct会在堆上分配,将指针传递给逻辑线程。这样的好处是在逻辑线程忙的时候,消息处理函数可以更及时返回,避免诸如窗口拖动卡顿之类的问题。
这一部分极其简单,因为简单,所以不容易出问题。之所以把底层架构放在历史之前谈,正是因为自从2011年10月份Seed诞生以来,引擎的这部分代码几乎从未变过。
对于Lua层来说,这部分的接口一直表现为一个全局事件,input.key、input.touch等等,参数struct会被iLuaWrapper(Seed中一个神秘组件)包装成Lua可以直接访问的数据,由Lua脚本去做任何上层的处理。

裸奔时代

Seed诞生以后的一段时间内,当时的工作重心在完善2D渲染基础、2D场景管理,2D物理等等较为繁杂的模块上。因为没有具体项目的负担,当时的输入模块只以满足调试需求、实现简单交互为目的。因此,Seed在2011年10月以前,一直处在输入裸奔的状态。譬如只用键盘做操控,那就直接注册input.key去监听需要的事件。为了实现诸如简单的按钮之类的功能,当时产生了一堆垃圾代码,在事件里直接判定坐标等等。当然,这样写代码是不可能做出没有BUG的游戏来的,于是我们在一个游戏项目的原型阶段结束后不久的时间,迅速推出了第一套框架。

山寨时代

因为需求紧迫,做任何游戏至少都少不了做一堆能tap的button出来,所以我们抓紧推出了第一版的input_ex插件。
input_ex以尽可能简单的方式实现了对指定对象的触摸事件处理。场景中的node可以被注册到input_ex中,以接受tap、hold、drag事件。在touchdown的时候,input_ex会遍历所有注册的结点以找到被命中的node,之后的消息都会派发给这个node。
好吧,至少现在可以创建一堆不同的button了。input_ex的严重不足主要体现在功能上:
        1、input_ex中的手写状态机,几乎决定了除了tap、hold、drag以外,每加一个新的操作种类都是一个巨大的困难。
        2、input_ex不能很好的处理多点触摸。在多点的设备上各种出问题,以至于后来在某些项目里,强行屏蔽了除了1号手指(对应安卓里的0号手指)以外的所有操作。
        3、消息最开始就确定了对象,随后的过程不能改变消息的对象。譬如一个scrollview上面有一堆button,那么当button截获了消息的时候,scrollview就无法处理相应的拖拽事件了。
除此以外,在使用上,每个节点存在一个独立的用于处理输入的对象,其生命周期需要手动管理,错误的使用会导致各种问题。初学者几乎很难写出正确的代码。因为实现复杂,input_ex本身也在很长的时间内都存在引用关系的BUG,导致不需要的资源不能被正确的释放。总体来说,使用input_ex插件做游戏简直是一段不堪回忆的黑历史。

山寨时代之后
在使用input_ex完成了三四个界面操作简单的小游戏后,我们开始构思新的输入系统。我们理想中的输入系统应该符合如下几个条件:
1、很好的支持多点触摸。这包含两方面:第一,必须能够很好的识别利用多个手指的操作,譬如scale, pinch, rotate等。第二,我们认为对于大屏幕的触屏设备,能让多个玩家在不同的地方互不干扰的进行多个操作也是很有必要的需求,这会给游戏设计师带来很多新奇的玩法创意。
2、一定要很方便的加入各种不同的操作识别。我们希望能够实现很多有创意的小游戏,依靠触摸的操作来做很多有意思的事情。我们也希望我们的界面能够交互起来更酷,可以操作控制的地方更多。那么操作一定不能只局限于区区数种,一定要在特定的游戏里就能通过代码添加大量不同的全新操作才行。
3、操作对象的识别更智能。在scrollview 上面的button做scroll操作时,操作对象要能正确的变成scrollview。
4、根据操作对象所接受的事件有所不同,以及其父结点所接受的事件有所不同,对同一事件的处理可能会有差别。譬如一个button接受tap,当触摸并移动的时候,只要没移开范围,逻辑应等待手指松开时再判定为tap成功(正如你在windows下按住一个按钮然后小范围拖动鼠标,click并不会因此而失败)。但假如这个button有一个父结点甚至是祖先结点接受drag,那么早在刚开始移动的时候,就应该判定为tap取消,事件转为drag事件而派发给相应的祖先结点。
5、不会为了满足上面的需求,把上层逻辑代码搞的太麻烦。最理想的情况下,上层逻辑代码只要选择好自己所接受的操作种类,然后安心等待事件监听器被调用就好了。
而我们不想要:
1、像安卓那样复杂的事件分派机制,所有的触摸都被绑在一起依次分派下去,在结点上依据类型的不同写代码去做对应的操作。我们认为应该要有一个很好的手势识别底层,仅仅把结点关心的信息抛给它。
2、像HTML DOM那样的事件冒泡机制。因为触摸处理的复杂性,在touch down的时候往往根本不能确定真正用户想要进行何种操作。而如果等操作进行完了才给予反馈,那操作过程就很难得到非常及时的反馈。在上述scrollview和button的例子里,button必须首先获得事件以立即展现被按下的效果,等到用户的操作能够明确为一个scroll操作之后,再由scrollview来处理后续的事件。再加上之前所述的期望4,已经不是简单的对同一事件的冒泡足以满足的。
真的有一套框架能完美的解决我们的需求吗?下一章起,我会逐步讲解我们为此所做的努力。

posted on 2013-04-12 01:49 天地之灵 阅读(2332) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2013年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(3)

随笔档案

文章档案

搜索

最新评论

阅读排行榜

评论排行榜