已落地 .tres/json 的内容定义入口,并把 ResourceContentSource/JsonContentSource 实现为可用的加载器;同时给出可运行的样例定义文件,默认会在 GameSession.CreateDefault() 中注册加载。
改动说明 - 实现内容加载器并支持 res:///user:// 路径解析与 JSON 枚举解析:scripts/Core/ContentRegistry.cs - 新增 .tres 资源定义接口与样例资源类(学科定义):scripts/Core/ContentResources.cs - 默认注册资源/JSON 数据源,启动时自动合并进内容库:scripts/Core/GameSession.cs - 样例 .tres 与 JSON 内容定义:resources/definitions/discipline_biology.tres, resources/definitions/disciplines.json, resources/definitions/archetypes.json - 当前 .tres 走“扁平字段 + RuleIds”,数值型 Modifier 更适合先用 JSON 落地,后续可以把更多字段迁入资源类。 - JSON 采用与 Models 定义一致的结构(DefinitionHeader/LocalizedText/ModifierBundle),便于后续扩展。
This commit is contained in:
parent
9c1593e717
commit
39682f14fe
70
resources/definitions/archetypes.json
Normal file
70
resources/definitions/archetypes.json
Normal file
@ -0,0 +1,70 @@
|
||||
[
|
||||
{
|
||||
"Header": {
|
||||
"Id": "core:archetype_grinder",
|
||||
"Name": {
|
||||
"Key": "archetype.grinder.name",
|
||||
"Fallback": "Grinder"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "archetype.grinder.desc",
|
||||
"Fallback": "They live in the lab and push everything faster."
|
||||
},
|
||||
"Tags": [ "archetype" ]
|
||||
},
|
||||
"Tiers": [
|
||||
{
|
||||
"RequiredCount": 2,
|
||||
"Modifiers": {
|
||||
"AttributeModifiers": [
|
||||
{ "Type": "Activation", "Add": 5, "Multiplier": 1.1 }
|
||||
],
|
||||
"RuleIds": [ "rule:grinder_stress_up" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"RequiredCount": 4,
|
||||
"Modifiers": {
|
||||
"AttributeModifiers": [
|
||||
{ "Type": "Activation", "Add": 10, "Multiplier": 1.2 }
|
||||
],
|
||||
"RuleIds": [ "rule:grinder_overwork" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Header": {
|
||||
"Id": "core:archetype_slacker",
|
||||
"Name": {
|
||||
"Key": "archetype.slacker.name",
|
||||
"Fallback": "Slacker"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "archetype.slacker.desc",
|
||||
"Fallback": "They recover morale but slow down the team."
|
||||
},
|
||||
"Tags": [ "archetype" ]
|
||||
},
|
||||
"Tiers": [
|
||||
{
|
||||
"RequiredCount": 2,
|
||||
"Modifiers": {
|
||||
"StatusModifiers": [
|
||||
{ "Type": "Mood", "Add": 5, "Multiplier": 1.05 }
|
||||
],
|
||||
"RuleIds": [ "rule:slacker_relax" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"RequiredCount": 4,
|
||||
"Modifiers": {
|
||||
"StatusModifiers": [
|
||||
{ "Type": "Stress", "Add": -5, "Multiplier": 0.9 }
|
||||
],
|
||||
"RuleIds": [ "rule:slacker_spread" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
21
resources/definitions/discipline_biology.tres
Normal file
21
resources/definitions/discipline_biology.tres
Normal file
@ -0,0 +1,21 @@
|
||||
[gd_resource type="Resource" script_class="DisciplineDefinitionResource" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/Core/DisciplineDefinitionResource.cs" id=1]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
Id = "core:discipline_biology_tres"
|
||||
NameKey = "discipline.biology.name"
|
||||
NameFallback = "Biology"
|
||||
DescriptionKey = "discipline.biology.desc"
|
||||
DescriptionFallback = "Lab-intensive discipline focused on experiments."
|
||||
IconPath = ""
|
||||
Tags = [ "discipline" ]
|
||||
BuffNameKey = "buff.pipette_master.name"
|
||||
BuffNameFallback = "Pipette Master"
|
||||
BuffDescriptionKey = "buff.pipette_master.desc"
|
||||
BuffDescriptionFallback = "Higher lab success, higher stamina cost."
|
||||
BuffRuleIds = [ "rule:discipline_biology_pipette_master" ]
|
||||
RolePoolIds = [ "core:role_alchemist", "core:role_lab_rat" ]
|
||||
ItemPoolIds = [ "core:item_pipette" ]
|
||||
TaskKeywordIds = [ "task_keyword_lab" ]
|
||||
68
resources/definitions/disciplines.json
Normal file
68
resources/definitions/disciplines.json
Normal file
@ -0,0 +1,68 @@
|
||||
[
|
||||
{
|
||||
"Header": {
|
||||
"Id": "core:discipline_economics",
|
||||
"Name": {
|
||||
"Key": "discipline.economics.name",
|
||||
"Fallback": "Economics"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "discipline.economics.desc",
|
||||
"Fallback": "Money drives everything; interest becomes a core loop."
|
||||
},
|
||||
"Tags": [ "discipline" ]
|
||||
},
|
||||
"Buff": {
|
||||
"Name": {
|
||||
"Key": "buff.capital_flow.name",
|
||||
"Fallback": "Capital Flow"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "buff.capital_flow.desc",
|
||||
"Fallback": "Idle funds generate interest each turn."
|
||||
},
|
||||
"Modifiers": {
|
||||
"ResourceModifiers": [
|
||||
{ "Type": "Money", "Add": 0, "Multiplier": 1.0 }
|
||||
],
|
||||
"RuleIds": [ "rule:discipline_economics_interest" ]
|
||||
}
|
||||
},
|
||||
"RolePoolIds": [ "core:role_surveyor", "core:role_orator" ],
|
||||
"ItemPoolIds": [ "core:item_bloomberg_terminal" ],
|
||||
"TaskKeywordIds": [ "task_keyword_finance" ]
|
||||
},
|
||||
{
|
||||
"Header": {
|
||||
"Id": "core:discipline_computer",
|
||||
"Name": {
|
||||
"Key": "discipline.computer.name",
|
||||
"Fallback": "Computer Science"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "discipline.computer.desc",
|
||||
"Fallback": "Compute-heavy discipline with strong tech output."
|
||||
},
|
||||
"Tags": [ "discipline" ]
|
||||
},
|
||||
"Buff": {
|
||||
"Name": {
|
||||
"Key": "buff.overclock.name",
|
||||
"Fallback": "Overclock"
|
||||
},
|
||||
"Description": {
|
||||
"Key": "buff.overclock.desc",
|
||||
"Fallback": "Server power is amplified for AI tasks."
|
||||
},
|
||||
"Modifiers": {
|
||||
"AttributeModifiers": [
|
||||
{ "Type": "Engineering", "Add": 5, "Multiplier": 1.05 }
|
||||
],
|
||||
"RuleIds": [ "rule:discipline_computer_overclock" ]
|
||||
}
|
||||
},
|
||||
"RolePoolIds": [ "core:role_geek", "core:role_coder" ],
|
||||
"ItemPoolIds": [ "core:item_rtx_cluster" ],
|
||||
"TaskKeywordIds": [ "task_keyword_ai" ]
|
||||
}
|
||||
]
|
||||
26
scripts/Core/ContentCollectionResource.cs
Normal file
26
scripts/Core/ContentCollectionResource.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace Core;
|
||||
|
||||
/// <summary>
|
||||
/// 资源集合(便于打包多个定义到一个 .tres)
|
||||
/// </summary>
|
||||
[GlobalClass]
|
||||
public partial class ContentCollectionResource : Resource, IContentResourceCollection
|
||||
{
|
||||
[Export] public Array<Resource> Items { get; set; } = new();
|
||||
|
||||
public IEnumerable<IContentResource> GetItems()
|
||||
{
|
||||
foreach (var item in Items)
|
||||
{
|
||||
if (item is IContentResource content)
|
||||
{
|
||||
yield return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Godot;
|
||||
using Models;
|
||||
|
||||
namespace Core;
|
||||
@ -103,7 +107,54 @@ public sealed class ResourceContentSource : IContentSource
|
||||
|
||||
public IEnumerable<T> LoadAll<T>() where T : class
|
||||
{
|
||||
yield break;
|
||||
foreach (var path in ResourcePaths)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var resource = ResourceLoader.Load<Resource>(path);
|
||||
if (resource == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var item in ExtractResources<T>(resource))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<T> ExtractResources<T>(Resource resource) where T : class
|
||||
{
|
||||
if (resource is IContentResource content)
|
||||
{
|
||||
if (content.GetDefinitionType() == typeof(T))
|
||||
{
|
||||
if (content.ToDefinition() is T typed)
|
||||
{
|
||||
yield return typed;
|
||||
}
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (resource is IContentResourceCollection collection)
|
||||
{
|
||||
foreach (var item in collection.GetItems())
|
||||
{
|
||||
if (item.GetDefinitionType() == typeof(T))
|
||||
{
|
||||
if (item.ToDefinition() is T typed)
|
||||
{
|
||||
yield return typed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,15 +165,135 @@ public sealed class JsonContentSource : IContentSource
|
||||
{
|
||||
public int Priority { get; }
|
||||
public List<string> DataPaths { get; } = new();
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
public JsonContentSource(int priority)
|
||||
{
|
||||
Priority = priority;
|
||||
_options = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
_options.Converters.Add(new JsonStringEnumConverter());
|
||||
}
|
||||
|
||||
public IEnumerable<T> LoadAll<T>() where T : class
|
||||
{
|
||||
yield break;
|
||||
foreach (var path in DataPaths)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var resolvedPath = ResolvePath(path);
|
||||
if (!File.Exists(resolvedPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var json = File.ReadAllText(resolvedPath);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryDeserializeList(json, out List<T> list))
|
||||
{
|
||||
foreach (var item in list)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryDeserializeEnvelope(json, out JsonContentEnvelope<T> envelope))
|
||||
{
|
||||
if (envelope.Items != null)
|
||||
{
|
||||
foreach (var item in envelope.Items)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
else if (envelope.Item != null)
|
||||
{
|
||||
yield return envelope.Item;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TryDeserializeSingle(json, out T single))
|
||||
{
|
||||
yield return single;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolvePath(string path)
|
||||
{
|
||||
if (path.StartsWith("res://") || path.StartsWith("user://"))
|
||||
{
|
||||
return ProjectSettings.GlobalizePath(path);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private bool TryDeserializeList<T>(string json, out List<T> list) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
list = JsonSerializer.Deserialize<List<T>>(json, _options);
|
||||
return list != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
list = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryDeserializeSingle<T>(string json, out T item) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
item = JsonSerializer.Deserialize<T>(json, _options);
|
||||
return item != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
item = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryDeserializeEnvelope<T>(string json, out JsonContentEnvelope<T> envelope) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
envelope = JsonSerializer.Deserialize<JsonContentEnvelope<T>>(json, _options);
|
||||
if (envelope == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return envelope.Items != null || envelope.Item != null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
envelope = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class JsonContentEnvelope<T> where T : class
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public List<T> Items { get; set; }
|
||||
public T Item { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
scripts/Core/ContentResources.cs
Normal file
27
scripts/Core/ContentResources.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Core;
|
||||
|
||||
/// <summary>
|
||||
/// 资源定义接口(用于 .tres/.res 的内容加载)
|
||||
/// 设计说明:
|
||||
/// 1) 资源负责“序列化友好”,模型负责“运行时友好”。
|
||||
/// 2) 通过 ToDefinition 映射到纯数据模型,保持解耦。
|
||||
/// 注意事项:
|
||||
/// - 资源字段尽量使用 Godot 可序列化的基础类型与 Array。
|
||||
/// 未来扩展:
|
||||
/// - 可加入验证器,确保 Id/路径等关键字段符合规范。
|
||||
/// </summary>
|
||||
public interface IContentResource
|
||||
{
|
||||
Type GetDefinitionType();
|
||||
object ToDefinition();
|
||||
}
|
||||
|
||||
public interface IContentResourceCollection
|
||||
{
|
||||
IEnumerable<IContentResource> GetItems();
|
||||
}
|
||||
|
||||
|
||||
109
scripts/Core/DisciplineDefinitionResource.cs
Normal file
109
scripts/Core/DisciplineDefinitionResource.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using Models;
|
||||
|
||||
namespace Core;
|
||||
|
||||
/// <summary>
|
||||
/// 学科定义资源(先落地一个完整示例)
|
||||
/// 设计说明:
|
||||
/// 1) 为避免 .tres 过度嵌套,字段保持扁平化。
|
||||
/// 2) Buff 规则通过 RuleIds 存储,数值效果可后续用 JSON 增补。
|
||||
/// 注意事项:
|
||||
/// - Id 与资源路径应稳定且小写下划线。
|
||||
/// 未来扩展:
|
||||
/// - 可补充 Modifiers/AllowedRoles 等字段,进一步丰富配置能力。
|
||||
/// </summary>
|
||||
[GlobalClass]
|
||||
public partial class DisciplineDefinitionResource : Resource, IContentResource
|
||||
{
|
||||
// --- Header ---
|
||||
[Export] public string Id { get; set; }
|
||||
[Export] public string NameKey { get; set; }
|
||||
[Export] public string NameFallback { get; set; }
|
||||
[Export] public string DescriptionKey { get; set; }
|
||||
[Export] public string DescriptionFallback { get; set; }
|
||||
[Export] public string IconPath { get; set; }
|
||||
[Export] public Array<string> Tags { get; set; } = new();
|
||||
|
||||
// --- Buff ---
|
||||
[Export] public string BuffNameKey { get; set; }
|
||||
[Export] public string BuffNameFallback { get; set; }
|
||||
[Export] public string BuffDescriptionKey { get; set; }
|
||||
[Export] public string BuffDescriptionFallback { get; set; }
|
||||
[Export] public Array<string> BuffRuleIds { get; set; } = new();
|
||||
|
||||
// --- Pools ---
|
||||
[Export] public Array<string> RolePoolIds { get; set; } = new();
|
||||
[Export] public Array<string> ItemPoolIds { get; set; } = new();
|
||||
[Export] public Array<string> TaskKeywordIds { get; set; } = new();
|
||||
|
||||
public Type GetDefinitionType() => typeof(DisciplineDefinition);
|
||||
|
||||
public object ToDefinition()
|
||||
{
|
||||
var header = new DefinitionHeader
|
||||
{
|
||||
Id = Id,
|
||||
IconPath = IconPath,
|
||||
Name = new LocalizedText
|
||||
{
|
||||
Key = NameKey,
|
||||
Fallback = NameFallback
|
||||
},
|
||||
Description = new LocalizedText
|
||||
{
|
||||
Key = DescriptionKey,
|
||||
Fallback = DescriptionFallback
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var tag in Tags)
|
||||
{
|
||||
header.Tags.Add(tag);
|
||||
}
|
||||
|
||||
var buff = new DisciplineBuff
|
||||
{
|
||||
Name = new LocalizedText
|
||||
{
|
||||
Key = BuffNameKey,
|
||||
Fallback = BuffNameFallback
|
||||
},
|
||||
Description = new LocalizedText
|
||||
{
|
||||
Key = BuffDescriptionKey,
|
||||
Fallback = BuffDescriptionFallback
|
||||
},
|
||||
Modifiers = new ModifierBundle()
|
||||
};
|
||||
|
||||
foreach (var ruleId in BuffRuleIds)
|
||||
{
|
||||
buff.Modifiers.RuleIds.Add(ruleId);
|
||||
}
|
||||
|
||||
var definition = new DisciplineDefinition
|
||||
{
|
||||
Header = header,
|
||||
Buff = buff
|
||||
};
|
||||
|
||||
AddRange(RolePoolIds, definition.RolePoolIds);
|
||||
AddRange(ItemPoolIds, definition.ItemPoolIds);
|
||||
AddRange(TaskKeywordIds, definition.TaskKeywordIds);
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
private static void AddRange(Array<string> source, List<string> target)
|
||||
{
|
||||
foreach (var value in source)
|
||||
{
|
||||
target.Add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,15 @@ public sealed class GameSession
|
||||
public static GameSession CreateDefault()
|
||||
{
|
||||
var registry = new ContentRegistry();
|
||||
var resourceSource = new ResourceContentSource(0);
|
||||
resourceSource.ResourcePaths.Add("res://resources/definitions/discipline_biology.tres");
|
||||
registry.RegisterSource(resourceSource);
|
||||
|
||||
var jsonSource = new JsonContentSource(10);
|
||||
jsonSource.DataPaths.Add("res://resources/definitions/disciplines.json");
|
||||
jsonSource.DataPaths.Add("res://resources/definitions/archetypes.json");
|
||||
registry.RegisterSource(jsonSource);
|
||||
|
||||
var content = registry.BuildDatabase();
|
||||
var localization = new GodotLocalizationService();
|
||||
var events = new DomainEventBus();
|
||||
|
||||
@ -11,6 +11,9 @@
|
||||
<Content Include="docs\任务与经济系统.md" />
|
||||
<Content Include="docs\角色与羁绊系统.md" />
|
||||
<Content Include="README.md" />
|
||||
<Content Include="resources\definitions\archetypes.json" />
|
||||
<Content Include="resources\definitions\disciplines.json" />
|
||||
<Content Include="resources\definitions\discipline_biology.tres" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="addons\" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user