这时,如果希望实现“点击动物、攻击动物;点击物品、捡起物品”的效果,那么需要对动物和物品进行事件监听。如果采用dom的方式,那么会出现几点难于处理的问题:
a.渲染的顺序和事件处理的顺序不同(有时候z-index小的需要先处理事件),需要额外处理。例如这个上面的例子里:点击怪物、点击物品的时候也容易点到人物,那么需要给人物做“点击事件穿透”的处理。而且事件处理的顺序不固定:假如我有一个技能(例如游戏里的治疗)需要点人物才可以释放,那么这时人物又需要有事件监听。所以一个元素是否需要处理事件、处理事件的先后,是随着游戏状态的不同而变化的,而 dom的事件绑定已经不能满足需要 。
b.有关联的元素难以放在同一个dom节点中:例如玩家的模型、玩家的名字和玩家身上的技能画效,理想情况下是放在一个 <div> 或者 <section> 容器里,便于管理(这样几个元素的定位就可以继承父元素,不用分别处理位置了)。但是这样,z-index会很难处理。例如玩家A在玩家B的上面,那么A会被B遮挡,因此需要A的z-index小一些,但是又需要让玩家A的名字不会被B的名字或者影子遮挡,就无法实现。简单点说, dom结构的可维护性会牺牲画面展示的效果,反之亦然 。
c.性能问题。即使牺牲了效果,用dom渲染,势必出现很多嵌套关系,所有元素的style都在频繁变化,连续触发浏览器的repaint甚至reflow。
canvas渲染逻辑与项目逻辑分离
如果将canvas的各种渲染操作(如 drawImage 、 fillText 等)与项目代码放在一起,那么势必导致项目后期无法维护。翻了一下几款现有的canvas库,结合vue的数据绑定+调试工具的方式,搞了一个全新的canvas库Easycanvas( github地址 ),并且像vue一样支持通过一个插件来调试canvas中的元素。
这样,整个游戏的渲染部分就容易很多,只需要管理游戏当前的状态、并且根据服务端从socket传回来的数据去更新数据就可以。 “数据的变化引起视图的变化”这个环节由Easycanvas负责 。例如下图的玩家包裹物品的实现,我们只需要给出包裹容器的位置、背包里每个元素的排布规则,然后将每个包裹的物品绑定到一个array上,然后去管理这个array即可(数据映射到画面的过程由Easycanvas负责)。
例如,5行8列共计40个物品的样式可以通过如下的形式传递给Easycanvas(index为物品索引,物品x方向间距36,y方向间距32)。而这个逻辑是一成不变的,无论物品的数组怎样变化、包裹被拖拽到什么位置,每个物品的相对位置都是固定的。至于canvas上的渲染则完全不需要项目本身来考虑,所以可维护性较好。