前言
hello and welcome back ,一个月不见 十分想念,最近外包狗有点忙,博客耽误了一下,以后一定一周一更~
今天我们来学习用tilemap创建场景,最基础的使用由于时间关系就先不赘述,今天讲的是使用tilemap
管理场景 ,比如哪些位置可以被锄头凿地,哪个地方可以放家具,哪个地方可以丢弃物品等
场景创建
简单介绍一下我的场景创建
右键->2D->tilemap->rectangular创建tilemap
Windows->2D->TilePalette打开画板
画板的使用
点击CreateNewPalette 把要切割的图片塞进去 就可以自动切割成小图片了,切成的类似于ui的sprite切割
切割之后我们就可以操作它在场景中随意涂画啦~
画图建议
笔者这里提一下我的场景布置 我这里将场景分了多个层
Ground1 用来画最基础的土地
Ground2 用来画场景中的花花草草,栏杆等物品
Ground3 用来画山,房子的前半面 原因下面讲
Instance 用来渲染玩家,植物,npc等物品
Front用来画房顶
Collision 用来画碰撞体
Ground3 画房子的前半面Front用来画房顶的原因:instance渲染要在Ground3后面,使用如果我不准备让玩家从红框位置穿过,如果可以穿过 给人的感觉就是爬着房子走过去,我又想让玩家在房顶位置穿过,因为front渲染在instance后面,玩家穿过不会被渲染出来,会给一种在房子后面穿过的感觉
当然 仁者见仁智者见智,大家不喜欢这个方法的话也可以按照自己的想法来搞,最重要的一点就是千万不要忘记创建新的Layer层,把Tilemap的SortLayer改成对应的渲染层
Tilemap的Collision
我们在collision层画好碰撞位置之后 我们要添加Rigidbody2D
创建格子信息
一个格子我们要知道它的x轴位置和y轴位置,这个格子的属性(是否可以被锄头锄,是否可以丢弃物品)
注意 格子信息之后要被存储 所以一定要把它序列化!!
格子位置信息
格子位置就是它的x轴和y轴的位置
[System.Serializable]
public class GridCoordinate
{
public int x;
public int y;
public GridCoordinate(int x,int y)
{
this.x = x;
this.y = y;
}
/// <summary>
/// 显示转换
/// </summary>
public static explicit operator Vector2(GridCoordinate grid)
{
return new Vector2((float)grid.x, (float)grid.y);
}
public static explicit operator Vector2Int(GridCoordinate grid)
{
return new Vector2Int(grid.x, grid.y);
}
public static explicit operator Vector3(GridCoordinate grid)
{
return new Vector3((float)grid.x, (float)grid.y,0);
}
public static explicit operator Vector3Int(GridCoordinate grid)
{
return new Vector3Int(grid.x, grid.y,0);
}
}
格子的属性
格子的信息我现在只想到了是否可以锄地 是否可以丢弃物品,是否可以放置家具,是否可以让npc移动(A*那个模块我在来讲)
这些信息我们可以用枚举来存储
public enum GridBoolProperty
{
diggable,//是否可以被挖掘
canDropItem,//是否可以被丢弃
canPlaceFurniture, //是否可以放置家具
isNPCObstacle//npc是否可以移动
}
格子的总信息
我们把前面两个信息整合到一起 就可以得到格子的总信息
[System.Serializable]
public class GridProperty
{
public GridCoordinate gridCoordinate;
public GridBoolProperty gridBoolProperty;
public bool gridBoolValue = false;
public GridProperty(GridCoordinate gridCoordinate, GridBoolProperty gridBoolProperty, bool gridBoolValue)
{
this.gridCoordinate = gridCoordinate;
this.gridBoolProperty = gridBoolProperty;
this.gridBoolValue = gridBoolValue;
}
}
格子的详细信息
详细信息我们要记录当前格子的详细信息,为了防止可以在种植过物品的位置重新种植物品,
目前包含的信息分别是格子在x轴的详细位置,格子在y轴的详细位置,是否被锄头砸过(砸过才可以种植种子),是否可以丢弃物品,是否可以放置家具,是否种植过物品(种植了物品就不能再次种植),是否交过水(不交水的话第二天种子不会成长),是否被砸过(砸过第二天没有种东西就会恢复成普通的地),种植的天数,距离上次收获的天数
public class GridPropertyDetails
{
public int gridX;
public int gridY;
public bool isDiggable = false;
public bool canDropItem = false;
public bool canPlaceFurniture = false;
public bool isNPCObstacle = false;
public int daysSinceDug = -1;//被锄头砸
public int daysSinceWatered = -1;//浇过水没
public int seedItemCode = -1;//种子id
public int growthDays = -1;//种植天数
public int daysSinceLastHarvest = -1;//重复收获的植物距离上次收获的天数
public GridPropertyDetails()
{
}
}
存储信息的工具
我们要用ScriptableObject来把上述的格子信息存储下来,方便之后foreach循环判定整个格子的信息,有的小伙伴会说 那我们直接用list给它存下来不久可以了吗,我只想说你想到太简单了,如果我们用List来存储它的话,一个场景格子有成千上万个,难道要一个一个的给他们赋值格子类型吗,显然是不现实的,所以我们要先把这些信息用工具存下来,利用tilemap来设置各个格子的信息,如果你看到这些还不明白的话,不要担心,到了下面你就会明白我将的做法
现在我们先来把代码搞出来
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="so_GridProperties",menuName = "Scritable/Properties")]
public class SO_GridProperties : ScriptableObject
{
public SceneName sceneName;
public int gridWidth;
public int gridHeight;
public int originX;
public int originY;
[SerializeField]
public List<GridProperty> gridPropertyList ;
}
这样我们右键Scritable->Properties就可以创建出来一个so_GridProperties
SceneName是场景名字 比如 农场,空地,卧室等
gridWidth格子的总宽度,
gridHeight 格子的总高度
originX x轴的偏移
originY y轴的偏移
gridPropertyList 是格子的详细信息
注意:originX originY的偏移是基于左下角格子的坐标而定的
比如我这个场景里左下角的各自坐标是-40,-30那么我这个的偏移就是-40,-30
记录格子信息
好啦,现在我们到很关键的一步了 记录各个格子的信息 ,这个功能只能在编译器中执行
前置:我们为GridBoolProperty枚举中的五个模式分别创建出五个对应的tilemap
思路 :在编译器模式下,启用(gameobject.setactive=true)的时候就把SO_GridProperties里面的格子信息List清空,弃用(gameobject.setactive=false)的时候就循环整个tilemap,如果tile不为空就把它存到SO_GridProperties的List中
例子
比如我想要规划某个位置是可以种地的地方 那么我就要把种地这个位置的tilemap启用,在想要让它种地的位置画上图,然后再将它的setActive设为false 他就会自动保存到List中了
1、启用Diggable2画图 蓝色框就是可以种植物品的位置
3把它关闭
4 自动被保存到这里啦 我们可以循环这个文件就可以操作整个场景了
代码
思路讲好了 上代码!!!
using UnityEditor;
using UnityEngine;
using UnityEngine.Tilemaps;
[ExecuteAlways]
public class TileMapGridProperty : MonoBehaviour
{
#if UNITY_EDITOR
private Tilemap tilemap;
[SerializeField] private SO_GridProperties gridProperties = null;
[SerializeField] private GridBoolProperty gridBoolProperty = GridBoolProperty.diggable;
private void OnEnable()
{
if (!Application.IsPlaying(gameObject))
{
tilemap = GetComponent<Tilemap>();
if (gridProperties != null)
gridProperties.gridPropertyList.Clear();
}
}
private void OnDisable()
{
if (!Application.IsPlaying(gameObject))
{
UpdateGridProperties();
//用于将目标对象标记为已更改,这通常是在修改了对象的属性但不希望创建撤销记录的情况下使用
if (gridProperties != null)
EditorUtility.SetDirty(gridProperties);
}
}
private void UpdateGridProperties()
{
tilemap.CompressBounds();//将 tilemap的origin 和 size 压缩到瓦片所存在的边界。
if (!Application.IsPlaying(gameObject))
{
if (gridProperties != null)
{
Vector3Int startCell = tilemap.cellBounds.min;
Vector3Int endCell = tilemap.cellBounds.max;
for(int x = startCell.x; x < endCell.x; ++x)
{
for(int y = startCell.y; y < endCell.y; ++y)
{
TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
if (tile != null)
gridProperties.gridPropertyList.Add(new GridProperty(new GridCoordinate(x, y), gridBoolProperty, true));
}
}
}
}
}
#endif
}
这个功能要在编译器中才可以使用,标记为[ExecuteAlways]
把它拖拽到tilemap上就可以实现我刚才将的操作啦~
简单讲一下代码逻辑吧
在启用这个gameobject的时候就把整个List清空
在禁用这个gameobject的时候就循环整个tilemap 把有画图的格子存下来,gridBoolProperty为格子的类型(种地的?可以丢弃物品的?等等)
尾声
好拉,时间不早代码刚好,今天的代码很简单,明天要上班不能熬夜写太多不然明天起不来又要扣钱,外包狗的悲哀(/(ㄒoㄒ)/~~),希望大家好好理解一下这个代码,千万不要去外包,没人权呐!
下周我来讲一下如何使用这个格子信息来种地 锄地 种树等功能,我们下周不见不散啦~