Я строю систему размещения/строительства на основе сетки в Unity. У меня есть сборки с разными формами и размерами - некоторые длинные, некоторые широкие, и каждый префаб описывает свою собственную маску сетки. Я использую систему выделения, чтобы показать, где будет размещен префаб, с «Quad» накладками, которые соответствуют занятой ячейкам сетки. Выравнивание верно при 0 °, но как только я поворачиваю префабу, сетка и яркие квадраты становятся смещенными, особенно для асимметричных/прямоугольных префабов (например, 1x3 или 2x4 фигуры). Выделенные квадраты сетки (Mask Center) с его поворотом в правильном месте. Я хочу решить выравнивание префаба только при вращении. Совместите, без взломов для перенапряжения?
public class GridManager : MonoBehaviour
{
public int width = 20;
public int height = 20;
public float cellSize = 1f;
private bool[,] occupied;
void Awake()
{
occupied = new bool[width, height];
}
public Vector2Int WorldToGrid(Vector3 pos)
{
int x = Mathf.FloorToInt(pos.x / cellSize);
int y = Mathf.FloorToInt(pos.z / cellSize);
return new Vector2Int(x, y);
}
public Vector3 GridToWorld(int x, int y)
{
// Sol-alt köşe
return new Vector3(x * cellSize, 0, y * cellSize);
}
public bool IsInBounds(int x, int y)
{
return x >= 0 && x < width && y >= 0 && y < height;
}
public bool CanPlaceObject(int x, int y)
{
return IsInBounds(x, y) && !occupied[x, y];
}
public void OccupyCell(int x, int y)
{
if (IsInBounds(x, y))
occupied[x, y] = true;
}
#if UNITY_EDITOR
private void OnDrawGizmos()
{
Gizmos.color = Color.gray;
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
Vector3 pos = GridToWorld(x, y);
Gizmos.DrawWireCube(pos + new Vector3(cellSize / 2, 0, cellSize / 2), new Vector3(cellSize, 0.02f, cellSize));
}
}
#endif
}
prefabgriddata : описывает, какие ячейки сетки занимают префаб (ее форма/маска) и локальное смещение сетки для выравнивания поворота
public class PrefabGridData : MonoBehaviour
{
[Tooltip("Griddeki işgal maskesi. Her Vector2Int = modelin sol-alt köşesinden kaç kare ötede.")]
public List occupiedCells = new List() { new Vector2Int(0,0) };
public Vector3 gridOffset;
public int maskWidth = 3;
public int maskHeight = 3;
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
float cellSize = 1f;
Vector3 basePos = transform.position + gridOffset;
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.2f);
foreach (var cell in occupiedCells)
{
Vector3 cellCenter = basePos +
new Vector3((cell.x + 0.5f) * cellSize, 0.1f, (cell.y + 0.5f) * cellSize);
Gizmos.DrawCube(cellCenter, new Vector3(cellSize, 0.1f, cellSize));
Gizmos.color = Color.blue;
Gizmos.DrawWireCube(cellCenter, new Vector3(cellSize, 0.1f, cellSize));
Gizmos.color = new Color(0.2f, 0.8f, 1f, 0.2f);
}
}
[CustomEditor(typeof(PrefabGridData))]
public class MaskEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
PrefabGridData data = (PrefabGridData)target;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Visual Grid Mask Editor", EditorStyles.boldLabel);
// Temp mask buffer
bool[,] tempMask = new bool[data.maskWidth, data.maskHeight];
foreach (var cell in data.occupiedCells)
if (cell.x >= 0 && cell.x < data.maskWidth && cell.y >= 0 && cell.y < data.maskHeight)
tempMask[cell.x, cell.y] = true;
bool changed = false;
for (int y = data.maskHeight - 1; y >= 0; y--)
{
EditorGUILayout.BeginHorizontal();
for (int x = 0; x < data.maskWidth; x++)
{
bool occupied = tempMask[x, y];
GUIStyle style = new GUIStyle(GUI.skin.button);
style.normal.textColor = occupied ? Color.green : Color.red;
style.fontStyle = occupied ? FontStyle.Bold : FontStyle.Normal;
string label = occupied ? "■" : "□";
if (GUILayout.Button(label, style, GUILayout.Width(30), GUILayout.Height(30)))
{
Undo.RecordObject(data, "Grid Mask Change");
tempMask[x, y] = !tempMask[x, y];
changed = true;
}
}
EditorGUILayout.EndHorizontal();
}
// CANLI güncelleme: Her tıklamada maskeyi direkt güncelle
if (changed)
{
data.occupiedCells.Clear();
for (int x = 0; x < data.maskWidth; x++)
for (int y = 0; y < data.maskHeight; y++)
if (tempMask[x, y])
data.occupiedCells.Add(new Vector2Int(x, y));
EditorUtility.SetDirty(data);
}
}
}
#endif
}
Buildingsystem : обрабатывает и размещает префабов на сетку, включая визуальные моменты и логику вращения. Могут быть размещены сборки любой формы/размера (с маской Prefabgriddata), и система выделения показывает, какие ячейки сетки будут заняты.
Я строю систему размещения/строительства на основе сетки в Unity. У меня есть сборки с разными формами и размерами - некоторые длинные, некоторые широкие, и каждый префаб описывает свою собственную маску сетки. Я использую систему выделения, чтобы показать, где будет размещен префаб, с «Quad» накладками, которые соответствуют занятой ячейкам сетки. Выравнивание верно при 0 °, но как только я поворачиваю префабу, сетка и яркие квадраты становятся смещенными, особенно для асимметричных/прямоугольных префабов (например, 1x3 или 2x4 фигуры). Выделенные квадраты сетки (Mask Center) с его поворотом в правильном месте. Я хочу решить выравнивание префаба только при вращении. Совместите, без взломов для перенапряжения?[code]public class GridManager : MonoBehaviour {
public int width = 20;
public int height = 20;
public float cellSize = 1f;
private bool[,] occupied;
void Awake() { occupied = new bool[width, height]; }
public Vector2Int WorldToGrid(Vector3 pos) { int x = Mathf.FloorToInt(pos.x / cellSize); int y = Mathf.FloorToInt(pos.z / cellSize); return new Vector2Int(x, y); }
public Vector3 GridToWorld(int x, int y) { // Sol-alt köşe return new Vector3(x * cellSize, 0, y * cellSize); }
public bool IsInBounds(int x, int y) { return x >= 0 && x < width && y >= 0 && y < height; }
public bool CanPlaceObject(int x, int y) { return IsInBounds(x, y) && !occupied[x, y]; }
public void OccupyCell(int x, int y) { if (IsInBounds(x, y)) occupied[x, y] = true; }
#if UNITY_EDITOR private void OnDrawGizmos() { Gizmos.color = Color.gray; for (int x = 0; x < width; x++) for (int y = 0; y < height; y++) { Vector3 pos = GridToWorld(x, y); Gizmos.DrawWireCube(pos + new Vector3(cellSize / 2, 0, cellSize / 2), new Vector3(cellSize, 0.02f, cellSize)); } } #endif } [/code] [b] prefabgriddata [/b]: описывает, какие ячейки сетки занимают префаб (ее форма/маска) и локальное смещение сетки для выравнивания поворота [code]public class PrefabGridData : MonoBehaviour { [Tooltip("Griddeki işgal maskesi. Her Vector2Int = modelin sol-alt köşesinden kaç kare ötede.")] public List occupiedCells = new List() { new Vector2Int(0,0) }; public Vector3 gridOffset; public int maskWidth = 3; public int maskHeight = 3;
// CANLI güncelleme: Her tıklamada maskeyi direkt güncelle if (changed) { data.occupiedCells.Clear(); for (int x = 0; x < data.maskWidth; x++) for (int y = 0; y < data.maskHeight; y++) if (tempMask[x, y]) data.occupiedCells.Add(new Vector2Int(x, y)); EditorUtility.SetDirty(data); } } }
#endif } [/code] [b] Buildingsystem [/b]: обрабатывает и размещает префабов на сетку, включая визуальные моменты и логику вращения. Могут быть размещены сборки любой формы/размера (с маской Prefabgriddata), и система выделения показывает, какие ячейки сетки будут заняты. [code]using UnityEngine; using UnityEngine.InputSystem; using System.Collections.Generic;
/// /// BuildingSystem handles previewing and placing prefabs on a grid with arbitrary masks, /// rotating both the prefab mesh and highlights for 0°, 90°, 180°, 270° steps, /// with correct grid-aligned placement without hard-coded offset hacks. /// public class BuildingSystem : MonoBehaviour { public GridManager grid; public GameObject buildingPrefab; public Material highlightValidMat; public Material highlightInvalidMat;
private GameObject previewContainer; private GameObject previewMesh; private PrefabGridData gridData; private List highlightQuads = new List(); private int rotation = 0; // 0..3 => *90°
// Raycast to grid Vector2 mp = Mouse.current.position.ReadValue(); Ray ray = Camera.main.ScreenPointToRay(mp); if (!Physics.Raycast(ray, out RaycastHit hit)) { if (previewContainer != null) previewContainer.SetActive(false); HideHighlights(); return; }
// Show highlights on each rotated cell ShowHighlights(rotatedCells, cs);
// Check if building can be placed bool canBuild = true; foreach (var c in rotatedCells) { Vector3 localPos = new Vector3( c.x * cs + cs / 2f, 0, c.y * cs + cs / 2f ); Vector3 worldPos = previewContainer.transform.TransformPoint(localPos); Vector2Int gp = grid.WorldToGrid(worldPos); if (!grid.CanPlaceObject(gp.x, gp.y)) { canBuild = false; break; } }
// Place on click if (Mouse.current.leftButton.wasPressedThisFrame && canBuild) { // Instantiate at mesh pivot world position Vector3 placePos = previewMesh.transform.position; Instantiate(buildingPrefab, placePos, rotQuat); // Mark occupied cells foreach (var c in rotatedCells) { Vector3 localPos = new Vector3( c.x * cs + cs / 2f, 0, c.y * cs + cs / 2f ); Vector3 worldPos = previewContainer.transform.TransformPoint(localPos); Vector2Int gp = grid.WorldToGrid(worldPos); grid.OccupyCell(gp.x, gp.y); } } }
/// /// Rotates mask cell coordinates clockwise by the given rotation step (0..3). /// List GetRotatedCells( List cells, int rot, int width, int height ) { var result = new List(cells.Count); foreach (var cell in cells) { int x = cell.x; int y = cell.y; Vector2Int rc; switch (rot) { case 1: // 90° rc = new Vector2Int(height - 1 - y, x); break; case 2: // 180° rc = new Vector2Int(width - 1 - x, height - 1 - y); break; case 3: // 270° rc = new Vector2Int(y, width - 1 - x); break; default: // 0° rc = new Vector2Int(x, y); break; } result.Add(rc); } return result; }
void ShowHighlights(List mask, float cellSize) { while (highlightQuads.Count < mask.Count) { var quad = GameObject.CreatePrimitive(PrimitiveType.Quad); quad.transform.parent = previewContainer.transform; quad.transform.localRotation = Quaternion.Euler(90, 0, 0); quad.transform.localScale = new Vector3(cellSize, cellSize, 1); quad.GetComponent().enabled = false; highlightQuads.Add(quad); } for (int i = 0; i < highlightQuads.Count; i++) { var quad = highlightQuads[i]; if (i < mask.Count) { quad.SetActive(true); var c = mask[i]; Vector3 localPos = new Vector3( c.x * cellSize + cellSize / 2f, 0.02f, c.y * cellSize + cellSize / 2f ); quad.transform.localPosition = localPos; Vector3 worldPos = previewContainer.transform.TransformPoint(localPos); Vector2Int gp = grid.WorldToGrid(worldPos); bool valid = grid.CanPlaceObject(gp.x, gp.y); quad.GetComponent().material = valid ? highlightValidMat : highlightInvalidMat; } else quad.SetActive(false); } }
void HideHighlights() { foreach (var q in highlightQuads) q.SetActive(false); }
void SetAlpha(GameObject obj, float alpha) { foreach (var r in obj.GetComponentsInChildren()) { var mat = r.material; Color c = mat.color; c.a = alpha; mat.color = c; mat.SetFloat("_Mode", 3); mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); mat.SetInt("_ZWrite", 0); mat.DisableKeyword("_ALPHATEST_ON"); mat.EnableKeyword("_ALPHABLEND_ON"); mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); mat.renderQueue = 3000; } } } [/code]