Unity技术求助:构建适配屏幕的7×20 GameObject吸附网格(编辑/运行模式)
7×20网格吸附与屏幕适配实现方案
一、编辑模式:关卡多米诺自动对齐工具
实现自定义编辑器脚本,强制多米诺GameObject在编辑时自动吸附到网格,禁止随意放置:
using UnityEditor; using UnityEngine; [CustomEditor(typeof(Domino))] public class DominoGridSnapper : Editor { private const int GridRows = 7; private const int GridCols = 20; public override void OnSceneGUI() { Domino domino = (Domino)target; Transform dominoTransform = domino.transform; // 从全局工具类获取适配后的网格参数 Vector2 cellSize = GridUtility.GetCellWorldSize(); Vector2 gridOrigin = GridUtility.GetGridOrigin(); // 计算当前位置对应的网格坐标 Vector2 localPos = dominoTransform.position - (Vector3)gridOrigin; int col = Mathf.RoundToInt(localPos.x / cellSize.x); int row = Mathf.RoundToInt(localPos.y / cellSize.y); // 限制在7×20网格范围内 col = Mathf.Clamp(col, 0, GridCols - 1); row = Mathf.Clamp(row, 0, GridRows - 1); // 更新到吸附后的世界位置 Vector3 snappedPos = gridOrigin + new Vector2(col * cellSize.x, row * cellSize.y); snappedPos.z = dominoTransform.position.z; if (dominoTransform.position != snappedPos) { Undo.RecordObject(dominoTransform, "Snap Domino to Grid"); dominoTransform.position = snappedPos; } // 绘制网格辅助线(可选,编辑时可视化网格) GridUtility.DrawGridGizmos(); } }
注:Domino为自定义多米诺脚本,仅需挂载在多米诺GameObject上即可生效
二、运行模式:多米诺释放吸附与蚂蚁移动逻辑
1. 多米诺释放吸附
在蚂蚁搬运脚本中,调用网格工具类计算吸附位置:
public class AntController : MonoBehaviour { private Domino carriedDomino; private const int GridRows = 7; private const int GridCols = 20; public void ReleaseDomino() { if (carriedDomino == null) return; Vector2 cellSize = GridUtility.GetCellWorldSize(); Vector2 gridOrigin = GridUtility.GetGridOrigin(); Vector2 localPos = carriedDomino.transform.position - (Vector3)gridOrigin; int col = Mathf.RoundToInt(localPos.x / cellSize.x); int row = Mathf.RoundToInt(localPos.y / cellSize.y); // 限制在网格范围内 col = Mathf.Clamp(col, 0, GridCols - 1); row = Mathf.Clamp(row, 0, GridRows - 1); Vector3 snappedPos = gridOrigin + new Vector2(col * cellSize.x, row * cellSize.y); carriedDomino.transform.position = snappedPos; carriedDomino = null; } }
2. 蚂蚁间隙移动逻辑
蚂蚁仅能在网格节点(多米诺间隙)移动,根据朝向计算目标位置:
public enum Direction { Up, Down, Left, Right } public class AntMovement : MonoBehaviour { public float moveSpeed = 2f; private Vector2 currentNode; // 当前所在网格节点坐标(如(0,0)为网格原点节点) private Direction facingDirection; private const int GridRows = 7; private const int GridCols = 20; void Start() { // 初始化到网格起始节点 currentNode = Vector2.zero; transform.position = GridUtility.GetNodeWorldPosition(currentNode); } public void Move() { Vector2 targetNode = currentNode; switch(facingDirection) { case Direction.Up: targetNode.y += 1; break; case Direction.Down: targetNode.y -= 1; break; case Direction.Left: targetNode.x -= 1; break; case Direction.Right: targetNode.x += 1; break; } // 检查目标节点是否在有效范围(网格节点数为8×21) bool isWithinBounds = targetNode.x >= 0 && targetNode.x <= GridCols && targetNode.y >= 0 && targetNode.y <= GridRows; if (!isWithinBounds) return; // 移动到目标节点世界位置 Vector3 targetWorldPos = GridUtility.GetNodeWorldPosition(targetNode); transform.position = Vector3.MoveTowards(transform.position, targetWorldPos, Time.deltaTime * moveSpeed); // 到达后更新当前节点 if (Vector3.Distance(transform.position, targetWorldPos) < 0.01f) { currentNode = targetNode; } } }
三、屏幕适配核心问题解决(原点+动态单元格大小)
创建全局静态工具类,统一处理网格适配逻辑,彻底解决硬编码和原点定位问题:
using UnityEngine; public static class GridUtility { private const int GridRows = 7; private const int GridCols = 20; private static Camera mainCam; static GridUtility() { mainCam = Camera.main; } // 获取适配屏幕的网格世界大小 public static Vector2 GetGridWorldSize() { float camHeight = 2f * mainCam.orthographicSize; float camWidth = camHeight * mainCam.aspect; float gridAspect = (float)GridCols / GridRows; float targetWidth, targetHeight; // 根据屏幕宽高比选择适配基准,保持7×20的网格比例 if (camWidth / camHeight > gridAspect) { targetHeight = camHeight * 0.9f; // 留10%边距避免贴边 targetWidth = targetHeight * gridAspect; } else { targetWidth = camWidth * 0.9f; targetHeight = targetWidth / gridAspect; } return new Vector2(targetWidth, targetHeight); } // 获取动态计算的单元格世界大小 public static Vector2 GetCellWorldSize() { Vector2 gridSize = GetGridWorldSize(); return new Vector2(gridSize.x / GridCols, gridSize.y / GridRows); } // 获取网格原点(左下角)的世界坐标 public static Vector2 GetGridOrigin() { Vector2 gridSize = GetGridWorldSize(); Vector2 camCenter = mainCam.transform.position; // 以屏幕中心为基准,将网格居中放置 return camCenter - gridSize / 2f; } // 获取网格节点的世界坐标 public static Vector3 GetNodeWorldPosition(Vector2 nodeCoords) { Vector2 cellSize = GetCellWorldSize(); Vector2 gridOrigin = GetGridOrigin(); return new Vector3(gridOrigin.x + nodeCoords.x * cellSize.x, gridOrigin.y + nodeCoords.y * cellSize.y, 0); } // 编辑模式下绘制网格辅助线 [DrawGizmo(GizmoType.Selected | GizmoType.NonSelected)] public static void DrawGridGizmos() { Vector2 gridSize = GetGridWorldSize(); Vector2 cellSize = GetCellWorldSize(); Vector2 gridOrigin = GetGridOrigin(); // 绘制竖线 for (int col = 0; col <= GridCols; col++) { float x = gridOrigin.x + col * cellSize.x; Gizmos.DrawLine(new Vector3(x, gridOrigin.y, 0), new Vector3(x, gridOrigin.y + gridSize.y, 0)); } // 绘制横线 for (int row = 0; row <= GridRows; row++) { float y = gridOrigin.y + row * cellSize.y; Gizmos.DrawLine(new Vector3(gridOrigin.x, y, 0), new Vector3(gridOrigin.x + gridSize.x, y, 0)); } } }
核心适配说明
- 动态单元格大小:根据相机正交尺寸和屏幕宽高比,自动计算适配的网格大小,严格保持7×20的比例,彻底避免硬编码导致的屏幕适配失效。
- 原点定位:以屏幕中心为基准,将网格居中放置,原点为网格左下角,确保不同屏幕尺寸下网格始终处于可视区域内。
- 边距预留:计算网格大小时预留10%边距,避免网格元素贴到屏幕边缘,提升视觉体验。
内容的提问来源于stack exchange,提问作者Milan
相关产品推荐
相关产品推荐

