Souljack/Assets/_Project/Scripts/LevelGenerating/ShellSpawner.cs

260 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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