• [Unity2D独立/合作开发] 实现以网格为单位的物品丢弃范围以及设置物品可操作范围


    学习目标:

     大家好啊我是说的道理,今天来点大家想看的东西。

    如果你也在做RPG类游戏的开发,有个问题是绕不过去的,就是当我们丢弃物品的时候,不能满地图都要丢掉,而是要考虑物品的可丢弃范围以及这个物品能不能丢在场景的某个位置,今天我能就用脚本来实现这些功能。

    首先检测一下你们的so_Itemlist,是否把物品设置可丢弃CanBeDropped以及丢弃范围ItemUseGridRadius

     

            然后就是代码部分

    explicit operator,C#中的自定义类型的运算转化符,将之后的脚本中我们将使用这四个静态函数

    1. using UnityEngine;
    2. [System.Serializable]
    3. public class GridCoordinate
    4. {
    5. public int x;
    6. public int y;
    7. public GridCoordinate(int x, int y)
    8. {
    9. this.x = x;
    10. this.y = y;
    11. }
    12. public static explicit operator Vector2(GridCoordinate gridCoordinate)
    13. {
    14. return new Vector2((float)gridCoordinate.x, (float)gridCoordinate.y);
    15. }
    16. public static explicit operator Vector2Int(GridCoordinate gridCoordinate)
    17. {
    18. return new Vector2Int(gridCoordinate.x, gridCoordinate.y);
    19. }
    20. public static explicit operator Vector3(GridCoordinate gridCoordinate)
    21. {
    22. return new Vector3(gridCoordinate.x, gridCoordinate.y,0f);
    23. }
    24. public static explicit operator Vector3Int(GridCoordinate gridCoordinate)
    25. {
    26. return new Vector3Int(gridCoordinate.x, gridCoordinate.y,0);
    27. }
    28. }

    回到我们的Enums脚本创建一个新的枚举,这些分别对应了我们的so_itemList上每个item的bool值

    1. public enum GridBoolProperty
    2. {
    3. diggable,
    4. canDropItem,
    5. canPlaneFurniture,
    6. isPath,
    7. isNPCObstacle,
    8. }

     于是我们创建一个GridProeprty

    1. [System.Serializable]
    2. public class GridPropetry
    3. {
    4. public GridCoordinate gridCoordinate;
    5. public GridBoolProperty gridBoolProperty;
    6. public bool gridBoolValue = false;
    7. public GridPropetry(GridCoordinate gridCoordinate,GridBoolProperty gridBoolProperty,bool gridBoolValue)
    8. {
    9. this.gridCoordinate = gridCoordinate;
    10. this.gridBoolProperty = gridBoolProperty;
    11. this.gridBoolValue = gridBoolValue;
    12. }
    13. }

    每个场景都需要一个记录gridpeoperty的数据结构支持

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. [CreateAssetMenu(fileName = "so_GridProperties", menuName = "Scriptable Objects/Grid Properties")]
    5. public class SO_GridProperties : ScriptableObject
    6. {
    7. public SceneName sceneName;
    8. public int gridWidth;
    9. public int gridHeight;
    10. public int originX;
    11. public int originY;
    12. [SerializeField] public List gridPropertyList;
    13. }

    以及一个Details细节脚本

    1. [System.Serializable]
    2. public class GridPropertyDetails
    3. {
    4. public int gridX;
    5. public int gridY;
    6. public bool isDiggable = false;
    7. public bool canDropItem = false;
    8. public bool canPlackFurntiure = false;
    9. public bool isPath = false;
    10. public bool isNPCObstacle = false;
    11. public int daysSinceDug = -1;
    12. public int daysSinceWatered = -1;
    13. public int seedItemCode = -1;
    14. public int growthDays = -1;
    15. public int daysSinceLastHarvest = -1;
    16. public GridPropertyDetails()
    17. {
    18. }
    19. }

     

    新建一个游戏对象叫gridpropertyManager再给他一个同名脚本

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Tilemaps;
    5. [RequireComponent(typeof(GenerateGUID))]
    6. public class GridPropertiesManager : Singleton<GridPropertiesManager>, ISaveable//继承ISaveable并实现方法
    7. {
    8. public Grid grid;
    9. private Dictionary<string, GridPropertyDetails> gridPropertyDictionary; //GridPropertyDetails的字典
    10. [SerializeField] private SO_GridProperties[] so_GridPropertiesArray = null; //创建的每个场景中合起来的数组
    11. private string iSaveableUniqueID; //GUID
    12. public string ISaveableUniqueID { get => iSaveableUniqueID; set => iSaveableUniqueID = value; }
    13. private GameObjectSave gameObjectSave; //scneneData
    14. public GameObjectSave GameObjectSave { get => gameObjectSave; set => gameObjectSave = value; }
    15. protected override void Awake()
    16. {
    17. base.Awake();
    18. ISaveableUniqueID = GetComponent().GUID;
    19. GameObjectSave = new GameObjectSave();
    20. }
    21. private void Start()
    22. {
    23. IntializeGridProperty();
    24. }
    25. private void OnEnable()
    26. {
    27. EventHandler.AfterSceneLoadEvent += AfterSceneLoaded;
    28. ISaveableRegister();
    29. }
    30. private void OnDisable()
    31. {
    32. EventHandler.AfterSceneLoadEvent -= AfterSceneLoaded;
    33. ISaveableDeregister();
    34. }
    35. private void AfterSceneLoaded()
    36. {
    37. grid = GameObject.FindObjectOfType();
    38. }
    39. public void ISaveableDeregister()
    40. {
    41. SaveStoreManager.Instance.iSaveableObjectLists.Remove(this);
    42. }
    43. public void ISaveableRegister()
    44. {
    45. SaveStoreManager.Instance.iSaveableObjectLists.Add(this);
    46. }
    47. public void ISaveableRestoreScene(string sceneName)
    48. {
    49. if(GameObjectSave.sceneData.TryGetValue(sceneName,out SceneSave sceneSave))
    50. {
    51. if(sceneSave.gridPropetyDeatilsDictionary != null)
    52. {
    53. gridPropertyDictionary = sceneSave.gridPropetyDeatilsDictionary;
    54. }
    55. }
    56. }
    57. public void ISaveableStoreScene(string sceneName)
    58. {
    59. GameObjectSave.sceneData.Remove(sceneName);
    60. SceneSave sceneSave = new SceneSave();
    61. sceneSave.gridPropetyDeatilsDictionary = gridPropertyDictionary;
    62. GameObjectSave.sceneData.Add(sceneName, sceneSave);
    63. }
    64. ///
    65. /// 初始化场景的中的GridProperty
    66. ///
    67. private void IntializeGridProperty()
    68. {
    69. foreach (SO_GridProperties so_GridProperties in so_GridPropertiesArray) //先遍历场景数据结构的数组
    70. {
    71. Dictionary<string, GridPropertyDetails> gridPropertyDictionary = new Dictionary<string, GridPropertyDetails>(); //对于每个场景创建一个字典,string即GUID
    72. foreach (GridPropetry gridPropetry in so_GridProperties.gridPropertyList) //遍历场景数据结构中的链表元素
    73. {
    74. GridPropertyDetails gridPropertyDetails;
    75. gridPropertyDetails = GetGridPropetyDetails(gridPropetry.gridCoordinate.x, gridPropetry.gridCoordinate.y,gridPropertyDictionary);//用坐标来初始化gridPropertyDetails
    76. if (gridPropertyDetails == null) //如果找不到就创建一个新的
    77. {
    78. gridPropertyDetails = new GridPropertyDetails();
    79. }
    80. //用switch来判断gridPropetry的gridBoolProperty枚举
    81. //以此来给gridPropertyDetails上的对应bool来赋值
    82. switch (gridPropetry.gridBoolProperty)
    83. {
    84. case GridBoolProperty.diggable:
    85. gridPropertyDetails.isDiggable = gridPropetry.gridBoolValue;
    86. break;
    87. case GridBoolProperty.canDropItem:
    88. gridPropertyDetails.canDropItem = gridPropetry.gridBoolValue;
    89. break;
    90. case GridBoolProperty.canPlaneFurniture:
    91. gridPropertyDetails.canPlackFurntiure = gridPropetry.gridBoolValue;
    92. break;
    93. case GridBoolProperty.isPath:
    94. gridPropertyDetails.isPath = gridPropetry.gridBoolValue;
    95. break;
    96. case GridBoolProperty.isNPCObstacle:
    97. gridPropertyDetails.isNPCObstacle = gridPropetry.gridBoolValue;
    98. break;
    99. default:
    100. break;
    101. }
    102. SetGridPropertyDetails(gridPropetry.gridCoordinate.x,gridPropetry.gridCoordinate.y,gridPropertyDetails,gridPropertyDictionary);
    103. }
    104. SceneSave sceneSave = new SceneSave();
    105. sceneSave.gridPropetyDeatilsDictionary = gridPropertyDictionary;//吧我们新建的gridPropertyDictionary复制给sceneSave的字典
    106. if (so_GridProperties.sceneName .ToString() == SceneControllerManager.Instance.startingSceneName.ToString())
    107. {
    108. this.gridPropertyDictionary = gridPropertyDictionary;
    109. }
    110. GameObjectSave.sceneData.Add(so_GridProperties.sceneName.ToString(), sceneSave);
    111. }
    112. }
    113. ///
    114. /// 重载函数,用默认的gridPropertyDictionary来储值
    115. ///
    116. ///
    117. ///
    118. ///
    119. public void SetGridPropertyDetails(int gridX,int gridY, GridPropertyDetails gridPropertyDetails)
    120. {
    121. SetGridPropertyDetails(gridX, gridY, gridPropertyDetails, gridPropertyDictionary);
    122. }
    123. private void SetGridPropertyDetails(int gridX, int gridY, GridPropertyDetails gridPropertyDetails, Dictionary<string, GridPropertyDetails> gridPropertyDictionary)
    124. {
    125. string key = "x" + gridX + "y" + gridY;
    126. gridPropertyDetails.gridX = gridX;
    127. gridPropertyDetails.gridY = gridY;
    128. gridPropertyDictionary[key] = gridPropertyDetails;
    129. }
    130. public GridPropertyDetails GetGridPropetyDetails(int gridX, int gridY,Dictionary<string , GridPropertyDetails> gridPropertyDictionary)
    131. {
    132. string key = "x" + gridX + "y" + gridY;
    133. GridPropertyDetails gridPropertyDetails;
    134. if(!gridPropertyDictionary.TryGetValue(key, out gridPropertyDetails))
    135. {
    136. return null;
    137. }
    138. else
    139. {
    140. return gridPropertyDetails;
    141. }
    142. }
    143. ///
    144. /// 重载函数,也是用默认的gridPropertyDictionary来取值
    145. ///
    146. ///
    147. ///
    148. ///
    149. public GridPropertyDetails GetGridPropetyDetails(int gridX, int gridY)
    150. {
    151. return GetGridPropetyDetails(gridX, gridY, gridPropertyDictionary);
    152. }
    153. }

     我们还需要给每个这种判断bool的tilemap创建脚本

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEditor;
    5. using UnityEngine.Tilemaps;
    6. [ExecuteAlways]
    7. public class TilemapGridProperties : MonoBehaviour
    8. {
    9. private Tilemap tilemap;
    10. [SerializeField] private SO_GridProperties gridProperties = null;
    11. [SerializeField] private GridBoolProperty gridBoolProperty = GridBoolProperty.diggable;
    12. private void OnEnable()
    13. {
    14. if (!Application.IsPlaying(gameObject))
    15. {
    16. tilemap = GetComponent();
    17. if(gridProperties != null)
    18. {
    19. gridProperties.gridPropertyList.Clear();
    20. }
    21. }
    22. }
    23. private void OnDisable()
    24. {
    25. if (!Application.IsPlaying(gameObject))
    26. {
    27. UseGridProperties();
    28. if (gridProperties != null)
    29. {
    30. Debug.Log("触发Editor");
    31. EditorUtility.SetDirty(gridProperties);
    32. }
    33. }
    34. }
    35. private void Update()
    36. {
    37. if (!Application.IsPlaying(gameObject))
    38. {
    39. }
    40. }
    41. private void UseGridProperties()
    42. {
    43. tilemap.CompressBounds();
    44. if (!Application.IsPlaying(gameObject))
    45. {
    46. if(gridProperties != null)
    47. {
    48. Vector3Int startCell = tilemap.cellBounds.min;
    49. Vector3Int endCell = tilemap.cellBounds.max;
    50. for (int x = startCell.x; x < endCell.x; x++)
    51. {
    52. for (int y = startCell.y; y < endCell.y; y++)
    53. {
    54. TileBase tile = tilemap.GetTile(new Vector3Int(x, y, 0));
    55. if(tile != null)
    56. {
    57. gridProperties.gridPropertyList.Add(new GridPropetry(new GridCoordinate(x, y), gridBoolProperty, true));
    58. }
    59. }
    60. }
    61. }
    62. }
    63. }
    64. }

    注意我们上上篇讲的特性【ExcuteAlways】

     然后回到Unity激活好我们创建的Grid Properties,

    这五个Bool都有Tilemap组件,以canDropItem为例。

    这里我们添加好后,就创建一个数据结构so_GridProperties_Scene1_Farm

    当我们关闭这个gridProperties之后

     

    打开我们创建的数据结构

    ,可见List多达上千条,添加成功 

     可以给其它两个场景也添加上去。

    自此主要功能已经实现了。

     

    添加UI:

      我们还需奥UI知道当放物体的时候是在哪一个grid网格上。

     

    我有绿红两个指针,绿色表示可以放,红色表示不能放,然后创建脚本GridCursor

    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5. public class GridCursor : MonoBehaviour
    6. {
    7. private Canvas canvas;
    8. private Grid grid;
    9. private Camera mainCamera;
    10. [SerializeField] private Image cursorImage = null;
    11. [SerializeField] private RectTransform cursorRectTransform = null;
    12. [SerializeField] private Sprite greenCursor = null;
    13. [SerializeField] private Sprite redCursor = null;
    14. private bool _cursorPositionIsValid = false;
    15. public bool CursorPositionIsValid
    16. {
    17. get => _cursorPositionIsValid;
    18. set => _cursorPositionIsValid = value;
    19. }
    20. private int _itemUseGridRadius = 0;
    21. public int ItemUseGridRaudius
    22. {
    23. get => _itemUseGridRadius;
    24. set => _itemUseGridRadius = value;
    25. }
    26. private ItemType _selectedItemType;
    27. public ItemType SelectedItemType
    28. {
    29. get => _selectedItemType;
    30. set => _selectedItemType = value;
    31. }
    32. private bool _cursorIsEnabled = false;
    33. public bool CursorIsEnabled
    34. {
    35. get => _cursorIsEnabled;
    36. set => _cursorIsEnabled = value;
    37. }
    38. private void Start()
    39. {
    40. mainCamera = Camera.main;
    41. canvas = GetComponentInParent();
    42. }
    43. private void OnEnable()
    44. {
    45. EventHandler.AfterSceneLoadEvent += AfterSceneLoaded;
    46. }
    47. private void OnDisable()
    48. {
    49. EventHandler.AfterSceneLoadEvent -= AfterSceneLoaded;
    50. }
    51. private void Update()
    52. {
    53. if (CursorIsEnabled)
    54. DisplayCursor();
    55. }
    56. private Vector3Int DisplayCursor()
    57. {
    58. if(grid != null)
    59. {
    60. Vector3Int gridPosition = GetGridPositionForCurosr();
    61. Vector3Int playerPosition = GetGridPositionForPlayer();
    62. SetCursorValidity(gridPosition ,playerPosition);
    63. cursorRectTransform.position = GetRectTransformForCursor(gridPosition);
    64. return gridPosition;
    65. }
    66. else
    67. {
    68. return Vector3Int.zero;
    69. }
    70. }
    71. private Vector3 GetRectTransformForCursor(Vector3Int gridPosition)
    72. {
    73. Vector3 gridWorldPostion = grid.CellToWorld(gridPosition);
    74. Vector2 gridScreenPosition = mainCamera.WorldToScreenPoint(gridWorldPostion);
    75. return RectTransformUtility.PixelAdjustPoint(gridScreenPosition, cursorRectTransform, canvas);
    76. }
    77. private void SetCursorValidity(Vector3Int cursorGridPosition, Vector3Int playerGridPosition)
    78. {
    79. SetCursorToValid();
    80. if(Mathf.Abs(cursorGridPosition.x - playerGridPosition.x) > ItemUseGridRaudius ||
    81. Mathf.Abs(cursorGridPosition.y - playerGridPosition.y) > ItemUseGridRaudius)
    82. {
    83. SetCursorToInValid();
    84. return;
    85. }
    86. ItemDetails itemDetails = InventoryManager.Instance.GetSelectedInventoryItemDetails(InventoryLocation.player);
    87. if(itemDetails == null)
    88. {
    89. SetCursorToInValid();
    90. return;
    91. }
    92. GridPropertyDetails gridPropertyDetails = GridPropertiesManager.Instance.GetGridPropetyDetails(cursorGridPosition.x, cursorGridPosition.y);
    93. if(gridPropertyDetails != null)
    94. {
    95. switch (itemDetails.itemType)
    96. {
    97. case ItemType.Seed:
    98. if (!IsCursorValidToSeed(gridPropertyDetails))
    99. {
    100. SetCursorToInValid();
    101. return;
    102. }
    103. break;
    104. case ItemType.Commodity:
    105. if (!IsCursorValidToCommidity(gridPropertyDetails))
    106. {
    107. SetCursorToInValid();
    108. return;
    109. }
    110. break;
    111. case ItemType.Hoeing_tool:
    112. if (!IsCursorValidToHoe(gridPropertyDetails,itemDetails))
    113. {
    114. SetCursorToInValid();
    115. return;
    116. }
    117. break;
    118. case ItemType.None:
    119. break;
    120. case ItemType.Count:
    121. break;
    122. default:
    123. break;
    124. }
    125. }
    126. else
    127. {
    128. SetCursorToInValid();
    129. return;
    130. }
    131. }
    132. private bool IsCursorValidToHoe(GridPropertyDetails gridPropertyDetails,ItemDetails itemDetails)
    133. {
    134. switch (itemDetails.itemType)
    135. {
    136. case ItemType.Hoeing_tool:
    137. if(gridPropertyDetails.isDiggable && gridPropertyDetails.daysSinceDug == -1)
    138. {
    139. Vector3 cursorWorldPostiin = new Vector3(GetCursorWorldPosition().x + 0.5f, GetCursorWorldPosition().y + 0.5f, 0f);
    140. List itemList = new List();
    141. HelperMethod.GetComponenetsAtBoxLocation(out itemList,cursorWorldPostiin,Settings.cursorSize,0f);
    142. bool foundRepable = false;
    143. foreach (Item item in itemList)
    144. {
    145. if(InventoryManager.Instance.GetItemDetails(item.ItemCode).itemType == ItemType.Reapable_scenery)
    146. {
    147. foundRepable = true;
    148. break;
    149. }
    150. }
    151. if(foundRepable)
    152. {
    153. return false;
    154. }
    155. else
    156. {
    157. return true;
    158. }
    159. }
    160. else
    161. {
    162. return false;
    163. }
    164. default:
    165. return false;
    166. }
    167. }
    168. private Vector3 GetCursorWorldPosition()
    169. {
    170. return grid.CellToWorld(GetGridPositionForCurosr());
    171. }
    172. private bool IsCursorValidToCommidity(GridPropertyDetails gridPropertyDetails)
    173. {
    174. return gridPropertyDetails.canDropItem;
    175. }
    176. private bool IsCursorValidToSeed(GridPropertyDetails gridPropertyDetails)
    177. {
    178. return gridPropertyDetails.canDropItem;
    179. }
    180. private void SetCursorToValid()
    181. {
    182. cursorImage.sprite = greenCursor;
    183. CursorPositionIsValid = true;
    184. }
    185. private void SetCursorToInValid()
    186. {
    187. cursorImage.sprite = redCursor;
    188. CursorPositionIsValid = false;
    189. }
    190. public Vector3Int GetGridPositionForPlayer()
    191. {
    192. return grid.WorldToCell(PlayerController.Instance.transform.position);
    193. }
    194. public Vector3Int GetGridPositionForCurosr()
    195. {
    196. Vector3 worldPosition = mainCamera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, -mainCamera.transform.position.z));
    197. return grid.WorldToCell(worldPosition);
    198. }
    199. private void AfterSceneLoaded()
    200. {
    201. grid = GameObject.FindObjectOfType();
    202. }
    203. public void EnableCursor()
    204. {
    205. cursorImage.color = new Color(1f, 1f, 1f, 1f);
    206. CursorIsEnabled = true;
    207. }
    208. public void DisableCursor()
    209. {
    210. cursorImage.color = Color.clear;
    211. CursorIsEnabled = false;
    212. }
    213. }

    在PlayerController脚本上,当点击的时候就去出

    1. private void PlayerClickInput()
    2. {
    3. if (!playerToolDisabled)
    4. {
    5. if (Input.GetMouseButtonDown(0))
    6. {
    7. if (gridCursor.CursorIsEnabled)
    8. {
    9. ProcessPlayerClickInput(cursorGridPosition,playerGridPosition);
    10. }
    11. }
    12. }
    13. }
    14. private void ProcessPlayerClickInput(Vector3Int cursorGridPosition,Vector3Int playerGridPosition)
    15. {
    16. ResetMovement();
    17. Vector3Int playerDirection = GetPlayerClickPosition(cursorGridPosition, playerGridPosition);
    18. GridPropertyDetails gridPropertyDetails = GridPropertiesManager.Instance.GetGridPropetyDetails(cursorGridPosition.x, cursorGridPosition.y);
    19. ItemDetails itemDetails = InventoryManager.Instance.GetSelectedInventoryItemDetails(InventoryLocation.player);
    20. if(itemDetails != null)
    21. {
    22. switch (itemDetails.itemType)
    23. {
    24. case ItemType.Seed:
    25. if (Input.GetMouseButtonDown(0))
    26. {
    27. ProcessPlayerClickInputSeed(itemDetails);
    28. }
    29. break;
    30. case ItemType.Commodity:
    31. if (Input.GetMouseButtonDown(0))
    32. {
    33. ProcessPlayerClickInputCommodity(itemDetails);
    34. }
    35. break;
    36. case ItemType.None:
    37. break;
    38. case ItemType.Count:
    39. break;
    40. default:
    41. break;
    42. }
    43. }
    44. }
    45. private Vector3Int GetPlayerClickPosition(Vector3Int cursorGridPosition, Vector3Int playerGridPosition)
    46. {
    47. if(cursorGridPosition.x > playerGridPosition.x)
    48. {
    49. return Vector3Int.right;
    50. }
    51. else if(cursorGridPosition.x < playerGridPosition.x)
    52. {
    53. return Vector3Int.left;
    54. }
    55. else if(cursorGridPosition.y > playerGridPosition.y)
    56. {
    57. return Vector3Int.up;
    58. }
    59. else
    60. {
    61. return Vector3Int.down;
    62. }
    63. }
    64. private void ProcessPlayerClickInputSeed(ItemDetails itemDetails)
    65. {
    66. if(itemDetails.canBeDropped && gridCursor.CursorPositionIsValid)
    67. {
    68. EventHandler.CallDropItemSelectedEvent();
    69. }
    70. }
    71. private void ProcessPlayerClickInputCommodity(ItemDetails itemDetails)
    72. {
    73. if (itemDetails.canBeDropped && gridCursor.CursorPositionIsValid)
    74. {
    75. EventHandler.CallDropItemSelectedEvent();
    76. }
    77. }

     别忘了添加上去


    学习产出:

     

     

  • 相关阅读:
    机器学习笔记之线性分类——逻辑回归
    Pygame入门 2022 (1)
    Linux的基本命令
    dsu on tree
    消息队列 记录
    【FPGA零基础学习之旅#13】串口发送模块设计与验证
    区域地表蒸散发及其组分(土壤蒸发、植被蒸腾、冠层截留蒸发)、植被总初级生产力数据的下载、处理、显示与统计
    还不知道产品帮助中心怎样制作?,来看看这个吧
    婴儿摇椅出口亚马逊欧盟CE认证检测标准
    Windows10 WSL Ubuntu root 密码重置
  • 原文地址:https://blog.csdn.net/dangoxiba/article/details/126906093