You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 20:24:55