Подземелье: убрано блуждание, спавнер персонажей, idle-стойка
- Персонажи больше не ходят по карте: старт пустой, никакого AI-движения - Убрана тёмная шахта/телепорт (движение больше не нужно) - Спавнер (панель внизу): + Раб / Монстр / Ребёнок / Игрок — создаёт персонажа, который стоит в idle на случайном этаже - Дроп на обычную клетку -> персонаж просто стоит (idle), дроп в слот -> назначение - Счётчик персонажей в шапке обновляется Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0882ecdf87
commit
4350f4a9e7
@@ -26,13 +26,15 @@ 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
|
||||
комнат, **тёмная шахта** (колонка тёмных комнат друг над другом = скрытая лестница;
|
||||
персонаж заходит и проходит по ней на другой этаж, анимация — боевой `walk_0`),
|
||||
**объекты** (фонари/свет) и **несколько персонажей** на этажах.
|
||||
ЛКМ по персонажу — выбрать/тащить, ЛКМ по фону/WASD — панорама, колесо — зум, `R` — сброс.
|
||||
**Тематические комнаты** (Спальня/Логово/Дойка) имеют слоты: при перетаскивании
|
||||
персонажа в такую комнату он занимает слот, **меняет скелет на состояние комнаты**
|
||||
и играет анимацию — **одиночную** (1 в комнате) или **групповую** (2+).
|
||||
комнат + объекты (фонари/свет). Карта стартует пустой; персонажи создаются **спавнером**
|
||||
(панель внизу: `+ Раб / Монстр / Ребёнок / Игрок`) и **стоят в idle**, пока их не перетащат.
|
||||
- **Типы персонажей** (Игрок/Раб/Монстр/Ребёнок) + генерация имён.
|
||||
- **Тематические комнаты со слотами по типу**: Дойка = 2 раба + 4 ребёнка,
|
||||
Логово = 1 раб + 1 монстр. Перетаскивание в подходящий слот меняет скелет на
|
||||
состояние комнаты и играет одиночную/групповую анимацию. Над комнатами — окна с именами.
|
||||
- **ЛКМ** — выбрать/тащить (в комнату или на пол — там персонаж просто стоит).
|
||||
**ПКМ** — убрать персонажа в **инвентарь** (панель справа; клик — вернуть).
|
||||
- `WASD`/ЛКМ-фон — панорама, колесо — зум, `R` — сброс, `Esc` — меню.
|
||||
2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений
|
||||
(UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум.
|
||||
3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно:
|
||||
|
||||
+62
-125
@@ -21,7 +21,6 @@ public partial class RoomMap : Node2D
|
||||
private Vector2 _homePos;
|
||||
|
||||
private static float FloorY(int row) => row * Cell + Cell * 0.9f;
|
||||
private float _shaftX; // X тёмной шахты (комнаты друг над другом = скрытая лестница)
|
||||
|
||||
private readonly List<Pawn> _pawns = new();
|
||||
private System.Random _rng;
|
||||
@@ -31,7 +30,7 @@ public partial class RoomMap : Node2D
|
||||
private PanelContainer _infoPanel;
|
||||
private Label _infoLabel;
|
||||
|
||||
private enum PState { Idle, Walk, GoShaft, Teleport, Held, Assigned }
|
||||
private enum PState { Idle, Held, Assigned }
|
||||
|
||||
private class Pawn
|
||||
{
|
||||
@@ -80,6 +79,12 @@ public partial class RoomMap : Node2D
|
||||
private static readonly string[] MonsterNames = { "Грок", "Зуг", "Морг", "Рарг", "Тхок", "Гнар", "Урук", "Драк" };
|
||||
private static readonly string[] ChildNames = { "Пип", "Тимо", "Лулу", "Бэн", "Ния" };
|
||||
|
||||
// ростеры для спавнера
|
||||
private static readonly string[] SlaveRoster = { "elf1", "sister", "satyress", "knight", "shortstack" };
|
||||
private static readonly string[] MonsterRoster = { "orc", "oni", "man" };
|
||||
private static readonly string[] ChildRoster = { "baby" };
|
||||
private static readonly string[] PlayerRoster = { "player" };
|
||||
|
||||
// инвентарь снятых персонажей
|
||||
private class InvItem { public string Name, Scene; public CType Type; }
|
||||
private readonly List<InvItem> _inventory = new();
|
||||
@@ -108,9 +113,56 @@ public partial class RoomMap : Node2D
|
||||
|
||||
BuildInfoPanel();
|
||||
BuildInventoryPanel();
|
||||
BuildSpawner();
|
||||
ResetView();
|
||||
}
|
||||
|
||||
// ---------- спавнер персонажей ----------
|
||||
private void BuildSpawner()
|
||||
{
|
||||
var bar = new HBoxContainer { Position = new Vector2(16, GetViewportRect().Size.Y - 46) };
|
||||
bar.AddThemeConstantOverride("separation", 8);
|
||||
var lbl = new Label { Text = "Спавн:" };
|
||||
lbl.AddThemeColorOverride("font_color", Colors.White);
|
||||
lbl.AddThemeConstantOverride("outline_size", 3);
|
||||
lbl.AddThemeColorOverride("font_outline_color", Colors.Black);
|
||||
bar.AddChild(lbl);
|
||||
foreach (var t in new[] { CType.Slave, CType.Monster, CType.Child, CType.Player })
|
||||
{
|
||||
var tt = t;
|
||||
var b = new Button { Text = "+ " + TypeName(t) };
|
||||
b.Pressed += () => SpawnCharacter(tt);
|
||||
bar.AddChild(b);
|
||||
}
|
||||
AddChild(bar);
|
||||
}
|
||||
|
||||
private void SpawnCharacter(CType type)
|
||||
{
|
||||
string[] roster = type == CType.Monster ? MonsterRoster : type == CType.Child ? ChildRoster
|
||||
: type == CType.Player ? PlayerRoster : SlaveRoster;
|
||||
string name = roster[_rng.Next(roster.Length)];
|
||||
string path = ResolveScene(name);
|
||||
if (path == null) return;
|
||||
var p = SpawnPawn(path, _rng.Next(Rows));
|
||||
if (p != null) ShowInfo(p);
|
||||
}
|
||||
|
||||
private static string ResolveScene(string name)
|
||||
{
|
||||
if (name == "baby") return "res://characters/baby/human_baby_native.tscn";
|
||||
foreach (var s in new[] { "combat", "stand" })
|
||||
{
|
||||
string path = $"res://characters/{name}/{name}_{s}_native.tscn";
|
||||
if (ResourceLoader.Exists(path)) return path;
|
||||
}
|
||||
var dir = DirAccess.Open($"res://characters/{name}");
|
||||
if (dir != null)
|
||||
foreach (var f in dir.GetFiles())
|
||||
if (f.EndsWith("_native.tscn")) return $"res://characters/{name}/{f}";
|
||||
return null;
|
||||
}
|
||||
|
||||
private void BuildRoomHeaders()
|
||||
{
|
||||
foreach (var r in _rooms)
|
||||
@@ -185,6 +237,7 @@ public partial class RoomMap : Node2D
|
||||
p.Node.QueueFree();
|
||||
_inventory.Add(new InvItem { Name = p.DisplayName, Type = p.Type, Scene = p.BaseScene });
|
||||
RefreshInventory();
|
||||
UpdateLabel();
|
||||
}
|
||||
|
||||
private void BuildInfoPanel()
|
||||
@@ -252,25 +305,8 @@ public partial class RoomMap : Node2D
|
||||
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));
|
||||
|
||||
BuildDarkRooms();
|
||||
BuildObjects();
|
||||
BuildPawns();
|
||||
}
|
||||
|
||||
// ---------- тёмная шахта: колонка тёмных комнат друг над другом (скрытая лестница) ----------
|
||||
private void BuildDarkRooms()
|
||||
{
|
||||
const int col = 5; // одна колонка на всю высоту
|
||||
_shaftX = col * Cell + Cell * 0.5f;
|
||||
for (int r = 0; r < Rows; r++)
|
||||
{
|
||||
// затемнение интерьера ячейки
|
||||
AddRect(new Rect2(col * Cell + 8, r * Cell + 8, Cell - 16, Cell - 16), new Color(0.02f, 0.02f, 0.05f, 0.94f));
|
||||
// слабое свечение по центру — вход в шахту
|
||||
AddTex("res://assets/room_sliced/Light.png",
|
||||
new Rect2(col * Cell + Cell * 0.3f, r * Cell + Cell * 0.3f, Cell * 0.4f, Cell * 0.4f),
|
||||
_world, new Color(0.45f, 0.38f, 0.9f, 0.4f));
|
||||
}
|
||||
_rng = new System.Random(20260803);
|
||||
}
|
||||
|
||||
private void AddRect(Rect2 r, Color c)
|
||||
@@ -300,23 +336,6 @@ public partial class RoomMap : Node2D
|
||||
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/sister/sister_combat_native.tscn", 1), // раб
|
||||
("res://characters/satyress/satyress_combat_native.tscn", 2), // раб
|
||||
("res://characters/knight/knight_combat_native.tscn", 3), // раб
|
||||
("res://characters/orc/orc_combat_native.tscn", 2), // монстр
|
||||
("res://characters/baby/human_baby_native.tscn", 1), // ребёнок
|
||||
("res://characters/baby/human_baby_native.tscn", 3), // ребёнок
|
||||
};
|
||||
foreach (var (path, row) in picks) SpawnPawn(path, row);
|
||||
}
|
||||
|
||||
private CType TypeOf(string name) =>
|
||||
name == "player" ? CType.Player : name == "baby" ? CType.Child :
|
||||
Monsters.Contains(name) ? CType.Monster : CType.Slave;
|
||||
@@ -354,8 +373,10 @@ public partial class RoomMap : Node2D
|
||||
WalkAnim = Pick(anims, "walk_0"), IdleAnim = Pick(anims, "idle_0"),
|
||||
Type = t, DisplayName = name ?? GenName(t)
|
||||
};
|
||||
Decide(p);
|
||||
p.State = PState.Idle; // просто стоит (никакого блуждания)
|
||||
PlayIdle(p);
|
||||
_pawns.Add(p);
|
||||
UpdateLabel();
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -365,90 +386,8 @@ public partial class RoomMap : Node2D
|
||||
return anims.Length > 0 ? anims[0] : null;
|
||||
}
|
||||
|
||||
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 Decide(Pawn p)
|
||||
{
|
||||
if (Rows > 1 && _rng.NextDouble() < 0.45)
|
||||
{
|
||||
p.TargetX = _shaftX;
|
||||
p.State = PState.GoShaft;
|
||||
PlayWalk(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
p.TargetX = (float)(_rng.NextDouble() * (p.MaxX - p.MinX) + p.MinX);
|
||||
p.State = PState.Walk;
|
||||
PlayWalk(p);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePawns(float dt)
|
||||
{
|
||||
foreach (var p in _pawns)
|
||||
{
|
||||
switch (p.State)
|
||||
{
|
||||
case PState.Held:
|
||||
case PState.Assigned:
|
||||
break;
|
||||
|
||||
case PState.Walk:
|
||||
case PState.GoShaft:
|
||||
{
|
||||
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, FloorY(p.Row));
|
||||
if (Mathf.Abs(p.TargetX - p.X) < 4f)
|
||||
{
|
||||
if (p.State == PState.GoShaft)
|
||||
{
|
||||
p.X = _shaftX;
|
||||
do { p.TargetRow = _rng.Next(Rows); } while (p.TargetRow == p.Row);
|
||||
p.State = PState.Teleport;
|
||||
p.FadeT = 0f; p.Swapped = false;
|
||||
PlayIdle(p);
|
||||
}
|
||||
else { p.State = PState.Idle; p.IdleTimer = 0.8f + (float)_rng.NextDouble() * 2.2f; PlayIdle(p); }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PState.Teleport: // исчезнуть в шахте на одном этаже и появиться на другом
|
||||
{
|
||||
const float dur = 0.5f;
|
||||
p.FadeT += dt / dur;
|
||||
if (!p.Swapped && p.FadeT >= 0.5f)
|
||||
{
|
||||
p.Swapped = true;
|
||||
p.Row = p.TargetRow;
|
||||
p.X = _shaftX;
|
||||
p.Node.Position = new Vector2(_shaftX, FloorY(p.Row));
|
||||
if (_selected == p) ShowInfo(p);
|
||||
}
|
||||
float a = p.FadeT < 0.5f ? 1f - p.FadeT * 2f : (p.FadeT - 0.5f) * 2f;
|
||||
p.Node.Modulate = new Color(1f, 1f, 1f, Mathf.Clamp(a, 0f, 1f));
|
||||
if (p.FadeT >= 1f)
|
||||
{
|
||||
p.Node.Modulate = Colors.White;
|
||||
p.State = PState.Idle;
|
||||
p.IdleTimer = 0.4f + (float)_rng.NextDouble() * 1.5f;
|
||||
PlayIdle(p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PState.Idle:
|
||||
p.IdleTimer -= dt;
|
||||
if (p.IdleTimer <= 0f) Decide(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Pawn PawnAt(Vector2 worldPos)
|
||||
{
|
||||
float w = Cell * 0.5f, h = Cell * 0.85f;
|
||||
@@ -487,7 +426,7 @@ public partial class RoomMap : Node2D
|
||||
p.Row = row;
|
||||
p.X = Mathf.Clamp(col * Cell + Cell * 0.5f, p.MinX, p.MaxX);
|
||||
p.Node.Position = new Vector2(p.X, FloorY(row));
|
||||
p.State = PState.Idle; p.IdleTimer = 0.5f; PlayIdle(p);
|
||||
p.State = PState.Idle; PlayIdle(p);
|
||||
if (_selected == p) ShowInfo(p);
|
||||
}
|
||||
|
||||
@@ -638,8 +577,8 @@ public partial class RoomMap : Node2D
|
||||
private void UpdateLabel()
|
||||
{
|
||||
_label.Text =
|
||||
$"Карта убежища — {Cols}×{Rows} комнат, тёмная шахта (скрытая лестница), {_pawns.Count} персонажа\n" +
|
||||
$"ЛКМ по персонажу — выбрать/тащить · ЛКМ по фону/WASD — панорама · колесо — зум · R — сброс · Esc — меню";
|
||||
$"Карта убежища — {Cols}×{Rows} комнат · персонажей на карте: {_pawns.Count}\n" +
|
||||
$"Спавн внизу · ЛКМ тащить в комнату/на пол · ПКМ → инвентарь · WASD/ЛКМ-фон — панорама · колесо — зум · Esc — меню";
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
@@ -653,8 +592,6 @@ 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)
|
||||
|
||||
Reference in New Issue
Block a user