Подземелье: назначение персонажей в тематические комнаты + анимации
- Комнаты Спальня/Логово/Дойка со слотами - Перетаскивание в комнату: персонаж занимает слот, скелет меняется на состояние комнаты (milk/birth/...), играет одиночную (1) или групповую (2+) анимацию - Клетка сброса берётся по центру тела (фикс съезда на строку ниже) - Панель показывает назначение/слот/режим анимации Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a254309ec9
commit
70b65fde0c
@@ -29,8 +29,10 @@ D:\Godot_v4.7.1\Godot_v4.7.1-stable_mono_win64.exe
|
||||
комнат, **тёмная шахта** (колонка тёмных комнат друг над другом = скрытая лестница;
|
||||
персонаж заходит и проходит по ней на другой этаж, анимация — боевой `walk_0`),
|
||||
**объекты** (фонари/свет) и **несколько персонажей** на этажах.
|
||||
ЛКМ по персонажу — выбрать/тащить (перенос в комнату), ЛКМ по фону/WASD — панорама,
|
||||
колесо — зум, `R` — сброс.
|
||||
ЛКМ по персонажу — выбрать/тащить, ЛКМ по фону/WASD — панорама, колесо — зум, `R` — сброс.
|
||||
**Тематические комнаты** (Спальня/Логово/Дойка) имеют слоты: при перетаскивании
|
||||
персонажа в такую комнату он занимает слот, **меняет скелет на состояние комнаты**
|
||||
и играет анимацию — **одиночную** (1 в комнате) или **групповую** (2+).
|
||||
2. **Просмотр спрайтов** (`scenes/SpriteViewer.tscn`) — галерея вытащенных изображений
|
||||
(UI, фоны, эффекты, нарезанные спрайты). `A/D` — картинка, `W/S` — категория, колесо — зум.
|
||||
3. **Нативные анимации** (`scenes/NativeViewer.tscn`) — браузер всех 38 персонажей нативно:
|
||||
|
||||
+128
-6
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
|
||||
// Демо-карта «убежища» в разрезе (à la Fallout Shelter): комнаты, лестницы, объекты
|
||||
@@ -30,20 +31,32 @@ public partial class RoomMap : Node2D
|
||||
private PanelContainer _infoPanel;
|
||||
private Label _infoLabel;
|
||||
|
||||
private enum PState { Idle, Walk, GoShaft, Teleport, Held }
|
||||
private enum PState { Idle, Walk, GoShaft, Teleport, Held, Assigned }
|
||||
|
||||
private class Pawn
|
||||
{
|
||||
public Node2D Node;
|
||||
public AnimationPlayer Ap;
|
||||
public string Name;
|
||||
public string Name, BaseScene;
|
||||
public int Row, TargetRow;
|
||||
public float X, TargetX, Speed, MinX, MaxX, IdleTimer, FadeT;
|
||||
public bool Swapped;
|
||||
public PState State;
|
||||
public string WalkAnim, IdleAnim;
|
||||
public Room AssignedRoom;
|
||||
}
|
||||
|
||||
// тематическая комната со слотами
|
||||
private class Room
|
||||
{
|
||||
public string Type;
|
||||
public int Col, Row, Span, Cap;
|
||||
public string[] States; // приоритет состояний-скелетов
|
||||
public readonly List<Pawn> Occ = new();
|
||||
}
|
||||
|
||||
private List<Room> _rooms;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
var bg = new ColorRect
|
||||
@@ -84,7 +97,14 @@ public partial class RoomMap : Node2D
|
||||
private void ShowInfo(Pawn p)
|
||||
{
|
||||
_selected = p;
|
||||
_infoLabel.Text = $"Персонаж: {p.Name}\nЭтаж: {p.Row + 1}/{Rows}\nтащи мышью → перенести в комнату";
|
||||
if (p.AssignedRoom != null)
|
||||
{
|
||||
var r = p.AssignedRoom;
|
||||
string mode = r.Occ.Count >= 2 ? "групповая" : "одиночная";
|
||||
_infoLabel.Text = $"Персонаж: {p.Name}\nНазначен: {r.Type} (слот {r.Occ.IndexOf(p) + 1}/{r.Cap})\nанимация: {mode}\nтащи мышью → убрать";
|
||||
}
|
||||
else
|
||||
_infoLabel.Text = $"Персонаж: {p.Name}\nЭтаж: {p.Row + 1}/{Rows}\nтащи мышью → назначить в комнату";
|
||||
_infoPanel.Visible = true;
|
||||
}
|
||||
|
||||
@@ -109,6 +129,13 @@ public partial class RoomMap : Node2D
|
||||
AddCell("res://assets/room_sliced/LactationRoom.png", 4, 3, 1, 1);
|
||||
AddCell("res://assets/room_sliced/Light.png", 2, 2, 1, 1);
|
||||
|
||||
_rooms = new List<Room>
|
||||
{
|
||||
new Room { Type = "Спальня", Col = 1, Row = 0, Span = 3, Cap = 2, States = new[] { "stand", "combat" } },
|
||||
new Room { Type = "Логово", Col = 0, Row = 2, Span = 1, Cap = 2, States = new[] { "birth", "down", "milk" } },
|
||||
new Room { Type = "Дойка", Col = 4, Row = 3, Span = 1, Cap = 1, States = new[] { "milk", "milk2" } },
|
||||
};
|
||||
|
||||
// рамки поверх всех ячеек — читаемость сетки
|
||||
for (int r = 0; r < Rows; r++)
|
||||
for (int c = 0; c < Cols; c++)
|
||||
@@ -194,7 +221,7 @@ public partial class RoomMap : Node2D
|
||||
string[] anims = ap != null ? ap.GetAnimationList() : System.Array.Empty<string>();
|
||||
var p = new Pawn
|
||||
{
|
||||
Node = node, Ap = ap, Name = path.Split('/')[3], Row = row,
|
||||
Node = node, Ap = ap, Name = path.Split('/')[3], BaseScene = path, Row = row,
|
||||
X = x, MinX = minX, MaxX = maxX,
|
||||
Speed = 70f + (float)_rng.NextDouble() * 55f,
|
||||
WalkAnim = Pick(anims, "walk_0"), IdleAnim = Pick(anims, "idle_0")
|
||||
@@ -237,6 +264,7 @@ public partial class RoomMap : Node2D
|
||||
switch (p.State)
|
||||
{
|
||||
case PState.Held:
|
||||
case PState.Assigned:
|
||||
break;
|
||||
|
||||
case PState.Walk:
|
||||
@@ -307,16 +335,110 @@ public partial class RoomMap : Node2D
|
||||
|
||||
private void DropPawn(Pawn p)
|
||||
{
|
||||
p.Node.Modulate = Colors.White;
|
||||
// клетку берём по ЦЕНТРУ тела, а не по ногам (иначе съезжает на строку ниже)
|
||||
int col = Mathf.Clamp((int)(p.Node.Position.X / Cell), 0, Cols - 1);
|
||||
int row = Mathf.Clamp((int)(p.Node.Position.Y / Cell), 0, Rows - 1);
|
||||
int row = Mathf.Clamp((int)((p.Node.Position.Y - Cell * 0.45f) / Cell), 0, Rows - 1);
|
||||
|
||||
var room = RoomAt(col, row);
|
||||
if (room != null && (room.Occ.Contains(p) || room.Occ.Count < room.Cap))
|
||||
{
|
||||
AssignToRoom(p, room);
|
||||
if (_selected == p) ShowInfo(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// обычный сброс: снять назначение и вернуть боевую сцену
|
||||
if (p.AssignedRoom != null) { RemoveFromRoom(p); SwapScene(p, p.BaseScene); }
|
||||
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.Node.Modulate = Colors.White;
|
||||
p.State = PState.Idle; p.IdleTimer = 0.5f; PlayIdle(p);
|
||||
if (_selected == p) ShowInfo(p);
|
||||
}
|
||||
|
||||
private Room RoomAt(int col, int row)
|
||||
{
|
||||
if (_rooms == null) return null;
|
||||
foreach (var r in _rooms)
|
||||
if (row == r.Row && col >= r.Col && col < r.Col + r.Span) return r;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AssignToRoom(Pawn p, Room room)
|
||||
{
|
||||
if (p.AssignedRoom != null && p.AssignedRoom != room) RemoveFromRoom(p);
|
||||
if (!room.Occ.Contains(p)) room.Occ.Add(p);
|
||||
p.AssignedRoom = room;
|
||||
p.State = PState.Assigned;
|
||||
|
||||
string sp = StateScene(p.Name, room.States) ?? p.BaseScene;
|
||||
SwapScene(p, sp);
|
||||
LayoutRoom(room);
|
||||
}
|
||||
|
||||
private void RemoveFromRoom(Pawn p)
|
||||
{
|
||||
var room = p.AssignedRoom;
|
||||
if (room == null) return;
|
||||
room.Occ.Remove(p);
|
||||
p.AssignedRoom = null;
|
||||
if (room.Occ.Count > 0) LayoutRoom(room);
|
||||
}
|
||||
|
||||
// расставить по слотам и включить анимацию: 1 в комнате — одиночную, 2+ — групповую
|
||||
private void LayoutRoom(Room room)
|
||||
{
|
||||
int n = room.Occ.Count;
|
||||
bool group = n >= 2;
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
var q = room.Occ[i];
|
||||
float sx = room.Col * Cell + (i + 1f) / (n + 1f) * (room.Span * Cell);
|
||||
q.X = sx;
|
||||
q.Node.Position = new Vector2(sx, FloorY(room.Row));
|
||||
q.Node.Scale = new Vector2(1f, 1f);
|
||||
var anims = q.Ap != null ? q.Ap.GetAnimationList() : System.Array.Empty<string>();
|
||||
string a = PickAction(anims, group);
|
||||
if (q.Ap != null && a != null) q.Ap.Play(a);
|
||||
}
|
||||
}
|
||||
|
||||
private static string StateScene(string name, string[] states)
|
||||
{
|
||||
foreach (var s in states)
|
||||
{
|
||||
string path = $"res://characters/{name}/{name}_{s}_native.tscn";
|
||||
if (ResourceLoader.Exists(path)) return path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// заменить скелет-сцену персонажа (боевая ↔ состояние комнаты)
|
||||
private void SwapScene(Pawn p, string path)
|
||||
{
|
||||
if (p.Node.GetChildCount() > 0) p.Node.GetChild(0).Free();
|
||||
var scene = ResourceLoader.Load<PackedScene>(path).Instantiate<Node2D>();
|
||||
scene.Position = -FootAnchor * CharScale;
|
||||
scene.Scale = new Vector2(CharScale, CharScale);
|
||||
p.Node.AddChild(scene);
|
||||
p.Ap = scene.GetNodeOrNull<AnimationPlayer>("AnimationPlayer");
|
||||
var anims = p.Ap != null ? p.Ap.GetAnimationList() : System.Array.Empty<string>();
|
||||
p.WalkAnim = Pick(anims, "walk_0");
|
||||
p.IdleAnim = Pick(anims, "idle_0");
|
||||
}
|
||||
|
||||
private static readonly string[] NotAction =
|
||||
{ "walk", "attack", "idle", "dd", "dfdfa", "ddd", "test", "animation", "information", "portrait", "stun", "knockback", "dead", "down" };
|
||||
|
||||
private static string PickAction(string[] anims, bool group)
|
||||
{
|
||||
var actions = anims.Where(a => !NotAction.Any(n => a.StartsWith(n))).ToList();
|
||||
if (actions.Count == 0) actions = anims.Where(a => a.StartsWith("idle")).ToList();
|
||||
if (actions.Count == 0) return anims.Length > 0 ? anims[0] : null;
|
||||
return group && actions.Count > 1 ? actions[1] : actions[0];
|
||||
}
|
||||
|
||||
private void AddCell(string path, int col, int row, int cw, int ch, Color? modulate = null)
|
||||
{
|
||||
AddTex(path, new Rect2(col * Cell, row * Cell, cw * Cell, ch * Cell), _world, modulate ?? Colors.White);
|
||||
|
||||
Reference in New Issue
Block a user