This commit is contained in:
2025-06-16 07:59:50 +08:00
commit 7a6cd423fc
54 changed files with 4068 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

BIN
.vs/Test/v17/.futdcache.v2 Normal file

Binary file not shown.

BIN
.vs/Test/v17/.suo Normal file

Binary file not shown.

47
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,47 @@
{
"version": "2.0.0",
"configurations": [
{
"name": "Play in Editor",
"type": "godot-mono",
"mode": "playInEditor",
"request": "launch"
},
{
"name": "Launch",
"type": "godot-mono",
"request": "launch",
"mode": "executable",
"preLaunchTask": "build",
"executable": "<insert-godot-executable-path-here>",
// See which arguments are available here:
// https://docs.godotengine.org/en/stable/getting_started/editor/command_line_tutorial.html
"executableArguments": [
"--path",
"${workspaceRoot}"
]
},
{
"name": "Launch (Select Scene)",
"type": "godot-mono",
"request": "launch",
"mode": "executable",
"preLaunchTask": "build",
"executable": "<insert-godot-executable-path-here>",
// See which arguments are available here:
// https://docs.godotengine.org/en/stable/getting_started/editor/command_line_tutorial.html
"executableArguments": [
"--path",
"${workspaceRoot}",
"${command:SelectLaunchScene}"
]
},
{
"name": "Attach",
"type": "godot-mono",
"request": "attach",
"address": "localhost",
"port": 23685
}
]
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"godotTools.editorPath.godot4": "k:\\Tool_Dev\\Godot\\Godot_v4.3-stable_mono_win64.exe"
}

18
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "<insert-godot-executable-path-here>",
"type": "process",
"args": [
"--build-solutions",
"--path",
"${workspaceRoot}",
"--no-window",
"-q"
],
"problemMatcher": "$msCompile"
}
]
}

112
PROJECT_STRUCTURE.md Normal file
View File

@ -0,0 +1,112 @@
# 项目结构说明
## 📁 项目根目录结构
```
test/
├── 📁 scripts/ # 脚本文件(按功能分类)
│ ├── 📁 core/ # 核心系统脚本
│ ├── 📁 data/ # 数据管理脚本
│ ├── 📁 inventory/ # 库存系统脚本
│ ├── 📁 production/ # 生产系统脚本
│ └── 📁 ui/ # 用户界面脚本
├── 📁 scenes/ # 场景文件
├── 📁 data/ # 数据文件
│ ├── 📁 config/ # 配置文件
│ ├── 📁 translations/ # 本地化文件
│ └── 📁 imports/ # 导入配置文件
├── 📁 assets/ # 资源文件
└── 📄 project.godot # 项目配置文件
```
## 📂 详细文件分类
### 🔧 scripts/ - 脚本文件
#### 📁 core/ - 核心系统
- `GameScene.cs` - 主游戏场景控制器
#### 📁 data/ - 数据管理
- `GameData.cs` - 游戏数据管理器
- `ResourceCategoryManager.cs` - 资源分类管理器
#### 📁 inventory/ - 库存系统
- `InventoryManager.cs` - 库存管理器(单例)
- `InventoryTableManager.cs` - 库存表格UI管理器
- `InventoryCategoryManager.cs` - 库存分类管理器
#### 📁 production/ - 生产系统
- `ManualCollectionPanel.cs` - 手动采集面板
- `ResourceGrid.cs` - 资源网格组件
#### 📁 ui/ - 用户界面
- `DynamicTabManager.cs` - 动态标签页管理器(管理"合成"和"生产线"标签)
- `MainMenu.cs` - 主菜单控制器
### 📁 scenes/ - 场景文件
- `game_scene.tscn` - 主游戏场景
- `ItemPanel.tscn` - 物品面板模板
- `InventoryItem.tscn` - 库存物品模板
### 📁 data/ - 数据文件
#### 📁 config/ - 配置文件
- `items.csv` - 物品数据配置
- `resource_categories.json` - 资源分类配置
- `inventory_categories.json` - 库存分类配置
#### 📁 translations/ - 本地化文件
- `items.*.translation` - 物品数据本地化文件Godot自动生成
#### 📁 imports/ - 导入配置
- `*.import` - Godot资源导入配置文件
### 📁 assets/ - 资源文件
- 图片、音频、字体等游戏资源
## 🎮 游戏标签页结构
### 📋 合成标签
包含以下分类:
- **手动采集** - 可手动采集的基础资源
- **基础资源** - 原始材料
- **液体资源** - 液体类资源
- **冶炼成品** - 冶炼后的材料
- **建筑设施** - 基础建筑和设备
### 🏭 生产线标签
包含以下分类:
- **生产设备** - 高级生产设备(组装机、化工厂等)
## 🎯 设计原则
1. **功能分离**: 按照功能模块分类存放脚本
2. **数据分离**: 配置文件与自动生成文件分开存放
3. **清晰结构**: 每个文件夹都有明确的职责
4. **易于维护**: 相关功能的文件集中管理
## 📋 自动加载顺序
项目中的自动加载脚本按以下顺序加载:
1. `GameData` - 游戏基础数据
2. `ResourceCategoryManager` - 资源分类管理
3. `InventoryManager` - 库存管理
4. `InventoryCategoryManager` - 库存分类管理
## 🔄 依赖关系
- UI层依赖于数据层和库存层
- 生产系统依赖于库存系统
- 所有系统都依赖于核心数据管理器
## ⚠️ 重要维护说明
### 防止 Translation 文件自动生成
Godot 会自动将 CSV 文件识别为本地化资源并生成 `.translation` 文件。为了保持 `data/config/` 文件夹的整洁:
1. **CSV 导入设置**:确保 `data/config/items.csv.import` 使用 `importer="keep"`
2. **文件夹隔离**:使用 `.gdignore` 文件让 Godot 忽略 `translations/``imports/` 文件夹
3. **定期清理**:如果发现 config 文件夹中出现新的 `.translation` 文件,及时删除
详细说明请参考 `data/README.md` 文件。

8
Test.csproj Normal file
View File

@ -0,0 +1,8 @@
<Project Sdk="Godot.NET.Sdk/4.3.0">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'android' ">net7.0</TargetFramework>
<TargetFramework Condition=" '$(GodotTargetPlatform)' == 'ios' ">net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
</Project>

19
Test.sln Normal file
View File

