diff --git a/DungeonShooting_Godot/prefab/ui/EditorTips.tscn b/DungeonShooting_Godot/prefab/ui/EditorTips.tscn new file mode 100644 index 0000000..f284eee --- /dev/null +++ b/DungeonShooting_Godot/prefab/ui/EditorTips.tscn @@ -0,0 +1,22 @@ +[gd_scene load_steps=2 format=3 uid="uid://bed63wcgyl08u"] + +[ext_resource type="Script" path="res://src/game/ui/editorTips/EditorTipsPanel.cs" id="1_3mdex"] + +[node name="EditorTips" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource("1_3mdex") + +[node name="Label" type="Label" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "提示内容" +horizontal_alignment = 1 +vertical_alignment = 1 +autowrap_mode = 3 diff --git a/DungeonShooting_Godot/prefab/ui/EditorWindow.tscn b/DungeonShooting_Godot/prefab/ui/EditorWindow.tscn index 5c70ba8..223d7fd 100644 --- a/DungeonShooting_Godot/prefab/ui/EditorWindow.tscn +++ b/DungeonShooting_Godot/prefab/ui/EditorWindow.tscn @@ -7,9 +7,20 @@ anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 script = ExtResource("1_bfitg") Layer = 3 +[node name="Bg" type="ColorRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +color = Color(0, 0, 0, 0.235294) + [node name="Window" type="Window" parent="."] title = "标题" initial_position = 1 diff --git a/DungeonShooting_Godot/prefab/ui/MapEditorCreateRoom.tscn b/DungeonShooting_Godot/prefab/ui/MapEditorCreateRoom.tscn index 1888357..fc9cc5c 100644 --- a/DungeonShooting_Godot/prefab/ui/MapEditorCreateRoom.tscn +++ b/DungeonShooting_Godot/prefab/ui/MapEditorCreateRoom.tscn @@ -12,18 +12,26 @@ script = ExtResource("1_dep3f") Layer = 3 -[node name="VBoxContainer" type="VBoxContainer" parent="."] +[node name="MarginContainer" type="MarginContainer" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +theme_override_constants/margin_left = 20 +theme_override_constants/margin_top = 50 +theme_override_constants/margin_right = 20 +theme_override_constants/margin_bottom = 20 -[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/separation = 20 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] layout_mode = 2 -[node name="RoomNameLabel" type="Label" parent="VBoxContainer/HBoxContainer"] +[node name="RoomNameLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 size_flags_stretch_ratio = 20.0 @@ -31,7 +39,58 @@ horizontal_alignment = 2 vertical_alignment = 1 -[node name="RoomNameInput" type="LineEdit" parent="VBoxContainer/HBoxContainer"] +[node name="RoomNameInput" type="LineEdit" parent="MarginContainer/VBoxContainer/HBoxContainer"] layout_mode = 2 size_flags_horizontal = 3 size_flags_stretch_ratio = 80.0 + +[node name="HBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="GroupNameLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 20.0 +text = "所属分组:" +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="GroupSelect" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 80.0 + +[node name="HBoxContainer3" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="TypeNameLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer3"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 20.0 +text = "房间类型:" +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="TypeSelect" type="OptionButton" parent="MarginContainer/VBoxContainer/HBoxContainer3"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 80.0 + +[node name="HBoxContainer4" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="WeightNameLabel" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer4"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 20.0 +text = "房间权重:" +horizontal_alignment = 2 +vertical_alignment = 1 + +[node name="WeightInput" type="SpinBox" parent="MarginContainer/VBoxContainer/HBoxContainer4"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 80.0 +min_value = 1.0 +max_value = 1000.0 +value = 100.0 diff --git a/DungeonShooting_Godot/src/framework/ui/grid/UiCell.cs b/DungeonShooting_Godot/src/framework/ui/grid/UiCell.cs index a351b94..8694d21 100644 --- a/DungeonShooting_Godot/src/framework/ui/grid/UiCell.cs +++ b/DungeonShooting_Godot/src/framework/ui/grid/UiCell.cs @@ -7,6 +7,11 @@ public abstract class UiCell : IDestroy where TUiCellNode : IUiCellNode { public bool IsDestroyed { get; private set; } + + /// + /// 当前 Cell 在 UiGrid 组件中的索引位置 + /// + public int Index { get; private set; } = -1; /// /// 所在的网格对象 @@ -40,6 +45,28 @@ } /// + /// 当前 Cell 选中时调用, 设置 UiGrid.SelectIndex 时触发 + /// + protected virtual void OnSelect() + { + } + + /// + /// 当前 Cell 取消选中时调用, 设置 UiGrid.SelectIndex 时触发 + /// + protected virtual void OnUnSelect() + { + } + + /// + /// 当 Cell 索引发生改变时调用, 在 UiGrid 中调用 Insert(), Remove() 等函数时被动触发当前 Cell 索引值改变, Cell 业务逻辑需要用到索引值时, 那么就可以重写该函数
+ /// 注意: 该函数第一次调用会在 OnSetData() 之前调用 + ///
+ protected virtual void OnRefreshIndex() + { + } + + /// /// 销毁当前cell时调用 /// protected virtual void OnDestroy() @@ -49,7 +76,7 @@ /// /// 初始化数据 /// - public void Init(UiGrid grid, TUiCellNode cellNode) + public void Init(UiGrid grid, TUiCellNode cellNode, int index) { if (_init) { @@ -60,16 +87,45 @@ Grid = grid; CellNode = cellNode; OnInit(); + SetIndex(index); } /// - /// 设置当前cell的值 + /// 设置当前cell的值, 这个函数由 UiGrid 调用 /// public void SetData(T data) { Data = data; OnSetData(data); } + + /// + /// 选中当前 Cell, 这个函数由 UiGrid 调用 + /// + public void Select() + { + OnSelect(); + } + + /// + /// 取消选中当前 Cell , 这个函数由 UiGrid 调用 + /// + public void UnSelect() + { + OnUnSelect(); + } + + /// + /// 设置当前 Cell 的索引 + /// + public void SetIndex(int index) + { + if (Index != index) + { + Index = index; + OnRefreshIndex(); + } + } public void Destroy() { diff --git a/DungeonShooting_Godot/src/framework/ui/grid/UiGrid.cs b/DungeonShooting_Godot/src/framework/ui/grid/UiGrid.cs index ac6ed69..bf49d56 100644 --- a/DungeonShooting_Godot/src/framework/ui/grid/UiGrid.cs +++ b/DungeonShooting_Godot/src/framework/ui/grid/UiGrid.cs @@ -9,9 +9,40 @@ ///
/// Ui节点类型 /// 传给Cell的数据类型 -public partial class UiGrid : IDestroy where TUiCellNode : IUiCellNode +public class UiGrid : IDestroy where TUiCellNode : IUiCellNode { public bool IsDestroyed { get; private set; } + + /// + /// 当前选中的 Cell 索引 + /// + public int SelectIndex + { + get => _selectIndex; + set + { + var newIndex = Mathf.Clamp(value, -1, _cellList.Count - 1); + if (_selectIndex != newIndex) + { + var prevIndex = _selectIndex; + _selectIndex = newIndex; + + //取消选中上一个 + if (prevIndex >= 0 && prevIndex < _cellList.Count) + { + var uiCell = _cellList[prevIndex]; + uiCell.UnSelect(); + } + + //选中新的 + if (newIndex >= 0) + { + var uiCell = _cellList[newIndex]; + uiCell.Select(); + } + } + } + } private TUiCellNode _template; private Vector2 _size = Vector2.Zero; @@ -25,6 +56,8 @@ private int _columns; private bool _autoColumns; + private int _selectIndex = -1; + public UiGrid(TUiCellNode template, Type cellType) { _gridContainer = new GridContainer(); @@ -202,12 +235,13 @@ /// public void SetDataList(TData[] array) { + //取消选中 + SelectIndex = -1; if (array.Length > _cellList.Count) { do { var cell = GetCellInstance(); - _cellList.Add(cell); _gridContainer.AddChild(cell.CellNode.GetUiInstance()); } while (array.Length > _cellList.Count); } @@ -234,7 +268,6 @@ public void Add(TData data) { var cell = GetCellInstance(); - _cellList.Add(cell); _gridContainer.AddChild(cell.CellNode.GetUiInstance()); cell.SetData(data); } @@ -292,7 +325,10 @@ { if (_cellPool.Count > 0) { - return _cellPool.Pop(); + var cell = _cellPool.Pop(); + cell.SetIndex(_cellList.Count); + _cellList.Add(cell); + return cell; } var uiCell = Activator.CreateInstance(_cellType) as UiCell; @@ -300,7 +336,8 @@ { throw new Exception($"cellType 无法转为'{typeof(UiCell).FullName}'类型!"); } - uiCell.Init(this, (TUiCellNode)_template.CloneUiCell()); + uiCell.Init(this, (TUiCellNode)_template.CloneUiCell(), _cellList.Count); + _cellList.Add(uiCell); return uiCell; } diff --git a/DungeonShooting_Godot/src/game/manager/EditorTipsManager.cs b/DungeonShooting_Godot/src/game/manager/EditorTipsManager.cs new file mode 100644 index 0000000..ad6d582 --- /dev/null +++ b/DungeonShooting_Godot/src/game/manager/EditorTipsManager.cs @@ -0,0 +1,31 @@ + +using System; +using UI.EditorTips; +using UI.EditorWindow; + +public static class EditorTipsManager +{ + /// + /// 弹出通用提示面板 + /// + /// 标题 + /// 显示内容 + /// 关闭时的回调 + public static void ShowTips(string title, string message, Action onClose = null) + { + var window = UiManager.Open_EditorWindow(); + window.SetWindowTitle(title); + if (onClose != null) + { + window.CloseEvent += onClose; + } + window.SetButtonList( + new EditorWindowPanel.ButtonData("确定", () => + { + window.CloseWindow(); + }) + ); + var body = window.OpenBody(UiManager.UiName.EditorTips); + body.SetMessage(message); + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/manager/ResourcePath.cs b/DungeonShooting_Godot/src/game/manager/ResourcePath.cs index c694463..5432aa9 100644 --- a/DungeonShooting_Godot/src/game/manager/ResourcePath.cs +++ b/DungeonShooting_Godot/src/game/manager/ResourcePath.cs @@ -48,6 +48,7 @@ public const string prefab_test_MoveComponent_tscn = "res://prefab/test/MoveComponent.tscn"; public const string prefab_test_TestActivity_tscn = "res://prefab/test/TestActivity.tscn"; public const string prefab_ui_BottomTips_tscn = "res://prefab/ui/BottomTips.tscn"; + public const string prefab_ui_EditorTips_tscn = "res://prefab/ui/EditorTips.tscn"; public const string prefab_ui_EditorTools_tscn = "res://prefab/ui/EditorTools.tscn"; public const string prefab_ui_EditorWindow_tscn = "res://prefab/ui/EditorWindow.tscn"; public const string prefab_ui_Loading_tscn = "res://prefab/ui/Loading.tscn"; diff --git a/DungeonShooting_Godot/src/game/manager/UiManager_Methods.cs b/DungeonShooting_Godot/src/game/manager/UiManager_Methods.cs index d0908ef..7a10f3a 100644 --- a/DungeonShooting_Godot/src/game/manager/UiManager_Methods.cs +++ b/DungeonShooting_Godot/src/game/manager/UiManager_Methods.cs @@ -7,6 +7,7 @@ public static class UiName { public const string BottomTips = "BottomTips"; + public const string EditorTips = "EditorTips"; public const string EditorTools = "EditorTools"; public const string EditorWindow = "EditorWindow"; public const string Loading = "Loading"; @@ -60,6 +61,46 @@ } /// + /// 打开 EditorTips, 并返回UI实例 + /// + public static UI.EditorTips.EditorTipsPanel Open_EditorTips() + { + return OpenUi(UiName.EditorTips); + } + + /// + /// 隐藏 EditorTips 的所有实例 + /// + public static void Hide_EditorTips() + { + var uiInstance = Get_EditorTips_Instance(); + foreach (var uiPanel in uiInstance) + { + uiPanel.HideUi(); + } + } + + /// + /// 销毁 EditorTips 的所有实例 + /// + public static void Dispose_EditorTips() + { + var uiInstance = Get_EditorTips_Instance(); + foreach (var uiPanel in uiInstance) + { + uiPanel.DisposeUi(); + } + } + + /// + /// 获取所有 EditorTips 的实例, 如果没有实例, 则返回一个空数组 + /// + public static UI.EditorTips.EditorTipsPanel[] Get_EditorTips_Instance() + { + return GetUiInstance(nameof(UI.EditorTips.EditorTips)); + } + + /// /// 打开 EditorTools, 并返回UI实例 /// public static UI.EditorTools.EditorToolsPanel Open_EditorTools() diff --git a/DungeonShooting_Godot/src/game/ui/editorTips/EditorTips.cs b/DungeonShooting_Godot/src/game/ui/editorTips/EditorTips.cs new file mode 100644 index 0000000..47121bb --- /dev/null +++ b/DungeonShooting_Godot/src/game/ui/editorTips/EditorTips.cs @@ -0,0 +1,45 @@ +namespace UI.EditorTips; + +/// +/// Ui代码, 该类是根据ui场景自动生成的, 请不要手动编辑该类, 以免造成代码丢失 +/// +public abstract partial class EditorTips : UiBase +{ + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: EditorTips.Label + /// + public Label L_Label + { + get + { + if (_L_Label == null) _L_Label = new Label(this, GetNodeOrNull("Label")); + return _L_Label; + } + } + private Label _L_Label; + + + public EditorTips() : base(nameof(EditorTips)) + { + } + + public sealed override void OnInitNestedUi() + { + } + + /// + /// 类型: , 路径: EditorTips.Label + /// + public class Label : UiNode + { + public Label(EditorTips uiPanel, Godot.Label node) : base(uiPanel, node) { } + public override Label Clone() => new (UiPanel, (Godot.Label)Instance.Duplicate()); + } + + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: EditorTips.Label + /// + public Label S_Label => L_Label; + +} diff --git a/DungeonShooting_Godot/src/game/ui/editorTips/EditorTipsPanel.cs b/DungeonShooting_Godot/src/game/ui/editorTips/EditorTipsPanel.cs new file mode 100644 index 0000000..b594ec5 --- /dev/null +++ b/DungeonShooting_Godot/src/game/ui/editorTips/EditorTipsPanel.cs @@ -0,0 +1,14 @@ +using Godot; + +namespace UI.EditorTips; + +public partial class EditorTipsPanel : EditorTips +{ + /// + /// 设置显示的文本 + /// + public void SetMessage(string message) + { + S_Label.Instance.Text = message; + } +} diff --git a/DungeonShooting_Godot/src/game/ui/editorWindow/CustomButtonCell.cs b/DungeonShooting_Godot/src/game/ui/editorWindow/CustomButtonCell.cs new file mode 100644 index 0000000..2117f90 --- /dev/null +++ b/DungeonShooting_Godot/src/game/ui/editorWindow/CustomButtonCell.cs @@ -0,0 +1,27 @@ +namespace UI.EditorWindow; + +public class CustomButtonCell : UiCell +{ + protected override void OnInit() + { + CellNode.L_Button.Instance.Pressed += OnClick; + } + + protected override void OnSetData(EditorWindowPanel.ButtonData data) + { + CellNode.L_Button.Instance.Text = data.Text; + } + + protected override void OnDestroy() + { + CellNode.L_Button.Instance.Pressed -= OnClick; + } + + private void OnClick() + { + if (Data.Callback != null) + { + Data.Callback(); + } + } +} \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindow.cs b/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindow.cs index cb6442d..5a6f77f 100644 --- a/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindow.cs +++ b/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindow.cs @@ -6,6 +6,19 @@ public abstract partial class EditorWindow : UiBase { /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: EditorWindow.Bg + /// + public Bg L_Bg + { + get + { + if (_L_Bg == null) _L_Bg = new Bg(this, GetNodeOrNull("Bg")); + return _L_Bg; + } + } + private Bg _L_Bg; + + /// /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: EditorWindow.Window /// public Window L_Window @@ -28,6 +41,15 @@ } /// + /// 类型: , 路径: EditorWindow.Bg + /// + public class Bg : UiNode + { + public Bg(EditorWindow uiPanel, Godot.ColorRect node) : base(uiPanel, node) { } + public override Bg Clone() => new (UiPanel, (Godot.ColorRect)Instance.Duplicate()); + } + + /// /// 类型: , 路径: EditorWindow.Window.Panel.VBoxContainer.Body /// public class Body : UiNode @@ -170,6 +192,11 @@ /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: EditorWindow.Bg + /// + public Bg S_Bg => L_Bg; + + /// /// 场景中唯一名称的节点, 节点类型: , 节点路径: EditorWindow.Window.Panel.VBoxContainer.Body /// public Body S_Body => L_Window.L_Panel.L_VBoxContainer.L_Body; diff --git a/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindowPanel.cs b/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindowPanel.cs index ba06445..cf490d3 100644 --- a/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindowPanel.cs +++ b/DungeonShooting_Godot/src/game/ui/editorWindow/EditorWindowPanel.cs @@ -1,3 +1,4 @@ +using System; using Godot; namespace UI.EditorWindow; @@ -5,14 +6,59 @@ public partial class EditorWindowPanel : EditorWindow { + public class ButtonData + { + /// + /// 显示文本 + /// + public string Text; + /// + /// 点击的回调 + /// + public Action Callback; + + public ButtonData(string text, Action callback) + { + Text = text; + Callback = callback; + } + } + + /// + /// 关闭窗口时回调 + /// + public event Action CloseEvent; + + private UiGrid _uiGrid; + public override void OnCreateUi() { - + S_CustomButton.Instance.Visible = false; } - public override void OnDisposeUi() + public override void OnShowUi() { - + S_Window.Instance.CloseRequested += CloseWindow; + } + + public override void OnHideUi() + { + S_Window.Instance.CloseRequested -= CloseWindow; + } + + /// + /// 设置自定义按钮 + /// + public void SetButtonList(params ButtonData[] buttons) + { + if (_uiGrid == null) + { + S_CustomButton.Instance.Visible = true; + _uiGrid = new UiGrid(S_CustomButton, typeof(CustomButtonCell)); + _uiGrid.SetHorizontalExpand(true); + } + _uiGrid.SetColumns(buttons.Length); + _uiGrid.SetDataList(buttons); } /// @@ -50,4 +96,24 @@ { S_Window.Instance.Size = size; } + + /// + /// 设置窗体最小大小 + /// + public void SetWindowMinSize(Vector2I size) + { + S_Window.Instance.MinSize = size; + } + + /// + /// 关闭窗口 + /// + public void CloseWindow() + { + if (CloseEvent != null) + { + CloseEvent(); + } + DisposeUi(); + } } diff --git a/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoom.cs b/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoom.cs index ac8225f..e49eb11 100644 --- a/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoom.cs +++ b/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoom.cs @@ -6,17 +6,17 @@ public abstract partial class MapEditorCreateRoom : UiBase { /// - /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer /// - public VBoxContainer L_VBoxContainer + public MarginContainer L_MarginContainer { get { - if (_L_VBoxContainer == null) _L_VBoxContainer = new VBoxContainer(this, GetNodeOrNull("VBoxContainer")); - return _L_VBoxContainer; + if (_L_MarginContainer == null) _L_MarginContainer = new MarginContainer(this, GetNodeOrNull("MarginContainer")); + return _L_MarginContainer; } } - private VBoxContainer _L_VBoxContainer; + private MarginContainer _L_MarginContainer; public MapEditorCreateRoom() : base(nameof(MapEditorCreateRoom)) @@ -28,7 +28,7 @@ } /// - /// 类型: , 路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer.RoomNameLabel + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer.RoomNameLabel /// public class RoomNameLabel : UiNode { @@ -37,7 +37,7 @@ } /// - /// 类型: , 路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer.RoomNameInput + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer.RoomNameInput /// public class RoomNameInput : UiNode { @@ -46,12 +46,12 @@ } /// - /// 类型: , 路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer /// public class HBoxContainer : UiNode { /// - /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer.RoomNameLabel + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.RoomNameLabel /// public RoomNameLabel L_RoomNameLabel { @@ -64,7 +64,7 @@ private RoomNameLabel _L_RoomNameLabel; /// - /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer.RoomNameInput + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.RoomNameInput /// public RoomNameInput L_RoomNameInput { @@ -81,12 +81,171 @@ } /// - /// 类型: , 路径: MapEditorCreateRoom.VBoxContainer + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2.GroupNameLabel + /// + public class GroupNameLabel : UiNode + { + public GroupNameLabel(MapEditorCreateRoom uiPanel, Godot.Label node) : base(uiPanel, node) { } + public override GroupNameLabel Clone() => new (UiPanel, (Godot.Label)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2.GroupSelect + /// + public class GroupSelect : UiNode + { + public GroupSelect(MapEditorCreateRoom uiPanel, Godot.OptionButton node) : base(uiPanel, node) { } + public override GroupSelect Clone() => new (UiPanel, (Godot.OptionButton)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2 + /// + public class HBoxContainer2 : UiNode + { + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.GroupNameLabel + /// + public GroupNameLabel L_GroupNameLabel + { + get + { + if (_L_GroupNameLabel == null) _L_GroupNameLabel = new GroupNameLabel(UiPanel, Instance.GetNodeOrNull("GroupNameLabel")); + return _L_GroupNameLabel; + } + } + private GroupNameLabel _L_GroupNameLabel; + + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.GroupSelect + /// + public GroupSelect L_GroupSelect + { + get + { + if (_L_GroupSelect == null) _L_GroupSelect = new GroupSelect(UiPanel, Instance.GetNodeOrNull("GroupSelect")); + return _L_GroupSelect; + } + } + private GroupSelect _L_GroupSelect; + + public HBoxContainer2(MapEditorCreateRoom uiPanel, Godot.HBoxContainer node) : base(uiPanel, node) { } + public override HBoxContainer2 Clone() => new (UiPanel, (Godot.HBoxContainer)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3.TypeNameLabel + /// + public class TypeNameLabel : UiNode + { + public TypeNameLabel(MapEditorCreateRoom uiPanel, Godot.Label node) : base(uiPanel, node) { } + public override TypeNameLabel Clone() => new (UiPanel, (Godot.Label)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3.TypeSelect + /// + public class TypeSelect : UiNode + { + public TypeSelect(MapEditorCreateRoom uiPanel, Godot.OptionButton node) : base(uiPanel, node) { } + public override TypeSelect Clone() => new (UiPanel, (Godot.OptionButton)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3 + /// + public class HBoxContainer3 : UiNode + { + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.TypeNameLabel + /// + public TypeNameLabel L_TypeNameLabel + { + get + { + if (_L_TypeNameLabel == null) _L_TypeNameLabel = new TypeNameLabel(UiPanel, Instance.GetNodeOrNull("TypeNameLabel")); + return _L_TypeNameLabel; + } + } + private TypeNameLabel _L_TypeNameLabel; + + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.TypeSelect + /// + public TypeSelect L_TypeSelect + { + get + { + if (_L_TypeSelect == null) _L_TypeSelect = new TypeSelect(UiPanel, Instance.GetNodeOrNull("TypeSelect")); + return _L_TypeSelect; + } + } + private TypeSelect _L_TypeSelect; + + public HBoxContainer3(MapEditorCreateRoom uiPanel, Godot.HBoxContainer node) : base(uiPanel, node) { } + public override HBoxContainer3 Clone() => new (UiPanel, (Godot.HBoxContainer)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4.WeightNameLabel + /// + public class WeightNameLabel : UiNode + { + public WeightNameLabel(MapEditorCreateRoom uiPanel, Godot.Label node) : base(uiPanel, node) { } + public override WeightNameLabel Clone() => new (UiPanel, (Godot.Label)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4.WeightInput + /// + public class WeightInput : UiNode + { + public WeightInput(MapEditorCreateRoom uiPanel, Godot.SpinBox node) : base(uiPanel, node) { } + public override WeightInput Clone() => new (UiPanel, (Godot.SpinBox)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4 + /// + public class HBoxContainer4 : UiNode + { + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.WeightNameLabel + /// + public WeightNameLabel L_WeightNameLabel + { + get + { + if (_L_WeightNameLabel == null) _L_WeightNameLabel = new WeightNameLabel(UiPanel, Instance.GetNodeOrNull("WeightNameLabel")); + return _L_WeightNameLabel; + } + } + private WeightNameLabel _L_WeightNameLabel; + + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.WeightInput + /// + public WeightInput L_WeightInput + { + get + { + if (_L_WeightInput == null) _L_WeightInput = new WeightInput(UiPanel, Instance.GetNodeOrNull("WeightInput")); + return _L_WeightInput; + } + } + private WeightInput _L_WeightInput; + + public HBoxContainer4(MapEditorCreateRoom uiPanel, Godot.HBoxContainer node) : base(uiPanel, node) { } + public override HBoxContainer4 Clone() => new (UiPanel, (Godot.HBoxContainer)Instance.Duplicate()); + } + + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer.VBoxContainer /// public class VBoxContainer : UiNode { /// - /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.HBoxContainer + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.HBoxContainer /// public HBoxContainer L_HBoxContainer { @@ -98,29 +257,140 @@ } private HBoxContainer _L_HBoxContainer; + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.HBoxContainer2 + /// + public HBoxContainer2 L_HBoxContainer2 + { + get + { + if (_L_HBoxContainer2 == null) _L_HBoxContainer2 = new HBoxContainer2(UiPanel, Instance.GetNodeOrNull("HBoxContainer2")); + return _L_HBoxContainer2; + } + } + private HBoxContainer2 _L_HBoxContainer2; + + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.HBoxContainer3 + /// + public HBoxContainer3 L_HBoxContainer3 + { + get + { + if (_L_HBoxContainer3 == null) _L_HBoxContainer3 = new HBoxContainer3(UiPanel, Instance.GetNodeOrNull("HBoxContainer3")); + return _L_HBoxContainer3; + } + } + private HBoxContainer3 _L_HBoxContainer3; + + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.HBoxContainer4 + /// + public HBoxContainer4 L_HBoxContainer4 + { + get + { + if (_L_HBoxContainer4 == null) _L_HBoxContainer4 = new HBoxContainer4(UiPanel, Instance.GetNodeOrNull("HBoxContainer4")); + return _L_HBoxContainer4; + } + } + private HBoxContainer4 _L_HBoxContainer4; + public VBoxContainer(MapEditorCreateRoom uiPanel, Godot.VBoxContainer node) : base(uiPanel, node) { } public override VBoxContainer Clone() => new (UiPanel, (Godot.VBoxContainer)Instance.Duplicate()); } + /// + /// 类型: , 路径: MapEditorCreateRoom.MarginContainer + /// + public class MarginContainer : UiNode + { + /// + /// 使用 Instance 属性获取当前节点实例对象, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer + /// + public VBoxContainer L_VBoxContainer + { + get + { + if (_L_VBoxContainer == null) _L_VBoxContainer = new VBoxContainer(UiPanel, Instance.GetNodeOrNull("VBoxContainer")); + return _L_VBoxContainer; + } + } + private VBoxContainer _L_VBoxContainer; + + public MarginContainer(MapEditorCreateRoom uiPanel, Godot.MarginContainer node) : base(uiPanel, node) { } + public override MarginContainer Clone() => new (UiPanel, (Godot.MarginContainer)Instance.Duplicate()); + } + /// - /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer.RoomNameLabel + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer.RoomNameLabel /// - public RoomNameLabel S_RoomNameLabel => L_VBoxContainer.L_HBoxContainer.L_RoomNameLabel; + public RoomNameLabel S_RoomNameLabel => L_MarginContainer.L_VBoxContainer.L_HBoxContainer.L_RoomNameLabel; /// - /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer.RoomNameInput + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer.RoomNameInput /// - public RoomNameInput S_RoomNameInput => L_VBoxContainer.L_HBoxContainer.L_RoomNameInput; + public RoomNameInput S_RoomNameInput => L_MarginContainer.L_VBoxContainer.L_HBoxContainer.L_RoomNameInput; /// - /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer.HBoxContainer + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer /// - public HBoxContainer S_HBoxContainer => L_VBoxContainer.L_HBoxContainer; + public HBoxContainer S_HBoxContainer => L_MarginContainer.L_VBoxContainer.L_HBoxContainer; /// - /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.VBoxContainer + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2.GroupNameLabel /// - public VBoxContainer S_VBoxContainer => L_VBoxContainer; + public GroupNameLabel S_GroupNameLabel => L_MarginContainer.L_VBoxContainer.L_HBoxContainer2.L_GroupNameLabel; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2.GroupSelect + /// + public GroupSelect S_GroupSelect => L_MarginContainer.L_VBoxContainer.L_HBoxContainer2.L_GroupSelect; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer2 + /// + public HBoxContainer2 S_HBoxContainer2 => L_MarginContainer.L_VBoxContainer.L_HBoxContainer2; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3.TypeNameLabel + /// + public TypeNameLabel S_TypeNameLabel => L_MarginContainer.L_VBoxContainer.L_HBoxContainer3.L_TypeNameLabel; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3.TypeSelect + /// + public TypeSelect S_TypeSelect => L_MarginContainer.L_VBoxContainer.L_HBoxContainer3.L_TypeSelect; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer3 + /// + public HBoxContainer3 S_HBoxContainer3 => L_MarginContainer.L_VBoxContainer.L_HBoxContainer3; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4.WeightNameLabel + /// + public WeightNameLabel S_WeightNameLabel => L_MarginContainer.L_VBoxContainer.L_HBoxContainer4.L_WeightNameLabel; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4.WeightInput + /// + public WeightInput S_WeightInput => L_MarginContainer.L_VBoxContainer.L_HBoxContainer4.L_WeightInput; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer.HBoxContainer4 + /// + public HBoxContainer4 S_HBoxContainer4 => L_MarginContainer.L_VBoxContainer.L_HBoxContainer4; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer.VBoxContainer + /// + public VBoxContainer S_VBoxContainer => L_MarginContainer.L_VBoxContainer; + + /// + /// 场景中唯一名称的节点, 节点类型: , 节点路径: MapEditorCreateRoom.MarginContainer + /// + public MarginContainer S_MarginContainer => L_MarginContainer; } diff --git a/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoomPanel.cs b/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoomPanel.cs index 087d66a..881db90 100644 --- a/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoomPanel.cs +++ b/DungeonShooting_Godot/src/game/ui/mapEditorCreateRoom/MapEditorCreateRoomPanel.cs @@ -1,36 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.RegularExpressions; using Godot; namespace UI.MapEditorCreateRoom; public partial class MapEditorCreateRoomPanel : MapEditorCreateRoom { + //key: 组名称, value: 选项索引 + private Dictionary _groupMap = new Dictionary(); public override void OnCreateUi() { - - } - - public override void OnShowUi() - { - - } - - public override void OnHideUi() - { - - } - - public override void OnDisposeUi() - { - - } - - private void OnVisibilityChanged() - { - //if (!S_ConfirmationDialog.Instance.Visible) + //初始化选项 + var groupButton = S_GroupSelect.Instance; + var index = 0; + foreach (var mapGroupInfo in MapProjectManager.GroupData) { - GD.Print("关闭UI"); - DisposeUi(); + var id = index++; + _groupMap.Add(mapGroupInfo.Value.Name, id); + groupButton.AddItem(mapGroupInfo.Value.Name, id); } + + var selectButton = S_TypeSelect.Instance; + var roomTypes = Enum.GetValues(); + for (var i = 0; i < roomTypes.Length; i++) + { + var item = roomTypes[i]; + var text = DungeonManager.DungeonRoomTypeToDescribeString(item); + selectButton.AddItem(text, (int)item); + } + } + + /// + /// 设置选中的组 + /// + public void SetSelectGroup(string groupName) + { + if (_groupMap.TryGetValue(groupName, out var value)) + { + S_GroupSelect.Instance.Selected = value; + return; + } + + S_GroupSelect.Instance.Selected = -1; + } + + /// + /// 设置选中的房间类型 + /// + public void SetSelectType(int index) + { + S_TypeSelect.Instance.Selected = index; + } + + /// + /// 填完数据后获取数据对象, 并进行验证, 如果验证失败, 则返回 null + /// + public MapProjectManager.MapRoomInfo GetRoomInfo() + { + var mapRoomInfo = new MapProjectManager.MapRoomInfo(); + mapRoomInfo.Name = S_RoomNameInput.Instance.Text; + //检查名称是否合规 + if (!Regex.IsMatch(mapRoomInfo.Name, "^\\w+$")) + { + EditorTipsManager.ShowTips("错误", "房间名称'" + mapRoomInfo.Name + "'不符合名称约束, 房间名称只允许包含大小写字母和数字!"); + return null; + } + + var groupIndex = S_GroupSelect.Instance.Selected; + foreach (var pair in _groupMap) + { + if (pair.Value == groupIndex) + { + mapRoomInfo.Group = pair.Key; + } + } + + if (mapRoomInfo.Group == null) + { + EditorTipsManager.ShowTips("错误", "组名错误!"); + return null; + } + + var typeIndex = S_TypeSelect.Instance.Selected; + mapRoomInfo.RoomType = (DungeonRoomType)typeIndex; + + //检测是否有同名房间 + var temp = mapRoomInfo.Group + "/" + DungeonManager.DungeonRoomTypeToString(mapRoomInfo.RoomType) + "/" + mapRoomInfo.Name; + var path = GameConfig.RoomTileDir + temp; + var dir = new DirectoryInfo(path); + if (dir.Exists && dir.GetFiles().Length > 0) + { + EditorTipsManager.ShowTips("错误", $"已经有相同路径的房间了!\n路径: {temp}"); + return null; + } + + return mapRoomInfo; } } diff --git a/DungeonShooting_Godot/src/game/ui/mapEditorProject/GroupButtonCell.cs b/DungeonShooting_Godot/src/game/ui/mapEditorProject/GroupButtonCell.cs index be53638..ed6eb3b 100644 --- a/DungeonShooting_Godot/src/game/ui/mapEditorProject/GroupButtonCell.cs +++ b/DungeonShooting_Godot/src/game/ui/mapEditorProject/GroupButtonCell.cs @@ -1,4 +1,6 @@ -namespace UI.MapEditorProject; +using Godot; + +namespace UI.MapEditorProject; public class GroupButtonCell : UiCell { @@ -13,14 +15,15 @@ CellNode.Instance.TooltipText = "路径: " + data.FullPath; } - protected override void OnDestroy() + protected override void OnRefreshIndex() { - CellNode.Instance.Pressed -= OnClick; + GD.Print("刷新索引: " + Index); } //选中工程 private void OnClick() { + Grid.SelectIndex = Index; ((MapEditorProjectPanel)CellNode.UiPanel).SelectGroup(Data); } } \ No newline at end of file diff --git a/DungeonShooting_Godot/src/game/ui/mapEditorProject/MapEditorProjectPanel.cs b/DungeonShooting_Godot/src/game/ui/mapEditorProject/MapEditorProjectPanel.cs index 27030cf..46dcd57 100644 --- a/DungeonShooting_Godot/src/game/ui/mapEditorProject/MapEditorProjectPanel.cs +++ b/DungeonShooting_Godot/src/game/ui/mapEditorProject/MapEditorProjectPanel.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using Godot; +using UI.EditorWindow; +using UI.MapEditorCreateRoom; namespace UI.MapEditorProject; @@ -15,7 +17,9 @@ /// public MapProjectManager.MapRoomInfo[] SelectRoomInfo; + //当前显示的组数据 private UiGrid _groupGrid; + //当前显示的房间数据 private UiGrid _roomGrid; public override void OnCreateUi() @@ -23,11 +27,11 @@ //初始化枚举选项 var roomTypes = Enum.GetValues(); var optionButton = S_RoomTypeButton.Instance; - optionButton.AddItem("全部", 1); + optionButton.AddItem("全部", -1); for (var i = 0; i < roomTypes.Length; i++) { var dungeonRoomType = roomTypes[i]; - optionButton.AddItem(DungeonManager.DungeonRoomTypeToDescribeString(dungeonRoomType), i + 1); + optionButton.AddItem(DungeonManager.DungeonRoomTypeToDescribeString(dungeonRoomType), (int)dungeonRoomType); } _groupGrid = new UiGrid(S_GroupButton, typeof(GroupButtonCell)); @@ -78,6 +82,9 @@ _roomGrid.SetDataList(SelectRoomInfo); } + /// + /// 选择地图并打开地图编辑器 + /// public void SelectRoom(MapProjectManager.MapRoomInfo room) { HideUi(); @@ -113,7 +120,35 @@ { var window = UiManager.Open_EditorWindow(); window.SetWindowTitle("创建地牢房间"); - window.SetWindowSize(new Vector2I(1000, 800)); - window.OpenBody(UiManager.UiName.MapEditorCreateRoom); + window.SetWindowSize(new Vector2I(700, 500)); + var body = window.OpenBody(UiManager.UiName.MapEditorCreateRoom); + if (SelectGroupInfo != null) + { + body.SetSelectGroup(SelectGroupInfo.Name); + } + body.SetSelectType(S_RoomTypeButton.Instance.Selected); + + window.SetButtonList( + new EditorWindowPanel.ButtonData("确定", () => + { + //获取填写的数据, 并创建ui + var mapRoomInfo = body.GetRoomInfo(); + if (mapRoomInfo != null) + { + window.CloseWindow(); + CreateRoom(mapRoomInfo); + } + }), + new EditorWindowPanel.ButtonData("取消", () => + { + window.CloseWindow(); + }) + ); + } + + //创建房间 + private void CreateRoom(MapProjectManager.MapRoomInfo roomInfo) + { + } }