diff --git a/README.md b/README.md index 32d33a9..5b082d4 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,9 @@ D:\Godot_v4.7.1\Godot_v4.7.1-stable_mono_win64.exe Главное меню (`scenes/MainMenu.tscn`) ведёт на три сцены (назад в меню — `Esc`): 1. **Подземелье** (`scenes/Dungeon.tscn`) — разрез убежища à la Fallout Shelter: сетка 6×5 - комнат. ЛКМ/WASD — панорама, колесо — зум, `R` — сброс. + комнат, **лестницы** между этажами, **объекты** (фонари/свет) и **несколько персонажей**, + которые ходят по этажам (нативные сцены с анимацией `walk_0`/`idle_0`). + ЛКМ/WASD — панорама, колесо — зум, `R` — сброс. 2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений (UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум. 3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно: diff --git a/project.godot b/project.godot index 675e0c1..93bd5e7 100644 --- a/project.godot +++ b/project.godot @@ -19,6 +19,11 @@ config/icon="res://icon.svg" project/assembly_name="ProjectRR" +[editor] + +version_control/plugin_name="GitPlugin" +version_control/autoload_on_startup=true + [physics] 3d/physics_engine="Jolt Physics" diff --git a/src/RoomMap.cs b/src/RoomMap.cs index db2d878..e1b80d9 100644 --- a/src/RoomMap.cs +++ b/src/RoomMap.cs @@ -1,6 +1,8 @@ +using System.Collections.Generic; using Godot; -// Демо-карта «убежища» в разрезе (à la Fallout Shelter): сетка комнат из игровых текстур. +// Демо-карта «убежища» в разрезе (à la Fallout Shelter): комнаты, лестницы, объекты +// и несколько персонажей, гуляющих по этажам. // ЛКМ-перетаскивание или WASD/стрелки — панорама // колесо мыши — зум, R — сброс вида public partial class RoomMap : Node2D @@ -8,6 +10,8 @@ public partial class RoomMap : Node2D private const float Cell = 256f; private const int Cols = 6; private const int Rows = 5; + private const float CharScale = 0.34f; + private static readonly Vector2 FootAnchor = new Vector2(640f, 604f); // ноги в кадре 1280x720 private Node2D _world; private Label _label; @@ -15,6 +19,18 @@ public partial class RoomMap : Node2D private float _viewZoom = 1f; private Vector2 _homePos; + private readonly List _pawns = new(); + private System.Random _rng; + + private class Pawn + { + public Node2D Node; + public AnimationPlayer Ap; + public float X, TargetX, FloorY, Speed, MinX, MaxX, IdleTimer; + public bool Walking; + public string WalkAnim, IdleAnim; + } + public override void _Ready() { var bg = new ColorRect @@ -65,6 +81,142 @@ public partial class RoomMap : Node2D for (int r = 0; r < Rows; r++) 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)); + + BuildStairs(); + BuildObjects(); + BuildPawns(); + } + + // ---------- лестницы (вертикальные шахты с перекладинами) ---------- + private void BuildStairs() + { + AddLadder(2 * Cell); + AddLadder(4 * Cell); + } + + private void AddLadder(float centerX) + { + const float w = 48f; + float x = centerX - w / 2f; + float h = Rows * Cell; + AddRect(new Rect2(x, 0, w, h), new Color(0.10f, 0.08f, 0.06f, 0.9f)); // шахта + AddRect(new Rect2(x + 3, 0, 7, h), new Color(0.45f, 0.30f, 0.17f)); // рельс слева + AddRect(new Rect2(x + w - 10, 0, 7, h), new Color(0.45f, 0.30f, 0.17f)); // рельс справа + 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) + { + var p = new Polygon2D + { + Color = c, + Polygon = new[] + { + r.Position, new Vector2(r.End.X, r.Position.Y), r.End, new Vector2(r.Position.X, r.End.Y) + } + }; + _world.AddChild(p); + } + + // ---------- объекты в комнатах ---------- + private void BuildObjects() + { + (int c, int r)[] lanterns = { (0, 0), (5, 0), (3, 1), (5, 2), (1, 3), (0, 4), (5, 4) }; + foreach (var (c, r) in lanterns) + AddTex("res://assets/room_sliced/Lantern.png", + new Rect2(c * Cell + Cell * 0.72f, r * Cell + Cell * 0.06f, Cell * 0.16f, Cell * 0.22f), _world, Colors.White); + + (int c, int r)[] lights = { (1, 1), (3, 3), (4, 0) }; + foreach (var (c, r) in lights) + AddTex("res://assets/room_sliced/Light.png", + new Rect2(c * Cell + Cell * 0.28f, r * Cell + Cell * 0.02f, Cell * 0.44f, Cell * 0.44f), _world, new Color(1, 1, 1, 0.75f)); + } + + // ---------- персонажи, гуляющие по этажам ---------- + private void BuildPawns() + { + _rng = new System.Random(20260803); + (string path, int row)[] picks = + { + ("res://characters/elf1/elf1_combat_native.tscn", 0), + ("res://characters/knight/knight_combat_native.tscn", 1), + ("res://characters/orc/orc_combat_native.tscn", 2), + ("res://characters/sister/sister_combat_native.tscn", 3), + ("res://characters/satyress/satyress_combat_native.tscn", 4), + }; + float minX = Cell * 0.4f, maxX = Cols * Cell - Cell * 0.4f; + + foreach (var (path, row) in picks) + { + if (!ResourceLoader.Exists(path)) continue; + var scene = ResourceLoader.Load(path).Instantiate(); + scene.Position = -FootAnchor * CharScale; + scene.Scale = new Vector2(CharScale, CharScale); + + var node = new Node2D(); + node.AddChild(scene); + _world.AddChild(node); + + float floorY = row * Cell + Cell * 0.9f; + float x = (float)(_rng.NextDouble() * (maxX - minX) + minX); + node.Position = new Vector2(x, floorY); + + var ap = scene.GetNodeOrNull("AnimationPlayer"); + string[] anims = ap != null ? ap.GetAnimationList() : System.Array.Empty(); + var p = new Pawn + { + Node = node, Ap = ap, X = x, FloorY = floorY, MinX = minX, MaxX = maxX, + Speed = 70f + (float)_rng.NextDouble() * 55f, + WalkAnim = Pick(anims, "walk_0"), IdleAnim = Pick(anims, "idle_0"), + TargetX = (float)(_rng.NextDouble() * (maxX - minX) + minX) + }; + StartWalk(p); + _pawns.Add(p); + } + } + + private static string Pick(string[] anims, string want) + { + foreach (var a in anims) if (a == want) return a; + return anims.Length > 0 ? anims[0] : null; + } + + private static void StartWalk(Pawn p) + { + p.Walking = true; + if (p.Ap != null && p.WalkAnim != null) p.Ap.Play(p.WalkAnim); + } + + private void StartIdle(Pawn p) + { + p.Walking = false; + p.IdleTimer = 0.8f + (float)_rng.NextDouble() * 2.2f; + if (p.Ap != null && p.IdleAnim != null) p.Ap.Play(p.IdleAnim); + } + + private void UpdatePawns(float dt) + { + foreach (var p in _pawns) + { + if (p.Walking) + { + float d = Mathf.Sign(p.TargetX - p.X); + p.X += d * p.Speed * dt; + p.Node.Scale = new Vector2(d >= 0 ? 1f : -1f, 1f); + p.Node.Position = new Vector2(p.X, p.FloorY); + if (Mathf.Abs(p.TargetX - p.X) < 4f) StartIdle(p); + } + else + { + p.IdleTimer -= dt; + if (p.IdleTimer <= 0f) + { + p.TargetX = (float)(_rng.NextDouble() * (p.MaxX - p.MinX) + p.MinX); + StartWalk(p); + } + } + } } private void AddCell(string path, int col, int row, int cw, int ch, Color? modulate = null) @@ -114,7 +266,7 @@ public partial class RoomMap : Node2D private void UpdateLabel() { _label.Text = - $"Карта убежища — {Cols}×{Rows} комнат из игровых текстур\n" + + $"Карта убежища — {Cols}×{Rows} комнат, лестницы, объекты, {_pawns.Count} персонажа гуляют\n" + $"ЛКМ/WASD — панорама · колесо — зум (x{_viewZoom:0.00}) · R — сброс · Esc — меню"; } @@ -129,6 +281,8 @@ public partial class RoomMap : Node2D if (Input.IsKeyPressed(Key.S) || Input.IsKeyPressed(Key.Down)) dir.Y -= 1; if (dir != Vector2.Zero) _world.Position += dir * (float)delta * 600f; + + UpdatePawns((float)delta); } public override void _UnhandledInput(InputEvent @event)