Prefab, не выровняясь с квадратами сетки при вращении в системе строительства на основе сеткиC#

Место общения программистов C#
Ответить
Anonymous
 Prefab, не выровняясь с квадратами сетки при вращении в системе строительства на основе сетки

Сообщение Anonymous »

Я строю систему размещения/строительства на основе сетки в 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), и система выделения показывает, какие ячейки сетки будут заняты.

Код: Выделить всё

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°

void Update()
{
// Rotate input
if (Keyboard.current.rKey.wasPressedThisFrame) rotation = (rotation + 1) % 4;
if (Keyboard.current.qKey.wasPressedThisFrame) rotation = (rotation + 3) % 4;

// 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;
}

// Get target cell
Vector2Int gridPos = grid.WorldToGrid(hit.point);
Vector3 basePos = grid.GridToWorld(gridPos.x, gridPos.y);
float cs = grid.cellSize;

// Create preview if needed
if (previewContainer == null)
{
previewContainer = new GameObject("PreviewPivot");
previewMesh = Instantiate(buildingPrefab, previewContainer.transform);
gridData = previewMesh.GetComponent
();
// Move mesh so its gridOffset aligns with pivot
previewMesh.transform.localPosition = -gridData.gridOffset;
SetAlpha(previewMesh, 0.5f);
}
previewContainer.SetActive(true);

// Set container position (no rotation here)
previewContainer.transform.position = basePos;
previewContainer.transform.rotation = Quaternion.identity;

// Apply mesh rotation about its gridOffset pivot
Quaternion rotQuat = Quaternion.Euler(0, rotation * 90f, 0);
previewMesh.transform.localRotation = rotQuat;

// Compute rotated mask cells
var rotatedCells = GetRotatedCells(
gridData.occupiedCells,
rotation,
gridData.maskWidth,
gridData.maskHeight
);

// 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;
}
}
}
> https://i.sstatic.net/hcnpakoy.>

Подробнее здесь: https://stackoverflow.com/questions/797 ... ing-system
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C#»