《乡村铁路》开发日志1-地块

前言

一看时间,距离第0篇发布已经过了超过一个月了😆,最近忙于享受福报,每天回到家就很晚了,完全没时间推动进度orz。

总之现在的功能还十分简陋,每篇就只写一个模块吧,这次是地块。

地块

其实这一段时间我倒是啥折腾弄出了个Wiki页面,有关地块的在这里。这个Wiki是用GitBook生成的,功能目前也完全能满足我的需求,之后每做一个系统啥的就(可能)会在Wiki上及时更新文档说明。一方面是仪式感(误),另一方面就是这个项目就算是能做出来,周期也会相当长,到后面我怕自己都忘记最初的一些设定了,也算是个笔记吧~

基本概念

在默认情况下,整个地图的尺寸为100米x100米的矩形,其中每个地块的尺寸为1米x1米,也就是说整个地图一共10,000个地块。当然现在100x100的尺寸也暂定的,初期所有的测试都是基于这个尺寸的地图,到时候根据实际情况(比如建筑摆上去之后规模不合适)可以调整。

地块目前有三种状态:室外地板,墙壁,室内地板。也就是说我们的墙都是厚度1米的厚墙233,也是为了方便建设和管理。

墙壁支持多方向,也就是根据上下左右四个方向有没有墙壁来更换当前的墙壁模型,这个东西用文字不太好描述,直接看图就很清楚了:

fig.1

  • 左图:单独的墙壁
  • 中图:一个房间
  • 右图:更复杂的连接关系

可以看到,不同形状的墙壁组合会调用不同的模型。

地块的内容就只有这些,其他的所有东西(角色,设施,植物等)都作为单独的实体独立运行。

实现细节

虽然ECS的设计原则有一点就是不要在系统中保存状态,但是为了方便根据坐标获取到对应的实体,我还是用了一个很Magic的写法:

1
2
3
4
public class TileMapSystem : ComponentSystem
{
public NativeArray<Entity> AllTileEntity;
}

然后在OnCreateManager方法里创建这个持久化存储的数组:AllTileEntity = new NativeArray<Entity>(mapWidth * mapHeight, Allocator.Persistent);。然后记得在OnDestroyManager方法里销毁就行了。

这里插一段,其实本来(也应该)是要把这个NativeArray放到单例组件里的,不过好像IComponentData里不能存NativeArray类型,如果想实现数组的话需要用到DynamicBuffer,这个还没去研究,之后有空了还是要把这块重构掉的。

其中地块实体在游戏过程中只有这些,不会增减,于是就可以通过坐标(用x+y*Width对应一个整数)直接取到每一个实体了。

目前实现的功能只有点击鼠标左键造墙点击鼠标右键拆墙

造墙拆墙

TileMapSystemOnUpdate方法中做了两件事情:处理鼠标点击事件和更新地块的模型。

因为设定是点击鼠标左键的时候在鼠标指向的地块造一堵墙,这就需要射线检测了,当时UnityECS的物理系统还没出来(现在有了,之后需要重构掉),就用Hybrid的方式,在场景中创建了10000个带有Collider组件的GameObject,在另外一个叫做ClickableSystem的系统中处理所有的点击事件,传递到TileMapSystem中进行处理。

找到这一帧鼠标点击的地块,如果存在且该地块的状态是室外的话,则将这个地块的状态设置为墙壁,并创建一个TileChangeRequest的组件,添加到地块实体中,用于下一步处理。

模型更新

这一步遍历所有含有TileChangeRequest组件的地块实体,并把对应的模型更换掉,如果这个地块变成了墙或者从墙变成了其他类型,那么则需要更新这个地块上下左右相邻的一共5个地块的状态,原因就是上面提到的多方向墙壁模型。

另外,地图的边缘一圈地块模型是除了地面之外的完成5个面,除了这一圈,里面所有的地块都是没有地平线以下的部分的,地板类型的地块其实就是一个上表面。而墙壁和草地在边缘的模型是不一样的,草地是一整块,而墙壁是普通的墙壁外加一个通用地基组合出来的。所以就还需要在模型变化的时候同时对地基进行特殊处理。

fig.2

下期预告

目前的进度很慢,下一篇(不知道啥时候了233)打算记录一下摄像机相关的东西。