开发笔记(4) SLG 地图

数据组织

我们将地图上的数据分为动态数据(行军)和静态数据(点数据),因为行军过程不会对地图上的点造成影响(飞行),并且不考虑实时碰撞,因此行军的逻辑是非常轻的,将其剥离出地图点数据,处理起来会更简单。也就是说,行军是地图上特殊的一类事件,而不是动态的点。

数据同步

主动拉取

主动拉取是指玩家在拖动屏幕时,能够看到地图上实时的点信息和行军信息。

  • 静态数据:最简单的处理,将玩家屏幕的中心点周围一定范围(>=玩家屏幕大小)的点信息,发送给客户端,但这样有一个问题,玩家稍微移动屏幕(中心点变动),都会导致频繁的请求和数据包,因此我们需要一定程度的”缓冲和限制”来避免频繁的请求。在这个基础上,我们在地图上建立了块(Block)这个概念,一个Block大概可以看做玩家屏幕大小的一个区块,玩家在拖动屏幕中,只有屏幕中心点跨Block了,才发送拉取数据请求,而每次拉取,服务器会将以该Block为中心的九宫格Blocks都发送给客户端,用于客户端平滑过渡。

  • 动态数据:目前只考虑行军,在行军建立时,算出行军所经历的所有Block,将行军这个事件挂在这些Block上,当玩家拉取该Block的数据时,取出这些行军信息,发送客户端。客户端根据行军上面的时间戳等信息,推算出行军当前位置。

实时推送

实时推送,是指玩家在屏幕上不进行任何操作,也能看到屏幕所在范围地图上发生的实时信息。这是通过视口(ViewPort)来实现的,视口是一个抽象的概念,可以看做玩家屏幕,玩家视口挂在哪个Block上,玩家就能实时收到该Block上的变化通知,默认情况下,玩家的视口挂载在主城所在的位置,随着玩家屏幕拖送,玩家的视口也会挂载在不同Block上。在服务器上,视口可以简单表示为PlayerId。

  • 当一个点数据变动时,底层会自动通知它所在Block上所有的玩家(视口)
  • 新建行军时,底层会实时通知相关Block,并且将该事件ID挂在Block事件队列上。当有新视口挂载在这些Block上时,将得到通知
  • 行军结束后,通知相关Block,并且将事件ID从相关Block事件队列上移除
  • 当玩家离开地图后,清除玩家视口信息

行军设计

一个典型的场景是,玩家选定一支队伍,然后派出行军,采矿/战斗/XXX,回城。在这上面,最开始我们的设计是,行军是队伍的一种状态,是队伍信息的一部分,行军抵达,只是队伍状态由行军转换为了采矿/战斗,这样实际上行军和队伍是一个东西。这样后来在分离出其它玩家/NPC的行军信息时就会很困难。

最后我们将行军单独抽了出来,行军可以承载任何类型的可移动单位,行军和可移动单位相互索引,这样行军本身只携带很少量的信息,并且到达目的地之后就会被删除。

模块设计

两个模块分别用于管理点和行军,外加共用底层模块来管理Block并支撑数据同步。Block模块需要维护三个映射: Player2BlocksBlock2PlayersBlock2Events,显然,在这套机制上,一个玩家是可以有多个视口的。

由于将数据同步机制做在了底层,因此优化的空间是比较大的。对于操作地图点的API,可以给出一个sync选项,用于标明是否需要底层自动通知,这样上层在操作多个点时,可以在修改完之后一并通知。对于事件信息,可以优先将消息发送给事件相关的玩家(如行军所属玩家和行军的目标玩家)等。