概述
金海豚21天gamejam开发大赛终于结束了,近期也忙着整理作品没时间做记录。不过最近终于也是闲下来了,因为约不到面试,就先来整理一下这个游戏的底层网格功能实现吧。
1.为什么需要网格?
实际上,根据最初的策划案,我们这个游戏是不需要制作网格系统的。不过当我制作了一个MVP给策划看了以后,我们一致认为要将整个游戏搭建在网格之上。
首先我们这个游戏有一个建造系统,当玩家点击屏幕时,可以在点击的位置放置一个建筑。如果不用网格的话,这个建筑的范围要怎么处理?如何判断一个位置有没有已经被建筑占据?这是我们没有办法解决的问题。所以我们去参考了《边缘世界》以及开罗系列游戏,这里使用网格可以做简单判断。
其次,我们的角色需要寻路。那么目前的各类寻路方案,据我所知的都需要网格,只不过具体的形式不同罢了。例如unity里的Navigation导航网格本质上也是将区域按网格划分。也有的寻路是将网格抽象成坐标点,在坐标点之间计算寻路,我认为也没有脱离网格的范畴。那么对于我们这个21天的游戏来说。底层之间使用网格,后续写寻路的压力会小很多。
最后一点考量在美术上。由于我们队伍里没有专业的美术,在美术风格与艺术形式上没有统一的方案。我们一开始认为AI生图能够辅助一些美术工作。结果发现是大错特错。AI生成的美术可以说是完全不能用。大概距离比赛结束还有一周半的时间时。我提出我们可以直接使用像素风格瓦片。显然,对于我们的情况,使用各类瓦片绘制出一张地图,远比让AI生成一张要实际。瓦片天生就是网格,这不是巧了吗?这下整个游戏搭建在网格系统之上已经是板上钉钉了。
2.网格要实现什么功能?
至少要确定网格的位置吧?那网格就要提供位置的查询,例如通过一个世界坐标位置查询在哪个网格内部,反过来,也要能通过一个网格坐标返回它的中心点的世界坐标位置。这一点倒是很好实现,伪代码如下:
public Vector2Int World2Cell(Vector2 pos)
{
int x=Mathf.FloorToInt(pos.x/单位长度);
int y=Mathf.FloorToInt(pos.y/单位长度);
return new Vector2Int(x,y);
}
public Vector2 Cell2World(Cell cell)
{
float x=(float)cell.x*单位长度+单位长度/2;
float y=(float)cell.y*单位长度+单位长度/2;
return new Vector2(x,y);
}
除此之外,每一个网格需要维护自身状态,例如这个网格上有没有建筑?这个网格上角色能否行动?上面有没有角色?关于这个问题,我将在下一段论述。
3.选择哪种实现方案?
提起网格,我首先就是想到unity中的瓦片。而且瓦片提供了很多api给我们用。但是我没有选择直接用瓦片,当时我考虑到我们团队中有策划和美术,如果它们等我写好代码再开始设计关卡,绘制地图,那这个效率就大打折扣了。如果能够并行开发是最好的。当时我们使用的是git+lfs大文件存储进行项目写作。几个人同时改一个内容还会有冲突。那我想,给策划一个瓦片用来绘制关卡信息,给美术一个瓦片做地图绘制,那我自己实现网格逻辑就好了。之后我从策划的瓦片图里读取信息,一点不耽误他们的工作。不过这是我临时想出的方案,不太成熟。
关于这个网格结构,在我最初的构想中,网格的状态并非由网格自己持有,而是将判断分散在不同系统中。网格结构体只持有一个网格坐标,并重载了运算符和Equals方法~~(结果后面全都没有用到QAQ)~~。例如我在(1,1)格放置了建筑,那么就在建筑系统中维护一个运行时列表,存的是放置了建筑的格子,给外部提供一个查找方法。其它系统同理。这样做的好处是,网格完全不需要提前建立好,我们用到哪一格,哪一格网格才会被创建出来。
这带来了很多问题,不同系统间怎么保证网格的单一性呢?比方说建筑系统new了一个(1,1),而寻路系统里还有一个(1,1)怎么办?虽然我重写了比较方法,这两个实例是相等的,但是随着系统的增加,这样大量创建值类型实例显然不好。因为在游戏过程中每个系统都是长期持有这个列表的,这意味着这个格子实例不会被gc,而且显然这些网格是被重复存储了。按照这种写法,当我要判断一个格子是否可供角色行走,我需要遍历所有系统做判断。我觉得到后期一定会忘记且难以管理。这迫使我选择另一种实现方案。
对于我们这个比赛的小项目来说,网格并不会很多。那么我们不论是使用字典还是列表存储全部的网格,性能开销并不大。那我完全可以让每一个网格自己持有自己的状态,我使用flag特性让各类标记可以叠加。这下舒服了,当我要查这一格上有没有建筑,我只需访问这一格就行,完全不需要耦合建造系统嘛。
最后我们还得提到寻路,实际上,如果采用只用坐标的形式的网格。等到寻路时我们依旧要创建大量的网格实例。这不是本末倒置了吗?所以我最终敲定方案。网格作为结构体除了持有x,y坐标和状态标签,还会在游戏开局时创建好全部实例。
单个网格结构体大致如下:
public struct Cell:IEquatable<Cell>
{
int x;
int y;
CellState cellState;
public bool Equals(Object obj);
}
[Flag]
public enum CellState
{
occupied=0,
walkable=1<<0,
canBuild=1<<1,
}
4.目前还有什么问题?
既然一个网格是可以根据坐标被计算出来的。那么我觉得应该有方法不用去实例化网格对象。不过碍于开发的时间和我自身能力的不足。就等以后再去实现吧。
Comments
评论区
欢迎在这里留言交流。