Подземелье: тёмные комнаты-телепорты вместо лестниц

- Убраны процедурные лестницы
- По одной тёмной комнате на этаж (затемнение + фиолетовое портальное свечение)
- Персонаж идёт в тёмную комнату своего этажа и телепортируется в тёмную комнату
  случайного другого этажа

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-08-03 17:17:34 +03:00
co-authored by Claude Opus 4.8
parent 46fdb230fe
commit 2a629adf2c
2 changed files with 48 additions and 58 deletions
+5 -3
View File
@@ -26,9 +26,11 @@ D:\Godot_v4.7.1\Godot_v4.7.1-stable_mono_win64.exe
Главное меню (`scenes/MainMenu.tscn`) ведёт на три сцены (назад в меню — `Esc`): Главное меню (`scenes/MainMenu.tscn`) ведёт на три сцены (назад в меню — `Esc`):
1. **Подземелье** (`scenes/Dungeon.tscn`) — разрез убежища à la Fallout Shelter: сетка 6×5 1. **Подземелье** (`scenes/Dungeon.tscn`) — разрез убежища à la Fallout Shelter: сетка 6×5
комнат, **лестницы** между этажами, **объекты** (фонари/свет) и **несколько персонажей**, комнат, **тёмные комнаты-телепорты** (по одной на этаж — персонаж заходит и
которые ходят по этажам (нативные сцены с анимацией `walk_0`/`idle_0`). переносится на другой этаж), **объекты** (фонари/свет) и **несколько персонажей**,
ЛКМ/WASD — панорама, колесо — зум, `R` — сброс. которые ходят по этажам (нативные сцены, `walk_0`/`idle_0`).
ЛКМ по персонажу — выбрать/тащить (перенос в комнату), ЛКМ по фону/WASD — панорама,
колесо — зум, `R` — сброс.
2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений 2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений
(UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум. (UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум.
3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно: 3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно:
+43 -55
View File
@@ -19,8 +19,8 @@ public partial class RoomMap : Node2D
private float _viewZoom = 1f; private float _viewZoom = 1f;
private Vector2 _homePos; private Vector2 _homePos;
private static readonly float[] LadderXs = { 2f * Cell, 4f * Cell };
private static float FloorY(int row) => row * Cell + Cell * 0.9f; private static float FloorY(int row) => row * Cell + Cell * 0.9f;
private float[] _darkX; // X центра тёмной комнаты-телепорта на каждом этаже
private readonly List<Pawn> _pawns = new(); private readonly List<Pawn> _pawns = new();
private System.Random _rng; private System.Random _rng;
@@ -30,15 +30,15 @@ public partial class RoomMap : Node2D
private PanelContainer _infoPanel; private PanelContainer _infoPanel;
private Label _infoLabel; private Label _infoLabel;
private enum PState { Idle, Walk, GoLadder, Climb, Held } private enum PState { Idle, Walk, GoDark, Held }
private class Pawn private class Pawn
{ {
public Node2D Node; public Node2D Node;
public AnimationPlayer Ap; public AnimationPlayer Ap;
public string Name; public string Name;
public int Row, TargetRow; public int Row;
public float X, TargetX, TargetY, Speed, MinX, MaxX, IdleTimer, LadderX; public float X, TargetX, Speed, MinX, MaxX, IdleTimer;
public PState State; public PState State;
public string WalkAnim, IdleAnim; public string WalkAnim, IdleAnim;
} }
@@ -113,28 +113,27 @@ public partial class RoomMap : Node2D
for (int c = 0; c < Cols; c++) for (int c = 0; c < Cols; c++)
AddCell("res://assets/room_sliced/Frame.png", c, r, 1, 1, new Color(1, 1, 1, 0.5f)); AddCell("res://assets/room_sliced/Frame.png", c, r, 1, 1, new Color(1, 1, 1, 0.5f));
BuildStairs(); BuildDarkRooms();
BuildObjects(); BuildObjects();
BuildPawns(); BuildPawns();
} }
// ---------- лестницы (вертикальные шахты с перекладинами) ---------- // ---------- тёмные комнаты-телепорты (по одной на этаж) ----------
private void BuildStairs() private void BuildDarkRooms()
{ {
AddLadder(2 * Cell); int[] cols = { 5, 0, 5, 0, 5 }; // столбец тёмной комнаты для каждого этажа (в шахматку по бокам)
AddLadder(4 * Cell); _darkX = new float[Rows];
} for (int r = 0; r < Rows; r++)
{
private void AddLadder(float centerX) int c = cols[r % cols.Length];
{ _darkX[r] = c * Cell + Cell * 0.5f;
const float w = 48f; // затемнение интерьера ячейки
float x = centerX - w / 2f; AddRect(new Rect2(c * Cell + 8, r * Cell + 8, Cell - 16, Cell - 16), new Color(0.02f, 0.02f, 0.05f, 0.93f));
float h = Rows * Cell; // портальное свечение по центру
AddRect(new Rect2(x, 0, w, h), new Color(0.10f, 0.08f, 0.06f, 0.9f)); // шахта AddTex("res://assets/room_sliced/Light.png",
AddRect(new Rect2(x + 3, 0, 7, h), new Color(0.45f, 0.30f, 0.17f)); // рельс слева new Rect2(c * Cell + Cell * 0.28f, r * Cell + Cell * 0.28f, Cell * 0.44f, Cell * 0.44f),
AddRect(new Rect2(x + w - 10, 0, 7, h), new Color(0.45f, 0.30f, 0.17f)); // рельс справа _world, new Color(0.5f, 0.4f, 1f, 0.55f));
for (float ry = 22; ry < h; ry += 40) }
AddRect(new Rect2(x + 6, ry, w - 12, 8), new Color(0.55f, 0.38f, 0.20f)); // ступени
} }
private void AddRect(Rect2 r, Color c) private void AddRect(Rect2 r, Color c)
@@ -215,17 +214,13 @@ public partial class RoomMap : Node2D
private void PlayWalk(Pawn p) { if (p.Ap != null && p.WalkAnim != null) p.Ap.Play(p.WalkAnim); } private void PlayWalk(Pawn p) { if (p.Ap != null && p.WalkAnim != null) p.Ap.Play(p.WalkAnim); }
private void PlayIdle(Pawn p) { if (p.Ap != null && p.IdleAnim != null) p.Ap.Play(p.IdleAnim); } private void PlayIdle(Pawn p) { if (p.Ap != null && p.IdleAnim != null) p.Ap.Play(p.IdleAnim); }
// решить, что делать после простоя: гулять по этажу или лезть на другой этаж // решить, что делать после простоя: гулять по этажу или уйти в тёмную комнату (телепорт)
private void Decide(Pawn p) private void Decide(Pawn p)
{ {
bool canUp = p.Row > 0, canDown = p.Row < Rows - 1; if (Rows > 1 && _rng.NextDouble() < 0.45)
if (_rng.NextDouble() < 0.5 && (canUp || canDown))
{ {
p.LadderX = LadderXs[_rng.Next(LadderXs.Length)]; p.TargetX = _darkX[p.Row];
int dir = (canUp && canDown) ? (_rng.NextDouble() < 0.5 ? -1 : 1) : (canUp ? -1 : 1); p.State = PState.GoDark;
p.TargetRow = p.Row + dir;
p.TargetX = p.LadderX;
p.State = PState.GoLadder;
PlayWalk(p); PlayWalk(p);
} }
else else
@@ -246,41 +241,20 @@ public partial class RoomMap : Node2D
break; break;
case PState.Walk: case PState.Walk:
case PState.GoLadder: case PState.GoDark:
{ {
float tx = p.State == PState.GoLadder ? p.LadderX : p.TargetX; float d = Mathf.Sign(p.TargetX - p.X);
float d = Mathf.Sign(tx - p.X);
p.X += d * p.Speed * dt; p.X += d * p.Speed * dt;
p.Node.Scale = new Vector2(d >= 0 ? 1f : -1f, 1f); p.Node.Scale = new Vector2(d >= 0 ? 1f : -1f, 1f);
p.Node.Position = new Vector2(p.X, FloorY(p.Row)); p.Node.Position = new Vector2(p.X, FloorY(p.Row));
if (Mathf.Abs(tx - p.X) < 4f) if (Mathf.Abs(p.TargetX - p.X) < 4f)
{ {
if (p.State == PState.GoLadder) if (p.State == PState.GoDark) Teleport(p);
{
p.X = p.LadderX;
p.TargetY = FloorY(p.TargetRow);
p.State = PState.Climb;
}
else { p.State = PState.Idle; p.IdleTimer = 0.8f + (float)_rng.NextDouble() * 2.2f; PlayIdle(p); } else { p.State = PState.Idle; p.IdleTimer = 0.8f + (float)_rng.NextDouble() * 2.2f; PlayIdle(p); }
} }
break; break;
} }
case PState.Climb:
{
float cy = p.Node.Position.Y;
cy += Mathf.Sign(p.TargetY - cy) * p.Speed * dt;
p.Node.Position = new Vector2(p.LadderX, cy);
p.Node.Scale = new Vector2(1f, 1f);
if (Mathf.Abs(p.TargetY - cy) < 4f)
{
p.Row = p.TargetRow;
p.Node.Position = new Vector2(p.LadderX, FloorY(p.Row));
p.State = PState.Idle; p.IdleTimer = 0.4f + (float)_rng.NextDouble() * 1.5f; PlayIdle(p);
}
break;
}
case PState.Idle: case PState.Idle:
p.IdleTimer -= dt; p.IdleTimer -= dt;
if (p.IdleTimer <= 0f) Decide(p); if (p.IdleTimer <= 0f) Decide(p);
@@ -289,6 +263,20 @@ public partial class RoomMap : Node2D
} }
} }
// телепорт: из тёмной комнаты своего этажа — в тёмную комнату случайного другого этажа
private void Teleport(Pawn p)
{
int dest = p.Row;
if (Rows > 1) do { dest = _rng.Next(Rows); } while (dest == p.Row);
p.Row = dest;
p.X = _darkX[dest];
p.Node.Position = new Vector2(p.X, FloorY(dest));
p.State = PState.Idle;
p.IdleTimer = 0.5f + (float)_rng.NextDouble() * 1.2f;
PlayIdle(p);
if (_selected == p) ShowInfo(p);
}
private Pawn PawnAt(Vector2 worldPos) private Pawn PawnAt(Vector2 worldPos)
{ {
float w = Cell * 0.5f, h = Cell * 0.85f; float w = Cell * 0.5f, h = Cell * 0.85f;
@@ -359,7 +347,7 @@ public partial class RoomMap : Node2D
private void UpdateLabel() private void UpdateLabel()
{ {
_label.Text = _label.Text =
$"Карта убежища — {Cols}×{Rows} комнат, лестницы, {_pawns.Count} персонажа (ходят по этажам)\n" + $"Карта убежища — {Cols}×{Rows} комнат, тёмные комнаты-телепорты, {_pawns.Count} персонажа\n" +
$"ЛКМ по персонажу — выбрать/тащить · ЛКМ по фону/WASD — панорама · колесо — зум · R — сброс · Esc — меню"; $"ЛКМ по персонажу — выбрать/тащить · ЛКМ по фону/WASD — панорама · колесо — зум · R — сброс · Esc — меню";
} }