1、目标
当角色锄地之后,地面会显示开垦后的样貌。
2、思路
上一篇中,虽然角色dig了hoe,同时grid属性也改变了,但是没有任何可视化的反馈。我们现在将添加新的功能,动态地将"dug ground"瓷砖添加到"GroundDecoration1"的tilemap上。
将使用如下的tileset进行绘制。

为了设置正确的dug ground tile,我们需要检查下周边的tiles是否被dug,此时存在16种组合方式。
第1、2种组合方式:

第3、4种组合方式:

第5、6种组合方式:

第7、8种组合方式:

第9、10种组合方式:

第11、12种组合方式:

第13、14种组合方式:

第15、16种组合方式:

连接周边grid的情况:



我们将增加"dug ground" tiles到GroundDecoration1 tilemaps。我们将标记GroundDecoration1和GroundDecoration2 tilemap层,以便可以快速定位到他们,这些标记放到Tag.cs脚本中。
修改GridPropertiesManager.cs脚本如下:

修改Player.cs脚本

3、修改Tag.cs脚本
cs
public static class Tags
{
public const string BoundsConfiner = "BoundsConfiner";
public const string ItemsParentTransform = "ItemsParentTransform";
public const string GroundDecoration1 = "GroundDecoration1";
public const string GroundDecoration2 = "GroundDecoration2";
}
添加了下面的2个静态变量。
4、修改GridPropertiesManager.cs脚本
cs
// 修改public为private
private Grid grid;
// 添加变量
private Tilemap groundDecoration1;
private Tilemap groundDecoration2;
[SerializeField] private Tile[] dugGround = null;
// 添加函数
private void ClearDisplayGroundDecorations()
{
// Remove ground decorations
groundDecoration1.ClearAllTiles();
groundDecoration2.ClearAllTiles();
}
private void ClearDisplayGridPropertyDetails()
{
ClearDisplayGroundDecorations();
}
private void ConnectDugGround(GridPropertyDetails gridPropertyDetails)
{
// Select tile based on surrounding dug tiles
Tile dugTile0 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY, 0), dugTile0);
// Set 4 tiles if dug surrounding current tile - up, down, left, right now that this central tile has been dug
GridPropertyDetails adjacentGridPropertyDetails;
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1);
if(adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile1 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1, 0), dugTile1);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile2 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1, 0), dugTile2);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile3 = SetDugTile(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY, 0), dugTile3);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile4 = SetDugTile(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY, 0), dugTile4);
}
}
private Tile SetDugTile(int xGrid, int yGrid)
{
// Get whether surrounding tiles(up,down,left,right) are dug or not
bool upDug = IsGridSquareDug(xGrid, yGrid + 1);
bool downDug = IsGridSquareDug(xGrid, yGrid - 1);
bool leftDug = IsGridSquareDug(xGrid - 1, yGrid);
bool rightDug = IsGridSquareDug(xGrid + 1, yGrid);
#region Set appropriate tile based on whether surrounding tiles are dug or not
if(!upDug && !downDug && !rightDug && !leftDug)
{
return dugGround[0];
}
else if(!upDug && downDug && rightDug && !leftDug)
{
return dugGround[1];
}
else if(!upDug && downDug && rightDug && leftDug)
{
return dugGround[2];
}
else if(!upDug && downDug && !rightDug && leftDug)
{
return dugGround[3];
}
else if(!upDug && downDug && !rightDug && !leftDug)
{
return dugGround[4];
}
else if(upDug && downDug && rightDug && !leftDug)
{
return dugGround[5];
}
else if(upDug && downDug && rightDug && leftDug)
{
return dugGround[6];
}
else if(upDug && downDug && !rightDug && leftDug)
{
return dugGround[7];
}
else if(upDug && downDug && !rightDug && !leftDug)
{
return dugGround[8];
}
else if(upDug && !downDug && rightDug && !leftDug)
{
return dugGround[9];
}
else if(upDug && !downDug && rightDug && leftDug)
{
return dugGround[10];
}
else if(upDug && !downDug && !rightDug && leftDug)
{
return dugGround[11];
}
else if(upDug && !downDug && !rightDug && !leftDug)
{
return dugGround[12];
}
else if(!upDug && !downDug && rightDug && !leftDug)
{
return dugGround[13];
}
else if(!upDug && !downDug && rightDug && leftDug)
{
return dugGround[14];
}
else if(!upDug && !downDug && !rightDug && leftDug)
{
return dugGround[15];
}
return null;
#endregion Set appropriate tile based on whether surrounding tiles are dug or not
}
private bool IsGridSquareDug(int xGrid, int yGrid)
{
GridPropertyDetails gridPropertyDetails = GetGridPropertyDetails(xGrid, yGrid);
if(gridPropertyDetails == null)
{
return false;
}
else if(gridPropertyDetails.daysSinceDug > -1)
{
return true;
}
else
{
return false;
}
}
private void DisplayGridPropertyDetails()
{
// Loop throught all grid items
foreach(KeyValuePair<string, GridPropertyDetails> item in gridPropertyDictionary)
{
GridPropertyDetails gridPropertyDetails = item.Value;
DisplayDugGround(gridPropertyDetails);
}
}
// 优化ISaveableRestoreScene函数
public void ISaveableRestoreScene(string sceneName)
{
// Get sceneSave for scene - it exists since we created it in initialise
if(GameObjectSave.sceneData.TryGetValue(sceneName, out SceneSave sceneSave))
{
// get grid property details dictionary - it exists since we created it in initialise
if(sceneSave.gridPropertyDetailsDictionary != null)
{
gridPropertyDictionary = sceneSave.gridPropertyDetailsDictionary;
}
// If grid properties exist
if(gridPropertyDictionary.Count > 0)
{
// grid property details found for the current scene destroy existing ground decoration
ClearDisplayGridPropertyDetails();
// Instantiate grid property details for current scene
DisplayGridPropertyDetails();
}
}
}
// 优化AfterSceneLoaded函数
private void AfterSceneLoaded()
{
// Get Grid
grid = GameObject.FindObjectOfType<Grid>();
// Get tilemaps
groundDecoration1 = GameObject.FindGameObjectWithTag(Tags.GroundDecoration1).GetComponent<Tilemap>();
groundDecoration2 = GameObject.FindGameObjectWithTag(Tags.GroundDecoration2).GetComponent<Tilemap>();
}
完整的代码如下:
cs
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;
[RequireComponent(typeof(GenerateGUID))]
public class GridPropertiesManager : SingletonMonobehaviour<GridPropertiesManager>, ISaveable
{
private Tilemap groundDecoration1;
private Tilemap groundDecoration2;
private Grid grid;
private Dictionary<string, GridPropertyDetails> gridPropertyDictionary;
[SerializeField] private SO_GridProperties[] so_gridPropertiesArray = null;
[SerializeField] private Tile[] dugGround = null;
private string _iSaveableUniqueID;
private GameObjectSave _gameObjectSave;
public string ISaveableUniqueID { get { return _iSaveableUniqueID; } set { _iSaveableUniqueID = value; } }
public GameObjectSave GameObjectSave { get { return _gameObjectSave; } set { _gameObjectSave = value; } }
protected override void Awake()
{
base.Awake();
ISaveableUniqueID = GetComponent<GenerateGUID>().GUID;
GameObjectSave = new GameObjectSave();
}
private void OnEnable()
{
ISaveableRegister();
EventHandler.AfterSceneLoadEvent += AfterSceneLoaded;
}
private void OnDisable()
{
ISaveableDeregister();
EventHandler.AfterSceneLoadEvent -= AfterSceneLoaded;
}
private void AfterSceneLoaded()
{
// Get Grid
grid = GameObject.FindObjectOfType<Grid>();
// Get tilemaps
groundDecoration1 = GameObject.FindGameObjectWithTag(Tags.GroundDecoration1).GetComponent<Tilemap>();
groundDecoration2 = GameObject.FindGameObjectWithTag(Tags.GroundDecoration2).GetComponent<Tilemap>();
}
public void ISaveableDeregister()
{
SaveLoadManager.Instance.iSaveableObjectList.Remove(this);
}
public void ISaveableRegister()
{
SaveLoadManager.Instance.iSaveableObjectList.Add(this);
}
public void ISaveableRestoreScene(string sceneName)
{
// Get sceneSave for scene - it exists since we created it in initialise
if(GameObjectSave.sceneData.TryGetValue(sceneName, out SceneSave sceneSave))
{
// get grid property details dictionary - it exists since we created it in initialise
if(sceneSave.gridPropertyDetailsDictionary != null)
{
gridPropertyDictionary = sceneSave.gridPropertyDetailsDictionary;
}
// If grid properties exist
if(gridPropertyDictionary.Count > 0)
{
// grid property details found for the current scene destroy existing ground decoration
ClearDisplayGridPropertyDetails();
// Instantiate grid property details for current scene
DisplayGridPropertyDetails();
}
}
}
public void ISaveableStoreScene(string sceneName)
{
// Remove sceneSave for scene
GameObjectSave.sceneData.Remove(sceneName);
// Create sceneSave for scene
SceneSave sceneSave = new SceneSave();
// create & add dict grid property details dictionary
sceneSave.gridPropertyDetailsDictionary = gridPropertyDictionary;
// Add scene save to game object scene data
GameObjectSave.sceneData.Add(sceneName, sceneSave);
}
private void Start()
{
InitialiseGridProperties();
}
private void ClearDisplayGroundDecorations()
{
// Remove ground decorations
groundDecoration1.ClearAllTiles();
groundDecoration2.ClearAllTiles();
}
private void ClearDisplayGridPropertyDetails()
{
ClearDisplayGroundDecorations();
}
public void DisplayDugGround(GridPropertyDetails gridPropertyDetails)
{
// Dug
if(gridPropertyDetails.daysSinceDug > -1)
{
ConnectDugGround(gridPropertyDetails);
}
}
private void ConnectDugGround(GridPropertyDetails gridPropertyDetails)
{
// Select tile based on surrounding dug tiles
Tile dugTile0 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY, 0), dugTile0);
// Set 4 tiles if dug surrounding current tile - up, down, left, right now that this central tile has been dug
GridPropertyDetails adjacentGridPropertyDetails;
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1);
if(adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile1 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY + 1, 0), dugTile1);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile2 = SetDugTile(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX, gridPropertyDetails.gridY - 1, 0), dugTile2);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile3 = SetDugTile(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX - 1, gridPropertyDetails.gridY, 0), dugTile3);
}
adjacentGridPropertyDetails = GetGridPropertyDetails(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY);
if (adjacentGridPropertyDetails != null && adjacentGridPropertyDetails.daysSinceDug > -1)
{
Tile dugTile4 = SetDugTile(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY);
groundDecoration1.SetTile(new Vector3Int(gridPropertyDetails.gridX + 1, gridPropertyDetails.gridY, 0), dugTile4);
}
}
private Tile SetDugTile(int xGrid, int yGrid)
{
// Get whether surrounding tiles(up,down,left,right) are dug or not
bool upDug = IsGridSquareDug(xGrid, yGrid + 1);
bool downDug = IsGridSquareDug(xGrid, yGrid - 1);
bool leftDug = IsGridSquareDug(xGrid - 1, yGrid);
bool rightDug = IsGridSquareDug(xGrid + 1, yGrid);
#region Set appropriate tile based on whether surrounding tiles are dug or not
if(!upDug && !downDug && !rightDug && !leftDug)
{
return dugGround[0];
}
else if(!upDug && downDug && rightDug && !leftDug)
{
return dugGround[1];
}
else if(!upDug && downDug && rightDug && leftDug)
{
return dugGround[2];
}
else if(!upDug && downDug && !rightDug && leftDug)
{
return dugGround[3];
}
else if(!upDug && downDug && !rightDug && !leftDug)
{
return dugGround[4];
}
else if(upDug && downDug && rightDug && !leftDug)
{
return dugGround[5];
}
else if(upDug && downDug && rightDug && leftDug)
{
return dugGround[6];
}
else if(upDug && downDug && !rightDug && leftDug)
{
return dugGround[7];
}
else if(upDug && downDug && !rightDug && !leftDug)
{
return dugGround[8];
}
else if(upDug && !downDug && rightDug && !leftDug)
{
return dugGround[9];
}
else if(upDug && !downDug && rightDug && leftDug)
{
return dugGround[10];
}
else if(upDug && !downDug && !rightDug && leftDug)
{
return dugGround[11];
}
else if(upDug && !downDug && !rightDug && !leftDug)
{
return dugGround[12];
}
else if(!upDug && !downDug && rightDug && !leftDug)
{
return dugGround[13];
}
else if(!upDug && !downDug && rightDug && leftDug)
{
return dugGround[14];
}
else if(!upDug && !downDug && !rightDug && leftDug)
{
return dugGround[15];
}
return null;
#endregion Set appropriate tile based on whether surrounding tiles are dug or not
}
private bool IsGridSquareDug(int xGrid, int yGrid)
{
GridPropertyDetails gridPropertyDetails = GetGridPropertyDetails(xGrid, yGrid);
if(gridPropertyDetails == null)
{
return false;
}
else if(gridPropertyDetails.daysSinceDug > -1)
{
return true;
}
else
{
return false;
}
}
private void DisplayGridPropertyDetails()
{
// Loop throught all grid items
foreach(KeyValuePair<string, GridPropertyDetails> item in gridPropertyDictionary)
{
GridPropertyDetails gridPropertyDetails = item.Value;
DisplayDugGround(gridPropertyDetails);
}
}
/// <summary>
/// This initialises the grid property dictionary with the values from the SO_GridProperties assets and stores the values for each scene in
/// GameObjectSave sceneData
/// </summary>
private void InitialiseGridProperties()
{
// loop through all gridproperties in the array
foreach(SO_GridProperties so_GridProperties in so_gridPropertiesArray)
{
// Create dictionary of grid property details
Dictionary<string, GridPropertyDetails> gridPropertyDictionary = new Dictionary<string, GridPropertyDetails>();
// Populate grid property dictionary - Iterate through all the grid properties in the so gridproperties list
foreach(GridProperty gridProperty in so_GridProperties.gridPropertyList)
{
GridPropertyDetails gridPropertyDetails;
gridPropertyDetails = GetGridPropertyDetails(gridProperty.gridCoordinate.x, gridProperty.gridCoordinate.y, gridPropertyDictionary);
if(gridPropertyDetails == null)
{
gridPropertyDetails = new GridPropertyDetails();
}
switch (gridProperty.gridBoolProperty)
{
case GridBoolProperty.diggable:
gridPropertyDetails.isDiggable = gridProperty.gridBoolValue;
break;
case GridBoolProperty.canDropItem:
gridPropertyDetails.canDropItem = gridProperty.gridBoolValue;
break;
case GridBoolProperty.canPlaceFurniture:
gridPropertyDetails.canPlaceFurniture = gridProperty.gridBoolValue;
break;
case GridBoolProperty.isPath:
gridPropertyDetails.isPath = gridProperty.gridBoolValue;
break;
case GridBoolProperty.isNPCObstacle:
gridPropertyDetails.isNPCObstacle = gridProperty.gridBoolValue;
break;
default:
break;
}
SetGridPropertyDetails(gridProperty.gridCoordinate.x, gridProperty.gridCoordinate.y, gridPropertyDetails, gridPropertyDictionary);
}
// Create scene save for this gameobject
SceneSave sceneSave = new SceneSave();
// Add grid property dictionary to scene save data
sceneSave.gridPropertyDetailsDictionary = gridPropertyDictionary;
// If starting scene set the griProertyDictionary member variable to the current iteration
if(so_GridProperties.sceneName.ToString() == SceneControllerManager.Instance.startingSceneName.ToString())
{
this.gridPropertyDictionary = gridPropertyDictionary;
}
// Add scene save to game object scene data
GameObjectSave.sceneData.Add(so_GridProperties.sceneName.ToString(), sceneSave);
}
}
/// <summary>
/// Returns the gridPropertyDetails at the gridlocation fro the supplied dictionary,
/// or null if no properties exist at that location
/// </summary>
/// <param name="gridX"></param>
/// <param name="gridY"></param>
/// <param name="gridPropertyDictionary"></param>
/// <returns></returns>
public GridPropertyDetails GetGridPropertyDetails(int gridX, int gridY,
Dictionary<string, GridPropertyDetails> gridPropertyDictionary)
{
// Construct key from coordinate
string key = "x" + gridX + "y" + gridY;
GridPropertyDetails gridPropertyDetails;
// Check if grid property details exist for coordinate and retrieve
if (!gridPropertyDictionary.TryGetValue(key, out gridPropertyDetails))
{
// if not found
return null;
}
else
{
return gridPropertyDetails;
}
}
public GridPropertyDetails GetGridPropertyDetails(int gridX, int gridY)
{
return GetGridPropertyDetails(gridX, gridY, gridPropertyDictionary);
}
/// <summary>
/// Set the grid property details to gridPropertyDetails fro the tile at (gridX, gridY) for current scene
/// </summary>
/// <param name="gridX"></param>
/// <param name="gridY"></param>
/// <param name="gridPropertyDetails"></param>
public void SetGridPropertyDetails(int gridX, int gridY, GridPropertyDetails gridPropertyDetails)
{
SetGridPropertyDetails(gridX, gridY, gridPropertyDetails, gridPropertyDictionary);
}
/// <summary>
/// Set the grid property details to gridPropertyDetails for the title at (gridX, gridY) for the gridPropertyDictionary.
/// </summary>
/// <param name="gridX"></param>
/// <param name="gridY"></param>
/// <param name="gridPropertyDetails"></param>
/// <param name="gridPropertyDictionary"></param>
public void SetGridPropertyDetails(int gridX, int gridY, GridPropertyDetails gridPropertyDetails, Dictionary<string, GridPropertyDetails> gridPropertyDictionary)
{
// Construct key from coordinate
string key = "x" + gridX + "y" + gridY;
gridPropertyDetails.gridX = gridX;
gridPropertyDetails.gridY = gridY;
// Set value
gridPropertyDictionary[key] = gridPropertyDetails;
}
}
5、修改Player.cs脚本
在HoeGroundAtCursorRoutine函数中添加如下代码:

6、设置绘图
(1)创建新的Palette

Create New Palette,并且命名为DugGround,保存到Assets -> Tilemap -> Tile Palettes下。


(2)创建新的Tiles
在Assets -> Sprites -> Sprite Textures -> Tile Sprites 下找到DugGround.

将DugGround拖到右边的Palette中,弹出的目录选择Assets -> Tilemap -> Tiles,创建新的目录Dug Ground,保存到该目录下。


左边灰色为Dug后的grid图案,右边银色为Water之后的grid图案。
7、配置Tile信息
点击Assets -> Tilemap -> Tiles -> Dug Ground,然后点击Open。

点击PersistentScene -> GridPropertiesManager,将Dug Ground中灰色的图片拖入到Dug Ground的List中。


8、添加Tag
点击GridPropertiesManager,点击Add Tag。

添加GroundDecoration1和GroundDecoration2两个Tag。

然后依次给Scene1/Scene2/Scene3场景Tilemap Grid下的GroundDecoration1和GroundDecoration2对象设置GroundDecoration1和GroundDecoration2两个Tag。


设置Prefabs -> Maps -> Tilemap Grid下的GroundDecoration1和GroundDecoration2对象设置GroundDecoration1和GroundDecoration2两个Tag。


9、运行游戏