@ -0,0 +1,19 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test.csproj", "{70D23D50-B427-4816-A2C3-FF2056DA4396}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
ExportDebug|Any CPU = ExportDebug|Any CPU
ExportRelease|Any CPU = ExportRelease|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{70D23D50-B427-4816-A2C3-FF2056DA4396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70D23D50-B427-4816-A2C3-FF2056DA4396}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70D23D50-B427-4816-A2C3-FF2056DA4396}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
{70D23D50-B427-4816-A2C3-FF2056DA4396}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
{70D23D50-B427-4816-A2C3-FF2056DA4396}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
{70D23D50-B427-4816-A2C3-FF2056DA4396}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
EndGlobalSection
EndGlobal

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://x8myw44ubs8k"
path="res://.godot/imported/PixPin_2025-06-15_11-35-40.png-f7908b72324f2ad4749c15407276e01d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/PixPin_2025-06-15_11-35-40.png"
dest_files=["res://.godot/imported/PixPin_2025-06-15_11-35-40.png-f7908b72324f2ad4749c15407276e01d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

1
assets/textures/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ddesmcbvjwu20"
path="res://.godot/imported/icon.svg-958a189a5c2fe57391e3d9b3ea181aa4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/textures/icon.svg"
dest_files=["res://.godot/imported/icon.svg-958a189a5c2fe57391e3d9b3ea181aa4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

59
data/README.md Normal file
View File

@ -0,0 +1,59 @@
# 数据文件夹说明
## 📁 文件夹结构
### 📁 config/ - 配置文件
包含游戏的核心配置文件:
- `items.csv` - 物品数据配置
- `resource_categories.json` - 资源分类配置
- `inventory_categories.json` - 库存分类配置
- `items.csv.import` - CSV文件的导入设置
### 📁 translations/ - 本地化文件
存放 Godot 自动生成的本地化文件(.translation
这个文件夹包含 `.gdignore` 文件,告诉 Godot 忽略其中的内容。
### 📁 imports/ - 导入配置备份
存放导入配置文件的备份。
这个文件夹包含 `.gdignore` 文件,告诉 Godot 忽略其中的内容。
## ⚠️ 重要说明
### 防止 Translation 文件自动生成
如果你发现 `config/` 文件夹中又出现了 `.translation` 文件,说明 CSV 文件的导入设置被重置了。
**解决方法:**
1. 检查 `data/config/items.csv.import` 文件
2. 确保内容如下:
```
[remap]
importer="keep"
type=""
[deps]
source_file="res://data/config/items.csv"
[params]
```
3. 如果不是这样,请修改为上述内容
4. 删除任何新生成的 `.translation` 文件:
```bash
del data\config\*.translation
```
### 为什么会生成 Translation 文件?
Godot 默认将 CSV 文件识别为本地化资源,会自动生成 `.translation` 文件。
通过将导入器设置为 "keep",我们告诉 Godot 将 CSV 文件保持原样,不进行特殊处理。
## 🔧 维护建议
1. **不要手动编辑** `.import` 文件,除非必要
2. **定期检查** config 文件夹,确保没有新的 translation 文件生成
3. **备份重要配置** 在修改配置文件前先备份
4. **使用版本控制** 跟踪配置文件的变化

View File

@ -0,0 +1,17 @@
{
"recipes": [
{
"id": "iron_ingot_smelting",
"outputItem": "iron_ingot",
"outputQuantity": 1,
"craftingMethod": "冶炼",
"craftingTime": 1.0,
"ingredients": [
{
"itemId": "iron_ore",
"quantity": 1
}
]
}
]
}

View File

@ -0,0 +1,20 @@
{
"categories": [
{
"categoryName": "原材料",
"items": [
"iron_ore",
"copper_ore",
"coal_ore",
"water"
]
},
{
"categoryName": "炼制成品",
"items": [
"iron_ingot",
"copper_ingot"
]
}
]
}

13
data/config/items.csv Normal file
View File

@ -0,0 +1,13 @@
Id,Name,Category,Description,Recipe,CraftTime,PowerConsumption,IconPath
iron_ore,铁矿,RawMaterial,基础原材料,可用于冶炼铁块,null,0,0,res://assets/textures/items/iron_ore.png
copper_ore,铜矿,RawMaterial,基础原材料,可用于冶炼铜块,null,0,0,res://assets/textures/items/copper_ore.png
stone_ore,石矿,RawMaterial,基础建筑材料,可用于建造建筑,null,0,0,res://assets/textures/items/stone_ore.png
coal_ore,煤矿,RawMaterial,重要的能源资源,可用于发电,null,0,0,res://assets/textures/items/coal_ore.png
water,,RawMaterial,基础资源,可用于多种生产,null,0,0,res://assets/textures/items/water.png
crude_oil,原油,RawMaterial,重要的能源资源,可用于生产燃料,null,0,0,res://assets/textures/items/crude_oil.png
iron_ingot,铁块,ProcessedMaterial,由铁矿冶炼而成的基础材料,iron_ore:1,1.0,60.0,res://assets/textures/items/iron_ingot.png
copper_ingot,铜块,ProcessedMaterial,由铜矿冶炼而成的基础材料,copper_ore:1,1.0,60.0,res://assets/textures/items/copper_ingot.png
smelter,冶炼厂,Building,用于冶炼矿石的基础建筑,iron_ingot:4;copper_ingot:2,3.0,360.0,res://assets/textures/buildings/smelter.png
miner,采矿机,Building,自动采集矿石的基础建筑,iron_ingot:3;copper_ingot:1,2.0,420.0,res://assets/textures/buildings/miner.png
assembler,组装机,ProductionDevice,高级生产设备,可制造复杂物品,iron_ingot:6;copper_ingot:4,5.0,480.0,res://assets/textures/buildings/assembler.png
chemical_plant,化工厂,ProductionDevice,专业化工生产设备,可处理液体,iron_ingot:8;copper_ingot:6,6.0,600.0,res://assets/textures/buildings/chemical_plant.png
1 Id Name Category Description Recipe CraftTime PowerConsumption IconPath
2 iron_ore 铁矿 RawMaterial 基础原材料,可用于冶炼铁块 null 0 0 res://assets/textures/items/iron_ore.png
3 copper_ore 铜矿 RawMaterial 基础原材料,可用于冶炼铜块 null 0 0 res://assets/textures/items/copper_ore.png
4 stone_ore 石矿 RawMaterial 基础建筑材料,可用于建造建筑 null 0 0 res://assets/textures/items/stone_ore.png
5 coal_ore 煤矿 RawMaterial 重要的能源资源,可用于发电 null 0 0 res://assets/textures/items/coal_ore.png
6 water RawMaterial 基础资源,可用于多种生产 null 0 0 res://assets/textures/items/water.png
7 crude_oil 原油 RawMaterial 重要的能源资源,可用于生产燃料 null 0 0 res://assets/textures/items/crude_oil.png
8 iron_ingot 铁块 ProcessedMaterial 由铁矿冶炼而成的基础材料 iron_ore:1 1.0 60.0 res://assets/textures/items/iron_ingot.png
9 copper_ingot 铜块 ProcessedMaterial 由铜矿冶炼而成的基础材料 copper_ore:1 1.0 60.0 res://assets/textures/items/copper_ingot.png
10 smelter 冶炼厂 Building 用于冶炼矿石的基础建筑 iron_ingot:4;copper_ingot:2 3.0 360.0 res://assets/textures/buildings/smelter.png
11 miner 采矿机 Building 自动采集矿石的基础建筑 iron_ingot:3;copper_ingot:1 2.0 420.0 res://assets/textures/buildings/miner.png
12 assembler 组装机 ProductionDevice 高级生产设备,可制造复杂物品 iron_ingot:6;copper_ingot:4 5.0 480.0 res://assets/textures/buildings/assembler.png
13 chemical_plant 化工厂 ProductionDevice 专业化工生产设备,可处理液体 iron_ingot:8;copper_ingot:6 6.0 600.0 res://assets/textures/buildings/chemical_plant.png

View File

@ -0,0 +1,10 @@
[remap]
importer="keep"
type=""
[deps]
source_file="res://data/config/items.csv"
[params]

View File

@ -0,0 +1,50 @@
[
{
"categoryName": "手动采集",
"itemIds": [
"iron_ore",
"copper_ore",
"coal_ore",
"stone_ore",
"water",
"crude_oil"
]
},
{
"categoryName": "基础资源",
"itemIds": [
"iron_ore",
"copper_ore",
"stone_ore",
"coal_ore"
]
},
{
"categoryName": "液体资源",
"itemIds": [
"water",
"crude_oil"
]
},
{
"categoryName": "冶炼",
"itemIds": [
"iron_ingot",
"copper_ingot"
]
},
{
"categoryName": "建筑设施",
"itemIds": [
"smelter",
"miner"
]
},
{
"categoryName": "生产设备",
"itemIds": [
"assembler",
"chemical_plant"
]
}
]

View File

@ -0,0 +1,2 @@
# Godot 会忽略这个文件夹中的所有文件
# 这些是自动生成的本地化文件,不需要被 Godot 处理

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

35
project.godot Normal file
View File

@ -0,0 +1,35 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="test"
run/main_scene="res://scenes/game_scene.tscn"
config/features=PackedStringArray("4.3", "C#", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
GameData="*res://scripts/data/GameData.cs"
ResourceCategoryManager="*res://scripts/data/ResourceCategoryManager.cs"
InventoryManager="*res://scripts/inventory/InventoryManager.cs"
InventoryCategoryManager="*res://scripts/inventory/InventoryCategoryManager.cs"
CraftingRecipeManager="*res://scripts/data/CraftingRecipeManager.cs"
[display]
window/size/viewport_width=1280
window/size/viewport_height=720
window/stretch/mode="canvas_items"
window/stretch/aspect="expand"
[dotnet]
project/assembly_name="test"

129
scenes/CraftingItem.tscn Normal file
View File

@ -0,0 +1,129 @@
[gd_scene load_steps=3 format=3 uid="uid://bod88mt8ewq2"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.12, 0.15, 0.18, 0.95)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.25, 0.35, 0.45, 0.8)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
shadow_color = Color(0, 0, 0, 0.4)
shadow_size = 3
shadow_offset = Vector2(1, 2)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2"]
content_margin_left = 2.0
content_margin_top = 2.0
content_margin_right = 2.0
content_margin_bottom = 2.0
bg_color = Color(0.2, 0.2, 0.2, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.4, 0.4, 0.4, 1)
corner_radius_top_left = 2
corner_radius_top_right = 2
corner_radius_bottom_right = 2
corner_radius_bottom_left = 2
[node name="CraftingItem" type="Panel"]
custom_minimum_size = Vector2(220, 52)
theme_override_styles/panel = SubResource("StyleBoxFlat_1")
[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 = 6
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 6
theme_override_constants/margin_bottom = 4
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="IconTexture" type="TextureRect" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(32, 32)
layout_mode = 2
expand_mode = 1
stretch_mode = 5
[node name="MiddleContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 2
[node name="NameLabel" type="Label" parent="MarginContainer/HBoxContainer/MiddleContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 12
text = "物品名称"
vertical_alignment = 1
[node name="MaterialsLabel" type="Label" parent="MarginContainer/HBoxContainer/MiddleContainer"]
modulate = Color(0.7, 0.7, 0.7, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "材料: 1x铁矿"
[node name="InfoRow" type="HBoxContainer" parent="MarginContainer/HBoxContainer/MiddleContainer"]
layout_mode = 2
theme_override_constants/separation = 8
[node name="CraftTimeLabel" type="Label" parent="MarginContainer/HBoxContainer/MiddleContainer/InfoRow"]
modulate = Color(0.8, 0.9, 1, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "1.0s"
[node name="MethodLabel" type="Label" parent="MarginContainer/HBoxContainer/MiddleContainer/InfoRow"]
modulate = Color(1, 0.9, 0.7, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "手动"
[node name="RightContainer" type="VBoxContainer" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
[node name="CraftButton" type="Button" parent="MarginContainer/HBoxContainer/RightContainer"]
custom_minimum_size = Vector2(48, 20)
layout_mode = 2
size_flags_vertical = 4
theme_override_font_sizes/font_size = 9
text = "合成"
[node name="QuantityContainer" type="HBoxContainer" parent="MarginContainer/HBoxContainer/RightContainer"]
layout_mode = 2
theme_override_constants/separation = 1
[node name="MinusButton" type="Button" parent="MarginContainer/HBoxContainer/RightContainer/QuantityContainer"]
custom_minimum_size = Vector2(18, 16)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "-"
[node name="QuantityInput" type="LineEdit" parent="MarginContainer/HBoxContainer/RightContainer/QuantityContainer"]
custom_minimum_size = Vector2(20, 16)
layout_mode = 2
theme_override_constants/minimum_character_width = 0
theme_override_font_sizes/font_size = 8
theme_override_styles/focus = SubResource("StyleBoxFlat_2")
theme_override_styles/normal = SubResource("StyleBoxFlat_2")
text = "1"
placeholder_text = "1"
alignment = 1
context_menu_enabled = false
[node name="PlusButton" type="Button" parent="MarginContainer/HBoxContainer/RightContainer/QuantityContainer"]
custom_minimum_size = Vector2(18, 16)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "+"

69
scenes/CraftingQueue.tscn Normal file
View File

@ -0,0 +1,69 @@
[gd_scene load_steps=2 format=3 uid="uid://bvn8xh2ywxe"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.15, 0.15, 0.2, 0.8)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.3, 0.3, 0.4, 0.6)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[node name="CraftingQueue" type="Panel"]
custom_minimum_size = Vector2(0, 80)
theme_override_styles/panel = SubResource("StyleBoxFlat_1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="TitleLabel" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "合成队列"
horizontal_alignment = 1
theme_override_font_sizes/font_size = 12
[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="QueueContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 4
[node name="Slot1" type="Panel" parent="MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot2" type="Panel" parent="MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot3" type="Panel" parent="MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot4" type="Panel" parent="MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot5" type="Panel" parent="MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(48, 48)
layout_mode = 2
size_flags_horizontal = 3

64
scenes/InventoryItem.tscn Normal file
View File

@ -0,0 +1,64 @@
[gd_scene load_steps=2 format=3 uid="uid://b3sb2a63y77ra"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.2, 0.2, 0.2, 0.8)
corner_radius_top_left = 4
corner_radius_top_right = 4
corner_radius_bottom_right = 4
corner_radius_bottom_left = 4
[node name="InventoryItem" type="Control"]
custom_minimum_size = Vector2(0, 35)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
[node name="Background" type="Panel" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_1")
[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 = 6
theme_override_constants/margin_top = 3
theme_override_constants/margin_right = 6
theme_override_constants/margin_bottom = 3
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/separation = 6
[node name="IconTexture" type="TextureRect" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(24, 24)
layout_mode = 2
expand_mode = 1
stretch_mode = 5
[node name="NameLabel" type="Label" parent="MarginContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 11
text = "物品名称"
vertical_alignment = 1
[node name="QuantityLabel" type="Label" parent="MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(30, 0)
layout_mode = 2
theme_override_font_sizes/font_size = 11
text = "999"
horizontal_alignment = 2
vertical_alignment = 1

127
scenes/ItemPanel.tscn Normal file
View File

@ -0,0 +1,127 @@
[gd_scene load_steps=3 format=3 uid="uid://cihq67dbb8q7m"]
[ext_resource type="Texture2D" uid="uid://x8myw44ubs8k" path="res://assets/textures/PixPin_2025-06-15_11-35-40.png" id="1_1w476"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1"]
bg_color = Color(0.15, 0.15, 0.2, 0.95)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.3, 0.3, 0.4, 0.8)
corner_radius_top_left = 6
corner_radius_top_right = 6
corner_radius_bottom_right = 6
corner_radius_bottom_left = 6
shadow_color = Color(0, 0, 0, 0.3)
shadow_size = 2
shadow_offset = Vector2(1, 1)
[node name="ItemPanel" type="Panel"]
custom_minimum_size = Vector2(250, 64)
theme_override_styles/panel = SubResource("StyleBoxFlat_1")
[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 = 5
theme_override_constants/margin_top = 4
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 4
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="TopRow" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="IconTexture" type="TextureRect" parent="MarginContainer/VBoxContainer/TopRow"]
custom_minimum_size = Vector2(36, 36)
layout_mode = 2
texture = ExtResource("1_1w476")
expand_mode = 1
stretch_mode = 5
[node name="MiddleContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/TopRow"]
layout_mode = 2
size_flags_horizontal = 3
[node name="TopInfoRow" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TopRow/MiddleContainer"]
layout_mode = 2
[node name="NameLabel" type="Label" parent="MarginContainer/VBoxContainer/TopRow/MiddleContainer/TopInfoRow"]
layout_mode = 2
theme_override_font_sizes/font_size = 11
text = "物品名称"
[node name="ProductionLabel" type="Label" parent="MarginContainer/VBoxContainer/TopRow/MiddleContainer/TopInfoRow"]
modulate = Color(0.8, 0.8, 0.8, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 9
text = "50/s"
[node name="Spacer" type="Control" parent="MarginContainer/VBoxContainer/TopRow/MiddleContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="RightContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/TopRow"]
layout_mode = 2
[node name="PowerLabel" type="Label" parent="MarginContainer/VBoxContainer/TopRow/RightContainer"]
modulate = Color(1, 0.8, 0.8, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "-300W"
horizontal_alignment = 2
[node name="RightSpacer" type="Control" parent="MarginContainer/VBoxContainer/TopRow/RightContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="BottomDeviceRow" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TopRow/RightContainer"]
layout_mode = 2
[node name="MinusButton" type="Button" parent="MarginContainer/VBoxContainer/TopRow/RightContainer/BottomDeviceRow"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
theme_override_font_sizes/font_size = 10
text = "-"
[node name="DeviceLabel" type="Label" parent="MarginContainer/VBoxContainer/TopRow/RightContainer/BottomDeviceRow"]
modulate = Color(0.7, 0.7, 0.7, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 8
text = "设备: 5"
[node name="PlusButton" type="Button" parent="MarginContainer/VBoxContainer/TopRow/RightContainer/BottomDeviceRow"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
theme_override_font_sizes/font_size = 10
text = "+"
[node name="ProgressContainer" type="Control" parent="MarginContainer/VBoxContainer"]
custom_minimum_size = Vector2(0, 5)
layout_mode = 2
size_flags_vertical = 0
[node name="ProgressBackground" type="ColorRect" parent="MarginContainer/VBoxContainer/ProgressContainer"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
color = Color(0.2, 0.2, 0.2, 1)
[node name="ProgressFill" type="ColorRect" parent="MarginContainer/VBoxContainer/ProgressContainer"]
layout_mode = 1
anchors_preset = -1
anchor_right = 0.65
anchor_bottom = 1.0
grow_vertical = 2
color = Color(0.3, 0.7, 0.3, 1)

228
scenes/game_scene.tscn Normal file
View File

@ -0,0 +1,228 @@
[gd_scene load_steps=5 format=3 uid="uid://bw51wdor6ucmk"]
[ext_resource type="Script" path="res://scripts/core/GameScene.cs" id="1_2k4vx"]
[ext_resource type="Script" path="res://scripts/ui/DynamicTabManager.cs" id="2_3k5vy"]
[ext_resource type="Script" path="res://scripts/inventory/InventoryTableManager.cs" id="3_inventory_table"]
[ext_resource type="Script" path="res://scripts/production/CraftingQueueManager.cs" id="4_crafting_queue"]
[node name="GameScene" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_2k4vx")
[node name="Background" 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.1, 0.1, 0.15, 1)
[node name="HSplitContainer" type="HSplitContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
split_offset = 230
[node name="LeftPanel" type="Panel" parent="HSplitContainer"]
custom_minimum_size = Vector2(276, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/LeftPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="PowerInfo" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer"]
custom_minimum_size = Vector2(0, 100)
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo"]
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 = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer"]
layout_mode = 2
[node name="PowerTitle" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "电力系统"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="PowerRow1" type="HBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PowerGeneration" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow1"]
layout_mode = 2
size_flags_horizontal = 3
text = "发电: 0 KW"
[node name="PowerConsumption" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow1"]
layout_mode = 2
size_flags_horizontal = 3
text = "耗电: 0 KW"
[node name="PowerRow2" type="HBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="PowerStorage" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow2"]
layout_mode = 2
size_flags_horizontal = 3
text = "蓄电: 0 KWh"
[node name="PowerDischarge" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow2"]
layout_mode = 2
size_flags_horizontal = 3
text = "放电: 0 KW"
[node name="CraftingQueue" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer"]
custom_minimum_size = Vector2(0, 80)
layout_mode = 2
script = ExtResource("4_crafting_queue")
[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue"]
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 = 10
theme_override_constants/margin_top = 8
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 8
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer"]
layout_mode = 2
[node name="TitleLabel" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 12
text = "合成队列"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="QueueContainer" type="HBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/separation = 2
[node name="Slot1" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot2" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot3" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot4" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot5" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot6" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot7" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="Slot8" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue/MarginContainer/VBoxContainer/QueueContainer"]
custom_minimum_size = Vector2(30, 30)
layout_mode = 2
size_flags_horizontal = 3
[node name="InventoryPanel" type="Panel" parent="HSplitContainer/LeftPanel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="MarginContainer" type="MarginContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel"]
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 = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel/MarginContainer"]
layout_mode = 2
[node name="InventoryTitle" type="Label" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel/MarginContainer/VBoxContainer"]
layout_mode = 2
text = "库存"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="InventoryTableManager" type="VBoxContainer" parent="HSplitContainer/LeftPanel/VBoxContainer/InventoryPanel/MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
script = ExtResource("3_inventory_table")
[node name="RightPanel" type="Panel" parent="HSplitContainer"]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/RightPanel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="CategoryTabs" type="TabContainer" parent="HSplitContainer/RightPanel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
theme_override_font_sizes/font_size = 24
script = ExtResource("2_3k5vy")

70
scenes/main_menu.tscn Normal file
View File

@ -0,0 +1,70 @@
[gd_scene load_steps=2 format=3 uid="uid://dc48a40brl4hm"]
[ext_resource type="Script" path="res://scripts/MainMenu.cs" id="1_3k4vx"]
[node name="MainMenu" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_3k4vx")
[node name="Background" 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.15, 0.15, 0.25, 1)
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -120.0
offset_top = -150.0
offset_right = 120.0
offset_bottom = 150.0
grow_horizontal = 2
grow_vertical = 2
[node name="Title" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "游戏主菜单"
horizontal_alignment = 1
vertical_alignment = 1
[node name="Spacer1" type="Control" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 30)
layout_mode = 2
[node name="NewGameBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(240, 50)
layout_mode = 2
text = "新游戏"
[node name="LoadGameBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(240, 50)
layout_mode = 2
text = "载入存档"
[node name="SettingsBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(240, 50)
layout_mode = 2
text = "设置"
[node name="ExitBtn" type="Button" parent="VBoxContainer"]
custom_minimum_size = Vector2(240, 50)
layout_mode = 2
text = "退出游戏"
[connection signal="pressed" from="VBoxContainer/NewGameBtn" to="." method="_OnNewGameBtnPressed"]
[connection signal="pressed" from="VBoxContainer/LoadGameBtn" to="." method="_OnLoadGameBtnPressed"]
[connection signal="pressed" from="VBoxContainer/SettingsBtn" to="." method="_OnSettingsBtnPressed"]
[connection signal="pressed" from="VBoxContainer/ExitBtn" to="." method="_OnExitBtnPressed"]

64
scenes/ss Normal file
View File

@ -0,0 +1,64 @@
[gd_scene format=3]
[node name="ItemPanel" type="Panel"]
custom_minimum_size = Vector2(320, 60)
[node name="HBoxContainer" type="HBoxContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
[node name="Icon" type="TextureRect" parent="HBoxContainer"]
custom_minimum_size = Vector2(48, 48)
expand = true
stretch_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer"]
[node name="TopRow" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
[node name="NameLabel" type="Label" parent="HBoxContainer/VBoxContainer/TopRow"]
text = "物品名称 50/s"
custom_colors/font_color = Color(1, 0.85, 0.3)
theme_override_font_sizes/font_size = 18
[node name="PowerIcon" type="TextureRect" parent="HBoxContainer/VBoxContainer/TopRow"]
custom_minimum_size = Vector2(20, 20)
expand = false
stretch_mode = 2
[node name="PowerLabel" type="Label" parent="HBoxContainer/VBoxContainer/TopRow"]
text = "-360.0W"
custom_colors/font_color = Color(0.7, 0.9, 1)
theme_override_font_sizes/font_size = 14
[node name="RecipeRow" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
[node name="EqualIcon" type="TextureRect" parent="HBoxContainer/VBoxContainer/RecipeRow"]
custom_minimum_size = Vector2(16, 16)
expand = false
stretch_mode = 2
[node name="MaterialIcon" type="TextureRect" parent="HBoxContainer/VBoxContainer/RecipeRow"]
custom_minimum_size = Vector2(32, 32)
expand = false
stretch_mode = 2
[node name="ControlRow" type="HBoxContainer" parent="HBoxContainer/VBoxContainer"]
[node name="MinusButton" type="Button" parent="HBoxContainer/VBoxContainer/ControlRow"]
text = "-"
custom_minimum_size = Vector2(32, 32)
[node name="CountEdit" type="LineEdit" parent="HBoxContainer/VBoxContainer/ControlRow"]
text = "50"
custom_minimum_size = Vector2(48, 32)
[node name="PlusButton" type="Button" parent="HBoxContainer/VBoxContainer/ControlRow"]
text = "+"
custom_minimum_size = Vector2(32, 32)
[node name="ProgressBar" type="ProgressBar" parent="HBoxContainer/VBoxContainer"]
custom_minimum_size = Vector2(200, 8)
value = 50
max_value = 100

26
scenes/test.tscn Normal file
View File

@ -0,0 +1,26 @@
[gd_scene format=3 uid="uid://b8kwq742j0g41"]
[node name="Test" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 0
offset_right = 183.0
offset_bottom = 40.0
alignment = 1
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "test"
[node name="HSeparator" type="HSeparator" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3

136
scripts/core/GameScene.cs Normal file
View File

@ -0,0 +1,136 @@
using Godot;
using System;
using System.Collections.Generic;
public partial class GameScene : Control
{
// 资源类型枚举
public enum ResourceType
{
IronOre,
CopperOre,
IronIngot,
CopperIngot
}
// 资源数据结构
public class ResourceData
{
public string Name { get; set; }
public int Amount { get; set; }
public ResourceType Type { get; set; }
public bool IsProcessed { get; set; }
}
// 电力系统数据
private float powerGeneration = 0;
private float powerConsumption = 0;
private float powerStorage = 0;
private float powerDischarge = 0;
// 库存数据保留用于兼容性但主要由InventoryManager管理
private Dictionary<ResourceType, ResourceData> inventory = new Dictionary<ResourceType, ResourceData>();
// UI引用
private Label powerGenerationLabel;
private Label powerConsumptionLabel;
private Label powerStorageLabel;
private Label powerDischargeLabel;
private TabContainer categoryTabs;
private CraftingQueueManager craftingQueue;
public override void _Ready()
{
GD.Print("GameScene _Ready 开始");
// 获取UI引用
powerGenerationLabel = GetNode<Label>("HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow1/PowerGeneration");
powerConsumptionLabel = GetNode<Label>("HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow1/PowerConsumption");
powerStorageLabel = GetNode<Label>("HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow2/PowerStorage");
powerDischargeLabel = GetNode<Label>("HSplitContainer/LeftPanel/VBoxContainer/PowerInfo/MarginContainer/VBoxContainer/PowerRow2/PowerDischarge");
categoryTabs = GetNode<TabContainer>("HSplitContainer/RightPanel/VBoxContainer/CategoryTabs");
craftingQueue = GetNode<CraftingQueueManager>("HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue");
// 初始化库存(保留用于兼容性)
InitializeInventory();
// 更新UI
UpdatePowerUI();
GD.Print("GameScene 初始化完成");
}
private void InitializeInventory()
{
inventory[ResourceType.IronOre] = new ResourceData { Name = "铁矿", Amount = 0, Type = ResourceType.IronOre, IsProcessed = false };
inventory[ResourceType.CopperOre] = new ResourceData { Name = "铜矿", Amount = 0, Type = ResourceType.CopperOre, IsProcessed = false };
inventory[ResourceType.IronIngot] = new ResourceData { Name = "铁块", Amount = 0, Type = ResourceType.IronIngot, IsProcessed = true };
inventory[ResourceType.CopperIngot] = new ResourceData { Name = "铜块", Amount = 0, Type = ResourceType.CopperIngot, IsProcessed = true };
}
private void UpdatePowerUI()
{
powerGenerationLabel.Text = $"发电: {powerGeneration:F1} KW";
powerConsumptionLabel.Text = $"耗电: {powerConsumption:F1} KW";
powerStorageLabel.Text = $"蓄电: {powerStorage:F1} KWh";
powerDischargeLabel.Text = $"放电: {powerDischarge:F1} KW";
}
// 添加资源到库存(保留用于兼容性)
public void AddResource(ResourceType type, int amount)
{
if (inventory.ContainsKey(type))
{
inventory[type].Amount += amount;
}
}
// 更新电力系统
public void UpdatePowerSystem(float generation, float consumption, float storage, float discharge)
{
powerGeneration = generation;
powerConsumption = consumption;
powerStorage = storage;
powerDischarge = discharge;
UpdatePowerUI();
}
public override void _Process(double delta)
{
// 更新电力信息显示
UpdatePowerInfo();
}
private void UpdatePowerInfo()
{
// 这里可以从电力管理器获取实际数据
// 目前使用占位符数据
if (powerGenerationLabel != null)
powerGenerationLabel.Text = "发电: 0 KW";
if (powerConsumptionLabel != null)
powerConsumptionLabel.Text = "耗电: 0 KW";
if (powerStorageLabel != null)
powerStorageLabel.Text = "蓄电: 0 KWh";
if (powerDischargeLabel != null)
powerDischargeLabel.Text = "放电: 0 KW";
}
public override void _Input(InputEvent @event)
{
if (@event is InputEventKey keyEvent && keyEvent.Pressed)
{
// 按C键测试合成铁块
if (keyEvent.Keycode == Key.C)
{
if (craftingQueue != null)
{
craftingQueue.StartIronIngotCrafting();
GD.Print("按下C键尝试开始铁块合成");
}
}
}
}
}

View File

@ -0,0 +1,143 @@
using Godot;
using System.Collections.Generic;
using System.Text.Json;
public partial class CraftingRecipeManager : Node
{
public static CraftingRecipeManager Instance { get; private set; }
public class Ingredient
{
public string ItemId { get; set; }
public int Quantity { get; set; }
}
public class CraftingRecipe
{
public string Id { get; set; }
public string OutputItem { get; set; }
public int OutputQuantity { get; set; }
public string CraftingMethod { get; set; }
public float CraftingTime { get; set; }
public List<Ingredient> Ingredients { get; set; } = new List<Ingredient>();
}
public class CraftingRecipeData
{
public List<CraftingRecipe> Recipes { get; set; } = new List<CraftingRecipe>();
}
private CraftingRecipeData recipeData;
private Dictionary<string, CraftingRecipe> recipeMap = new Dictionary<string, CraftingRecipe>();
private Dictionary<string, List<CraftingRecipe>> outputItemMap = new Dictionary<string, List<CraftingRecipe>>();
public override void _Ready()
{
if (Instance == null)
{
Instance = this;
LoadCraftingRecipes();
}
else
{
QueueFree();
}
}
private void LoadCraftingRecipes()
{
string configPath = "res://data/config/crafting_recipes.json";
if (!FileAccess.FileExists(configPath))
{
GD.PrintErr($"合成配方配置文件不存在: {configPath}");
return;
}
using var file = FileAccess.Open(configPath, FileAccess.ModeFlags.Read);
if (file == null)
{
GD.PrintErr($"无法打开合成配方配置文件: {configPath}");
return;
}
string jsonContent = file.GetAsText();
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
recipeData = JsonSerializer.Deserialize<CraftingRecipeData>(jsonContent, options);
if (recipeData?.Recipes != null)
{
// 构建配方映射
foreach (var recipe in recipeData.Recipes)
{
recipeMap[recipe.Id] = recipe;
// 按输出物品分组
if (!outputItemMap.ContainsKey(recipe.OutputItem))
{
outputItemMap[recipe.OutputItem] = new List<CraftingRecipe>();
}
outputItemMap[recipe.OutputItem].Add(recipe);
GD.Print($"加载合成配方: {recipe.Id} - {recipe.OutputItem} ({recipe.CraftingMethod})");
}
GD.Print($"成功加载 {recipeData.Recipes.Count} 个合成配方");
}
}
catch (JsonException e)
{
GD.PrintErr($"解析合成配方配置文件失败: {e.Message}");
}
}
public List<CraftingRecipe> GetAllRecipes()
{
return recipeData?.Recipes ?? new List<CraftingRecipe>();
}
public CraftingRecipe GetRecipe(string recipeId)
{
return recipeMap.GetValueOrDefault(recipeId);
}
public List<CraftingRecipe> GetRecipesForItem(string itemId)
{
return outputItemMap.GetValueOrDefault(itemId) ?? new List<CraftingRecipe>();
}
public bool CanCraft(string recipeId)
{
var recipe = GetRecipe(recipeId);
if (recipe == null || InventoryManager.Instance == null)
{
return false;
}
// 检查是否有足够的材料
foreach (var ingredient in recipe.Ingredients)
{
if (InventoryManager.Instance.GetItemQuantity(ingredient.ItemId) < ingredient.Quantity)
{
return false;
}
}
return true;
}
public override void _ExitTree()
{
if (Instance == this)
{
Instance = null;
}
}
}

197
scripts/data/GameData.cs Normal file
View File

@ -0,0 +1,197 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
public partial class GameData : Node
{
// 物品分类枚举
public enum ItemCategory
{
RawMaterial, // 原材料
ProcessedMaterial, // 冶炼成品
Building, // 建筑
Component, // 组件
Product // 产品
}
// 物品数据结构
public class ItemData
{
public string Id { get; set; } // 物品ID
public string Name { get; set; } // 物品名称
public ItemCategory Category { get; set; } // 物品分类
public string Description { get; set; } // 物品描述
public Dictionary<string, int> Recipe { get; set; } // 合成配方
public float CraftTime { get; set; } // 合成时间
public float PowerConsumption { get; set; } // 耗电量
public string IconPath { get; set; } // 图标路径
}
// 单例实例
private static GameData instance;
public static GameData Instance
{
get
{
return instance;
}
}
// 物品数据字典
private Dictionary<string, ItemData> items = new Dictionary<string, ItemData>();
public override void _Ready()
{
instance = this; // 设置单例实例
LoadItemsFromCSV();
}
// 从CSV文件加载物品数据
private void LoadItemsFromCSV()
{
try
{
// 读取CSV文件
string csvPath = "res://data/config/items.csv";
if (!Godot.FileAccess.FileExists(csvPath))
{
GD.PrintErr("物品数据文件不存在: " + csvPath);
return;
}
var file = Godot.FileAccess.Open(csvPath, Godot.FileAccess.ModeFlags.Read);
if (file == null)
{
GD.PrintErr("无法打开物品数据文件: " + csvPath);
return;
}
// 读取标题行
string header = file.GetLine();
string[] headers = header.Split(',');
// 读取数据行
while (!file.EofReached())
{
string line = file.GetLine();
if (string.IsNullOrEmpty(line)) continue;
string[] values = line.Split(',');
if (values.Length != headers.Length) continue;
// 创建物品数据
var item = new ItemData
{
Id = values[0],
Name = values[1],
Category = (ItemCategory)Enum.Parse(typeof(ItemCategory), values[2]),
Description = values[3],
Recipe = ParseRecipe(values[4]),
CraftTime = float.Parse(values[5]),
PowerConsumption = float.Parse(values[6]),
IconPath = values[7]
};
AddItem(item);
}
file.Close();
GD.Print("成功加载物品数据");
}
catch (Exception e)
{
GD.PrintErr("加载物品数据时出错: " + e.Message);
}
}
// 解析配方字符串
private Dictionary<string, int> ParseRecipe(string recipeStr)
{
if (string.IsNullOrEmpty(recipeStr) || recipeStr == "null")
return null;
var recipe = new Dictionary<string, int>();
string[] pairs = recipeStr.Split(';');
foreach (string pair in pairs)
{
string[] parts = pair.Split(':');
if (parts.Length == 2)
{
string itemId = parts[0];
int amount = int.Parse(parts[1]);
recipe[itemId] = amount;
}
}
return recipe;
}
// 添加物品
private void AddItem(ItemData item)
{
items[item.Id] = item;
}
// 获取物品数据
public ItemData GetItem(string id)
{
if (items.ContainsKey(id))
{
return items[id];
}
return null;
}
// 获取所有物品
public Dictionary<string, ItemData> GetAllItems()
{
return items;
}
// 获取指定分类的所有物品
public Dictionary<string, ItemData> GetItemsByCategory(ItemCategory category)
{
var result = new Dictionary<string, ItemData>();
foreach (var item in items)
{
if (item.Value.Category == category)
{
result[item.Key] = item.Value;
}
}
return result;
}
// 检查是否有足够的材料进行合成
public bool HasEnoughMaterials(string itemId, Dictionary<string, int> inventory)
{
var item = GetItem(itemId);
if (item == null || item.Recipe == null) return false;
foreach (var requirement in item.Recipe)
{
if (!inventory.ContainsKey(requirement.Key) ||
inventory[requirement.Key] < requirement.Value)
{
return false;
}
}
return true;
}
// 计算合成所需时间
public float GetCraftTime(string itemId)
{
var item = GetItem(itemId);
return item?.CraftTime ?? 0f;
}
// 计算合成所需电力
public float GetPowerConsumption(string itemId)
{
var item = GetItem(itemId);
return item?.PowerConsumption ?? 0f;
}
}

View File

@ -0,0 +1,169 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Text.Json;
public partial class ResourceCategoryManager : Node
{
// 资源分类数据结构
public class ResourceCategory
{
public string CategoryName { get; set; }
public List<string> ItemIds { get; set; }
}
// 单例实例
private static ResourceCategoryManager instance;
public static ResourceCategoryManager Instance
{
get
{
return instance;
}
}
// 资源分类列表
private List<ResourceCategory> categories = new List<ResourceCategory>();
public override void _Ready()
{
GD.Print("ResourceCategoryManager _Ready 开始");
instance = this; // 设置单例实例
LoadResourceCategories();
}
// 从JSON文件加载资源分类配置
private void LoadResourceCategories()
{
try
{
string jsonPath = "res://data/config/resource_categories.json";
GD.Print($"尝试加载配置文件: {jsonPath}");
if (!Godot.FileAccess.FileExists(jsonPath))
{
GD.PrintErr("资源分类配置文件不存在: " + jsonPath);
return;
}
var file = Godot.FileAccess.Open(jsonPath, Godot.FileAccess.ModeFlags.Read);
if (file == null)
{
GD.PrintErr("无法打开资源分类配置文件: " + jsonPath);
return;
}
string jsonContent = file.GetAsText();
file.Close();
// GD.Print($"JSON文件内容长度: {jsonContent.Length}");
// GD.Print($"JSON文件内容: {jsonContent}");
// 解析JSON
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
categories = JsonSerializer.Deserialize<List<ResourceCategory>>(jsonContent, options);
if (categories == null)
{
GD.PrintErr("JSON反序列化结果为null");
categories = new List<ResourceCategory>();
return;
}
GD.Print($"成功加载资源分类配置,共 {categories.Count} 个分类");
// 打印每个分类的详细信息
foreach (var category in categories)
{
GD.Print($"分类: {category.CategoryName}, 物品数量: {category.ItemIds?.Count ?? 0}");
if (category.ItemIds != null)
{
foreach (var itemId in category.ItemIds)
{
GD.Print($" - {itemId}");
}
}
}
}
catch (Exception e)
{
GD.PrintErr("加载资源分类配置时出错: " + e.Message);
GD.PrintErr("堆栈跟踪: " + e.StackTrace);
}
}
// 获取所有分类
public List<ResourceCategory> GetAllCategories()
{
GD.Print($"GetAllCategories 被调用,返回 {categories.Count} 个分类");
return categories;
}
// 根据分类名称获取分类
public ResourceCategory GetCategoryByName(string categoryName)
{
return categories.Find(c => c.CategoryName == categoryName);
}
// 根据物品ID获取所属分类
public ResourceCategory GetCategoryByItemId(string itemId)
{
return categories.Find(c => c.ItemIds.Contains(itemId));
}
// 获取指定分类的所有物品数据
public Dictionary<string, GameData.ItemData> GetItemsByCategory(string categoryName)
{
GD.Print($"GetItemsByCategory 被调用,分类名称: {categoryName}");
var category = GetCategoryByName(categoryName);
if (category == null)
{
GD.PrintErr($"未找到分类: {categoryName}");
return new Dictionary<string, GameData.ItemData>();
}
GD.Print($"找到分类 {categoryName},包含 {category.ItemIds?.Count ?? 0} 个物品ID");
// 检查GameData.Instance
if (GameData.Instance == null)
{
GD.PrintErr("GameData.Instance 为 null");
return new Dictionary<string, GameData.ItemData>();
}
var result = new Dictionary<string, GameData.ItemData>();
foreach (string itemId in category.ItemIds)
{
// GD.Print($"尝试获取物品: {itemId}");
var item = GameData.Instance.GetItem(itemId);
if (item != null)
{
GD.Print($"成功获取物品: {itemId} - {item.Name}");
result[itemId] = item;
}
else
{
GD.PrintErr($"未找到物品: {itemId}");
}
}
GD.Print($"GetItemsByCategory 返回 {result.Count} 个物品");
return result;
}
// 获取分类名称列表
public List<string> GetCategoryNames()
{
var names = new List<string>();
foreach (var category in categories)
{
names.Add(category.CategoryName);
}
return names;
}
}

View File

@ -0,0 +1,121 @@
using Godot;
using System.Collections.Generic;
using System.Text.Json;
public partial class InventoryCategoryManager : Node
{
public static InventoryCategoryManager Instance { get; private set; }
public class InventoryCategory
{
public string CategoryName { get; set; }
public List<string> Items { get; set; } = new List<string>();
}
public class InventoryCategoryData
{
public List<InventoryCategory> Categories { get; set; } = new List<InventoryCategory>();
}
private InventoryCategoryData categoryData;
private Dictionary<string, InventoryCategory> categoryMap = new Dictionary<string, InventoryCategory>();
public override void _Ready()
{
if (Instance == null)
{
Instance = this;
LoadInventoryCategories();
}
else
{
QueueFree();
}
}
private void LoadInventoryCategories()
{
string configPath = "res://data/config/inventory_categories.json";
if (!FileAccess.FileExists(configPath))
{
GD.PrintErr($"库存分类配置文件不存在: {configPath}");
return;
}
using var file = FileAccess.Open(configPath, FileAccess.ModeFlags.Read);
if (file == null)
{
GD.PrintErr($"无法打开库存分类配置文件: {configPath}");
return;
}
string jsonContent = file.GetAsText();
try
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
categoryData = JsonSerializer.Deserialize<InventoryCategoryData>(jsonContent, options);
if (categoryData?.Categories != null)
{
// 构建分类映射
foreach (var category in categoryData.Categories)
{
categoryMap[category.CategoryName] = category;
GD.Print($"加载库存分类: {category.CategoryName}, 包含 {category.Items.Count} 个物品");
}
GD.Print($"成功加载 {categoryData.Categories.Count} 个库存分类");
}
}
catch (JsonException e)
{
GD.PrintErr($"解析库存分类配置文件失败: {e.Message}");
}
}
public List<InventoryCategory> GetAllCategories()
{
return categoryData?.Categories ?? new List<InventoryCategory>();
}
public InventoryCategory GetCategory(string categoryName)
{
return categoryMap.GetValueOrDefault(categoryName);
}
public Dictionary<string, GameData.ItemData> GetItemsByCategory(string categoryName)
{
var result = new Dictionary<string, GameData.ItemData>();
var category = GetCategory(categoryName);
if (category == null || GameData.Instance == null)
{
return result;
}
foreach (var itemId in category.Items)
{
var itemData = GameData.Instance.GetItem(itemId);
if (itemData != null)
{
result[itemId] = itemData;
}
}
return result;
}
public override void _ExitTree()
{
if (Instance == this)
{
Instance = null;
}
}
}

View File

@ -0,0 +1,368 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
public partial class InventoryManager : Node
{
// 库存数据结构
public class InventoryItem
{
public string ItemId { get; set; }
public int Quantity { get; set; }
public InventoryItem(string itemId, int quantity = 0)
{
ItemId = itemId;
Quantity = quantity;
}
}
// 单例实例
private static InventoryManager instance;
public static InventoryManager Instance
{
get
{
return instance;
}
}
// 库存数据字典 - 物品ID -> 库存项
private Dictionary<string, InventoryItem> inventory = new Dictionary<string, InventoryItem>();
// 线程锁
private readonly object inventoryLock = new object();
// 事件委托
public delegate void InventoryChangedEventHandler(string itemId, int oldQuantity, int newQuantity);
public event InventoryChangedEventHandler InventoryChanged;
public override void _Ready()
{
GD.Print("InventoryManager _Ready 开始");
// 确保单例
if (instance == null)
{
instance = this;
InitializeInventory();
}
else
{
GD.PrintErr("InventoryManager 实例已存在!");
QueueFree();
}
}
// 初始化库存
private void InitializeInventory()
{
GD.Print("初始化库存系统");
lock (inventoryLock)
{
// 为所有已知物品创建库存条目初始数量为0
if (GameData.Instance != null)
{
var allItems = GameData.Instance.GetAllItems();
foreach (var item in allItems)
{
inventory[item.Key] = new InventoryItem(item.Key, 0);
}
GD.Print($"初始化了 {inventory.Count} 个物品的库存条目");
}
else
{
GD.PrintErr("GameData.Instance 为 null无法初始化库存");
}
}
// 添加一些测试数据
AddTestData();
}
// 添加测试数据
private void AddTestData()
{
GD.Print("添加测试库存数据");
AddItem("iron_ore", 100);
AddItem("copper_ore", 50);
AddItem("coal_ore", 75);
AddItem("water", 200);
AddItem("iron_ingot", 25);
AddItem("copper_ingot", 15);
}
// 添加物品到库存
public bool AddItem(string itemId, int quantity)
{
if (quantity <= 0) return false;
lock (inventoryLock)
{
int oldQuantity = GetItemQuantityUnsafe(itemId);
if (!inventory.ContainsKey(itemId))
{
inventory[itemId] = new InventoryItem(itemId, quantity);
}
else
{
inventory[itemId].Quantity += quantity;
}
int newQuantity = inventory[itemId].Quantity;
GD.Print($"添加物品: {itemId} +{quantity} (总量: {newQuantity})");
// 在锁外触发事件
CallDeferred(nameof(TriggerInventoryChanged), itemId, oldQuantity, newQuantity);
return true;
}
}
// 从库存中移除物品
public bool RemoveItem(string itemId, int quantity)
{
if (quantity <= 0) return false;
lock (inventoryLock)
{
if (!inventory.ContainsKey(itemId)) return false;
int oldQuantity = inventory[itemId].Quantity;
if (oldQuantity < quantity) return false; // 库存不足
inventory[itemId].Quantity -= quantity;
int newQuantity = inventory[itemId].Quantity;
GD.Print($"移除物品: {itemId} -{quantity} (剩余: {newQuantity})");
// 在锁外触发事件
CallDeferred(nameof(TriggerInventoryChanged), itemId, oldQuantity, newQuantity);
return true;
}
}
// 设置物品数量
public void SetItemQuantity(string itemId, int quantity)
{
lock (inventoryLock)
{
int oldQuantity = GetItemQuantityUnsafe(itemId);
if (!inventory.ContainsKey(itemId))
{
inventory[itemId] = new InventoryItem(itemId, quantity);
}
else
{
inventory[itemId].Quantity = quantity;
}
GD.Print($"设置物品数量: {itemId} = {quantity}");
// 在锁外触发事件
CallDeferred(nameof(TriggerInventoryChanged), itemId, oldQuantity, quantity);
}
}
// 获取物品数量(线程安全)
public int GetItemQuantity(string itemId)
{
lock (inventoryLock)
{
return GetItemQuantityUnsafe(itemId);
}
}
// 获取物品数量(非线程安全,内部使用)
private int GetItemQuantityUnsafe(string itemId)
{
if (inventory.ContainsKey(itemId))
{
return inventory[itemId].Quantity;
}
return 0;
}
// 检查是否有足够的物品
public bool HasEnoughItems(string itemId, int requiredQuantity)
{
return GetItemQuantity(itemId) >= requiredQuantity;
}
// 检查是否有足够的材料(用于配方检查)
public bool HasEnoughMaterials(Dictionary<string, int> recipe)
{
if (recipe == null) return true;
lock (inventoryLock)
{
foreach (var requirement in recipe)
{
if (GetItemQuantityUnsafe(requirement.Key) < requirement.Value)
{
return false;
}
}
return true;
}
}
// 消耗材料(用于生产)
public bool ConsumeMaterials(Dictionary<string, int> recipe)
{
if (recipe == null) return true;
lock (inventoryLock)
{
// 先检查是否有足够材料
foreach (var requirement in recipe)
{
if (GetItemQuantityUnsafe(requirement.Key) < requirement.Value)
{
return false;
}
}
// 消耗材料
foreach (var requirement in recipe)
{
int oldQuantity = GetItemQuantityUnsafe(requirement.Key);
inventory[requirement.Key].Quantity -= requirement.Value;
int newQuantity = inventory[requirement.Key].Quantity;
// 在锁外触发事件
CallDeferred(nameof(TriggerInventoryChanged), requirement.Key, oldQuantity, newQuantity);
}
return true;
}
}
// 获取所有库存物品
public Dictionary<string, InventoryItem> GetAllInventory()
{
lock (inventoryLock)
{
var result = new Dictionary<string, InventoryItem>();
foreach (var item in inventory)
{
result[item.Key] = new InventoryItem(item.Value.ItemId, item.Value.Quantity);
}
return result;
}
}
// 获取有库存的物品(数量>0
public Dictionary<string, InventoryItem> GetAvailableItems()
{
lock (inventoryLock)
{
var result = new Dictionary<string, InventoryItem>();
foreach (var item in inventory)
{
if (item.Value.Quantity > 0)
{
result[item.Key] = new InventoryItem(item.Value.ItemId, item.Value.Quantity);
}
}
return result;
}
}
// 获取指定分类的库存物品
public Dictionary<string, InventoryItem> GetInventoryByCategory(GameData.ItemCategory category)
{
lock (inventoryLock)
{
var result = new Dictionary<string, InventoryItem>();
foreach (var item in inventory)
{
var itemData = GameData.Instance?.GetItem(item.Key);
if (itemData != null && itemData.Category == category)
{
result[item.Key] = new InventoryItem(item.Value.ItemId, item.Value.Quantity);
}
}
return result;
}
}
// 清空库存
public void ClearInventory()
{
Dictionary<string, int> oldQuantities;
lock (inventoryLock)
{
oldQuantities = new Dictionary<string, int>();
foreach (var item in inventory)
{
oldQuantities[item.Key] = item.Value.Quantity;
item.Value.Quantity = 0;
}
GD.Print("清空所有库存");
}
// 在锁外触发事件
foreach (var item in oldQuantities)
{
if (item.Value > 0)
{
CallDeferred(nameof(TriggerInventoryChanged), item.Key, item.Value, 0);
}
}
}
// 获取库存总数量
public int GetTotalItemCount()
{
lock (inventoryLock)
{
return inventory.Values.Sum(item => item.Quantity);
}
}
// 调试:打印所有库存
public void PrintInventory()
{
lock (inventoryLock)
{
GD.Print("=== 当前库存 ===");
foreach (var item in inventory)
{
if (item.Value.Quantity > 0)
{
var itemData = GameData.Instance?.GetItem(item.Key);
string itemName = itemData?.Name ?? item.Key;
GD.Print($"{itemName}: {item.Value.Quantity}");
}
}
GD.Print("===============");
}
}
// 触发库存变化事件(在主线程中调用)
private void TriggerInventoryChanged(string itemId, int oldQuantity, int newQuantity)
{
InventoryChanged?.Invoke(itemId, oldQuantity, newQuantity);
}
// 清理单例
public override void _ExitTree()
{
if (instance == this)
{
instance = null;
}
}
}

View File

@ -0,0 +1,265 @@
using Godot;
using System.Collections.Generic;
public partial class InventoryTableManager : VBoxContainer
{
private PackedScene inventoryItemScene;
private Dictionary<string, GridContainer> categoryContainers = new Dictionary<string, GridContainer>();
public override void _Ready()
{
GD.Print("InventoryTableManager _Ready 开始");
// 加载库存物品场景
inventoryItemScene = GD.Load<PackedScene>("res://scenes/InventoryItem.tscn");
if (inventoryItemScene == null)
{
GD.PrintErr("无法加载InventoryItem场景");
return;
}
// 初始化库存显示
InitializeInventoryDisplay();
// 订阅库存变化事件
if (InventoryManager.Instance != null)
{
InventoryManager.Instance.InventoryChanged += OnInventoryChanged;
}
GD.Print("InventoryTableManager 初始化完成");
}
private void InitializeInventoryDisplay()
{
GD.Print("初始化库存Table显示");
// 等待InventoryCategoryManager初始化
if (InventoryCategoryManager.Instance == null)
{
CallDeferred(nameof(InitializeInventoryDisplay));
return;
}
CreateCategoryBlocks();
UpdateInventoryDisplay();
}
private void CreateCategoryBlocks()
{
// 清空现有内容
foreach (Node child in GetChildren())
{
child.QueueFree();
}
categoryContainers.Clear();
var categories = InventoryCategoryManager.Instance.GetAllCategories();
foreach (var category in categories)
{
CreateCategoryBlock(category);
}
}
private void CreateCategoryBlock(InventoryCategoryManager.InventoryCategory category)
{
GD.Print($"创建库存分类块: {category.CategoryName}");
// 创建分类容器
var categoryContainer = new VBoxContainer();
categoryContainer.Name = $"{category.CategoryName}Block";
// 添加顶部间距
var topSpacer = new Control();
topSpacer.CustomMinimumSize = new Vector2(0, 10);
categoryContainer.AddChild(topSpacer);
// 创建标题行
var titleContainer = new HBoxContainer();
// 分类名称标签
var titleLabel = new Label();
titleLabel.Text = category.CategoryName;
titleLabel.HorizontalAlignment = HorizontalAlignment.Left;
titleLabel.AddThemeFontSizeOverride("font_size", 14);
titleLabel.Modulate = new Color(0.9f, 0.9f, 0.9f, 1.0f);
titleContainer.AddChild(titleLabel);
// 添加小间距
var labelSpacer = new Control();
labelSpacer.CustomMinimumSize = new Vector2(10, 0);
titleContainer.AddChild(labelSpacer);
// HSeparator 横线分隔符
var separator = new HSeparator();
separator.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
separator.SizeFlagsVertical = Control.SizeFlags.ShrinkCenter;
titleContainer.AddChild(separator);
categoryContainer.AddChild(titleContainer);
// 添加小间距
var spacer = new Control();
spacer.CustomMinimumSize = new Vector2(0, 5);
categoryContainer.AddChild(spacer);
// 创建物品列表容器 - 使用GridContainer实现每行2个物品
var itemsContainer = new GridContainer();
itemsContainer.Name = $"{category.CategoryName}Items";
itemsContainer.Columns = 2; // 每行2列
itemsContainer.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
itemsContainer.AddThemeConstantOverride("h_separation", 4); // 减少水平间距
itemsContainer.AddThemeConstantOverride("v_separation", 4); // 垂直间距
categoryContainer.AddChild(itemsContainer);
// 保存容器引用
categoryContainers[category.CategoryName] = itemsContainer;
// 添加到主容器
AddChild(categoryContainer);
}
private void OnInventoryChanged(string itemId, int oldQuantity, int newQuantity)
{
GD.Print($"库存Table变化: {itemId} {oldQuantity} -> {newQuantity}");
UpdateInventoryDisplay();
}
private void UpdateInventoryDisplay()
{
if (InventoryManager.Instance == null || InventoryCategoryManager.Instance == null)
{
return;
}
// 清空所有分类的物品显示
foreach (var container in categoryContainers.Values)
{
foreach (Node child in container.GetChildren())
{
child.QueueFree();
}
}
// 获取所有分类并更新显示
var categories = InventoryCategoryManager.Instance.GetAllCategories();
foreach (var category in categories)
{
UpdateCategoryDisplay(category);
}
}
private void UpdateCategoryDisplay(InventoryCategoryManager.InventoryCategory category)
{
if (!categoryContainers.ContainsKey(category.CategoryName))
{
return;
}
var container = categoryContainers[category.CategoryName];
var items = InventoryCategoryManager.Instance.GetItemsByCategory(category.CategoryName);
foreach (var kvp in items)
{
var itemId = kvp.Key;
var itemData = kvp.Value;
// 获取库存数量
int quantity = InventoryManager.Instance.GetItemQuantity(itemId);
// 只显示有库存的物品或者显示所有物品包括0数量
// 这里我们选择显示所有物品0数量的显示为灰色
CreateInventoryItemDisplay(container, itemData, quantity);
}
}
private void CreateInventoryItemDisplay(GridContainer container, GameData.ItemData itemData, int quantity)
{
// 实例化库存物品UI
var inventoryItem = inventoryItemScene.Instantiate<Control>();
if (inventoryItem == null)
{
GD.PrintErr($"无法实例化InventoryItem for {itemData.Name}");
return;
}
// 设置尺寸标志,让物品能够填充分配的空间
inventoryItem.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
inventoryItem.SizeFlagsVertical = Control.SizeFlags.ShrinkCenter;
// 设置图标
var iconTexture = inventoryItem.GetNode<TextureRect>("MarginContainer/HBoxContainer/IconTexture");
if (iconTexture != null && !string.IsNullOrEmpty(itemData.IconPath))
{
// 检查文件是否存在
if (Godot.FileAccess.FileExists(itemData.IconPath))
{
var texture = GD.Load<Texture2D>(itemData.IconPath);
if (texture != null)
{
iconTexture.Texture = texture;
}
}
else
{
// 使用默认图标
var defaultIcon = GD.Load<Texture2D>("res://assets/textures/icon.svg");
if (defaultIcon != null)
{
iconTexture.Texture = defaultIcon;
}
}
}
// 设置名称
var nameLabel = inventoryItem.GetNode<Label>("MarginContainer/HBoxContainer/NameLabel");
if (nameLabel != null)
{
nameLabel.Text = itemData.Name;
// 如果数量为0设置为灰色
if (quantity == 0)
{
nameLabel.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
}
else
{
nameLabel.Modulate = new Color(1.0f, 1.0f, 1.0f, 1.0f);
}
}
// 设置数量
var quantityLabel = inventoryItem.GetNode<Label>("MarginContainer/HBoxContainer/QuantityLabel");
if (quantityLabel != null)
{
quantityLabel.Text = quantity.ToString();
// 根据数量设置颜色
if (quantity == 0)
{
quantityLabel.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
}
else if (quantity < 10)
{
quantityLabel.Modulate = new Color(1.0f, 0.8f, 0.4f, 1.0f); // 橙色 - 库存较低
}
else
{
quantityLabel.Modulate = new Color(0.8f, 1.0f, 0.8f, 1.0f); // 绿色 - 库存充足
}
}
container.AddChild(inventoryItem);
}
public override void _ExitTree()
{
// 取消订阅事件
if (InventoryManager.Instance != null)
{
InventoryManager.Instance.InventoryChanged -= OnInventoryChanged;
}
}
}

View File

@ -0,0 +1,384 @@
using Godot;
using System.Collections.Generic;
public partial class CraftingQueueManager : Panel
{
public class CraftingQueueItem
{
public string RecipeId { get; set; }
public int Quantity { get; set; } = 1;
public float RemainingTime { get; set; }
public float TotalTime { get; set; }
public bool IsActive { get; set; }
}
private const int MAX_QUEUE_SIZE = 8;
private List<CraftingQueueItem> craftingQueue = new List<CraftingQueueItem>();
private List<Panel> slotPanels = new List<Panel>();
public override void _Ready()
{
GD.Print("CraftingQueueManager _Ready 开始");
// 获取所有槽位面板
var queueContainer = GetNode<HBoxContainer>("MarginContainer/VBoxContainer/QueueContainer");
for (int i = 1; i <= MAX_QUEUE_SIZE; i++)
{
var slot = queueContainer.GetNode<Panel>($"Slot{i}");
slotPanels.Add(slot);
}
// 初始化队列显示
UpdateQueueDisplay();
GD.Print("CraftingQueueManager 初始化完成");
}
public override void _Process(double delta)
{
ProcessCrafting((float)delta);
}
private void ProcessCrafting(float deltaTime)
{
if (craftingQueue.Count == 0) return;
// 处理第一个(当前正在制作的)物品
var currentItem = craftingQueue[0];
if (!currentItem.IsActive)
{
// 开始制作
currentItem.IsActive = true;
GD.Print($"开始制作: {currentItem.RecipeId}");
// 重新更新显示以确保进度条正确初始化
UpdateQueueDisplay();
return; // 这一帧先不处理时间让UI先更新
}
currentItem.RemainingTime -= deltaTime;
if (currentItem.RemainingTime <= 0)
{
// 制作完成
CompleteCrafting(currentItem);
craftingQueue.RemoveAt(0);
UpdateQueueDisplay();
}
else
{
// 更新进度显示
float progress = 1.0f - (currentItem.RemainingTime / currentItem.TotalTime);
UpdateSlotProgress(0, progress);
}
}
private void CompleteCrafting(CraftingQueueItem item)
{
var recipe = CraftingRecipeManager.Instance?.GetRecipe(item.RecipeId);
if (recipe == null || InventoryManager.Instance == null)
{
GD.PrintErr($"无法完成制作: {item.RecipeId}");
return;
}
// 添加产品到库存(数量 = 配方产出数量 × 批量数量)
int totalOutputQuantity = recipe.OutputQuantity * item.Quantity;
InventoryManager.Instance.AddItem(recipe.OutputItem, totalOutputQuantity);
GD.Print($"制作完成: {recipe.OutputItem} x{totalOutputQuantity} (批量: {item.Quantity})");
}
public bool AddToQueue(string recipeId, int quantity = 1)
{
if (craftingQueue.Count >= MAX_QUEUE_SIZE)
{
GD.Print("合成队列已满");
return false;
}
var recipe = CraftingRecipeManager.Instance?.GetRecipe(recipeId);
if (recipe == null)
{
GD.PrintErr($"找不到配方: {recipeId}");
return false;
}
// 检查是否有足够的材料制作指定数量
foreach (var ingredient in recipe.Ingredients)
{
int requiredQuantity = ingredient.Quantity * quantity;
if (InventoryManager.Instance.GetItemQuantity(ingredient.ItemId) < requiredQuantity)
{
GD.Print($"材料不足,无法制作 {quantity} 个: {recipeId}");
return false;
}
}
// 扣除材料(数量 = 配方需求 × 批量数量)
foreach (var ingredient in recipe.Ingredients)
{
int totalRequired = ingredient.Quantity * quantity;
InventoryManager.Instance.RemoveItem(ingredient.ItemId, totalRequired);
GD.Print($"扣除材料: {ingredient.ItemId} x{totalRequired} (批量: {quantity})");
}
// 计算总制作时间(时间 = 基础时间 × 数量)
float totalCraftingTime = recipe.CraftingTime * quantity;
var queueItem = new CraftingQueueItem
{
RecipeId = recipeId,
Quantity = quantity,
RemainingTime = totalCraftingTime,
TotalTime = totalCraftingTime,
IsActive = false
};
craftingQueue.Add(queueItem);
UpdateQueueDisplay();
GD.Print($"添加到合成队列: {recipeId} x{quantity},总时间: {totalCraftingTime}s已扣除材料");
return true;
}
private void UpdateQueueDisplay()
{
// 清空所有槽位
for (int i = 0; i < slotPanels.Count; i++)
{
ClearSlot(i);
}
// 显示队列中的物品
for (int i = 0; i < craftingQueue.Count && i < slotPanels.Count; i++)
{
var queueItem = craftingQueue[i];
var recipe = CraftingRecipeManager.Instance?.GetRecipe(queueItem.RecipeId);
if (recipe != null)
{
UpdateSlotDisplay(i, recipe, queueItem);
}
}
}
private void UpdateSlotDisplay(int slotIndex, CraftingRecipeManager.CraftingRecipe recipe, CraftingQueueItem queueItem)
{
var slot = slotPanels[slotIndex];
// 立即清除现有的子节点而不是使用QueueFree
var childrenToRemove = new List<Node>();
foreach (Node child in slot.GetChildren())
{
childrenToRemove.Add(child);
}
foreach (Node child in childrenToRemove)
{
slot.RemoveChild(child);
child.QueueFree();
}
// 创建背景层(灰色)
var backgroundRect = new ColorRect();
backgroundRect.Name = "Background";
backgroundRect.Color = new Color(0.3f, 0.3f, 0.3f, 1.0f);
backgroundRect.AnchorLeft = 0.0f;
backgroundRect.AnchorTop = 0.0f;
backgroundRect.AnchorRight = 1.0f;
backgroundRect.AnchorBottom = 1.0f;
slot.AddChild(backgroundRect);
// 创建图标显示层
var iconTexture = new TextureRect();
iconTexture.Name = "ProductIcon";
iconTexture.ExpandMode = TextureRect.ExpandModeEnum.FitWidthProportional;
iconTexture.StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered;
iconTexture.AnchorLeft = 0.1f;
iconTexture.AnchorTop = 0.1f;
iconTexture.AnchorRight = 0.9f;
iconTexture.AnchorBottom = 0.9f;
// 获取产物的图标
var outputItemData = GameData.Instance?.GetItem(recipe.OutputItem);
if (outputItemData != null && !string.IsNullOrEmpty(outputItemData.IconPath))
{
// 尝试加载物品图标
if (FileAccess.FileExists(outputItemData.IconPath))
{
var texture = GD.Load<Texture2D>(outputItemData.IconPath);
if (texture != null)
{
iconTexture.Texture = texture;
GD.Print($"槽位{slotIndex}加载产物图标: {outputItemData.IconPath}");
}
else
{
// 使用默认图标
LoadDefaultIcon(iconTexture);
}
}
else
{
// 使用默认图标
LoadDefaultIcon(iconTexture);
}
}
else
{
// 使用默认图标
LoadDefaultIcon(iconTexture);
}
slot.AddChild(iconTexture);
// 创建进度层(白色半透明,覆盖在图标上面)
var progressRect = new ColorRect();
progressRect.Name = "Progress";
progressRect.AnchorLeft = 0.0f;
progressRect.AnchorRight = 1.0f;
progressRect.AnchorBottom = 1.0f;
progressRect.Color = new Color(1.0f, 1.0f, 1.0f, 0.3f); // 白色透明度0.3
// 根据是否激活设置初始进度
if (queueItem.IsActive)
{
float progress = 1.0f - (queueItem.RemainingTime / queueItem.TotalTime);
progressRect.AnchorTop = 1.0f - progress; // 从下往上填充
GD.Print($"槽位{slotIndex}初始化进度条,进度: {progress:F2}");
}
else
{
progressRect.AnchorTop = 1.0f; // 等待状态,无进度显示
}
slot.AddChild(progressRect);
// 添加数量标签如果数量大于1
if (queueItem.Quantity > 1)
{
var quantityLabel = new Label();
quantityLabel.Name = "QuantityLabel";
quantityLabel.Text = queueItem.Quantity.ToString();
quantityLabel.AnchorLeft = 0.6f;
quantityLabel.AnchorTop = 0.6f;
quantityLabel.AnchorRight = 1.0f;
quantityLabel.AnchorBottom = 1.0f;
quantityLabel.HorizontalAlignment = HorizontalAlignment.Center;
quantityLabel.VerticalAlignment = VerticalAlignment.Center;
quantityLabel.AddThemeStyleboxOverride("normal", new StyleBoxFlat());
var styleBox = quantityLabel.GetThemeStylebox("normal") as StyleBoxFlat;
if (styleBox != null)
{
styleBox.BgColor = new Color(0.2f, 0.2f, 0.2f, 0.8f); // 半透明黑色背景
styleBox.CornerRadiusTopLeft = 3;
styleBox.CornerRadiusTopRight = 3;
styleBox.CornerRadiusBottomLeft = 3;
styleBox.CornerRadiusBottomRight = 3;
}
quantityLabel.AddThemeColorOverride("font_color", new Color(1.0f, 1.0f, 1.0f, 1.0f)); // 白色文字
quantityLabel.AddThemeFontSizeOverride("font_size", 8);
slot.AddChild(quantityLabel);
}
// 重置槽位颜色为默认
slot.Modulate = new Color(1.0f, 1.0f, 1.0f, 1.0f);
}
private void LoadDefaultIcon(TextureRect iconTexture)
{
var defaultIcon = GD.Load<Texture2D>("res://assets/textures/icon.svg");
if (defaultIcon != null)
{
iconTexture.Texture = defaultIcon;
GD.Print("使用默认图标 icon.svg");
}
else
{
GD.PrintErr("无法加载默认图标 icon.svg");
}
}
private void UpdateSlotProgress(int slotIndex, float progress)
{
if (slotIndex >= slotPanels.Count)
{
GD.PrintErr($"UpdateSlotProgress: 无效的槽位索引 {slotIndex}");
return;
}
var slot = slotPanels[slotIndex];
if (slot == null || !IsInstanceValid(slot))
{
GD.PrintErr($"UpdateSlotProgress: 槽位{slotIndex}无效");
return;
}
// 安全获取Progress节点
ColorRect progressRect = null;
try
{
if (slot.HasNode("Progress"))
{
progressRect = slot.GetNode<ColorRect>("Progress");
}
else
{
GD.PrintErr($"UpdateSlotProgress: 槽位{slotIndex}没有Progress节点");
return;
}
}
catch (System.Exception e)
{
GD.PrintErr($"UpdateSlotProgress: 获取Progress节点失败: {e.Message}");
return;
}
if (progressRect != null && IsInstanceValid(progressRect))
{
// 从下往上填充progress为0时AnchorTop为1.0progress为1时AnchorTop为0.0
float anchorTop = 1.0f - progress;
progressRect.AnchorTop = anchorTop;
// GD.Print($"更新槽位{slotIndex}进度: {progress:F2}, AnchorTop: {anchorTop:F2}");
}
else
{
GD.PrintErr($"UpdateSlotProgress: 槽位{slotIndex}的Progress节点无效");
}
}
private void ClearSlot(int slotIndex)
{
var slot = slotPanels[slotIndex];
// 立即清除子节点
var childrenToRemove = new List<Node>();
foreach (Node child in slot.GetChildren())
{
childrenToRemove.Add(child);
}
foreach (Node child in childrenToRemove)
{
slot.RemoveChild(child);
child.QueueFree();
}
// 为空槽位添加灰色背景
var backgroundRect = new ColorRect();
backgroundRect.Name = "Background";
backgroundRect.Color = new Color(0.2f, 0.2f, 0.2f, 1.0f); // 更深的灰色表示空槽位
backgroundRect.AnchorLeft = 0.0f;
backgroundRect.AnchorTop = 0.0f;
backgroundRect.AnchorRight = 1.0f;
backgroundRect.AnchorBottom = 1.0f;
slot.AddChild(backgroundRect);
// 重置槽位颜色
slot.Modulate = new Color(1.0f, 1.0f, 1.0f, 1.0f);
}
// 公共方法,供外部调用添加铁块制作
public void StartIronIngotCrafting()
{
AddToQueue("iron_ingot_smelting");
}
}

View File

@ -0,0 +1,192 @@
using Godot;
public partial class ManualCollectionPanel : Control
{
// 采集相关参数
private const float COLLECTION_TIME = 1.0f; // 采集时间1秒
// 状态变量
private bool isCollecting = false;
private float collectionProgress = 0.0f;
private string itemId;
// UI引用
private ColorRect progressFill;
private Color originalProgressColor;
private Color collectingProgressColor = new Color(1.0f, 0.8f, 0.3f, 1.0f); // 采集时的橙色
public override void _Ready()
{
GD.Print("ManualCollectionPanel _Ready 开始");
// 获取进度条引用 - 现在需要从父节点获取
var parent = GetParent<Control>();
if (parent != null)
{
progressFill = parent.GetNode<ColorRect>("MarginContainer/VBoxContainer/ProgressContainer/ProgressFill");
if (progressFill != null)
{
originalProgressColor = progressFill.Color;
GD.Print("成功获取进度条引用");
}
else
{
GD.PrintErr("无法获取进度条引用");
}
}
else
{
GD.PrintErr("无法获取父节点");
}
// 连接鼠标事件
GuiInput += OnGuiInput;
GD.Print("已连接鼠标事件");
GD.Print("ManualCollectionPanel _Ready 完成");
}
// 设置物品ID
public void SetItemId(string id)
{
itemId = id;
GD.Print($"设置手动采集面板物品ID: {itemId}");
}
// 处理输入事件
private void OnGuiInput(InputEvent @event)
{
if (@event is InputEventMouseButton mouseEvent)
{
if (mouseEvent.ButtonIndex == MouseButton.Left)
{
//打印鼠标事件
GD.Print($"鼠标事件: {mouseEvent}");
if (mouseEvent.Pressed)
{
// 开始采集
StartCollection();
}
else
{
// 停止采集
StopCollection();
}
}
}
}
// 开始采集
private void StartCollection()
{
if (string.IsNullOrEmpty(itemId)) return;
isCollecting = true;
collectionProgress = 0.0f;
// 改变进度条颜色表示正在采集
if (progressFill != null)
{
progressFill.Color = collectingProgressColor;
}
GD.Print($"开始采集: {itemId}");
}
// 停止采集
private void StopCollection()
{
if (!isCollecting) return;
isCollecting = false;
collectionProgress = 0.0f;
// 恢复进度条
UpdateProgressBar();
// 恢复原始颜色
if (progressFill != null)
{
progressFill.Color = originalProgressColor;
}
GD.Print($"停止采集: {itemId}");
}
// 完成采集
private void CompleteCollection()
{
if (string.IsNullOrEmpty(itemId)) return;
// 添加物品到库存
if (InventoryManager.Instance != null)
{
InventoryManager.Instance.AddItem(itemId, 1);
GD.Print($"采集完成,获得: {itemId} x1");
}
// 重置采集状态
collectionProgress = 0.0f;
// 如果还在按住,继续下一轮采集
if (isCollecting)
{
GD.Print($"继续采集: {itemId}");
}
else
{
// 恢复进度条显示
UpdateProgressBar();
if (progressFill != null)
{
progressFill.Color = originalProgressColor;
}
}
}
// 更新进度条显示
private void UpdateProgressBar()
{
if (progressFill != null)
{
if (isCollecting)
{
// 采集中显示当前进度
progressFill.AnchorRight = collectionProgress;
}
else
{
// 非采集状态显示满进度(表示可采集)
progressFill.AnchorRight = 0f;
}
}
}
public override void _Process(double delta)
{
if (isCollecting)
{
// 更新采集进度
collectionProgress += (float)delta / COLLECTION_TIME;
// 限制进度在0-1之间
collectionProgress = Mathf.Clamp(collectionProgress, 0.0f, 1.0f);
// 更新进度条显示
UpdateProgressBar();
// 检查是否完成采集
if (collectionProgress >= 1.0f)
{
CompleteCollection();
}
}
}
// 清理
public override void _ExitTree()
{
GuiInput -= OnGuiInput;
}
}

View File

@ -0,0 +1,33 @@
using Godot;
using System.Collections.Generic;
public partial class ResourceGrid : GridContainer
{
[Export]
public PackedScene ItemPanelScene;
public override void _Ready()
{
// 获取基础资源分类的所有物品
var items = ResourceCategoryManager.Instance.GetItemsByCategory("基础资源");
foreach (var item in items.Values)
{
var panel = (Panel)ItemPanelScene.Instantiate();
// 设置图标为白色底色
var icon = panel.GetNode<TextureRect>("HBoxContainer/Icon");
icon.Texture = null;
icon.Modulate = new Color(1, 1, 1, 1); // 白色
// 设置名称
var nameLabel = panel.GetNode<Label>("HBoxContainer/VBoxContainer/TopRow/NameLabel");
nameLabel.Text = item.Name;
// 其余内容可根据需要设置
// ...
AddChild(panel);
}
}
}

View File

@ -0,0 +1,733 @@
using Godot;
using System.Collections.Generic;
public partial class DynamicTabManager : TabContainer
{
private PackedScene itemPanelScene;
private PackedScene craftingItemScene;
public override void _Ready()
{
GD.Print("DynamicTabManager _Ready 开始");
// 设置标签靠左对齐
TabAlignment = TabBar.AlignmentMode.Left;
// 加载ItemPanel场景用于生产线
itemPanelScene = GD.Load<PackedScene>("res://scenes/ItemPanel.tscn");
if (itemPanelScene == null)
{
GD.PrintErr("无法加载ItemPanel场景");
return;
}
// 加载CraftingItem场景用于合成
craftingItemScene = GD.Load<PackedScene>("res://scenes/CraftingItem.tscn");
if (craftingItemScene == null)
{
GD.PrintErr("无法加载CraftingItem场景");
return;
}
GD.Print("开始初始化标签页");
InitializeTabs();
}
private void InitializeTabs()
{
GD.Print("开始创建固定标签页");
// 清空现有标签页
foreach (Node child in GetChildren())
{
child.QueueFree();
}
// 创建固定的标签页
CreateFixedTabs();
GD.Print("成功创建固定标签页");
}
private void CreateFixedTabs()
{
// 创建"合成"标签
CreateTabForCrafting();
// 创建"生产线"标签
CreateTabForProduction();
}
private void CreateTabForCrafting()
{
GD.Print("创建合成标签");
// 创建合成标签的滚动容器
var scrollContainer = new ScrollContainer();
scrollContainer.Name = "CraftingScroll";
// 创建垂直容器来放置所有分类块
var vboxContainer = new VBoxContainer();
vboxContainer.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
vboxContainer.SizeFlagsVertical = Control.SizeFlags.ExpandFill;
scrollContainer.AddChild(vboxContainer);
// 获取所有分类
var categoryManager = ResourceCategoryManager.Instance;
if (categoryManager == null)
{
GD.PrintErr("ResourceCategoryManager 实例为null");
return;
}
var allCategories = categoryManager.GetAllCategories();
// 为合成相关的分类创建块
foreach (var category in allCategories)
{
// 合成标签包含:手动采集、冶炼、建筑设施
if (category.CategoryName == "手动采集" ||
category.CategoryName == "冶炼" ||
category.CategoryName == "建筑设施")
{
CreateCategoryBlock(vboxContainer, category, "合成");
}
}
// 添加到TabContainer
AddChild(scrollContainer);
SetTabTitle(GetTabCount() - 1, "合成");
}
private void CreateTabForProduction()
{
GD.Print("创建生产线标签");
// 创建生产线标签的滚动容器
var scrollContainer = new ScrollContainer();
scrollContainer.Name = "ProductionScroll";
// 创建垂直容器来放置所有分类块
var vboxContainer = new VBoxContainer();
vboxContainer.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
vboxContainer.SizeFlagsVertical = Control.SizeFlags.ExpandFill;
scrollContainer.AddChild(vboxContainer);
// 获取所有分类
var categoryManager = ResourceCategoryManager.Instance;
if (categoryManager == null)
{
GD.PrintErr("ResourceCategoryManager 实例为null");
return;
}
var allCategories = categoryManager.GetAllCategories();
// 为生产线相关的分类创建块
foreach (var category in allCategories)
{
// 生产线标签包含:生产设备
if (category.CategoryName == "生产设备")
{
CreateCategoryBlock(vboxContainer, category, "生产线");
}
}
// 添加到TabContainer
AddChild(scrollContainer);
SetTabTitle(GetTabCount() - 1, "生产线");
}
private void CreateCategoryBlock(VBoxContainer parentContainer, ResourceCategoryManager.ResourceCategory category, string tabType)
{
GD.Print($"创建分类块: {category.CategoryName}");
// 创建分类块的容器
var categoryContainer = new VBoxContainer();
categoryContainer.Name = $"{category.CategoryName}Block";
// 添加间距
var topSpacer = new Control();
topSpacer.CustomMinimumSize = new Vector2(0, 10);
categoryContainer.AddChild(topSpacer);
// 创建标题行(分类名称 + 横线)
var titleContainer = new HBoxContainer();
// 分类名称标签
var titleLabel = new Label();
titleLabel.Text = category.CategoryName;
titleLabel.HorizontalAlignment = HorizontalAlignment.Left;
titleContainer.AddChild(titleLabel);
// 添加小间距
var labelSpacer = new Control();
labelSpacer.CustomMinimumSize = new Vector2(10, 0);
titleContainer.AddChild(labelSpacer);
// HSeparator 横线分隔符
var separator = new HSeparator();
separator.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
separator.SizeFlagsVertical = Control.SizeFlags.ShrinkCenter;
titleContainer.AddChild(separator);
categoryContainer.AddChild(titleContainer);
// 添加小间距
var spacer = new Control();
spacer.CustomMinimumSize = new Vector2(0, 5);
categoryContainer.AddChild(spacer);
// 创建物品网格 - 使用HFlowContainer实现自适应宽度
var flowContainer = new HFlowContainer();
flowContainer.Name = $"{category.CategoryName}Grid";
flowContainer.SizeFlagsHorizontal = Control.SizeFlags.ExpandFill;
flowContainer.AddThemeConstantOverride("h_separation", 10); // 水平间距
flowContainer.AddThemeConstantOverride("v_separation", 8); // 垂直间距
// 添加该分类的所有物品
AddItemsToFlow(flowContainer, category.CategoryName, tabType);
categoryContainer.AddChild(flowContainer);
// 添加到父容器
parentContainer.AddChild(categoryContainer);
}
private void AddItemsToFlow(HFlowContainer flowContainer, string categoryName, string tabType)
{
GD.Print($"为分类 '{categoryName}' 添加物品到流容器,标签类型: {tabType}");
var categoryManager = ResourceCategoryManager.Instance;
if (categoryManager == null)
{
GD.PrintErr("ResourceCategoryManager 实例为null");
return;
}
var items = categoryManager.GetItemsByCategory(categoryName);
GD.Print($"分类 '{categoryName}' 中有 {items.Count} 个物品");
foreach (var kvp in items)
{
var itemId = kvp.Key;
var itemData = kvp.Value;
GD.Print($"创建物品面板: {itemId} - {itemData.Name}");
Control itemPanel;
// 手动采集始终使用ItemPanel保持按住采集功能
if (categoryName == "手动采集")
{
// 手动采集使用ItemPanel
itemPanel = itemPanelScene.Instantiate<Control>();
if (itemPanel == null)
{
GD.PrintErr($"无法实例化ItemPanel for {itemId}");
continue;
}
// 添加手动采集脚本
var manualCollectionScript = new ManualCollectionPanel();
manualCollectionScript.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.FullRect);
manualCollectionScript.MouseFilter = Control.MouseFilterEnum.Stop; // 拦截鼠标事件
itemPanel.AddChild(manualCollectionScript);
manualCollectionScript.SetItemId(itemId);
GD.Print($"为 {itemId} 添加手动采集功能");
// 设置物品数据(手动采集不是生产设备)
SetupItemPanel(itemPanel, itemData, false);
}
// 其他合成物品根据标签类型选择面板
else if (tabType == "合成")
{
// 合成标签的非手动采集物品使用CraftingItem
itemPanel = craftingItemScene.Instantiate<Control>();
if (itemPanel == null)
{
GD.PrintErr($"无法实例化CraftingItem for {itemId}");
continue;
}
// 设置合成物品数据
SetupCraftingItem(itemPanel, itemData, categoryName);
}
else
{
// 生产线标签使用ItemPanel
itemPanel = itemPanelScene.Instantiate<Control>();
if (itemPanel == null)
{
GD.PrintErr($"无法实例化ItemPanel for {itemId}");
continue;
}
// 判断是否为生产设备
bool isProductionDevice = categoryName != "手动采集";
// 设置物品数据
SetupItemPanel(itemPanel, itemData, isProductionDevice);
}
// 添加到流容器
flowContainer.AddChild(itemPanel);
}
}
private void SetupItemPanel(Control itemPanel, GameData.ItemData itemData, bool isProductionDevice = true)
{
try
{
// 设置图标
var iconTexture = itemPanel.GetNode<TextureRect>("MarginContainer/VBoxContainer/TopRow/IconTexture");
if (iconTexture != null && !string.IsNullOrEmpty(itemData.IconPath))
{
// 检查文件是否存在
if (Godot.FileAccess.FileExists(itemData.IconPath))
{
var texture = GD.Load<Texture2D>(itemData.IconPath);
if (texture != null)
{
iconTexture.Texture = texture;
GD.Print($"设置图标成功: {itemData.IconPath}");
}
else
{
GD.PrintErr($"无法加载图标: {itemData.IconPath}");
}
}
else
{
GD.Print($"图标文件不存在: {itemData.IconPath},使用默认图标");
// 默认使用icon.svg
var defaultIcon = GD.Load<Texture2D>("res://assets/textures/icon.svg");
if (defaultIcon != null)
{
iconTexture.Texture = defaultIcon;
}
else
{
GD.PrintErr("无法加载默认图标-1");
}
}
}
// 设置名称
var nameLabel = itemPanel.GetNode<Label>("MarginContainer/VBoxContainer/TopRow/MiddleContainer/TopInfoRow/NameLabel");
if (nameLabel != null)
{
nameLabel.Text = itemData.Name;
GD.Print($"设置名称成功: {itemData.Name}");
}
// 设置产率
var productionLabel = itemPanel.GetNode<Label>("MarginContainer/VBoxContainer/TopRow/MiddleContainer/TopInfoRow/ProductionLabel");
if (productionLabel != null)
{
if (isProductionDevice)
{
productionLabel.Text = "0/s"; // 生产设备显示产率
}
else
{
productionLabel.Text = "手动"; // 手动采集显示"手动"
productionLabel.Modulate = new Color(0.8f, 1.0f, 0.8f, 1.0f); // 淡绿色
}
}
// 根据isProductionDevice决定是否显示设备相关UI
var rightContainer = itemPanel.GetNode<VBoxContainer>("MarginContainer/VBoxContainer/TopRow/RightContainer");
if (rightContainer != null)
{
rightContainer.Visible = isProductionDevice;
}
if (isProductionDevice)
{
// 设置设备数量
var deviceLabel = itemPanel.GetNode<Label>("MarginContainer/VBoxContainer/TopRow/RightContainer/BottomDeviceRow/DeviceLabel");
if (deviceLabel != null)
{
deviceLabel.Text = "设备: 0"; // 默认设备数量
}
// 设置功耗
var powerLabel = itemPanel.GetNode<Label>("MarginContainer/VBoxContainer/TopRow/RightContainer/PowerLabel");
if (powerLabel != null)
{
powerLabel.Text = "-0W"; // 默认功耗
}
}
// 设置进度条
var progressFill = itemPanel.GetNode<ColorRect>("MarginContainer/VBoxContainer/ProgressContainer/ProgressFill");
if (progressFill != null)
{
if (isProductionDevice)
{
progressFill.AnchorRight = 0.0f; // 生产设备默认0%进度
}
else
{
// 手动采集默认0%进度,采集时会动态变化
progressFill.AnchorRight = 0.0f; // 0% 进度
progressFill.Color = new Color(0.3f, 0.8f, 0.3f, 1.0f); // 绿色
}
}
}
catch (System.Exception e)
{
GD.PrintErr($"设置ItemPanel时出错: {e.Message}");
}
}
private void SetupCraftingItem(Control craftingItem, GameData.ItemData itemData, string categoryName)
{
try
{
// 设置图标
var iconTexture = craftingItem.GetNode<TextureRect>("MarginContainer/HBoxContainer/IconTexture");
if (iconTexture != null && !string.IsNullOrEmpty(itemData.IconPath))
{
// 检查文件是否存在
if (Godot.FileAccess.FileExists(itemData.IconPath))
{
var texture = GD.Load<Texture2D>(itemData.IconPath);
if (texture != null)
{
iconTexture.Texture = texture;
GD.Print($"设置合成物品图标成功: {itemData.IconPath}");
}
else
{
LoadDefaultIconForCrafting(iconTexture);
}
}
else
{
GD.Print($"合成物品图标文件不存在: {itemData.IconPath},使用默认图标");
LoadDefaultIconForCrafting(iconTexture);
}
}
// 设置名称
var nameLabel = craftingItem.GetNode<Label>("MarginContainer/HBoxContainer/MiddleContainer/NameLabel");
if (nameLabel != null)
{
nameLabel.Text = itemData.Name;
GD.Print($"设置合成物品名称成功: {itemData.Name}");
}
// 从合成配方系统获取真实信息
var recipes = CraftingRecipeManager.Instance?.GetRecipesForItem(itemData.Id);
var recipe = recipes?.Count > 0 ? recipes[0] : null;
// 设置材料需求(现在在物品名称下面)
var materialsLabel = craftingItem.GetNode<Label>("MarginContainer/HBoxContainer/MiddleContainer/MaterialsLabel");
if (materialsLabel != null)
{
if (recipe != null && recipe.Ingredients.Count > 0)
{
// 显示第一个材料,如果有多个材料可以显示"..."
var firstIngredient = recipe.Ingredients[0];
var itemName = GameData.Instance?.GetItem(firstIngredient.ItemId)?.Name ?? firstIngredient.ItemId;
if (recipe.Ingredients.Count == 1)
{
materialsLabel.Text = $"材料: {firstIngredient.Quantity}x{itemName}";
}
else
{
materialsLabel.Text = $"材料: {firstIngredient.Quantity}x{itemName}...";
}
}
else
{
materialsLabel.Text = "无需材料";
}
}
// 设置合成时间(现在在材料下面)
var craftTimeLabel = craftingItem.GetNode<Label>("MarginContainer/HBoxContainer/MiddleContainer/InfoRow/CraftTimeLabel");
if (craftTimeLabel != null)
{
if (recipe != null)
{
craftTimeLabel.Text = $"{recipe.CraftingTime:F1}s";
}
else
{
craftTimeLabel.Text = "无配方";
craftTimeLabel.Modulate = new Color(1.0f, 0.5f, 0.5f, 1.0f); // 红色表示无配方
}
}
// 设置合成方式(现在在材料下面)
var methodLabel = craftingItem.GetNode<Label>("MarginContainer/HBoxContainer/MiddleContainer/InfoRow/MethodLabel");
if (methodLabel != null)
{
if (recipe != null)
{
methodLabel.Text = recipe.CraftingMethod;
}
else if (categoryName == "手动采集")
{
methodLabel.Text = "手动";
}
else if (categoryName == "冶炼")
{
methodLabel.Text = "冶炼";
}
else
{
methodLabel.Text = "制作";
}
}
// 设置合成按钮
var craftButton = craftingItem.GetNode<Button>("MarginContainer/HBoxContainer/RightContainer/CraftButton");
if (craftButton != null)
{
// 如果没有配方,禁用按钮
if (recipe == null)
{
craftButton.Disabled = true;
craftButton.Text = "无配方";
craftButton.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
}
else
{
craftButton.Disabled = false;
craftButton.Text = "合成";
craftButton.Modulate = new Color(1.0f, 1.0f, 1.0f, 1.0f);
// 连接按钮点击事件
craftButton.Pressed += () => OnCraftButtonPressed(itemData.Id, craftingItem);
}
}
// 设置数量控制组件
SetupQuantityControls(craftingItem, recipe != null);
}
catch (System.Exception e)
{
GD.PrintErr($"设置CraftingItem时出错: {e.Message}");
}
}
private void LoadDefaultIconForCrafting(TextureRect iconTexture)
{
var defaultIcon = GD.Load<Texture2D>("res://assets/textures/icon.svg");
if (defaultIcon != null)
{
iconTexture.Texture = defaultIcon;
}
else
{
GD.PrintErr("无法加载默认图标");
}
}
private void OnCraftButtonPressed(string itemId, Control craftingItem)
{
GD.Print($"点击合成按钮: {itemId}");
// 检查合成配方管理器是否可用
if (CraftingRecipeManager.Instance == null)
{
GD.PrintErr("CraftingRecipeManager 实例为null无法进行合成");
return;
}
// 查询该物品的合成配方
var recipes = CraftingRecipeManager.Instance.GetRecipesForItem(itemId);
if (recipes == null || recipes.Count == 0)
{
GD.Print($"物品 {itemId} 没有找到合成配方");
return;
}
// 使用第一个找到的配方(后续可以扩展为让用户选择)
var recipe = recipes[0];
GD.Print($"找到合成配方: {recipe.Id} - {recipe.OutputItem}");
// 获取数量输入框的值
int quantity = 1;
try
{
var quantityInput = craftingItem.GetNode<LineEdit>("MarginContainer/HBoxContainer/RightContainer/QuantityContainer/QuantityInput");
if (quantityInput != null)
{
quantity = GetQuantityValue(quantityInput);
}
}
catch (System.Exception e)
{
GD.PrintErr($"获取数量输入值时出错: {e.Message}");
quantity = 1; // 默认为1
}
GD.Print($"准备合成 {quantity} 个 {itemId}");
// 检查是否有足够的材料制作指定数量
bool canCraftAll = true;
var insufficientMaterials = new List<string>();
foreach (var ingredient in recipe.Ingredients)
{
int currentAmount = InventoryManager.Instance?.GetItemQuantity(ingredient.ItemId) ?? 0;
int requiredAmount = ingredient.Quantity * quantity;
if (currentAmount < requiredAmount)
{
canCraftAll = false;
var itemName = GameData.Instance?.GetItem(ingredient.ItemId)?.Name ?? ingredient.ItemId;
insufficientMaterials.Add($"{itemName} (需要 {requiredAmount}, 当前 {currentAmount})");
}
}
if (!canCraftAll)
{
GD.Print($"材料不足,无法合成 {quantity} 个 {itemId}");
foreach (var material in insufficientMaterials)
{
GD.Print($"缺少材料: {material}");
}
return;
}
// 获取合成队列管理器
var craftingQueue = GetCraftingQueueManager();
if (craftingQueue == null)
{
GD.PrintErr("无法找到CraftingQueueManager无法添加到合成队列");
return;
}
// 批量添加到合成队列(一次性添加指定数量)
bool success = craftingQueue.AddToQueue(recipe.Id, quantity);
if (success)
{
GD.Print($"成功添加 {quantity} 个 {itemId} 到合成队列");
}
else
{
GD.Print($"添加 {itemId} 到合成队列失败(可能队列已满)");
}
}
private CraftingQueueManager GetCraftingQueueManager()
{
// 从场景树中查找CraftingQueueManager
// 它应该在GameScene下的左侧面板中
var gameScene = GetTree().CurrentScene;
if (gameScene == null)
{
GD.PrintErr("无法获取当前场景");
return null;
}
// 尝试通过路径查找CraftingQueueManager
var craftingQueue = gameScene.GetNode<CraftingQueueManager>("HSplitContainer/LeftPanel/VBoxContainer/CraftingQueue");
if (craftingQueue == null)
{
GD.PrintErr("无法找到CraftingQueueManager节点");
}
return craftingQueue;
}
private void SetupQuantityControls(Control craftingItem, bool hasRecipe)
{
try
{
var quantityInput = craftingItem.GetNode<LineEdit>("MarginContainer/HBoxContainer/RightContainer/QuantityContainer/QuantityInput");
var minusButton = craftingItem.GetNode<Button>("MarginContainer/HBoxContainer/RightContainer/QuantityContainer/MinusButton");
var plusButton = craftingItem.GetNode<Button>("MarginContainer/HBoxContainer/RightContainer/QuantityContainer/PlusButton");
if (quantityInput != null && minusButton != null && plusButton != null)
{
// 设置初始值
quantityInput.Text = "1";
// 如果没有配方,禁用数量控制
quantityInput.Editable = hasRecipe;
minusButton.Disabled = !hasRecipe;
plusButton.Disabled = !hasRecipe;
if (!hasRecipe)
{
quantityInput.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
minusButton.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
plusButton.Modulate = new Color(0.6f, 0.6f, 0.6f, 1.0f);
}
// 连接减号按钮事件
minusButton.Pressed += () => {
int currentValue = GetQuantityValue(quantityInput);
if (currentValue > 1)
{
quantityInput.Text = (currentValue - 1).ToString();
}
};
// 连接加号按钮事件
plusButton.Pressed += () => {
int currentValue = GetQuantityValue(quantityInput);
if (currentValue < 99) // 限制最大值为99
{
quantityInput.Text = (currentValue + 1).ToString();
}
};
// 连接输入框文本变化事件
quantityInput.TextChanged += (string newText) => {
ValidateQuantityInput(quantityInput, newText);
};
}
}
catch (System.Exception e)
{
GD.PrintErr($"设置数量控制组件时出错: {e.Message}");
}
}
private int GetQuantityValue(LineEdit quantityInput)
{
if (int.TryParse(quantityInput.Text, out int value))
{
return Mathf.Clamp(value, 1, 99);
}
return 1;
}
private void ValidateQuantityInput(LineEdit quantityInput, string newText)
{
// 只允许数字输入
if (string.IsNullOrEmpty(newText))
{
quantityInput.Text = "1";
return;
}
if (int.TryParse(newText, out int value))
{
// 限制范围在1-99之间
value = Mathf.Clamp(value, 1, 99);
if (value.ToString() != newText)
{
quantityInput.Text = value.ToString();
quantityInput.CaretColumn = quantityInput.Text.Length;
}
}
else
{
// 如果不是有效数字恢复为1
quantityInput.Text = "1";
quantityInput.CaretColumn = quantityInput.Text.Length;
}
}
}

60
scripts/ui/MainMenu.cs Normal file
View File

@ -0,0 +1,60 @@
using Godot;
public partial class MainMenu : Control
{
// 主菜单控制脚本
public override void _Ready()
{
GD.Print("主菜单已加载");
}
// 新游戏按钮点击事件
private void _OnNewGameBtnPressed()
{
GD.Print("点击了新游戏");
// 切换到游戏场景
GetTree().ChangeSceneToFile("res://scenes/game_scene.tscn");
}
// 载入存档按钮点击事件
private void _OnLoadGameBtnPressed()
{
GD.Print("点击了载入存档");
// 这里可以实现存档载入逻辑
// LoadGame();
}
// 设置按钮点击事件
private void _OnSettingsBtnPressed()
{
GD.Print("点击了设置");
// 这里可以切换到设置界面
// GetTree().ChangeSceneToFile("res://scenes/settings_scene.tscn");
}
// 退出游戏按钮点击事件
private void _OnExitBtnPressed()
{
GD.Print("点击了退出游戏");
// 退出游戏
GetTree().Quit();
}
// 存档载入函数示例
private void LoadGame()
{
if (FileAccess.FileExists("user://savegame.save"))
{
using var saveFile = FileAccess.Open("user://savegame.save", FileAccess.ModeFlags.Read);
var saveData = saveFile.GetVar();
// 处理存档数据
GD.Print("存档已载入:", saveData);
}
else
{
GD.Print("没有找到存档文件");
}
}
}