Init
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
BIN
.vs/ProjectEvaluation/test.metadata.v7.bin
Normal file
BIN
.vs/ProjectEvaluation/test.metadata.v7.bin
Normal file
Binary file not shown.
BIN
.vs/ProjectEvaluation/test.projects.v7.bin
Normal file
BIN
.vs/ProjectEvaluation/test.projects.v7.bin
Normal file
Binary file not shown.
BIN
.vs/Test/DesignTimeBuild/.dtbcache.v2
Normal file
BIN
.vs/Test/DesignTimeBuild/.dtbcache.v2
Normal file
Binary file not shown.
Binary file not shown.
0
.vs/Test/FileContentIndex/read.lock
Normal file
0
.vs/Test/FileContentIndex/read.lock
Normal file
BIN
.vs/Test/v17/.futdcache.v2
Normal file
BIN
.vs/Test/v17/.futdcache.v2
Normal file
Binary file not shown.
BIN
.vs/Test/v17/.suo
Normal file
BIN
.vs/Test/v17/.suo
Normal file
Binary file not shown.
47
.vscode/launch.json
vendored
Normal file
47
.vscode/launch.json
vendored
Normal 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
3
.vscode/settings.json
vendored
Normal 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
18
.vscode/tasks.json
vendored
Normal 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
112
PROJECT_STRUCTURE.md
Normal 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
8
Test.csproj
Normal 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
19
Test.sln
Normal 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
|
||||
BIN
assets/textures/PixPin_2025-06-15_11-35-40.png
Normal file
BIN
assets/textures/PixPin_2025-06-15_11-35-40.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.9 KiB |
34
assets/textures/PixPin_2025-06-15_11-35-40.png.import
Normal file
34
assets/textures/PixPin_2025-06-15_11-35-40.png.import
Normal 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
1
assets/textures/icon.svg
Normal 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 |
37
assets/textures/icon.svg.import
Normal file
37
assets/textures/icon.svg.import
Normal 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
59
data/README.md
Normal 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. **使用版本控制** 跟踪配置文件的变化
|
||||
17
data/config/crafting_recipes.json
Normal file
17
data/config/crafting_recipes.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
20
data/config/inventory_categories.json
Normal file
20
data/config/inventory_categories.json
Normal 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
13
data/config/items.csv
Normal 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
|
||||
|
10
data/config/items.csv.import
Normal file
10
data/config/items.csv.import
Normal file
@ -0,0 +1,10 @@
|
||||
[remap]
|
||||
|
||||
importer="keep"
|
||||
type=""
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://data/config/items.csv"
|
||||
|
||||
[params]
|
||||
50
data/config/resource_categories.json
Normal file
50
data/config/resource_categories.json
Normal 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
2
data/translations/.gdignore
Normal file
2
data/translations/.gdignore
Normal file
@ -0,0 +1,2 @@
|
||||
# Godot 会忽略这个文件夹中的所有文件
|
||||
# 这些是自动生成的本地化文件,不需要被 Godot 处理
|
||||
BIN
data/translations/items.Category.translation
Normal file
BIN
data/translations/items.Category.translation
Normal file
Binary file not shown.
BIN
data/translations/items.CraftTime.translation
Normal file
BIN
data/translations/items.CraftTime.translation
Normal file
Binary file not shown.
BIN
data/translations/items.Description.translation
Normal file
BIN
data/translations/items.Description.translation
Normal file
Binary file not shown.
BIN
data/translations/items.IconPath.translation
Normal file
BIN
data/translations/items.IconPath.translation
Normal file
Binary file not shown.
BIN
data/translations/items.Name.translation
Normal file
BIN
data/translations/items.Name.translation
Normal file
Binary file not shown.
BIN
data/translations/items.PowerConsumption.translation
Normal file
BIN
data/translations/items.PowerConsumption.translation
Normal file
Binary file not shown.
BIN
data/translations/items.Recipe.translation
Normal file
BIN
data/translations/items.Recipe.translation
Normal file
Binary file not shown.
35
project.godot
Normal file
35
project.godot
Normal 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
129
scenes/CraftingItem.tscn
Normal 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
69
scenes/CraftingQueue.tscn
Normal 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
64
scenes/InventoryItem.tscn
Normal 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
127
scenes/ItemPanel.tscn
Normal 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
228
scenes/game_scene.tscn
Normal 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
70
scenes/main_menu.tscn
Normal 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
64
scenes/ss
Normal 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
26
scenes/test.tscn
Normal 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
136
scripts/core/GameScene.cs
Normal 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键,尝试开始铁块合成");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
143
scripts/data/CraftingRecipeManager.cs
Normal file
143
scripts/data/CraftingRecipeManager.cs
Normal 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
197
scripts/data/GameData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
169
scripts/data/ResourceCategoryManager.cs
Normal file
169
scripts/data/ResourceCategoryManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
121
scripts/inventory/InventoryCategoryManager.cs
Normal file
121
scripts/inventory/InventoryCategoryManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
368
scripts/inventory/InventoryManager.cs
Normal file
368
scripts/inventory/InventoryManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
265
scripts/inventory/InventoryTableManager.cs
Normal file
265
scripts/inventory/InventoryTableManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
384
scripts/production/CraftingQueueManager.cs
Normal file
384
scripts/production/CraftingQueueManager.cs
Normal 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.0,progress为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");
|
||||
}
|
||||
}
|
||||
192
scripts/production/ManualCollectionPanel.cs
Normal file
192
scripts/production/ManualCollectionPanel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
33
scripts/production/ResourceGrid.cs
Normal file
33
scripts/production/ResourceGrid.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
733
scripts/ui/DynamicTabManager.cs
Normal file
733
scripts/ui/DynamicTabManager.cs
Normal 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
60
scripts/ui/MainMenu.cs
Normal 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("没有找到存档文件");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user