260 lines
12 KiB
C#
260 lines
12 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
namespace _Project.Scripts.LevelGenerating
|
||
{
|
||
public class ShellResult
|
||
{
|
||
public Transform Root;
|
||
public List<RoomView> Views = new List<RoomView>();
|
||
public List<DoorController> AllDoors = new List<DoorController>();
|
||
}
|
||
|
||
public class ShellSpawner : MonoBehaviour
|
||
{
|
||
[SerializeField] private GameObject planePrefab;
|
||
[SerializeField] private float planeBaseSize = 10f;
|
||
[SerializeField] private float wallHeight = 3f;
|
||
[SerializeField] private float colliderThickness = 0.2f;
|
||
|
||
[Header("Двери и проёмы")]
|
||
[Tooltip("Префаб двойной двери (с компонентом DoorController)")]
|
||
[SerializeField] private GameObject doorPrefab;
|
||
[Tooltip("Высота дверного проёма в метрах. От doorHeight до wallHeight будет построена перемычка с текстурой комнаты.")]
|
||
[SerializeField] private float doorHeight = 2.2f;
|
||
[Tooltip("Толщина перемычки над дверью в метрах (0 = плоская, >0 = объёмная с нижней гранью над головой)")]
|
||
[Min(0f)] [SerializeField] private float lintelThickness = 0.3f;
|
||
[Tooltip("Дополнительный поворот префаба двери (по умолчанию 180 по Y)")]
|
||
[SerializeField] private Vector3 doorRotationOffset = new Vector3(0f, 180f, 0f);
|
||
[Tooltip("Локальное смещение двери относительно проёма (X - вдоль стены, Y - высота, Z - вперёд/назад)")]
|
||
[SerializeField] private Vector3 doorPositionOffset = Vector3.zero;
|
||
|
||
public ShellResult Spawn(FloorData floor)
|
||
{
|
||
if (planePrefab == null)
|
||
{
|
||
Debug.LogError("ShellSpawner: не назначен planePrefab");
|
||
return null;
|
||
}
|
||
|
||
var result = new ShellResult
|
||
{
|
||
Root = new GameObject($"Floor_{floor.Index}_Shell").transform
|
||
};
|
||
|
||
int groundLayer = LayerMask.NameToLayer("Ground");
|
||
int ceilingLayer = LayerMask.NameToLayer("Ceiling");
|
||
|
||
// 1. Объект на каждую комнату + пол и потолок
|
||
for (int i = 0; i < floor.Rooms.Count; i++)
|
||
{
|
||
var room = floor.Rooms[i];
|
||
|
||
var roomGo = new GameObject($"Room_{i}_{room.rt}");
|
||
roomGo.transform.SetParent(result.Root, false);
|
||
|
||
var view = roomGo.AddComponent<RoomView>();
|
||
view.Data = room;
|
||
view.Index = i;
|
||
view.Height = wallHeight;
|
||
result.Views.Add(view);
|
||
|
||
var scale = new Vector3(view.SizeMeters.x / planeBaseSize, 1f, view.SizeMeters.z / planeBaseSize);
|
||
Vector3 c = view.CenterMeters;
|
||
|
||
// пол: нормаль вверх
|
||
var floorGo = Make(roomGo.transform, "Floor", new Vector3(c.x, 0f, c.z),
|
||
Quaternion.identity, scale, true);
|
||
if (groundLayer != -1) floorGo.layer = groundLayer;
|
||
view.FloorRenderers.AddRange(floorGo.GetComponentsInChildren<Renderer>());
|
||
|
||
// потолок: перевёрнут, нормаль вниз
|
||
var ceilingGo = Make(roomGo.transform, "Ceiling", new Vector3(c.x, wallHeight, c.z),
|
||
Quaternion.Euler(180f, 0f, 0f), scale, false);
|
||
if (ceilingLayer != -1) ceilingGo.layer = ceilingLayer;
|
||
view.CeilingRenderers.AddRange(ceilingGo.GetComponentsInChildren<Renderer>());
|
||
}
|
||
|
||
// 2. Сплошные стены раскладываются по комнатам-владельцам
|
||
foreach (var run in floor.Walls)
|
||
{
|
||
if (run.RoomIndex < 0 || run.RoomIndex >= result.Views.Count)
|
||
{
|
||
Debug.LogWarning($"Стена ссылается на несуществующую комнату {run.RoomIndex}");
|
||
continue;
|
||
}
|
||
SpawnWallPiece(result.Views[run.RoomIndex], run.Start, run.Length, run.Facing, 0f, wallHeight, 0f, "Wall");
|
||
}
|
||
|
||
// 3. Дверные проёмы: объёмная перемычка над дверью + сама двойная дверь
|
||
foreach (var dw in floor.Doorways)
|
||
{
|
||
if (dw.RoomIndex < 0 || dw.RoomIndex >= result.Views.Count) continue;
|
||
SpawnDoorway(result.Views[dw.RoomIndex], dw, result);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private void SpawnDoorway(RoomView view, Doorway dw, ShellResult result)
|
||
{
|
||
// 1. Объёмная перемычка над дверью (от doorHeight до wallHeight)
|
||
float topWallHeight = wallHeight - doorHeight;
|
||
if (topWallHeight > 0.01f)
|
||
{
|
||
SpawnLintel(view, dw, doorHeight, topWallHeight);
|
||
}
|
||
|
||
// 2. Спавн префаба двойной двери на каждом проёме комнаты
|
||
if (!dw.SpawnDoor || doorPrefab == null) return;
|
||
|
||
float len = dw.Length * LevelGrid.Module;
|
||
float sx = dw.Start.x * LevelGrid.Module;
|
||
float sz = dw.Start.y * LevelGrid.Module;
|
||
|
||
Vector3 basePos;
|
||
Quaternion baseRot;
|
||
|
||
switch (dw.Facing)
|
||
{
|
||
case WallFacing.ZP: // южная стена, нормаль в +Z
|
||
basePos = new Vector3(sx + len / 2f, 0f, sz);
|
||
baseRot = Quaternion.Euler(0f, 0f, 0f);
|
||
break;
|
||
case WallFacing.ZN: // северная стена, нормаль в -Z
|
||
basePos = new Vector3(sx + len / 2f, 0f, sz);
|
||
baseRot = Quaternion.Euler(0f, 180f, 0f);
|
||
break;
|
||
case WallFacing.XP: // западная стена, нормаль в +X
|
||
basePos = new Vector3(sx, 0f, sz + len / 2f);
|
||
baseRot = Quaternion.Euler(0f, 90f, 0f);
|
||
break;
|
||
default: // XN: восточная стена, нормаль в -X
|
||
basePos = new Vector3(sx, 0f, sz + len / 2f);
|
||
baseRot = Quaternion.Euler(0f, -90f, 0f);
|
||
break;
|
||
}
|
||
|
||
Quaternion finalRot = baseRot * Quaternion.Euler(doorRotationOffset);
|
||
Vector3 finalPos = basePos + finalRot * doorPositionOffset;
|
||
|
||
var doorGo = Instantiate(doorPrefab, finalPos, finalRot, view.transform);
|
||
doorGo.name = $"DoubleDoor_{dw.Facing}";
|
||
|
||
var controller = doorGo.GetComponent<DoorController>();
|
||
if (controller == null) controller = doorGo.AddComponent<DoorController>();
|
||
|
||
view.Doors.Add(controller);
|
||
result.AllDoors.Add(controller);
|
||
}
|
||
|
||
// Строит перемычку над дверью с учётом толщины lintelThickness:
|
||
// лицевую вертикальную стенку (сдвинутую на halfThick внутрь комнаты) и нижнюю горизонтальную грань
|
||
private void SpawnLintel(RoomView view, Doorway dw, float bottomY, float pieceHeight)
|
||
{
|
||
float halfThick = lintelThickness * 0.5f;
|
||
|
||
// Лицевая вертикальная сторона перемычки
|
||
SpawnWallPiece(view, dw.Start, dw.Length, dw.Facing, bottomY, pieceHeight, halfThick, "Wall_AboveDoor");
|
||
|
||
// Если толщина > 0, закрываем нижний торец перемычки (на высоте doorHeight, нормалью вниз)
|
||
if (halfThick > 0.001f)
|
||
{
|
||
float len = dw.Length * LevelGrid.Module;
|
||
float sx = dw.Start.x * LevelGrid.Module;
|
||
float sz = dw.Start.y * LevelGrid.Module;
|
||
|
||
Vector3 capPos;
|
||
Vector3 capScale;
|
||
|
||
switch (dw.Facing)
|
||
{
|
||
case WallFacing.ZP: // от линии стены в +Z на halfThick
|
||
capPos = new Vector3(sx + len / 2f, bottomY, sz + halfThick / 2f);
|
||
capScale = new Vector3(len / planeBaseSize, 1f, halfThick / planeBaseSize);
|
||
break;
|
||
case WallFacing.ZN: // от линии стены в -Z на halfThick
|
||
capPos = new Vector3(sx + len / 2f, bottomY, sz - halfThick / 2f);
|
||
capScale = new Vector3(len / planeBaseSize, 1f, halfThick / planeBaseSize);
|
||
break;
|
||
case WallFacing.XP: // от линии стены в +X на halfThick
|
||
capPos = new Vector3(sx + halfThick / 2f, bottomY, sz + len / 2f);
|
||
capScale = new Vector3(halfThick / planeBaseSize, 1f, len / planeBaseSize);
|
||
break;
|
||
default: // XN: от линии стены в -X на halfThick
|
||
capPos = new Vector3(sx - halfThick / 2f, bottomY, sz + len / 2f);
|
||
capScale = new Vector3(halfThick / planeBaseSize, 1f, len / planeBaseSize);
|
||
break;
|
||
}
|
||
|
||
// Поворот 180 по X — нормаль смотрит вниз (в проём над дверью)
|
||
var capGo = Make(view.transform, "Wall_AboveDoor_Bottom", capPos,
|
||
Quaternion.Euler(180f, 0f, 0f), capScale, false);
|
||
|
||
int wallLayer = LayerMask.NameToLayer("Wall");
|
||
if (wallLayer != -1) capGo.layer = wallLayer;
|
||
view.WallRenderers.AddRange(capGo.GetComponentsInChildren<Renderer>());
|
||
}
|
||
}
|
||
|
||
private void SpawnWallPiece(RoomView view, Vector2Int startCell, int lengthModules, WallFacing facing,
|
||
float bottomY, float pieceHeight, float forwardOffset, string pieceName)
|
||
{
|
||
float len = lengthModules * LevelGrid.Module;
|
||
float y = bottomY + pieceHeight / 2f;
|
||
float sx = startCell.x * LevelGrid.Module;
|
||
float sz = startCell.y * LevelGrid.Module;
|
||
|
||
Vector3 pos;
|
||
Quaternion rot;
|
||
Vector3 scale;
|
||
|
||
switch (facing)
|
||
{
|
||
case WallFacing.ZP:
|
||
pos = new Vector3(sx + len / 2f, y, sz + forwardOffset);
|
||
rot = Quaternion.Euler(90f, 0f, 0f);
|
||
scale = new Vector3(len / planeBaseSize, 1f, pieceHeight / planeBaseSize);
|
||
break;
|
||
case WallFacing.ZN:
|
||
pos = new Vector3(sx + len / 2f, y, sz - forwardOffset);
|
||
rot = Quaternion.Euler(-90f, 0f, 0f);
|
||
scale = new Vector3(len / planeBaseSize, 1f, pieceHeight / planeBaseSize);
|
||
break;
|
||
case WallFacing.XP:
|
||
pos = new Vector3(sx + forwardOffset, y, sz + len / 2f);
|
||
rot = Quaternion.Euler(0f, 0f, -90f);
|
||
scale = new Vector3(pieceHeight / planeBaseSize, 1f, len / planeBaseSize);
|
||
break;
|
||
default: // XN
|
||
pos = new Vector3(sx - forwardOffset, y, sz + len / 2f);
|
||
rot = Quaternion.Euler(0f, 0f, 90f);
|
||
scale = new Vector3(pieceHeight / planeBaseSize, 1f, len / planeBaseSize);
|
||
break;
|
||
}
|
||
|
||
var go = Make(view.transform, pieceName, pos, rot, scale, true);
|
||
int wallLayer = LayerMask.NameToLayer("Wall");
|
||
if (wallLayer != -1) go.layer = wallLayer;
|
||
view.WallRenderers.AddRange(go.GetComponentsInChildren<Renderer>());
|
||
}
|
||
|
||
private GameObject Make(Transform parent, string name, Vector3 pos, Quaternion rot, Vector3 scale,
|
||
bool withCollider)
|
||
{
|
||
var go = Instantiate(planePrefab, pos, rot, parent);
|
||
go.name = name;
|
||
go.transform.localScale = scale;
|
||
|
||
foreach (var c in go.GetComponents<Collider>()) Destroy(c);
|
||
|
||
if (withCollider)
|
||
{
|
||
var box = go.AddComponent<BoxCollider>();
|
||
box.size = new Vector3(planeBaseSize, colliderThickness, planeBaseSize);
|
||
box.center = new Vector3(0f, -colliderThickness / 2f, 0f);
|
||
}
|
||
return go;
|
||
}
|
||
}
|
||
} |