|
寒假开始时候做的一个游戏,仿火纹的战棋游戏。现在的进度在完成了战斗处理就完成基本框架了。写个教程给大家,也算是整理一下写的东西。
食用本教程你需要有:
- 掌握C#以及unity基本技能
- 基础的数据结构
- 基本的算法知识
大部分都会讲的,特别基础的部分会一笔带过。第一次写教程,有问题都可以提出。
<hr/>一 :地图
在做任何事之前一定要规划好,我现在特别后悔当初没有写导图,现在看来结构有些问题,但既然写了就凑合着用用,吸取下自己的教训。
战旗游戏,最基本的元素是什么地图,任何元素都是在地图上实现的。这里地图实现我用的tilemap实现。tilemap 瓷砖地图,顾名思义就是把图片像砖块一样拼贴在一起。很多2D游戏都会用到。tilemap详细教程浏览B站视频,这里只讲到工程需要的。
导入tilemap后,我们先右键“层级”面板 ->“3D对象”->“瓦片”即可创建出基本的tilemap。
Grid下有网格组件,控制子对象网格大小。而tilemap才是实现图片的对象
我们再创建个&#34;MapManager&#34;的脚本,放到Grid内,该脚本就是我们控制地图生成,显示的组件。
tilemap并不能直接显示sprit,而是需要tile,所以我们这里创建一些tile,以及图块。(图片请自备),需要的图片大致分类土地,山地,小河,海,房屋,墙壁等。
这是我自制的图集,在Sprite Editor拆封。土地部分实际上只需要无边界的一块即可。
这个只是最基本的图块。如图4,5,6行。分别是陆地,小河,墙壁。可以看到它们连接的边数是不同的,这样就能做出任意的轨迹。但tile只能使用一个图片,我们可以通过脚本去控制图片的类型,但还是有点麻烦。这里就需要官方出的2Dextras插件,在GitHub上百度应该能找到。(一直不理解官方为什么不把这个插件加进去)
安装后,用项目右键创建最上端就是瓦片,选择rule tile。这么多tile都是继承TileBase这个类写的脚本,之后我也会教大家写一个自定义tile。
这是我以及创建好的rule map。九宫格是tile的生成规则。默认sprite为不符合下面任意一个规则显示的图片。九宫格箭头的含义是该tile指向的位置有相同的tile,X则为没有,空则无所谓。同时将rule改为已旋转即可实现图片旋转。
接着让我们了解下tilemap的生成图块的API
BoxFill(Vector3Int position, TileBase tile, int startX, int startY, int endX, int endY);
//position用于确定原点,tile即图块类型,startX/Y开始坐标 endX/Y结束坐标
public void SetTiles(Vector3Int[] positionArray, TileBase[] tileArray);
//两个数组分别对应坐标和图块
这里我们就需要读取tilemap,这里我用的是官方最新出的AddressableAssets。相关教程可观看unity官方达哥出的教程。
安装完成后,打开窗口,资源管理,Addressable,group。
拖动所有tile到group上,修改左边的名次,点击任意label,manage label,添加你喜欢的名字。将所有tile添加上该label。完成。
让我们重新回到mapmanager脚本中。显然为了显示地图我们需要一个二维数组,以及地图数据,建立一个节点类,地图类,其实这里应该用struct的。但是应该寻路的需要只能用class,现在想来应该把两个分开的。有能力可以自行改造。
public class Node
{
public Vector3Int pos;//位置信息
public Node parent;//父节点,寻路用(就是这个)
public int elemType;//地图类型
public int action = -1;//行动力,寻路用
public UnitBase unit; //该图块的单位
public Node(Vector3Int pos)
{
this.pos = pos;
}
public Node(Vector3Int pos, Node parent) : this(pos)
{
this.parent = parent;
}
public Node(Vector3Int pos, int elemType) : this(pos)
{
this.elemType = elemType;
}
}
//未完成
public struct MapData
{
public int width;//地图宽度
public int high;//地图长度
public mapType mapType;//enum 地图类型,主要用于不同场景,地图显示参考的是战纹的地图
public int[,] elemTypes;//地图元素
public BuildBase[] builds;//建筑基类,暂时没用
public MapData(int width, int high, mapType mapType, int[,] elemTypes , BuildBase[] builds)
{
this.width = width;
this.high = high;
this.mapType = mapType;
this.elemTypes = elemTypes;
this.builds = builds;
}
}
public enum elemType { soil, road, river, mountain , tree , wall , bridge ,house}mapmanger先声明这些:
public Tilemap[] tilemaps;//tilemap层
public IList<TileBase> elemTile;
public Node[,] mapNodes;//节点信息我们来写地图显示方法,其实你也可以直接写死在sence里,那么这部分都可以不用看了。
public void Initialition(MapData mapData)这里接收一个mapdata来初始化,我想的是由一个GameDataManager来分发游戏数据。
AssetLabelReference asset=new AssetLabelReference();//标签资源
asset.labelString = mapData.mapType.ToString();//这里把标签名设置为地图类型,
用于读取同一图集的tile。好吧,label不是乱写的
Addressables.LoadResourceLocationsAsync(asset).Completed += MapManager_Completed;
//这东西不支持await,所以Completed事件,读取完成调用MapManager_Completed
//这个方法并不是读取资源,而是观察资源,下面的方法创建了一个映射,来显示正确的显示顺序
void MapManager_Completed(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<IList<UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation>> obj)
{
var name = obj.Result;这里类型是ilist<>
assetsMapping = new int[name.Count+1];//assetsMapping是外面声明的int[]
for (int i = 0; i < name.Count; i++)
{
elemType e = (elemType)Enum.Parse(typeof(elemType), name.PrimaryKey, true);
assetsMapping[(int)e] = i;
}
Addressables.LoadAssetsAsync<TileBase>(asset, null).Completed += LoadedElem;
//这个才是真正读取tile的方法。
}
void LoadedElem(UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<IList<TileBase>> obj)
{
elemTile =obj.Result;
List<Vector3Int> positions = new List<Vector3Int>();//坐标
List<TileBase> tiles = new List<TileBase>();//该坐标的tile
mapNodes = new Node[mapData.width, mapData.high];//声明节点大小
for (int x = 0; x < mapData.width; x++)
{
for (int y = 0; y < mapData.high; y++)
{
mapNodes[x, y] = new Node(new Vector3Int(x, y, 0), mapData.elemTypes[x, y]);
初始化每个节点
if (mapData.elemTypes[x, y] != 0)
{
当节点类型不为0时,添加坐标和tile
positions.Add(new Vector3Int(x, y, 0));
tiles.Add(elemTile[assetsMapping[mapData.elemTypes[x, y]]]);
}
}
}
for (int i = 0; i < tilemaps.Length; i++)初始化各图层大小,原点
{
tilemaps.origin = Vector3Int.zero;
tilemaps.size = new Vector3Int(mapData.width, mapData.high, 0);
}
for (int i = 0; i < mapData.builds.Length; i++)//将建筑信息写入unit,这里有个BUG,人物和建筑无法重叠
{
var p = mapData.builds.Pos;
mapNodes[p.x, p.y].unit = mapData.builds;
}
0图层填充土地,1图层填充其他的tile
tilemaps[0].BoxFill(Vector3Int.zero, elemTile[assetsMapping[0]], 0, 0, mapData.width, mapData.high );
tilemaps[1].SetTiles(positions.ToArray(), tiles.ToArray());
if(Initialed != null)
Initialed.Invoke();//地图生成完的事件
}运行应该就能看到地图显示了。
本章教程到此结束,如果有任何问题,请提出来。也可以关注的我推特 @wozpren2 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|