欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 国际 > 【Unity】网格系统:物体使用网格坐标定位

【Unity】网格系统:物体使用网格坐标定位

2024/11/20 2:41:43 来源:https://blog.csdn.net/weixin_44122117/article/details/143860322  浏览:    关键词:【Unity】网格系统:物体使用网格坐标定位

需求分析

前面物体放置在地板上都是地板任意位置放置,本节开始对物体放置的位置做限制。

  • 建立网格,网格可以设置起始世界坐标、单元格大小和规格;
  • 单元格中包括内部物体的信息;
  • 物体的位置通过网格的坐标确定;
  • 单元格中已经存在物体,该位置不能再放入其他物体;

成果展示

![[obsidian://open?vault=MDnotes&file=gif_%E7%BD%91%E6%A0%BC%E7%B3%BB%E7%BB%9F.gif]]

Scene部分

![[png_网格系统-1.png]]

场景中删除了手动拖入的预制体,改为通过代码将物体在指定网格单元格中。
每个预制体都增加脚本PlaceObject.cs
![[png_网格系统-2.png]]

脚本部分

设计网格类GridXZ

属性:宽度高度、单元格大小、原点世界坐标位置、网格矩阵;
方法:

  • 创建网格(构造函数),需要将每个单元格填充入对应类实例。
  • 世界坐标和网格坐标相互转换,根据世界坐标位置获取网格坐标位置,根据网格坐标获取世界坐标位置。
  • 设置或获取单元格中实例,定义单元格中实例发生变化的响应事件。
public class GridXZ<TGridObject>
{public event EventHandler<OnGridObjectChangedEventArgs> OnGridObjectChanged;public class OnGridObjectChangedEventArgs : EventArgs{public int x;public int z;}private int width;private int height;private float cellSize;private Vector3 originPosition;private TGridObject[,] gridArray;public GridXZ(int width, int height, float cellSize, Vector3 originPosition, Func<GridXZ<TGridObject>, int, int, TGridObject> createGridObject){this.width = width;this.height = height;this.cellSize = cellSize;this.originPosition = originPosition;gridArray = new TGridObject[width, height];for (int x = 0; x < gridArray.GetLength(0); x++){for (int z = 0; z < gridArray.GetLength(1); z++){gridArray[x, z] = createGridObject(this, x, z);}}//绘制了调试线,帮助观察网格的状态#region debugDrawLinebool showDebug = true;if (showDebug){TextMesh[,] debugTextArray = new TextMesh[width, height];for (int x = 0; x < gridArray.GetLength(0); x++){for (int z = 0; z < gridArray.GetLength(1); z++){debugTextArray[x, z] = UtilsClass.CreateWorldText("", null, GetWorldPosition(x, z) + new Vector3(cellSize, 0, cellSize) * .5f,8, Color.white, TextAnchor.MiddleCenter, TextAlignment.Center);Debug.DrawLine(GetWorldPosition(x, z), GetWorldPosition(x, z + 1), Color.white, 100f);Debug.DrawLine(GetWorldPosition(x, z), GetWorldPosition(x + 1, z), Color.white, 100f);}}Debug.DrawLine(GetWorldPosition(0, height), GetWorldPosition(width, height), Color.white, 100f);Debug.DrawLine(GetWorldPosition(width, 0), GetWorldPosition(width, height), Color.white, 100f);OnGridObjectChanged += (object sender, OnGridObjectChangedEventArgs eventArgs) =>{debugTextArray[eventArgs.x, eventArgs.z].text = gridArray[eventArgs.x, eventArgs.z]?.ToString();};}#endregion}public int GetWidth(){return width;}public int GetHeight(){return height;}public float GetCellSize(){return cellSize;}public Vector3 GetWorldPosition(int x, int z){return new Vector3(x, 0, z) * cellSize + originPosition;}public void GetXZ(Vector3 worldPosition, out int x, out int z){x = Mathf.FloorToInt((worldPosition - originPosition).x / cellSize);z = Mathf.FloorToInt((worldPosition - originPosition).z / cellSize);}public void SetGridObject(int x, int z, TGridObject value){if (x >= 0 && z >= 0 && x < width && z < height){gridArray[x, z] = value;TriggerGridObjectChanged(x, z);}}public void TriggerGridObjectChanged(int x, int z){OnGridObjectChanged?.Invoke(this, new OnGridObjectChangedEventArgs { x = x, z = z });}public void SetGridObject(Vector3 worldPosition, TGridObject value){GetXZ(worldPosition, out int x, out int z);SetGridObject(x, z, value);}public TGridObject GetGridObject(int x, int z){if (x >= 0 && z >= 0 && x < width && z < height){return gridArray[x, z];}else{return default(TGridObject);}}public TGridObject GetGridObject(Vector3 worldPosition){int x, z;GetXZ(worldPosition, out x, out z);return GetGridObject(x, z);}public Vector2Int ValidateGridPosition(Vector2Int gridPosition){return new Vector2Int(Mathf.Clamp(gridPosition.x, 0, width - 1),Mathf.Clamp(gridPosition.y, 0, height - 1));}
}

设计单元格内对象GridObject

属性:对应的网格实例、坐标、单元格中可以填充的内容。
针对单元格中可以填充的内容,不同的业务,设计的类可能不同。
本篇中,单元格中是放置处理过的物体预制体,为了统一每个预制体,所有可以放入单元格的预制体会绑定一个脚本PlaceObject
因此设计时,会加入该属性,当单元格中放入物体时,该属性会被赋值。当物体被销毁时,该属性值会被null
放该属性发生变化时,会触发网格类中定义的事件。

public class GridObject
{private GridXZ<GridObject> grid;private int x;private int z;private PlaceObject placeObject;public GridObject(GridXZ<GridObject> grid, int x, int z){this.grid = grid;this.x = x;this.z = z;}public PlaceObject GetPlaceObject(){return placeObject;}public void SetPlaceObject(PlaceObject placeObject){this.placeObject = placeObject;grid.TriggerGridObjectChanged(x, z);}public void ClearPlaceObject(){placeObject = null;grid.TriggerGridObjectChanged(x, z);}public bool CanBuild(){return placeObject == null;}
//可以用来标记单元格中的内容public override string ToString(){return x + "," + z;// + "\n" + placeObject?.goodsName;}
}

单元格中可以填充的内容类PlaceObject

将前面章节中实例化预制体的部分写入该类中。
属性:PlacedObjectTypeSO实例、物体原点坐标、方向、父物体。
可以创建物体、销毁物体;
获取物体所占的所有网格坐标;

public class PlaceObject : MonoBehaviour
{public static PlaceObject Create(Vector3 worldPosition, Vector2Int origin,PlacedObjectTypeSO.Dir dir, PlacedObjectTypeSO placedObjectTypeSO, Transform parent){Transform placeObjectTransform = Instantiate(placedObjectTypeSO.prefab,worldPosition,Quaternion.Euler(0, placedObjectTypeSO.GetRotationAngle(dir), 0));placeObjectTransform.SetParent(parent);placeObjectTransform.gameObject.SetActive(true);PlaceObject placeObject = placeObjectTransform.GetComponent<PlaceObject>();placeObject.origin = origin;placeObject.dir = dir;placeObject.placedObjectTypeSO = placedObjectTypeSO;return placeObject;}private PlacedObjectTypeSO placedObjectTypeSO;private Vector2Int origin;private PlacedObjectTypeSO.Dir dir;public void DestorySelf(){Destroy(gameObject);}public List<Vector2Int> GetGridPositionList(){return placedObjectTypeSO.GetGridPositionList(origin, dir);}public GoodsName goodsName {get {return placedObjectTypeSO.goodsName;}}
}

使用网格

修改PlaceObjectBuilding中网格相关的部分

实例化网格类

//初始状态,每个单元格中都没有物体
grid = new GridXZ<GridObject>(16, 16, 1, new Vector3(0, 0, 0), (GridXZ<GridObject> g, int x, int z) => new GridObject(g, x, z));

放置物体

传入网格坐标、方向、PlacedObjectTypeSO实例;
检查传入坐标单元格及物体需要占据的全部单元格是否已经被占用;
依次给所占的所有单元格中内容赋值;

[! WARNING] 修正物体放置的位置
鼠标选择单元格时,获取的是单元格中的任意位置,而物体放置时位置是以其Anchor为原点的。因此需要将鼠标的点击位置修正为单元格的Anchor,使物体能够刚好放入单元格中。
修正结果需要分别显现在物体放置和物体跟随鼠标两个地方。

Vector3 clickedPosition = Mouse3D.GetMouseWorldPosition();
grid.GetXZ(clickedPosition, out int x, out int z);
Vector2Int rotationOffset = placedObjectTypeSO.GetRotationOffset(dir);
Vector3 placedObjectWorldPosition = grid.GetWorldPosition(x, z) + new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize();

放置物体时,可以通过代码直接放置、也可以点击单元格放置。

private void ExcutePlaceObjectOnGrid(Vector2Int gridPosition, PlacedObjectTypeSO placedObjectTypeSO, Dir dir)
{Vector2Int rotationOffset = placedObjectTypeSO.GetRotationOffset(dir);Vector3 placedObjectWorldPosition = grid.GetWorldPosition(gridPosition.x, gridPosition.y) +new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize();//需要判断当前位置是否能够放置,是否已经被占用List<Vector2Int> locateGridPositions = placedObjectTypeSO.GetGridPositionList(gridPosition, dir);bool canBuild = true;foreach (var item in locateGridPositions){if (!grid.GetGridObject(item.x, item.y).CanBuild()){canBuild = false;break;}}if (canBuild){PlaceObject placeObject = PlaceObject.Create(placedObjectWorldPosition, gridPosition, dir, placedObjectTypeSO, transform.parent);//需要标记对应网格被占用locateGridPositions.ForEach(_ =>{grid.GetGridObject(_.x, _.y).SetPlaceObject(placeObject);});}}//直接代码放置物体
ExcutePlaceObjectOnGrid(new Vector2Int(3, 10), placedObjectTypeSOList[0], Dir.Down);//点击单元格放置物体
private void Update()
{if (selectedPlacedObjectTypeSO != null){if (Input.GetMouseButtonDown(0)){Vector3 placePosition = Mouse3D.GetMouseWorldPosition();grid.GetXZ(placePosition, out int x, out int z);Vector2Int rotationOffset = selectedPlacedObjectTypeSO.GetRotationOffset(dir);if (Mouse3D.GetClickedTransform().parent == transform.parent){ExcutePlaceObjectOnGrid(new Vector2Int(x, z), selectedPlacedObjectTypeSO, dir);DeselectObjectType();}}}
}

删除物体

删除物体也同样需要清除物体所占用的所有单元格中的物体信息。

public void DestroyPlacedObject(PlaceObject placeObject)
{if (placeObject != null){AddGoods(placeObject.goodsName);placeObject.DestorySelf();List<Vector2Int> gridPositionList = placeObject.GetGridPositionList();foreach (var gridPosition in gridPositionList){grid.GetGridObject(gridPosition.x, gridPosition.y).ClearPlaceObject();}}
}

使用删除
业务中,是玩家触碰到物体时,物体被摧毁并放入仓库,因此在Player.cs脚本中修改相关的代码。

public class Player : MonoBehaviour
{private void OnTriggerEnter(Collider c){Transform cProfab = c.transform.parent.parent;if (Enum.TryParse(cProfab.tag, true, out GoodsName goodsName)){PlaceObjectBuilding.Instance.DestroyPlacedObject(cProfab.GetComponent<PlaceObject>());}}
}

完整代码

public class PlaceObjectBuilding : MonoBehaviour
{private void Awake(){Instance = this;inventory = new Inventory(new List<Goods>(), (goods) =>{inventory.DeleteGoods(goods.GetGoodsName());selectedPlacedObjectTypeSO = placedObjectTypeSOList.Find(_ => _.nameString == goods.GetGoodsName().ToString());RefreshSelectedObjectType();});ui_inventory.Init(inventory);grid = new GridXZ<GridObject>(16, 16, 1, new Vector3(0, 0, 0), (GridXZ<GridObject> g, int x, int z) => new GridObject(g, x, z));inventory.AddGoods(placedObjectTypeSOList[1]);inventory.AddGoods(placedObjectTypeSOList[1]);inventory.AddGoods(placedObjectTypeSOList[2]);inventory.AddGoods(placedObjectTypeSOList[3]);inventory.AddGoods(placedObjectTypeSOList[4]);ExcutePlaceObjectOnGrid(new Vector2Int(3, 10), placedObjectTypeSOList[0], Dir.Down);ExcutePlaceObjectOnGrid(new Vector2Int(15, 6), placedObjectTypeSOList[0], Dir.Down);ExcutePlaceObjectOnGrid(new Vector2Int(9, 1), placedObjectTypeSOList[2], Dir.Down);}private void ExcutePlaceObjectOnGrid(Vector2Int gridPosition, PlacedObjectTypeSO placedObjectTypeSO, Dir dir){Vector2Int rotationOffset = placedObjectTypeSO.GetRotationOffset(dir);Vector3 placedObjectWorldPosition = grid.GetWorldPosition(gridPosition.x, gridPosition.y) +new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize();//需要判断当前位置是否能够放置,是否已经被占用List<Vector2Int> locateGridPositions = placedObjectTypeSO.GetGridPositionList(gridPosition, dir);bool canBuild = true;foreach (var item in locateGridPositions){if (!grid.GetGridObject(item.x, item.y).CanBuild()){canBuild = false;break;}}if (canBuild){PlaceObject placeObject = PlaceObject.Create(placedObjectWorldPosition, gridPosition, dir, placedObjectTypeSO, transform.parent);//需要标记对应网格被占用locateGridPositions.ForEach(_ =>{grid.GetGridObject(_.x, _.y).SetPlaceObject(placeObject);});}}private void Update(){if (selectedPlacedObjectTypeSO != null){if (Input.GetMouseButtonDown(0)){Vector3 placePosition = Mouse3D.GetMouseWorldPosition();grid.GetXZ(placePosition, out int x, out int z);Vector2Int rotationOffset = selectedPlacedObjectTypeSO.GetRotationOffset(dir);if (Mouse3D.GetClickedTransform().parent.parent == craftTable.parent){PlaceObject.Create(placePosition + new Vector3(rotationOffset.x, 0, rotationOffset.y), Vector2Int.zero, dir, selectedPlacedObjectTypeSO, craftTable);craftingRecipeSOList.ForEach(_ =>{PlacedObjectTypeSO outGoodsSo = _.GoodsOnTableChanged(selectedPlacedObjectTypeSO);if (outGoodsSo != null){for (int i = 0; i < craftTable.childCount; i++){Destroy(craftTable.GetChild(i).gameObject);}craftingRecipeSOList.ForEach(recipeSo =>{recipeSo.Init();});inventory.AddGoods(outGoodsSo);};});DeselectObjectType();}else if (Mouse3D.GetClickedTransform().parent == transform.parent){ExcutePlaceObjectOnGrid(new Vector2Int(x, z), selectedPlacedObjectTypeSO, dir);DeselectObjectType();}}if (Input.GetKeyDown(KeyCode.Alpha0)){GoodsName goodsName = (GoodsName)Enum.Parse(typeof(GoodsName), selectedPlacedObjectTypeSO.nameString);inventory.AddGoods(selectedPlacedObjectTypeSO);DeselectObjectType();}if (Input.GetKeyDown(KeyCode.R)){dir = GetNextDir(dir);}}}public Vector3 GetMouseWorldSnappedPosition(){Vector3 mousePosition = Mouse3D.GetMouseWorldPosition();if (grid == null) return mousePosition;grid.GetXZ(mousePosition, out int x, out int z);if (selectedPlacedObjectTypeSO != null){Vector2Int rotationOffset = selectedPlacedObjectTypeSO.GetRotationOffset(dir);Vector3 placedObjectWorldPosition = grid.GetWorldPosition(x, z) + new Vector3(rotationOffset.x, 0, rotationOffset.y) * grid.GetCellSize();return placedObjectWorldPosition;}else{return mousePosition;}}public void DestroyPlacedObject(PlaceObject placeObject){if (placeObject != null){AddGoods(placeObject.goodsName);placeObject.DestorySelf();List<Vector2Int> gridPositionList = placeObject.GetGridPositionList();foreach (var gridPosition in gridPositionList){grid.GetGridObject(gridPosition.x, gridPosition.y).ClearPlaceObject();}}}
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com