diff --git a/.vsconfig b/.vsconfig
new file mode 100644
index 0000000..f019fd0
--- /dev/null
+++ b/.vsconfig
@@ -0,0 +1,6 @@
+{
+ "version": "1.0",
+ "components": [
+ "Microsoft.VisualStudio.Workload.ManagedGame"
+ ]
+}
diff --git a/Assets/BP_Scripts/BP_test.asset.meta b/Assets/BP_Scripts/BP_test.asset.meta
index d98cb01..36be857 100644
--- a/Assets/BP_Scripts/BP_test.asset.meta
+++ b/Assets/BP_Scripts/BP_test.asset.meta
@@ -1,8 +1,8 @@
fileFormatVersion: 2
-guid: 6da43df589d0e9e4486e200a9058c15f
+guid: c4e90a3b2f66a4843a646f1c775239d1
NativeFormatImporter:
externalObjects: {}
- mainObjectFileID: 11400000
+ mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Assets/BP_Scripts/BT_Kart_Advanced.asset b/Assets/BP_Scripts/BT_Kart_Advanced.asset
new file mode 100644
index 0000000..1ad5b85
--- /dev/null
+++ b/Assets/BP_Scripts/BT_Kart_Advanced.asset
@@ -0,0 +1,41 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 7a686a47eee2fa44cb0a34b5d86e4d5e, type: 3}
+ m_Name: BT_Kart_Advanced
+ m_EditorClassIdentifier:
+ _serializedGraph: '{"type":"NodeCanvas.BehaviourTrees.BehaviourTree","nodes":[{"_name":"\u8f66\u8f86\u4e3b\u63a7\uff08\u5e76\u884c\uff09","_position":{"x":400.0,"y":60.0},"$type":"NodeCanvas.BehaviourTrees.Parallel","$id":"0"},{"_name":"\u8f93\u5165\u540c\u6b65\u6a21\u5757","_position":{"x":180.0,"y":210.0},"$type":"NodeCanvas.BehaviourTrees.Sequencer","$id":"1"},{"_action":{"accelerate":{"_name":"Accelerate"},"turn":{"_name":"Turn"},"brake":{"_name":"Brake"},"handbrake":{"_name":"Handbrake"},"gear":{"_name":"Gear"},"driveMode":{"_name":"DriveMode"},"headlights":{"_name":"Lights"},"engineOn":{"_name":"EngineOn"},"horn":{"_name":"Horn"},"$type":"NLD.Nodes.SetVehicleInput"},"_name":"\u540c\u6b65\u8f93\u5165\u5230BaseInput","_position":{"x":180.0,"y":360.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"2"},{"_name":"\u72b6\u6001\u76d1\u63a7\u6a21\u5757","_position":{"x":400.0,"y":210.0},"$type":"NodeCanvas.BehaviourTrees.Sequencer","$id":"3"},{"_action":{"outLocalSpeed":{"_name":"LocalSpeed"},"outVelocity":{"_name":"Velocity"},"outGear":{"_name":"CurrentGear"},"outState":{"_name":"CurrentState"},"outFuelPercent":{"_name":"FuelPercent"},"outFuelConsumption":{"_name":"FuelConsumption"},"outEngineOn":{"_name":"EngineStatus"},"outLightsOn":{"_name":"LightsStatus"},"$type":"NLD.Nodes.ReadVehicleStatusTask"},"_name":"\u8bfb\u53d6\u8f66\u8f86\u72b6\u6001\u5230\u9ed1\u677f","_position":{"x":400.0,"y":360.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"4"},{"_name":"\u72b6\u6001\u8bfb\u53d6","_position":{"x":620.0,"y":210.0},"$type":"NodeCanvas.BehaviourTrees.Sequencer","$id":"5"},{"_action":{"outLocalSpeed":{},"outVelocity":{},"outGear":{},"outState":{},"outFuelPercent":{},"outFuelConsumption":{},"outEngineOn":{},"outLightsOn":{},"$type":"NLD.Nodes.ReadVehicleStatusTask"},"_name":"\u8bfb\u53d6\u72b6\u6001","_position":{"x":620.0,"y":360.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"6"}],"connections":[{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"3"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"5"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"1"},"_targetNode":{"$ref":"2"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"3"},"_targetNode":{"$ref":"4"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"5"},"_targetNode":{"$ref":"6"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"}],"canvasGroups":[],"localBlackboard":{"_variables":{"Accelerate":{"_name":"Accelerate","_id":"606f050e-c25c-444b-ae49-da6562991999","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Turn":{"_name":"Turn","_id":"ef72b931-18bf-4f81-adf3-df1280305ba2","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Brake":{"_name":"Brake","_id":"c9ae4b2c-ebb0-45f7-a7e7-7a28603d9651","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Handbrake":{"_name":"Handbrake","_id":"d8ddd4c5-47f5-47ad-a67c-a0b73627a6fd","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Gear":{"_name":"Gear","_id":"d6c7207d-ebe9-44a5-a810-46bb55371d2a","$type":"NodeCanvas.Framework.Variable`1[[KartGame.KartSystems.GearMode,
+ KartGame, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"DriveMode":{"_value":2,"_name":"DriveMode","_id":"6fd1842d-306f-4c08-8aaa-39d224a695c5","$type":"NodeCanvas.Framework.Variable`1[[KartGame.KartSystems.DriveTrainMode,
+ KartGame, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"EngineOn":{"_value":true,"_name":"EngineOn","_id":"40934b08-0dfa-4dca-9f47-5f9daeee7169","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Lights":{"_name":"Lights","_id":"9078b220-6821-4adc-8d74-0a3944d9dfaf","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Horn":{"_name":"Horn","_id":"2842451d-9339-446b-8eee-a5f987099680","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"LocalSpeed":{"_name":"LocalSpeed","_id":"28ddb0c2-3e81-4f2c-a918-de8330092f93","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"Velocity":{"_name":"Velocity","_id":"b112d672-fe70-4b2f-abc7-79a9298c7ec1","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"CurrentGear":{"_name":"CurrentGear","_id":"755079af-f39e-4c6a-9a54-75d814f6f89d","$type":"NodeCanvas.Framework.Variable`1[[KartGame.KartSystems.GearMode,
+ KartGame, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"CurrentState":{"_name":"CurrentState","_id":"b18d2e52-d381-4afa-9626-446127f37397","$type":"NodeCanvas.Framework.Variable`1[[KartGame.KartSystems.VehicleState,
+ KartGame, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"FuelPercent":{"_value":1.0,"_name":"FuelPercent","_id":"676d92f5-7aa7-432e-b56b-dd31a1c76a17","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"FuelConsumption":{"_name":"FuelConsumption","_id":"745142e7-9911-4569-af87-3d2e93ef3982","$type":"NodeCanvas.Framework.Variable`1[[System.Single,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"EngineStatus":{"_value":true,"_name":"EngineStatus","_id":"4b3fd72d-0058-4d92-a07c-e18f6f11a02c","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"},"LightsStatus":{"_name":"LightsStatus","_id":"bef36727-02f3-4c3b-b9df-13a65270ff09","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean,
+ mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}}},"derivedData":{"repeat":true,"$type":"NodeCanvas.BehaviourTrees.BehaviourTree+DerivedSerializationData"}}'
+ _objectReferences: []
+ _graphSource:
+ _version: 3.33
+ _category:
+ _comments:
+ _translation: {x: 0, y: 0}
+ _zoomFactor: 1
+ _haltSerialization: 0
+ _externalSerializationFile: {fileID: 0}
diff --git a/Assets/BP_Scripts/BT_Kart_Advanced.asset.meta b/Assets/BP_Scripts/BT_Kart_Advanced.asset.meta
new file mode 100644
index 0000000..1016d8f
--- /dev/null
+++ b/Assets/BP_Scripts/BT_Kart_Advanced.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: eb5849b7cdf4f494ea240a506245ddb7
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Karting/Tutorials/5 Build and Publish/Criteria.meta b/Assets/BP_Scripts/GameplayEditor.meta
similarity index 77%
rename from Assets/Karting/Tutorials/5 Build and Publish/Criteria.meta
rename to Assets/BP_Scripts/GameplayEditor.meta
index 8a1fbac..265bfa2 100644
--- a/Assets/Karting/Tutorials/5 Build and Publish/Criteria.meta
+++ b/Assets/BP_Scripts/GameplayEditor.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: efb2368ee8bed9140a42c2805613ed11
+guid: 4cfe3e64882de254fba06f94153dab56
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config.meta b/Assets/BP_Scripts/GameplayEditor/Config.meta
new file mode 100644
index 0000000..7d8d006
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 00ebdf5df6fcdac4690f8271175946b9
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs b/Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs
new file mode 100644
index 0000000..7b96c89
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs
@@ -0,0 +1,66 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 相机资源映射
+ ///
+ [CreateAssetMenu(fileName = "CameraLut", menuName = "Gameplay/Lut/Camera Lut")]
+ public class ActivityCameraLut : ScriptableObject
+ {
+ [Tooltip("相机资源映射列表(新版,包含详细参数)")]
+ public List CameraMappings = new List();
+
+ [Tooltip("是否使用新版详细配置")]
+ public bool UseDetailedConfig = true;
+
+ public CameraMapping FindMapping(int mappingId)
+ {
+ return CameraMappings.Find(m => m.MappingID == mappingId);
+ }
+
+ ///
+ /// 添加相机映射(向后兼容)
+ ///
+ public CameraMapping AddMapping(int mappingId, string prefabPath)
+ {
+ var mapping = new CameraMapping
+ {
+ MappingID = mappingId,
+ PrefabPath = prefabPath
+ };
+ mapping.ApplyDefaults();
+ CameraMappings.Add(mapping);
+ return mapping;
+ }
+
+ ///
+ /// 验证所有相机配置
+ ///
+ public bool ValidateAll(out List errors)
+ {
+ errors = new List();
+ var idSet = new HashSet();
+
+ foreach (var mapping in CameraMappings)
+ {
+ if (!mapping.Validate(out var error))
+ {
+ errors.Add($"CameraID={mapping.MappingID}: {error}");
+ }
+
+ if (idSet.Contains(mapping.MappingID))
+ {
+ errors.Add($"重复的CameraID: {mapping.MappingID}");
+ }
+ else
+ {
+ idSet.Add(mapping.MappingID);
+ }
+ }
+
+ return errors.Count == 0;
+ }
+ }
+}
diff --git a/Assets/Karting/Tutorials/5 Build and Publish/Criteria/PublishCriteria.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs.meta
similarity index 83%
rename from Assets/Karting/Tutorials/5 Build and Publish/Criteria/PublishCriteria.cs.meta
rename to Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs.meta
index 3680b95..86504b5 100644
--- a/Assets/Karting/Tutorials/5 Build and Publish/Criteria/PublishCriteria.cs.meta
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityCameraLut.cs.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c3420f187011c1a43824ca0d4c88d6c6
+guid: c8a2de62fbe079740b13718f921aefa1
MonoImporter:
externalObjects: {}
serializedVersion: 2
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs b/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs
new file mode 100644
index 0000000..dd9e1e9
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 特效资源映射
+ ///
+ [CreateAssetMenu(fileName = "FXLut", menuName = "Gameplay/Lut/FX Lut")]
+ public class ActivityFXLut : ScriptableObject
+ {
+ [Tooltip("特效资源映射列表(新版,包含生命周期参数)")]
+ public List FxMappings = new List();
+
+ [Tooltip("是否使用新版详细配置")]
+ public bool UseDetailedConfig = true;
+
+ public FXMapping FindMapping(int mappingId)
+ {
+ return FxMappings.Find(m => m.MappingID == mappingId);
+ }
+
+ ///
+ /// 添加特效映射(向后兼容)
+ ///
+ public FXMapping AddMapping(int mappingId, string prefabPath)
+ {
+ var mapping = new FXMapping
+ {
+ MappingID = mappingId,
+ PrefabPath = prefabPath
+ };
+ mapping.ApplyDefaults();
+ FxMappings.Add(mapping);
+ return mapping;
+ }
+
+ ///
+ /// 获取特定类型的所有特效
+ ///
+ public List GetMappingsByDuration(float minDuration, float maxDuration)
+ {
+ return FxMappings.FindAll(m => m.Duration >= minDuration && m.Duration <= maxDuration);
+ }
+
+ ///
+ /// 获取所有循环特效
+ ///
+ public List GetLoopingEffects()
+ {
+ return FxMappings.FindAll(m => m.Loop);
+ }
+
+ ///
+ /// 验证所有特效配置
+ ///
+ public bool ValidateAll(out List errors)
+ {
+ errors = new List();
+ var idSet = new HashSet();
+
+ foreach (var mapping in FxMappings)
+ {
+ if (!mapping.Validate(out var error))
+ {
+ errors.Add($"FXID={mapping.MappingID}: {error}");
+ }
+
+ if (idSet.Contains(mapping.MappingID))
+ {
+ errors.Add($"重复的FXID: {mapping.MappingID}");
+ }
+ else
+ {
+ idSet.Add(mapping.MappingID);
+ }
+ }
+
+ return errors.Count == 0;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs.meta
new file mode 100644
index 0000000..1eec528
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityFXLut.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a0200246c4705bf4e8857c7cc838f97b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs b/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs
new file mode 100644
index 0000000..0f80681
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 地图信息映射
+ ///
+ [CreateAssetMenu(fileName = "MapInfoLut", menuName = "Gameplay/Lut/MapInfo Lut")]
+ public class ActivityMapInfoLut : ScriptableObject
+ {
+ [Tooltip("地图信息ID")]
+ public int InfoID;
+
+ [Tooltip("点位列表")]
+ public List Points = new List();
+
+ public MapPoint FindPoint(string pointId)
+ {
+ return Points.Find(p => p.PointID == pointId);
+ }
+
+ public MapPoint FindPoint(int index)
+ {
+ if (index >= 0 && index < Points.Count)
+ return Points[index];
+ return null;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs.meta
new file mode 100644
index 0000000..66b5b75
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityMapInfoLut.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 02c0e07dc8fdfef46a628060d1fffd95
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs
new file mode 100644
index 0000000..2fb20f8
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs
@@ -0,0 +1,301 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using NodeCanvas.BehaviourTrees;
+using GameplayEditor.Core;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 玩法关卡配置数据
+ /// 对应Excel: ActivityStageConfig Sheet
+ ///
+ [CreateAssetMenu(fileName = "ActivityStageConfig", menuName = "Gameplay/Activity Stage Config")]
+ public class ActivityStageConfig : ScriptableObject
+ {
+ [Header("基础信息")]
+ [Tooltip("行为树ID(唯一)")]
+ public int ActivityStageID;
+
+ [Tooltip("关卡备注")]
+ [TextArea(2, 4)]
+ public string Doc;
+
+ [Tooltip("场景名称")]
+ public string SceneName;
+
+ [Header("地图配置")]
+ [Tooltip("地图打点组ID(引用ActivityMapInfoLut)")]
+ public int MapInfo;
+
+ [Header("加载配置")]
+ [Tooltip("延迟关闭加载图(秒)")]
+ [Range(0, 10)]
+ public float CloseLoadingDelay;
+
+ [Header("相机配置")]
+ [Tooltip("默认相机ID(引用ActivityCameraLut)")]
+ public int CameraID;
+
+ [Header("运行时配置")]
+ [Tooltip("Tick率模式")]
+ public TickRateMode TickMode = TickRateMode.Normal;
+
+ [Tooltip("行为树运行帧率(tick/秒),Normal模式下有效")]
+ [Range(20, 120)]
+ public int TickRate = 60;
+
+ [Tooltip("无限制模式下是否使用固定DeltaTime")]
+ public bool UseFixedDeltaTimeInUnlimited = true;
+
+ [Tooltip("无限制模式下的固定DeltaTime(秒)")]
+ public float UnlimitedDeltaTime = 0.016f;
+
+ [Header("关联资产")]
+ [Tooltip("头文件行为树(可选)")]
+ public BehaviourTree HeaderTree;
+
+ [Tooltip("正文行为树")]
+ public BehaviourTree BodyTree;
+
+ [Tooltip("运行时数据配置")]
+ public Core.BehaviourTreeRuntimeData RuntimeData;
+
+ ///
+ /// 获取基础StageID
+ ///
+ public int GetBaseStageID()
+ {
+ return Core.BehaviourTreeExtended.GetBaseStageID(ActivityStageID);
+ }
+
+ ///
+ /// 是否为头文件配置
+ ///
+ public bool IsHeaderFile()
+ {
+ return Core.BehaviourTreeExtended.IsHeaderFile(ActivityStageID);
+ }
+
+ ///
+ /// 验证配置完整性
+ ///
+ public bool Validate(out string errorMessage)
+ {
+ if (ActivityStageID <= 0)
+ {
+ errorMessage = "ActivityStageID必须大于0";
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(SceneName))
+ {
+ errorMessage = "SceneName不能为空";
+ return false;
+ }
+
+ if (BodyTree == null)
+ {
+ errorMessage = "正文行为树不能为空";
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// 应用默认值(用于修复缺失配置)
+ ///
+ public void ApplyDefaults()
+ {
+ var defaultConfig = GameplayDefaultConfig.Instance;
+ defaultConfig.ApplyDefaults(this);
+ }
+
+ ///
+ /// 验证并尝试自动修复
+ ///
+ public bool ValidateAndFix(out string errorMessage)
+ {
+ // 先应用默认值
+ ApplyDefaults();
+
+ // 然后验证
+ return Validate(out errorMessage);
+ }
+
+ ///
+ /// 获取完整验证报告
+ ///
+ public List GetValidationReport()
+ {
+ var reports = new List();
+
+ if (ActivityStageID <= 0)
+ reports.Add("[错误] ActivityStageID必须大于0");
+
+ if (string.IsNullOrWhiteSpace(SceneName))
+ reports.Add($"[警告] SceneName为空,将使用默认值: {GameplayDefaultConfig.Instance.DefaultSceneName}");
+
+ if (BodyTree == null)
+ reports.Add("[错误] 正文行为树不能为空");
+
+ if (TickRate < GameplayDefaultConfig.Instance.MinTickRate ||
+ TickRate > GameplayDefaultConfig.Instance.MaxTickRate)
+ {
+ reports.Add($"[警告] TickRate {TickRate} 超出推荐范围 [{GameplayDefaultConfig.Instance.MinTickRate}, {GameplayDefaultConfig.Instance.MaxTickRate}]");
+ }
+
+ if (CloseLoadingDelay < 0)
+ reports.Add("[警告] CloseLoadingDelay不能为负数");
+
+ if (reports.Count == 0)
+ reports.Add("[通过] 配置验证通过");
+
+ return reports;
+ }
+ }
+
+ ///
+ /// 玩法关卡配置容器
+ /// 管理所有关卡配置
+ ///
+ [CreateAssetMenu(fileName = "StageConfigDatabase", menuName = "Gameplay/Stage Config Database")]
+ public class ActivityStageConfigDatabase : ScriptableObject
+ {
+ [Tooltip("所有关卡配置")]
+ public List Configs = new List();
+
+ ///
+ /// 根据StageID查找配置
+ ///
+ public ActivityStageConfig FindByStageID(int stageId)
+ {
+ return Configs.Find(c => c.ActivityStageID == stageId);
+ }
+
+ ///
+ /// 根据场景名称查找配置
+ ///
+ public ActivityStageConfig FindBySceneName(string sceneName)
+ {
+ return Configs.Find(c => c.SceneName == sceneName);
+ }
+
+ ///
+ /// 添加或更新配置
+ ///
+ public void AddOrUpdate(ActivityStageConfig config)
+ {
+ var existing = FindByStageID(config.ActivityStageID);
+ if (existing != null)
+ {
+ Configs.Remove(existing);
+ }
+ Configs.Add(config);
+ }
+
+ ///
+ /// 移除配置
+ ///
+ public bool Remove(int stageId)
+ {
+ var config = FindByStageID(stageId);
+ if (config != null)
+ {
+ return Configs.Remove(config);
+ }
+ return false;
+ }
+
+ ///
+ /// 获取所有基础StageID(去重)
+ ///
+ public List GetAllBaseStageIDs()
+ {
+ var result = new HashSet();
+ foreach (var config in Configs)
+ {
+ result.Add(config.GetBaseStageID());
+ }
+ return new List(result);
+ }
+ }
+
+ ///
+ /// 玩法关卡配置解析器
+ /// 从Excel解析ActivityStageConfig Sheet
+ ///
+ [System.Serializable]
+ public class ActivityStageConfigData
+ {
+ public int ActivityStageID;
+ public string Doc;
+ public string SceneName;
+ public int MapInfo;
+ public float CloseLoadingDelay;
+ public int CameraID;
+ public TickRateMode TickMode = TickRateMode.Normal;
+ public int TickRate = 60;
+ public bool UseFixedDeltaTimeInUnlimited = true;
+ public float UnlimitedDeltaTime = 0.016f;
+
+ ///
+ /// 从字典创建
+ ///
+ public static ActivityStageConfigData FromDictionary(Dictionary data)
+ {
+ var result = new ActivityStageConfigData();
+
+ if (data.TryGetValue("ActivityStageID", out var idStr) && int.TryParse(idStr, out var id))
+ result.ActivityStageID = id;
+
+ if (data.TryGetValue("Doc", out var doc))
+ result.Doc = doc;
+
+ if (data.TryGetValue("SceneName", out var sceneName))
+ result.SceneName = sceneName;
+
+ if (data.TryGetValue("MapInfo", out var mapInfoStr) && int.TryParse(mapInfoStr, out var mapInfo))
+ result.MapInfo = mapInfo;
+
+ if (data.TryGetValue("CloseLoadingDelay", out var delayStr) && float.TryParse(delayStr, out var delay))
+ result.CloseLoadingDelay = delay;
+
+ if (data.TryGetValue("CameraID", out var cameraIdStr) && int.TryParse(cameraIdStr, out var cameraId))
+ result.CameraID = cameraId;
+
+ if (data.TryGetValue("TickMode", out var tickModeStr) && System.Enum.TryParse(tickModeStr, out var tickMode))
+ result.TickMode = tickMode;
+
+ if (data.TryGetValue("TickRate", out var tickRateStr) && int.TryParse(tickRateStr, out var tickRate))
+ result.TickRate = tickRate;
+
+ if (data.TryGetValue("UseFixedDeltaTimeInUnlimited", out var useFixedDtStr) && bool.TryParse(useFixedDtStr, out var useFixedDt))
+ result.UseFixedDeltaTimeInUnlimited = useFixedDt;
+
+ if (data.TryGetValue("UnlimitedDeltaTime", out var unlimitedDtStr) && float.TryParse(unlimitedDtStr, out var unlimitedDt))
+ result.UnlimitedDeltaTime = unlimitedDt;
+
+ return result;
+ }
+
+ ///
+ /// 应用到ScriptableObject
+ ///
+ public void ApplyTo(ActivityStageConfig config)
+ {
+ config.ActivityStageID = ActivityStageID;
+ config.Doc = Doc;
+ config.SceneName = SceneName;
+ config.MapInfo = MapInfo;
+ config.CloseLoadingDelay = CloseLoadingDelay;
+ config.CameraID = CameraID;
+ config.TickMode = TickMode;
+ config.TickRate = TickRate;
+ config.UseFixedDeltaTimeInUnlimited = UseFixedDeltaTimeInUnlimited;
+ config.UnlimitedDeltaTime = UnlimitedDeltaTime;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs.meta
new file mode 100644
index 0000000..3036537
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityStageConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 284c4f6ffb93e9e4f8faa5287e82fce5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs b/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs
new file mode 100644
index 0000000..2d4846d
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// UI资源映射
+ ///
+ [CreateAssetMenu(fileName = "UILut", menuName = "Gameplay/Lut/UI Lut")]
+ public class ActivityUiLut : ScriptableObject
+ {
+ [Tooltip("UI资源映射列表")]
+ public List UiMappings = new List();
+
+ public ResourceMapping FindMapping(int mappingId)
+ {
+ return UiMappings.Find(m => m.MappingID == mappingId);
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs.meta
new file mode 100644
index 0000000..526ae5b
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/ActivityUiLut.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 84f836a2bb387f94ca1ee3d55c088233
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs
new file mode 100644
index 0000000..c035789
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs
@@ -0,0 +1,361 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 关卡对话信息映射条目
+ /// 建立关卡ID与DialogInfo配置表的关联
+ ///
+ [Serializable]
+ public class DialogInfoByStageEntry
+ {
+ [Tooltip("玩法关卡ID")]
+ public int StageID;
+
+ [Tooltip("对应文本配置表名(如 DialogInfo_策划A)")]
+ public string DialogInfoSheetName;
+
+ [Tooltip("备注说明")]
+ public string Doc;
+ }
+
+ ///
+ /// 关卡对话信息映射配置
+ /// 对应Excel: ActivityDialogInfoByStage Sheet
+ /// 作为索引表,将StageID映射到具体的DialogInfo配置表
+ ///
+ [CreateAssetMenu(fileName = "DialogInfoByStageConfig", menuName = "Gameplay/Dialog Info By Stage Config")]
+ public class DialogInfoByStageConfig : ScriptableObject
+ {
+ [Tooltip("映射条目列表")]
+ public List Entries = new List();
+
+ [Tooltip("配置来源Excel路径")]
+ public string SourceExcelPath = "";
+
+ ///
+ /// StageID到SheetName的映射缓存
+ ///
+ [NonSerialized]
+ private Dictionary _stageToSheetCache;
+
+ ///
+ /// 根据StageID查找对应的DialogInfo表名
+ ///
+ public string GetSheetNameByStageID(int stageId)
+ {
+ if (_stageToSheetCache == null)
+ BuildCache();
+
+ return _stageToSheetCache.TryGetValue(stageId, out var sheetName) ? sheetName : null;
+ }
+
+ ///
+ /// 查找条目
+ ///
+ public DialogInfoByStageEntry FindEntry(int stageId)
+ {
+ return Entries.Find(e => e.StageID == stageId);
+ }
+
+ ///
+ /// 添加或更新映射
+ ///
+ public void AddOrUpdateEntry(DialogInfoByStageEntry entry)
+ {
+ var existing = FindEntry(entry.StageID);
+ if (existing != null)
+ {
+ Entries.Remove(existing);
+ }
+ Entries.Add(entry);
+ _stageToSheetCache = null; // 清除缓存
+ }
+
+ ///
+ /// 移除映射
+ ///
+ public bool RemoveEntry(int stageId)
+ {
+ var entry = FindEntry(stageId);
+ if (entry != null)
+ {
+ Entries.Remove(entry);
+ _stageToSheetCache = null;
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 构建映射缓存
+ ///
+ public void BuildCache()
+ {
+ _stageToSheetCache = new Dictionary();
+ foreach (var entry in Entries)
+ {
+ if (!_stageToSheetCache.ContainsKey(entry.StageID))
+ {
+ _stageToSheetCache[entry.StageID] = entry.DialogInfoSheetName;
+ }
+ else
+ {
+ Debug.LogWarning($"[DialogInfoByStage] 重复的StageID: {entry.StageID}");
+ }
+ }
+ }
+
+ ///
+ /// 获取所有StageID列表
+ ///
+ public List GetAllStageIDs()
+ {
+ if (_stageToSheetCache == null)
+ BuildCache();
+
+ return new List(_stageToSheetCache.Keys);
+ }
+
+ ///
+ /// 获取映射字典(供DialogInfoDatabase使用)
+ ///
+ public Dictionary GetStageToSheetDictionary()
+ {
+ if (_stageToSheetCache == null)
+ BuildCache();
+ return new Dictionary(_stageToSheetCache);
+ }
+
+ ///
+ /// 验证配置完整性
+ ///
+ public bool Validate(out List errors)
+ {
+ errors = new List();
+
+ var stageIdSet = new HashSet();
+ var sheetNameSet = new HashSet();
+
+ foreach (var entry in Entries)
+ {
+ // 检查StageID
+ if (entry.StageID <= 0)
+ {
+ errors.Add($"无效的StageID: {entry.StageID}");
+ }
+ else if (stageIdSet.Contains(entry.StageID))
+ {
+ errors.Add($"重复的StageID: {entry.StageID}");
+ }
+ else
+ {
+ stageIdSet.Add(entry.StageID);
+ }
+
+ // 检查SheetName
+ if (string.IsNullOrWhiteSpace(entry.DialogInfoSheetName))
+ {
+ errors.Add($"StageID={entry.StageID} 的DialogInfoSheetName不能为空");
+ }
+ else
+ {
+ sheetNameSet.Add(entry.DialogInfoSheetName);
+ }
+ }
+
+ return errors.Count == 0;
+ }
+
+ ///
+ /// 获取统计信息
+ ///
+ public string GetStatistics()
+ {
+ return $"总共 {Entries.Count} 个关卡映射,涉及 {new HashSet(Entries.ConvertAll(e => e.DialogInfoSheetName)).Count} 个DialogInfo表";
+ }
+ }
+
+ ///
+ /// 对话信息运行时管理器
+ /// 整合DialogInfoByStageConfig和DialogInfoDatabase,提供统一的查询接口
+ /// 同时管理运行时对话状态
+ ///
+ public class DialogInfoManager
+ {
+ private static DialogInfoManager _instance;
+ public static DialogInfoManager Instance => _instance ??= new DialogInfoManager();
+
+ private DialogInfoByStageConfig _byStageConfig;
+ private DialogInfoDatabase _database;
+
+ ///
+ /// 是否已初始化
+ ///
+ public bool IsInitialized => _database != null && _byStageConfig != null;
+
+ // 运行时对话状态
+ private Dictionary _dialogStates = new Dictionary();
+
+ ///
+ /// 对话状态
+ ///
+ private class DialogState
+ {
+ public bool IsActive;
+ public float StartTime;
+ public float Duration;
+ }
+
+ ///
+ /// 初始化管理器
+ ///
+ public void Initialize(DialogInfoByStageConfig byStageConfig, DialogInfoDatabase database)
+ {
+ _byStageConfig = byStageConfig;
+ _database = database;
+
+ // 构建数据库缓存
+ if (_byStageConfig != null && _database != null)
+ {
+ var mapping = _byStageConfig.GetStageToSheetDictionary();
+ _database.BuildStageCache(mapping);
+ }
+ }
+
+ ///
+ /// 获取指定关卡的所有对话
+ ///
+ public List GetDialogsByStage(int stageId)
+ {
+ if (_database == null)
+ {
+ Debug.LogError("[DialogInfoManager] 未初始化,数据库为空");
+ return new List();
+ }
+
+ return _database.GetDialogsByStage(stageId);
+ }
+
+ ///
+ /// 根据对话ID查找对话
+ ///
+ public DialogInfoData GetDialogByID(int dialogId)
+ {
+ if (_database == null)
+ {
+ Debug.LogError("[DialogInfoManager] 未初始化,数据库为空");
+ return null;
+ }
+
+ return _database.FindDialogByID(dialogId);
+ }
+
+ ///
+ /// 获取关卡对应的DialogInfo表名
+ ///
+ public string GetSheetNameByStage(int stageId)
+ {
+ if (_byStageConfig == null)
+ {
+ Debug.LogError("[DialogInfoManager] 未初始化,索引配置为空");
+ return null;
+ }
+
+ return _byStageConfig.GetSheetNameByStageID(stageId);
+ }
+
+ // ═════════════════════════════════════════════════════════════════
+ // 运行时对话状态管理
+ // ═════════════════════════════════════════════════════════════════
+
+ ///
+ /// 显示对话
+ ///
+ public bool ShowDialog(int dialogId)
+ {
+ var dialog = GetDialogByID(dialogId);
+ if (dialog == null)
+ {
+ Debug.LogWarning($"[DialogInfoManager] 未找到对话配置: ID={dialogId}");
+ return false;
+ }
+
+ // 记录对话状态
+ _dialogStates[dialogId] = new DialogState
+ {
+ IsActive = true,
+ StartTime = Time.time,
+ Duration = dialog.Duration
+ };
+
+ // TODO: 调用UI系统显示对话
+ // UIManager.Instance?.ShowDialog(dialog);
+
+ Debug.Log($"[DialogInfoManager] 显示对话: ID={dialogId}, Text={dialog.Text}");
+ return true;
+ }
+
+ ///
+ /// 关闭对话
+ ///
+ public void CloseDialog(int dialogId)
+ {
+ if (_dialogStates.ContainsKey(dialogId))
+ {
+ _dialogStates[dialogId].IsActive = false;
+ }
+
+ // TODO: 调用UI系统关闭对话
+ // UIManager.Instance?.CloseDialog(dialogId);
+
+ Debug.Log($"[DialogInfoManager] 关闭对话: ID={dialogId}");
+ }
+
+ ///
+ /// 关闭所有对话
+ ///
+ public void CloseAllDialogs()
+ {
+ foreach (var kvp in _dialogStates)
+ {
+ kvp.Value.IsActive = false;
+ }
+
+ // TODO: 调用UI系统关闭所有对话
+ // UIManager.Instance?.CloseAllDialogs();
+
+ Debug.Log("[DialogInfoManager] 关闭所有对话");
+ }
+
+ ///
+ /// 检查对话是否激活
+ ///
+ public bool IsDialogActive(int dialogId)
+ {
+ if (_dialogStates.TryGetValue(dialogId, out var state))
+ {
+ return state.IsActive;
+ }
+ return false;
+ }
+
+ ///
+ /// 获取对话剩余时间
+ ///
+ public float GetDialogRemainingTime(int dialogId)
+ {
+ if (_dialogStates.TryGetValue(dialogId, out var state) && state.IsActive)
+ {
+ if (state.Duration <= 0)
+ return -1; // 永久显示
+
+ var elapsed = Time.time - state.StartTime;
+ return Mathf.Max(0, state.Duration - elapsed);
+ }
+ return 0;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs.meta
new file mode 100644
index 0000000..c719f52
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoByStageConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 808c51ff53034314d89581dd7d0e7682
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs
new file mode 100644
index 0000000..af839ca
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs
@@ -0,0 +1,415 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 对话信息数据
+ /// 对应Excel: DialogInfo Sheet
+ ///
+ [Serializable]
+ public class DialogInfoData
+ {
+ [Tooltip("配置ID")]
+ public int ID;
+
+ [Tooltip("是否开启蒙层")]
+ public bool IsMask = false;
+
+ [Tooltip("蒙层位置(万分比屏幕坐标)")]
+ public Vector2 MaskTarget = Vector2.zero;
+
+ [Tooltip("关闭蒙层键值")]
+ public string ClickKey = "";
+
+ [Tooltip("战中类型")]
+ public int DialogType = 0;
+
+ [Tooltip("UI样式,用来改变同类型战中的样式")]
+ public int UiType = 0;
+
+ [Tooltip("UI预制体路径")]
+ public string PrefabPath = "";
+
+ [Tooltip("显示角色名")]
+ public string CharacterName = "";
+
+ [Tooltip("对话正文(支持富文本)")]
+ [TextArea(3, 10)]
+ public string Text = "";
+
+ [Tooltip("显示时间(秒,0表示一直显示直到点击)")]
+ public float Duration = 0f;
+
+ [Tooltip("特殊参数1")]
+ public string Params1 = "";
+
+ [Tooltip("特殊参数2")]
+ public string Params2 = "";
+
+ [Tooltip("特殊参数3")]
+ public string Params3 = "";
+
+ [Tooltip("特殊参数4")]
+ public string Params4 = "";
+
+ [Tooltip("特殊参数5")]
+ public string Params5 = "";
+
+ [Header("富文本")]
+ [Tooltip("是否启用富文本")]
+ public bool EnableRichText = true;
+
+ [Tooltip("富文本样式名称(留空使用默认)")]
+ public string RichTextStyleName = "";
+
+ [Tooltip("自定义富文本配置(可选)")]
+ public RichTextConfig CustomRichTextConfig;
+
+ [Tooltip("文本是否需要验证")]
+ public bool ValidateRichText = true;
+
+ [Header("多语言")]
+ [Tooltip("英文文本")]
+ public string Text_EN = "";
+
+ [Tooltip("日文文本")]
+ public string Text_JP = "";
+
+ [Tooltip("韩文文本")]
+ public string Text_KR = "";
+
+ [Tooltip("法文文本")]
+ public string Text_FR = "";
+
+ [Tooltip("德文文本")]
+ public string Text_DE = "";
+
+ [Tooltip("西班牙文文本")]
+ public string Text_ES = "";
+
+ ///
+ /// 获取参数列表
+ ///
+ public string[] GetParams()
+ {
+ return new[] { Params1, Params2, Params3, Params4, Params5 };
+ }
+
+ ///
+ /// 替换文本中的占位符
+ ///
+ public string FormatText(params string[] args)
+ {
+ if (string.IsNullOrEmpty(Text))
+ return Text;
+
+ try
+ {
+ return string.Format(Text, args);
+ }
+ catch (FormatException)
+ {
+ Debug.LogWarning($"[DialogInfo] 文本格式化失败: ID={ID}, Text={Text}");
+ return Text;
+ }
+ }
+
+ ///
+ /// 验证配置有效性
+ ///
+ public bool Validate(out string errorMessage)
+ {
+ if (ID <= 0)
+ {
+ errorMessage = $"DialogInfo ID必须大于0";
+ return false;
+ }
+
+ if (string.IsNullOrWhiteSpace(Text))
+ {
+ errorMessage = $"DialogInfo ID={ID} 的Text不能为空";
+ return false;
+ }
+
+ // 富文本验证
+ if (ValidateRichText && EnableRichText && !string.IsNullOrEmpty(Text) && Text.Contains("<"))
+ {
+ var config = CustomRichTextConfig ?? GetEffectiveRichTextConfig();
+ if (config != null)
+ {
+ var validator = config.CreateValidator();
+ var result = validator.Validate(Text);
+
+ if (!result.IsValid)
+ {
+ errorMessage = $"DialogInfo ID={ID} 的富文本验证失败: {string.Join(", ", result.Errors)}";
+ return false;
+ }
+ }
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// 验证富文本(返回详细结果)
+ ///
+ public Utils.RichTextValidator.ValidationResult ValidateRichTextContent()
+ {
+ if (!EnableRichText || string.IsNullOrEmpty(Text) || !Text.Contains("<"))
+ {
+ return new Utils.RichTextValidator.ValidationResult
+ {
+ IsValid = true,
+ CleanedText = Text
+ };
+ }
+
+ var config = GetEffectiveRichTextConfig();
+ if (config == null)
+ {
+ return new Utils.RichTextValidator.ValidationResult
+ {
+ IsValid = true,
+ CleanedText = Text
+ };
+ }
+
+ var validator = config.CreateValidator();
+ return validator.Validate(Text);
+ }
+
+ ///
+ /// 获取有效的富文本配置
+ ///
+ public RichTextConfig GetEffectiveRichTextConfig()
+ {
+ // 优先使用自定义配置
+ if (CustomRichTextConfig != null)
+ return CustomRichTextConfig;
+
+ // 否则使用默认配置
+ var defaultConfig = GameplayDefaultConfig.Instance;
+ if (defaultConfig?.DefaultDialogRichTextStyle != null)
+ {
+ // 从样式创建配置
+ var config = ScriptableObject.CreateInstance();
+ config.InitializeDefaults();
+ return config;
+ }
+
+ return null;
+ }
+
+ ///
+ /// 清理富文本(移除非法标签)
+ ///
+ public string SanitizeRichText()
+ {
+ var result = ValidateRichTextContent();
+ return result.CleanedText ?? Text;
+ }
+
+ ///
+ /// 应用默认值
+ ///
+ public void ApplyDefaults()
+ {
+ var defaultConfig = GameplayDefaultConfig.Instance;
+ defaultConfig.ApplyDefaults(this);
+ }
+
+ ///
+ /// 验证并尝试自动修复
+ ///
+ public bool ValidateAndFix(out string errorMessage)
+ {
+ ApplyDefaults();
+ return Validate(out errorMessage);
+ }
+
+ }
+
+ ///
+ /// 对话信息配置容器
+ /// 单个DialogInfo表的配置
+ ///
+ [CreateAssetMenu(fileName = "DialogInfoConfig", menuName = "Gameplay/Dialog Info Config")]
+ public class DialogInfoConfig : ScriptableObject
+ {
+ [Tooltip("配置来源Sheet名称")]
+ public string SheetName = "";
+
+ [Tooltip("配置来源Excel路径")]
+ public string SourceExcelPath = "";
+
+ [Tooltip("对话配置列表")]
+ public List Dialogs = new List();
+
+ ///
+ /// 根据ID查找对话配置
+ ///
+ public DialogInfoData FindByID(int id)
+ {
+ return Dialogs.Find(d => d.ID == id);
+ }
+
+ ///
+ /// 添加或更新对话配置
+ ///
+ public void AddOrUpdate(DialogInfoData data)
+ {
+ var existing = FindByID(data.ID);
+ if (existing != null)
+ {
+ Dialogs.Remove(existing);
+ }
+ Dialogs.Add(data);
+ }
+
+ ///
+ /// 验证所有配置
+ ///
+ public bool ValidateAll(out List errors)
+ {
+ errors = new List();
+
+ // 检查ID唯一性
+ var idSet = new HashSet();
+ foreach (var dialog in Dialogs)
+ {
+ if (idSet.Contains(dialog.ID))
+ {
+ errors.Add($"重复的DialogInfo ID: {dialog.ID}");
+ }
+ idSet.Add(dialog.ID);
+
+ // 验证单个配置
+ if (!dialog.Validate(out var error))
+ {
+ errors.Add(error);
+ }
+ }
+
+ return errors.Count == 0;
+ }
+ }
+
+ ///
+ /// 对话信息数据库
+ /// 管理多个DialogInfo配置表
+ /// 支持按StageID查找对应对话
+ ///
+ [CreateAssetMenu(fileName = "DialogInfoDatabase", menuName = "Gameplay/Dialog Info Database")]
+ public class DialogInfoDatabase : ScriptableObject
+ {
+ [Tooltip("所有对话配置表")]
+ public List Configs = new List();
+
+ ///
+ /// StageID到Config的映射缓存
+ ///
+ [NonSerialized]
+ private Dictionary _stageToConfigCache;
+
+ ///
+ /// 根据对话ID查找配置(在所有表中搜索)
+ ///
+ public DialogInfoData FindDialogByID(int dialogId)
+ {
+ foreach (var config in Configs)
+ {
+ var dialog = config.FindByID(dialogId);
+ if (dialog != null)
+ return dialog;
+ }
+ return null;
+ }
+
+ ///
+ /// 获取指定关卡的所有对话
+ ///
+ public List GetDialogsByStage(int stageId)
+ {
+ var result = new List();
+
+ // 通过映射表查找对应的Config
+ if (_stageToConfigCache == null)
+ BuildStageCache();
+
+ if (_stageToConfigCache.TryGetValue(stageId, out var config))
+ {
+ result.AddRange(config.Dialogs);
+ }
+
+ return result;
+ }
+
+ ///
+ /// 添加配置表
+ ///
+ public void AddConfig(DialogInfoConfig config)
+ {
+ if (!Configs.Contains(config))
+ {
+ Configs.Add(config);
+ _stageToConfigCache = null; // 清除缓存
+ }
+ }
+
+ ///
+ /// 构建StageID缓存(需要配合DialogInfoByStageConfig使用)
+ ///
+ public void BuildStageCache(Dictionary stageToSheetName = null)
+ {
+ _stageToConfigCache = new Dictionary();
+
+ if (stageToSheetName == null)
+ return;
+
+ foreach (var kvp in stageToSheetName)
+ {
+ var stageId = kvp.Key;
+ var sheetName = kvp.Value;
+
+ // 查找匹配的Config
+ var config = Configs.Find(c =>
+ c.SheetName == sheetName ||
+ c.name.Contains(sheetName));
+
+ if (config != null)
+ {
+ _stageToConfigCache[stageId] = config;
+ }
+ }
+ }
+
+ ///
+ /// 清空缓存
+ ///
+ public void ClearCache()
+ {
+ _stageToConfigCache = null;
+ }
+
+ ///
+ /// 获取所有对话(跨所有配置表)
+ ///
+ public IEnumerable GetAllDialogs()
+ {
+ foreach (var config in Configs)
+ {
+ if (config?.Dialogs != null)
+ {
+ foreach (var dialog in config.Dialogs)
+ {
+ yield return dialog;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs.meta
new file mode 100644
index 0000000..e23eff0
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/DialogInfoConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ad328400186ec474d9505f3bb33d8aaa
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs
new file mode 100644
index 0000000..c248668
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs
@@ -0,0 +1,344 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 事件类型枚举
+ ///
+ public enum EventType
+ {
+ Battle = 1, // 战斗
+ Choice = 2, // 抉择
+ Reward = 3, // 奖励
+ Shop = 4, // 商店
+ Rest = 5 // 休息区
+ }
+
+ ///
+ /// 事件池类型
+ ///
+ public enum EventPool
+ {
+ Battle = 1,
+ Choice = 2,
+ Reward = 3,
+ Shop = 4,
+ Rest = 5
+ }
+
+ ///
+ /// 事件节点配置
+ /// 对应初始化配置列表
+ ///
+ [Serializable]
+ public class EventNodeConfig
+ {
+ [Tooltip("行索引(自动生成)")]
+ public int RowIndex;
+
+ [Tooltip("关卡章节ID")]
+ public int LevelID;
+
+ [Tooltip("节点ID(唯一)")]
+ public int NodeID;
+
+ [Tooltip("节点层级")]
+ public int NodeLayer;
+
+ [Tooltip("节点池类型组(1战斗|2抉择|3奖励|4商店|5休息区)")]
+ public List EventTypeGroup = new List();
+
+ [Tooltip("类型权重(万分比,如2000|2000|2000|2000|2000)")]
+ public List Priority = new List();
+
+ [Tooltip("存储事件ID映射")]
+ public int EventMappingID;
+
+ [Tooltip("交互点是否可见")]
+ public bool InteractVisible = true;
+
+ [Tooltip("节点是否可交互")]
+ public bool NodeState = true;
+ }
+
+ ///
+ /// 事件Action配置
+ /// 对应事件Action配置列表
+ ///
+ [Serializable]
+ public class EventActionConfig
+ {
+ [Tooltip("事件ID(唯一)")]
+ public int EventID;
+
+ [Tooltip("节点类型(1战斗|2抉择|3奖励|4商店|5休息区)")]
+ public EventType Type;
+
+ [Tooltip("事件池")]
+ public EventPool Pool;
+
+ [Tooltip("消耗完后是否移除事件池")]
+ public bool Removed = true;
+ }
+
+ ///
+ /// 活动关卡事件构建配置容器
+ ///
+ [CreateAssetMenu(fileName = "EventBuilderConfig", menuName = "Gameplay/Event Builder Config")]
+ public class EventBuilderConfig : ScriptableObject
+ {
+ [Header("节点配置")]
+ [Tooltip("初始化配置列表")]
+ public List NodeConfigs = new List();
+
+ [Header("事件配置")]
+ [Tooltip("事件Action配置列表")]
+ public List ActionConfigs = new List();
+
+ [Header("事件映射")]
+ [Tooltip("EventMappingID 到 EventID 列表的映射配置")]
+ public EventMappingConfig EventMappingConfig;
+
+ [Header("元数据")]
+ [Tooltip("关联的关卡ID")]
+ public int LevelID;
+
+ [Tooltip("配置来源Excel路径")]
+ public string SourceExcelPath;
+
+ ///
+ /// 根据NodeID查找节点配置
+ ///
+ public EventNodeConfig FindNodeByID(int nodeId)
+ {
+ return NodeConfigs.Find(n => n.NodeID == nodeId);
+ }
+
+ ///
+ /// 根据EventID查找事件配置
+ ///
+ public EventActionConfig FindActionByID(int eventId)
+ {
+ return ActionConfigs.Find(a => a.EventID == eventId);
+ }
+
+ ///
+ /// 获取指定层级的所有节点
+ ///
+ public List GetNodesByLayer(int layer)
+ {
+ return NodeConfigs.FindAll(n => n.NodeLayer == layer);
+ }
+
+ ///
+ /// 根据 EventMappingID 获取对应的事件列表
+ /// 如果 EventMappingConfig 中未找到,则根据 nodeConfig 的 EventTypeGroup 做 Fallback 匹配
+ ///
+ public List GetEventsByMappingID(int mappingId, EventNodeConfig nodeConfig = null)
+ {
+ var result = new List();
+
+ // 1. 优先查找显式映射表
+ if (EventMappingConfig != null)
+ {
+ var entry = EventMappingConfig.FindByMappingID(mappingId);
+ if (entry != null && entry.EventIDs != null)
+ {
+ foreach (var eventId in entry.EventIDs)
+ {
+ var action = FindActionByID(eventId);
+ if (action != null) result.Add(action);
+ }
+ if (result.Count > 0) return result;
+ }
+ }
+
+ // 2. Fallback:根据 EventTypeGroup 筛选 ActionConfigs
+ if (nodeConfig != null && nodeConfig.EventTypeGroup != null)
+ {
+ foreach (var action in ActionConfigs)
+ {
+ if (nodeConfig.EventTypeGroup.Contains((int)action.Type))
+ {
+ result.Add(action);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// 添加或更新节点配置
+ ///
+ public void AddOrUpdateNode(EventNodeConfig config)
+ {
+ var existing = FindNodeByID(config.NodeID);
+ if (existing != null)
+ {
+ NodeConfigs.Remove(existing);
+ }
+ NodeConfigs.Add(config);
+ }
+
+ ///
+ /// 添加或更新事件配置
+ ///
+ public void AddOrUpdateAction(EventActionConfig config)
+ {
+ var existing = FindActionByID(config.EventID);
+ if (existing != null)
+ {
+ ActionConfigs.Remove(existing);
+ }
+ ActionConfigs.Add(config);
+ }
+
+ ///
+ /// 验证配置完整性
+ ///
+ public bool Validate(out string errorMessage)
+ {
+ // 检查节点ID唯一性
+ var nodeIds = new HashSet();
+ foreach (var node in NodeConfigs)
+ {
+ if (nodeIds.Contains(node.NodeID))
+ {
+ errorMessage = $"重复的NodeID: {node.NodeID}";
+ return false;
+ }
+ nodeIds.Add(node.NodeID);
+ }
+
+ // 检查事件ID唯一性
+ var eventIds = new HashSet();
+ foreach (var action in ActionConfigs)
+ {
+ if (eventIds.Contains(action.EventID))
+ {
+ errorMessage = $"重复的EventID: {action.EventID}";
+ return false;
+ }
+ eventIds.Add(action.EventID);
+ }
+
+ errorMessage = null;
+ return true;
+ }
+
+ ///
+ /// 应用默认值到所有节点和事件
+ ///
+ public void ApplyDefaults()
+ {
+ var defaultConfig = GameplayDefaultConfig.Instance;
+
+ foreach (var node in NodeConfigs)
+ {
+ defaultConfig.ApplyDefaults(node);
+ }
+ }
+
+ ///
+ /// 验证并尝试自动修复
+ ///
+ public bool ValidateAndFix(out string errorMessage)
+ {
+ ApplyDefaults();
+ return Validate(out errorMessage);
+ }
+
+ ///
+ /// 检查权重总和是否正确
+ ///
+ public bool ValidateWeights(out List errorMessages)
+ {
+ errorMessages = new List();
+ var defaultConfig = GameplayDefaultConfig.Instance;
+
+ foreach (var node in NodeConfigs)
+ {
+ if (node.Priority == null || node.Priority.Count == 0)
+ {
+ errorMessages.Add($"NodeID={node.NodeID} 的Priority为空");
+ continue;
+ }
+
+ int total = 0;
+ foreach (var weight in node.Priority)
+ {
+ total += weight;
+ }
+
+ if (total != defaultConfig.TotalWeight)
+ {
+ errorMessages.Add($"NodeID={node.NodeID} 的权重总和为 {total},应为 {defaultConfig.TotalWeight}");
+ }
+ }
+
+ return errorMessages.Count == 0;
+ }
+ }
+
+ ///
+ /// 事件构建数据(解析中间结构)
+ ///
+ [Serializable]
+ public class EventBuilderData
+ {
+ public List NodeConfigs = new List();
+ public List ActionConfigs = new List();
+ public int LevelID;
+ public EventMappingConfig EventMappingConfig;
+
+ ///
+ /// 应用到ScriptableObject
+ ///
+ public void ApplyTo(EventBuilderConfig config)
+ {
+ config.NodeConfigs = NodeConfigs;
+ config.ActionConfigs = ActionConfigs;
+ config.LevelID = LevelID;
+ config.EventMappingConfig = EventMappingConfig;
+ }
+
+ ///
+ /// 基于 EventTypeGroup 自动生成默认的 EventMapping 配置
+ /// 用于当前 Excel 中没有独立 EventMapping 表时的 Fallback
+ ///
+ public EventMappingConfig BuildDefaultMappings()
+ {
+ var mappingConfig = ScriptableObject.CreateInstance();
+
+ foreach (var node in NodeConfigs)
+ {
+ if (node.EventMappingID <= 0) continue;
+ if (node.EventTypeGroup == null || node.EventTypeGroup.Count == 0) continue;
+
+ var entry = new EventMappingEntry
+ {
+ MappingID = node.EventMappingID,
+ Doc = $"自动生成的默认映射 (NodeID={node.NodeID})"
+ };
+
+ foreach (var action in ActionConfigs)
+ {
+ if (node.EventTypeGroup.Contains((int)action.Type))
+ {
+ entry.EventIDs.Add(action.EventID);
+ }
+ }
+
+ if (entry.EventIDs.Count > 0)
+ {
+ mappingConfig.AddOrUpdateEntry(entry);
+ }
+ }
+
+ return mappingConfig;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs.meta
new file mode 100644
index 0000000..e00494a
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/EventBuilderConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8b5566f322dad2b40b88aedc4eb2cf28
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs
new file mode 100644
index 0000000..b8c82f5
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 事件映射条目
+ /// EventMappingID 与具体 EventID 列表的关联
+ ///
+ [Serializable]
+ public class EventMappingEntry
+ {
+ [Tooltip("事件映射ID(来自EventNodeConfig.EventMappingID)")]
+ public int MappingID;
+
+ [Tooltip("关联的事件ID列表")]
+ public List EventIDs = new List();
+
+ [Tooltip("备注")]
+ public string Doc;
+ }
+
+ ///
+ /// 事件映射配置表
+ /// 建立 EventMappingID -> EventID List 的映射
+ ///
+ [CreateAssetMenu(fileName = "EventMappingConfig", menuName = "Gameplay/Event Mapping Config")]
+ public class EventMappingConfig : ScriptableObject
+ {
+ [Tooltip("事件映射条目列表")]
+ public List Entries = new List();
+
+ ///
+ /// 根据 MappingID 查找条目
+ ///
+ public EventMappingEntry FindByMappingID(int mappingId)
+ {
+ return Entries.Find(e => e.MappingID == mappingId);
+ }
+
+ ///
+ /// 添加或更新映射条目
+ ///
+ public void AddOrUpdateEntry(EventMappingEntry entry)
+ {
+ var existing = FindByMappingID(entry.MappingID);
+ if (existing != null)
+ {
+ Entries.Remove(existing);
+ }
+ Entries.Add(entry);
+ }
+
+ ///
+ /// 验证配置完整性
+ ///
+ public bool Validate(out List errors)
+ {
+ errors = new List();
+ var idSet = new HashSet();
+
+ foreach (var entry in Entries)
+ {
+ if (entry.MappingID <= 0)
+ {
+ errors.Add($"无效的 MappingID: {entry.MappingID}");
+ continue;
+ }
+
+ if (idSet.Contains(entry.MappingID))
+ {
+ errors.Add($"重复的 MappingID: {entry.MappingID}");
+ }
+ idSet.Add(entry.MappingID);
+
+ if (entry.EventIDs == null || entry.EventIDs.Count == 0)
+ {
+ errors.Add($"MappingID={entry.MappingID} 的 EventIDs 为空");
+ }
+ }
+
+ return errors.Count == 0;
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs.meta
new file mode 100644
index 0000000..6ed70ae
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/EventMappingConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f2fa8e9510aa0cb42a2f9317a57cd344
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs
new file mode 100644
index 0000000..f3a7d36
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs
@@ -0,0 +1,398 @@
+using System;
+using System.Collections.Generic;
+using GameplayEditor.Core;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 游戏玩法配置默认值中心
+ /// 集中管理所有配置项的默认值,支持项目级自定义
+ ///
+ [CreateAssetMenu(fileName = "GameplayDefaultConfig", menuName = "Gameplay/Default Config")]
+ public class GameplayDefaultConfig : ScriptableObject
+ {
+ #region 单例访问
+
+ private static GameplayDefaultConfig _instance;
+
+ ///
+ /// 获取默认配置实例
+ ///
+ public static GameplayDefaultConfig Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = Resources.Load("GameplayDefaultConfig");
+ if (_instance == null)
+ {
+ // 创建默认配置
+ _instance = CreateDefaultConfig();
+ }
+ }
+ return _instance;
+ }
+ }
+
+ ///
+ /// 设置自定义实例(用于测试或动态替换)
+ ///
+ public static void SetInstance(GameplayDefaultConfig config)
+ {
+ _instance = config;
+ }
+
+ #endregion
+
+ #region ActivityStageConfig 默认值
+
+ [Header("关卡配置默认值")]
+ [Tooltip("默认场景名称")]
+ public string DefaultSceneName = "MainScene";
+
+ [Tooltip("默认Tick率模式")]
+ public TickRateMode DefaultTickMode = TickRateMode.Normal;
+
+ [Tooltip("默认Tick率")]
+ [Range(20, 120)]
+ public int DefaultTickRate = 60;
+
+ [Tooltip("默认延迟关闭加载图时间")]
+ public float DefaultCloseLoadingDelay = 1f;
+
+ [Tooltip("默认相机ID")]
+ public int DefaultCameraID = 1001;
+
+ [Tooltip("默认地图打点组ID")]
+ public int DefaultMapInfo = 10001;
+
+ [Tooltip("无限制模式下是否使用固定DeltaTime")]
+ public bool DefaultUseFixedDeltaTimeInUnlimited = true;
+
+ [Tooltip("无限制模式下的固定DeltaTime")]
+ public float DefaultUnlimitedDeltaTime = 0.016f;
+
+ #endregion
+
+ #region DialogInfoConfig 默认值
+
+ [Header("对话配置默认值")]
+ [Tooltip("默认对话显示时间")]
+ public float DefaultDialogDuration = 3f;
+
+ [Tooltip("默认是否开启蒙层")]
+ public bool DefaultDialogIsMask = false;
+
+ [Tooltip("默认蒙层位置")]
+ public Vector2 DefaultDialogMaskTarget = Vector2.zero;
+
+ [Tooltip("默认战中类型")]
+ public int DefaultDialogType = 0;
+
+ [Tooltip("默认UI样式")]
+ public int DefaultDialogUiType = 0;
+
+ [Tooltip("默认角色名")]
+ public string DefaultDialogCharacterName = "角色";
+
+ [Tooltip("默认是否启用富文本")]
+ public bool DefaultDialogEnableRichText = true;
+
+ [Tooltip("默认富文本样式")]
+ public RichTextStyle DefaultDialogRichTextStyle;
+
+ #endregion
+
+ #region EventBuilderConfig 默认值
+
+ [Header("事件配置默认值")]
+ [Tooltip("默认节点层级")]
+ public int DefaultNodeLayer = 1;
+
+ [Tooltip("默认事件类型组")]
+ public List DefaultEventTypeGroup = new List { 1, 2, 3, 4, 5 };
+
+ [Tooltip("默认类型权重(万分比)")]
+ public List DefaultPriority = new List { 2000, 2000, 2000, 2000, 2000 };
+
+ [Tooltip("默认交互点可见性")]
+ public bool DefaultInteractVisible = true;
+
+ [Tooltip("默认节点交互状态")]
+ public bool DefaultNodeState = true;
+
+ [Tooltip("默认事件消耗后是否移除")]
+ public bool DefaultEventRemoved = true;
+
+ #endregion
+
+ #region CameraConfig 默认值
+
+ [Header("相机配置默认值")]
+ [Tooltip("默认FOV")]
+ [Range(1, 179)]
+ public float DefaultCameraFOV = 60f;
+
+ [Tooltip("默认优先级")]
+ public int DefaultCameraPriority = 10;
+
+ [Tooltip("默认跟随偏移")]
+ public Vector3 DefaultCameraFollowOffset = new Vector3(0, 5, -10);
+
+ [Tooltip("默认注视偏移")]
+ public Vector3 DefaultCameraLookAtOffset = Vector3.zero;
+
+ [Tooltip("默认过渡时间")]
+ public float DefaultCameraBlendTime = 0.5f;
+
+ [Tooltip("默认过渡样式")]
+ public CinemachineBlendStyle DefaultCameraBlendStyle = CinemachineBlendStyle.EaseInOut;
+
+ #endregion
+
+ #region FXConfig 默认值
+
+ [Header("特效配置默认值")]
+ [Tooltip("默认特效持续时间")]
+ public float DefaultFXDuration = 2f;
+
+ [Tooltip("默认是否循环")]
+ public bool DefaultFXLoop = false;
+
+ [Tooltip("默认是否自动销毁")]
+ public bool DefaultFXAutoDestroy = true;
+
+ [Tooltip("默认延迟时间")]
+ public float DefaultFXDelay = 0f;
+
+ [Tooltip("默认播放速度")]
+ public float DefaultFXSpeed = 1f;
+
+ [Tooltip("默认缩放")]
+ public float DefaultFXScale = 1f;
+
+ [Tooltip("默认使用对象池")]
+ public bool DefaultFXUseObjectPool = true;
+
+ #endregion
+
+ #region 行为树默认值
+
+ [Header("行为树默认值")]
+ [Tooltip("默认最大递归深度")]
+ public int DefaultMaxRecursionDepth = 10;
+
+ [Tooltip("默认最大节点深度")]
+ public int DefaultMaxNodeDepth = 100;
+
+ [Tooltip("默认节点执行警告阈值")]
+ public int DefaultFrameExecutionThreshold = 60;
+
+ [Tooltip("默认节点执行耗时警告阈值(毫秒)")]
+ public float DefaultExecutionTimeThresholdMs = 5f;
+
+ #endregion
+
+ #region ID范围约束
+
+ [Header("ID范围约束")]
+ [Tooltip("启用严格ID范围检查")]
+ public bool EnableStrictIDRangeCheck = true;
+
+ [Tooltip("关卡ID最小值")]
+ public int MinStageID = 1000;
+
+ [Tooltip("关卡ID最大值")]
+ public int MaxStageID = 1999;
+
+ [Tooltip("节点ID最小值")]
+ public int MinNodeID = 10000;
+
+ [Tooltip("节点ID最大值")]
+ public int MaxNodeID = 19999;
+
+ [Tooltip("事件ID最小值")]
+ public int MinEventID = 9000;
+
+ [Tooltip("事件ID最大值")]
+ public int MaxEventID = 9999;
+
+ [Tooltip("组合方法ID最小值")]
+ public int MinCompositeMethodID = 200000;
+
+ [Tooltip("组合方法ID最大值")]
+ public int MaxCompositeMethodID = 999999;
+
+ #endregion
+
+ #region 验证阈值
+
+ [Header("验证阈值")]
+ [Tooltip("ID最小值")]
+ public int MinID = 1;
+
+ [Tooltip("ID最大值")]
+ public int MaxID = 999999;
+
+ [Tooltip("最小Tick率")]
+ public int MinTickRate = 20;
+
+ [Tooltip("最大Tick率")]
+ public int MaxTickRate = 120;
+
+ [Tooltip("最小权重")]
+ public int MinWeight = 0;
+
+ [Tooltip("最大权重")]
+ public int MaxWeight = 10000;
+
+ [Tooltip("权重总和")]
+ public int TotalWeight = 10000;
+
+ #endregion
+
+ #region 帮助方法
+
+ ///
+ /// 应用关卡配置默认值
+ ///
+ public void ApplyDefaults(ActivityStageConfig config)
+ {
+ if (config == null) return;
+
+ if (string.IsNullOrWhiteSpace(config.SceneName))
+ config.SceneName = DefaultSceneName;
+
+ if (config.TickRate < MinTickRate || config.TickRate > MaxTickRate)
+ config.TickRate = DefaultTickRate;
+
+ if (config.CloseLoadingDelay <= 0)
+ config.CloseLoadingDelay = DefaultCloseLoadingDelay;
+
+ if (config.CameraID <= 0)
+ config.CameraID = DefaultCameraID;
+
+ if (config.MapInfo <= 0)
+ config.MapInfo = DefaultMapInfo;
+ }
+
+ ///
+ /// 应用对话配置默认值
+ ///
+ public void ApplyDefaults(DialogInfoData config)
+ {
+ if (config == null) return;
+
+ if (config.Duration <= 0)
+ config.Duration = DefaultDialogDuration;
+
+ if (string.IsNullOrWhiteSpace(config.CharacterName))
+ config.CharacterName = DefaultDialogCharacterName;
+ }
+
+ ///
+ /// 应用事件配置默认值
+ ///
+ public void ApplyDefaults(EventNodeConfig config)
+ {
+ if (config == null) return;
+
+ if (config.NodeLayer <= 0)
+ config.NodeLayer = DefaultNodeLayer;
+
+ if (config.EventTypeGroup == null || config.EventTypeGroup.Count == 0)
+ config.EventTypeGroup = new List(DefaultEventTypeGroup);
+
+ if (config.Priority == null || config.Priority.Count == 0)
+ config.Priority = new List(DefaultPriority);
+ }
+
+ ///
+ /// 应用相机配置默认值
+ ///
+ public void ApplyDefaults(CameraMapping config)
+ {
+ if (config == null) return;
+
+ if (config.FOV <= 0)
+ config.FOV = DefaultCameraFOV;
+
+ if (config.Priority <= 0)
+ config.Priority = DefaultCameraPriority;
+ }
+
+ ///
+ /// 应用特效配置默认值
+ ///
+ public void ApplyDefaults(FXMapping config)
+ {
+ if (config == null) return;
+
+ if (config.Duration <= 0)
+ config.Duration = DefaultFXDuration;
+
+ if (config.Speed <= 0)
+ config.Speed = DefaultFXSpeed;
+
+ if (config.Scale <= 0)
+ config.Scale = DefaultFXScale;
+ }
+
+ #endregion
+
+ #region 私有方法
+
+ ///
+ /// 创建默认配置
+ ///
+ private static GameplayDefaultConfig CreateDefaultConfig()
+ {
+ var config = CreateInstance();
+ config.name = "GameplayDefaultConfig";
+ return config;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// Cinemachine混合样式
+ ///
+ public enum CinemachineBlendStyle
+ {
+ Cut,
+ EaseInOut,
+ EaseIn,
+ EaseOut,
+ HardIn,
+ HardOut,
+ Linear
+ }
+
+ ///
+ /// 富文本样式
+ ///
+ [Serializable]
+ public class RichTextStyle
+ {
+ [Tooltip("样式名称")]
+ public string StyleName = "Default";
+
+ [Tooltip("字体大小")]
+ public int FontSize = 24;
+
+ [Tooltip("字体颜色")]
+ public Color FontColor = Color.white;
+
+ [Tooltip("是否粗体")]
+ public bool Bold = false;
+
+ [Tooltip("是否斜体")]
+ public bool Italic = false;
+
+ [Tooltip("是否下划线")]
+ public bool Underline = false;
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs.meta
new file mode 100644
index 0000000..f5b8eb5
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/GameplayDefaultConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e5567297bb87af94ba65bbd1f22e4e7d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs
new file mode 100644
index 0000000..8e76eac
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs
@@ -0,0 +1,247 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// ID段配置
+ ///
+ [Serializable]
+ public class IDRange
+ {
+ [Tooltip("策划/所有者名称")]
+ public string OwnerName;
+
+ [Tooltip("起始ID")]
+ public int StartID;
+
+ [Tooltip("结束ID")]
+ public int EndID;
+
+ [Tooltip("当前已用ID")]
+ public int CurrentUsedID;
+
+ [Tooltip("描述/备注")]
+ [TextArea(1, 2)]
+ public string Description;
+
+ ///
+ /// 剩余可用ID数量
+ ///
+ public int RemainingCount => EndID - CurrentUsedID;
+
+ ///
+ /// 使用率(0-1)
+ ///
+ public float UsageRate => (float)(CurrentUsedID - StartID + 1) / (EndID - StartID + 1);
+
+ ///
+ /// 是否已耗尽
+ ///
+ public bool IsExhausted => CurrentUsedID >= EndID;
+
+ ///
+ /// 分配新ID
+ ///
+ public int AllocateID()
+ {
+ if (IsExhausted)
+ return -1;
+
+ return ++CurrentUsedID;
+ }
+
+ ///
+ /// 检查ID是否在此范围内
+ ///
+ public bool Contains(int id)
+ {
+ return id >= StartID && id <= EndID;
+ }
+
+ ///
+ /// 验证配置有效性
+ ///
+ public bool Validate(out string error)
+ {
+ if (string.IsNullOrWhiteSpace(OwnerName))
+ {
+ error = "所有者名称不能为空";
+ return false;
+ }
+ if (StartID <= 0)
+ {
+ error = "起始ID必须大于0";
+ return false;
+ }
+ if (EndID < StartID)
+ {
+ error = "结束ID必须大于等于起始ID";
+ return false;
+ }
+ if (CurrentUsedID < StartID - 1)
+ {
+ error = "当前已用ID不能小于起始ID-1";
+ return false;
+ }
+
+ error = null;
+ return true;
+ }
+ }
+
+ ///
+ /// ID段分配配置
+ ///
+ [CreateAssetMenu(fileName = "IDRangeConfig", menuName = "Gameplay/ID Range Config")]
+ public class IDRangeConfig : ScriptableObject
+ {
+ [Tooltip("ID段列表")]
+ public List Ranges = new List();
+
+ ///
+ /// 分配新ID
+ ///
+ /// 所有者名称
+ /// 分配的ID,-1表示失败
+ public int AllocateID(string ownerName)
+ {
+ var range = Ranges.Find(r => r.OwnerName == ownerName);
+ if (range == null)
+ {
+ Debug.LogError($"[IDRange] 未找到所有者: {ownerName}");
+ return -1;
+ }
+
+ var id = range.AllocateID();
+ if (id < 0)
+ {
+ Debug.LogError($"[IDRange] ID段已耗尽: {ownerName} ({range.StartID}-{range.EndID})");
+ return -1;
+ }
+
+ #if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+ UnityEditor.AssetDatabase.SaveAssets();
+ #endif
+
+ Debug.Log($"[IDRange] 为 {ownerName} 分配ID: {id}");
+ return id;
+ }
+
+ ///
+ /// 添加新ID段
+ ///
+ public bool AddRange(IDRange range, out string error)
+ {
+ if (!range.Validate(out error))
+ return false;
+
+ // 检查所有者名称是否已存在
+ if (Ranges.Exists(r => r.OwnerName == range.OwnerName))
+ {
+ error = $"所有者 '{range.OwnerName}' 已存在";
+ return false;
+ }
+
+ // 检查ID段是否重叠
+ foreach (var existing in Ranges)
+ {
+ if (range.StartID <= existing.EndID && range.EndID >= existing.StartID)
+ {
+ error = $"ID段与 '{existing.OwnerName}' 重叠";
+ return false;
+ }
+ }
+
+ Ranges.Add(range);
+
+ #if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+ UnityEditor.AssetDatabase.SaveAssets();
+ #endif
+
+ error = null;
+ return true;
+ }
+
+ ///
+ /// 移除ID段
+ ///
+ public bool RemoveRange(string ownerName)
+ {
+ var range = Ranges.Find(r => r.OwnerName == ownerName);
+ if (range != null)
+ {
+ Ranges.Remove(range);
+
+ #if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+ UnityEditor.AssetDatabase.SaveAssets();
+ #endif
+
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// 根据ID查找所属段
+ ///
+ public IDRange FindRangeByID(int id)
+ {
+ return Ranges.Find(r => r.Contains(id));
+ }
+
+ ///
+ /// 检查ID是否冲突
+ ///
+ public bool CheckConflict(int id, out string ownerName)
+ {
+ var range = FindRangeByID(id);
+ if (range != null)
+ {
+ ownerName = range.OwnerName;
+ return true;
+ }
+ ownerName = null;
+ return false;
+ }
+
+ ///
+ /// 获取预警的ID段(使用率>80%)
+ ///
+ public List GetWarningRanges(float threshold = 0.8f)
+ {
+ return Ranges.Where(r => r.UsageRate >= threshold).ToList();
+ }
+
+ ///
+ /// 获取已耗尽的ID段
+ ///
+ public List GetExhaustedRanges()
+ {
+ return Ranges.Where(r => r.IsExhausted).ToList();
+ }
+
+ ///
+ /// 建议的ID段分配方案
+ ///
+ public static List GetRecommendedRanges()
+ {
+ return new List
+ {
+ new IDRange { OwnerName = "系统保留", StartID = 100000, EndID = 199999,
+ Description = "通用组合节点、系统节点" },
+ new IDRange { OwnerName = "策划A", StartID = 200000, EndID = 299999,
+ Description = "策划A专用ID段" },
+ new IDRange { OwnerName = "策划B", StartID = 300000, EndID = 399999,
+ Description = "策划B专用ID段" },
+ new IDRange { OwnerName = "策划C", StartID = 400000, EndID = 499999,
+ Description = "策划C专用ID段" },
+ };
+ }
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs.meta
new file mode 100644
index 0000000..f97f98d
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/IDRangeConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5a86d9dee8c7946448dc913e62bdb508
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs
new file mode 100644
index 0000000..c35dbcd
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// LUT配置数据库
+ /// 统一管理所有LUT配置
+ ///
+ [CreateAssetMenu(fileName = "LutDatabase", menuName = "Gameplay/Lut/Lut Database")]
+ public class LutConfigDatabase : ScriptableObject
+ {
+ [Tooltip("UI LUT配置")]
+ public List UiLuts = new List();
+
+ [Tooltip("相机LUT配置")]
+ public List CameraLuts = new List();
+
+ [Tooltip("特效LUT配置")]
+ public List FxLuts = new List();
+
+ [Tooltip("地图信息LUT配置")]
+ public List MapInfoLuts = new List();
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs.meta
new file mode 100644
index 0000000..8665bcf
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigDatabase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 35a481d5fec3d494dbdeabf3f5f331af
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs
new file mode 100644
index 0000000..2c250d2
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs
@@ -0,0 +1,297 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 通用资源映射基类
+ ///
+ [Serializable]
+ public class ResourceMapping
+ {
+ [Tooltip("关卡ID")]
+ public int StageID;
+
+ [Tooltip("映射ID")]
+ public int MappingID;
+
+ [Tooltip("Prefab路径")]
+ public string PrefabPath;
+
+ [Tooltip("运行时加载的Prefab(自动填充)")]
+ public GameObject LoadedPrefab;
+
+ ///
+ /// 验证路径是否有效
+ ///
+ public bool ValidatePath()
+ {
+ if (string.IsNullOrWhiteSpace(PrefabPath))
+ return false;
+
+ #if UNITY_EDITOR
+ return UnityEditor.AssetDatabase.LoadAssetAtPath(PrefabPath) != null;
+ #else
+ return true;
+ #endif
+ }
+
+ ///
+ /// 应用默认值
+ ///
+ public virtual void ApplyDefaults()
+ {
+ // 基类默认值已在创建时设置
+ }
+ }
+
+ ///
+ /// 相机详细映射配置
+ /// 包含Cinemachine相机参数
+ ///
+ [Serializable]
+ public class CameraMapping : ResourceMapping
+ {
+ [Header("相机参数")]
+ [Tooltip("视场角(FOV),范围1-179")]
+ [Range(1, 179)]
+ public float FOV = 60f;
+
+ [Tooltip("相机优先级,高优先级相机会覆盖低优先级")]
+ public int Priority = 10;
+
+ [Header("跟随设置")]
+ [Tooltip("跟随目标偏移")]
+ public Vector3 FollowOffset = new Vector3(0, 5, -10);
+
+ [Tooltip("跟随目标的最小距离")]
+ public float FollowMinDistance = 0.1f;
+
+ [Tooltip("跟随目标的最大距离")]
+ public float FollowMaxDistance = 100f;
+
+ [Header("注视设置")]
+ [Tooltip("注视目标偏移")]
+ public Vector3 LookAtOffset = Vector3.zero;
+
+ [Tooltip("注视点的偏移高度")]
+ public float LookAtHeight = 1.5f;
+
+ [Header("过渡设置")]
+ [Tooltip("过渡时间(秒),0表示立即切换")]
+ public float BlendTime = 0.5f;
+
+ [Tooltip("过渡样式")]
+ public CinemachineBlendStyle BlendStyle = CinemachineBlendStyle.EaseInOut;
+
+ [Header("边界设置")]
+ [Tooltip("是否启用软边界")]
+ public bool UseSoftZone = true;
+
+ [Tooltip("死区宽度")]
+ [Range(0, 1)]
+ public float DeadZoneWidth = 0.1f;
+
+ [Tooltip("死区高度")]
+ [Range(0, 1)]
+ public float DeadZoneHeight = 0.1f;
+
+ [Header("震动设置")]
+ [Tooltip("默认震动幅度")]
+ public float DefaultShakeAmplitude = 1f;
+
+ [Tooltip("默认震动频率")]
+ public float DefaultShakeFrequency = 1f;
+
+ [Tooltip("默认震动持续时间")]
+ public float DefaultShakeDuration = 0.5f;
+
+ [Header("其他")]
+ [Tooltip("相机备注")]
+ public string CameraDescription = "";
+
+ ///
+ /// 应用默认值
+ ///
+ public override void ApplyDefaults()
+ {
+ base.ApplyDefaults();
+ var defaultConfig = GameplayDefaultConfig.Instance;
+
+ if (FOV <= 0 || FOV >= 180)
+ FOV = defaultConfig.DefaultCameraFOV;
+
+ if (Priority <= 0)
+ Priority = defaultConfig.DefaultCameraPriority;
+
+ if (BlendTime < 0)
+ BlendTime = defaultConfig.DefaultCameraBlendTime;
+ }
+
+ ///
+ /// 验证相机配置
+ ///
+ public bool Validate(out string errorMessage)
+ {
+ if (MappingID <= 0)
+ {
+ errorMessage = "Camera MappingID必须大于0";
+ return false;
+ }
+
+ if (FOV <= 0 || FOV >= 180)
+ {
+ errorMessage = $"FOV {FOV} 超出有效范围(0, 180)";
+ return false;
+ }
+
+ if (Priority < 0)
+ {
+ errorMessage = "Priority不能为负数";
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ ///
+ /// 特效生命周期映射配置
+ ///
+ [Serializable]
+ public class FXMapping : ResourceMapping
+ {
+ [Header("生命周期")]
+ [Tooltip("持续时间(秒),0表示使用粒子系统默认时长")]
+ public float Duration = 2f;
+
+ [Tooltip("延迟时间(秒),延迟后开始播放")]
+ public float Delay = 0f;
+
+ [Tooltip("是否循环播放")]
+ public bool Loop = false;
+
+ [Tooltip("是否自动销毁")]
+ public bool AutoDestroy = true;
+
+ [Tooltip("销毁前的等待时间(秒)")]
+ public float DestroyDelay = 0.5f;
+
+ [Header("播放控制")]
+ [Tooltip("播放速度倍率")]
+ [Range(0.1f, 5f)]
+ public float Speed = 1f;
+
+ [Tooltip("缩放倍率")]
+ [Range(0.1f, 10f)]
+ public float Scale = 1f;
+
+ [Tooltip("透明度,1为不透明")]
+ [Range(0f, 1f)]
+ public float Alpha = 1f;
+
+ [Header("颜色控制")]
+ [Tooltip("是否启用颜色覆盖")]
+ public bool OverrideColor = false;
+
+ [Tooltip("覆盖颜色")]
+ public Color OverrideTint = Color.white;
+
+ [Header("对象池")]
+ [Tooltip("是否使用对象池")]
+ public bool UseObjectPool = true;
+
+ [Tooltip("对象池初始大小")]
+ public int PoolInitialSize = 5;
+
+ [Tooltip("对象池最大大小")]
+ public int PoolMaxSize = 20;
+
+ [Header("空间设置")]
+ [Tooltip("是否跟随目标")]
+ public bool AttachToTarget = false;
+
+ [Tooltip("跟随目标时的本地位置偏移")]
+ public Vector3 LocalPositionOffset = Vector3.zero;
+
+ [Tooltip("跟随目标时的本地旋转偏移")]
+ public Vector3 LocalRotationOffset = Vector3.zero;
+
+ [Header("层级设置")]
+ [Tooltip("排序层级")]
+ public int SortingLayer = 0;
+
+ [Tooltip("排序序号")]
+ public int SortingOrder = 0;
+
+ [Header("其他")]
+ [Tooltip("特效备注")]
+ public string FXDescription = "";
+
+ ///
+ /// 应用默认值
+ ///
+ public override void ApplyDefaults()
+ {
+ base.ApplyDefaults();
+ var defaultConfig = GameplayDefaultConfig.Instance;
+
+ if (Duration <= 0)
+ Duration = defaultConfig.DefaultFXDuration;
+
+ if (Speed <= 0)
+ Speed = defaultConfig.DefaultFXSpeed;
+
+ if (Scale <= 0)
+ Scale = defaultConfig.DefaultFXScale;
+ }
+
+ ///
+ /// 验证特效配置
+ ///
+ public bool Validate(out string errorMessage)
+ {
+ if (MappingID <= 0)
+ {
+ errorMessage = "FX MappingID必须大于0";
+ return false;
+ }
+
+ if (Duration < 0)
+ {
+ errorMessage = "Duration不能为负数";
+ return false;
+ }
+
+ if (Speed <= 0)
+ {
+ errorMessage = "Speed必须大于0";
+ return false;
+ }
+
+ if (Scale <= 0)
+ {
+ errorMessage = "Scale必须大于0";
+ return false;
+ }
+
+ errorMessage = null;
+ return true;
+ }
+ }
+
+ ///
+ /// 地图点位
+ ///
+ [Serializable]
+ public class MapPoint
+ {
+ [Tooltip("点位ID")]
+ public string PointID;
+
+ [Tooltip("坐标")]
+ public Vector3 Position;
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs.meta
new file mode 100644
index 0000000..7432f3c
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/LutConfigs.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9b0d4ae0d9ea94c47ac1dd82536d7126
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs b/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs
new file mode 100644
index 0000000..6e28285
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs
@@ -0,0 +1,426 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace GameplayEditor.Config
+{
+ ///
+ /// 富文本标签类型枚举
+ ///
+ public enum RichTextTag
+ {
+ Bold, // 粗体
+ Italic, // 斜体
+ Color, // 颜色
+ Size, // 字号
+ Material, // 材质
+ Quad, // 图片
+ Gradient, // 渐变
+ Underline, // 下划线
+ Strikethrough, // 删除线
+ Superscript, // 上标
+ Subscript, // 下标
+ Mark, // 标记高亮
+ Link, // 超链接
+ Font, // 字体
+ LineBreak, // 换行
+ Space, // 空格
+ Align, // 对齐
+ Cspace, // 字符间距
+ LineHeight, // 行高
+ Pos, // 位置偏移
+ Voffset, // 垂直偏移
+ NoBreak // 不换行
+ }
+
+ ///
+ /// 富文本标签定义
+ ///
+ [Serializable]
+ public class RichTextTagDefinition
+ {
+ [Tooltip("标签类型")]
+ public RichTextTag TagType;
+
+ [Tooltip("标签名称(如b、color等)")]
+ public string TagName;
+
+ [Tooltip("是否支持属性")]
+ public bool SupportsAttribute;
+
+ [Tooltip("属性名称(如color=#ff0000中的color)")]
+ public string AttributeName;
+
+ [Tooltip("属性格式示例")]
+ public string AttributeExample;
+
+ [Tooltip("是否允许嵌套")]
+ public bool AllowNesting;
+
+ [Tooltip("最大嵌套深度(0表示不允许嵌套)")]
+ public int MaxNestingDepth;
+
+ [Tooltip("是否需要闭合标签")]
+ public bool RequireClosingTag;
+
+ [Tooltip("标签描述")]
+ [TextArea(2, 3)]
+ public string Description;
+
+ [Tooltip("是否启用此标签")]
+ public bool Enabled = true;
+
+ public RichTextTagDefinition()
+ {
+ }
+
+ public RichTextTagDefinition(RichTextTag tagType, string tagName, bool supportsAttribute = false,
+ string attributeName = "", string attributeExample = "", bool allowNesting = true,
+ int maxNestingDepth = 3, bool requireClosingTag = true, string description = "")
+ {
+ TagType = tagType;
+ TagName = tagName;
+ SupportsAttribute = supportsAttribute;
+ AttributeName = attributeName;
+ AttributeExample = attributeExample;
+ AllowNesting = allowNesting;
+ MaxNestingDepth = maxNestingDepth;
+ RequireClosingTag = requireClosingTag;
+ Description = description;
+ Enabled = true;
+ }
+ }
+
+ ///
+ /// 颜色预设
+ ///
+ [Serializable]
+ public class RichTextColorPreset
+ {
+ [Tooltip("颜色名称")]
+ public string ColorName;
+
+ [Tooltip("颜色值")]
+ public Color Color;
+
+ [Tooltip("颜色代码(如#FF0000)")]
+ public string ColorCode;
+
+ public RichTextColorPreset(string name, Color color, string code)
+ {
+ ColorName = name;
+ Color = color;
+ ColorCode = code;
+ }
+ }
+
+ ///
+ /// 字号预设
+ ///
+ [Serializable]
+ public class RichTextSizePreset
+ {
+ [Tooltip("尺寸名称")]
+ public string SizeName;
+
+ [Tooltip("尺寸值")]
+ public int SizeValue;
+
+ public RichTextSizePreset(string name, int value)
+ {
+ SizeName = name;
+ SizeValue = value;
+ }
+ }
+
+ ///
+ /// 富文本配置
+ /// 定义项目支持的富文本标签和规范
+ ///
+ [CreateAssetMenu(fileName = "RichTextConfig", menuName = "Gameplay/Rich Text Config")]
+ public class RichTextConfig : ScriptableObject
+ {
+ [Header("全局设置")]
+ [Tooltip("是否启用富文本")]
+ public bool EnableRichText = true;
+
+ [Tooltip("最大嵌套深度")]
+ [Range(1, 10)]
+ public int MaxNestingDepth = 3;
+
+ [Tooltip("最大标签数量(防止性能问题)")]
+ [Range(10, 500)]
+ public int MaxTagCount = 100;
+
+ [Tooltip("验证时是否区分大小写")]
+ public bool CaseSensitive = false;
+
+ [Header("支持的标签")]
+ [Tooltip("启用的富文本标签列表")]
+ public List SupportedTags = new List();
+
+ [Header("颜色预设")]
+ [Tooltip("预定义的颜色列表")]
+ public List ColorPresets = new List();
+
+ [Header("字号预设")]
+ [Tooltip("预定义的字号列表")]
+ public List SizePresets = new List();
+
+ [Header("验证规则")]
+ [Tooltip("非法标签处理方式")]
+ public InvalidTagHandling InvalidTagHandling = InvalidTagHandling.Strip;
+
+ [Tooltip("是否自动修复常见的标签错误")]
+ public bool AutoFixCommonErrors = true;
+
+ [Tooltip("是否记录验证日志")]
+ public bool LogValidation = false;
+
+ // 缓存字典
+ [NonSerialized] private Dictionary _tagCache;
+ [NonSerialized] private Dictionary _colorCache;
+ [NonSerialized] private Dictionary _sizeCache;
+
+ private void OnEnable()
+ {
+ BuildCache();
+ }
+
+ ///
+ /// 初始化默认配置
+ ///
+ public void InitializeDefaults()
+ {
+ SupportedTags.Clear();
+
+ // 添加默认支持的标签
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Bold, "b", false, "", "", true, 5, true, "粗体文本"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Italic, "i", false, "", "", true, 5, true, "斜体文本"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Color, "color", true, "color", "#FF0000 或 red", false, 1, true, "文本颜色"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Size, "size", true, "size", "24 或 +2", false, 1, true, "字号大小"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Underline, "u", false, "", "", true, 3, true, "下划线"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Strikethrough, "s", false, "", "", true, 3, true, "删除线"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Superscript, "sup", false, "", "", false, 1, true, "上标"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Subscript, "sub", false, "", "", false, 1, true, "下标"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Mark, "mark", true, "color", "#FFFF00", false, 1, true, "高亮标记"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.Link, "a", true, "href", "https://example.com", false, 1, true, "超链接"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.LineBreak, "br", false, "", "", false, 0, false, "换行"));
+
+ SupportedTags.Add(new RichTextTagDefinition(
+ RichTextTag.NoBreak, "nobr", false, "", "", true, 1, true, "不换行区域"));
+
+ // 初始化颜色预设
+ ColorPresets.Clear();
+ ColorPresets.Add(new RichTextColorPreset("red", Color.red, "#FF0000"));
+ ColorPresets.Add(new RichTextColorPreset("green", Color.green, "#00FF00"));
+ ColorPresets.Add(new RichTextColorPreset("blue", Color.blue, "#0000FF"));
+ ColorPresets.Add(new RichTextColorPreset("yellow", Color.yellow, "#FFFF00"));
+ ColorPresets.Add(new RichTextColorPreset("white", Color.white, "#FFFFFF"));
+ ColorPresets.Add(new RichTextColorPreset("black", Color.black, "#000000"));
+ ColorPresets.Add(new RichTextColorPreset("cyan", Color.cyan, "#00FFFF"));
+ ColorPresets.Add(new RichTextColorPreset("magenta", Color.magenta, "#FF00FF"));
+ ColorPresets.Add(new RichTextColorPreset("orange", new Color(1f, 0.5f, 0f), "#FF8000"));
+ ColorPresets.Add(new RichTextColorPreset("gray", Color.gray, "#808080"));
+
+ // 初始化字号预设
+ SizePresets.Clear();
+ SizePresets.Add(new RichTextSizePreset("small", 12));
+ SizePresets.Add(new RichTextSizePreset("normal", 16));
+ SizePresets.Add(new RichTextSizePreset("medium", 20));
+ SizePresets.Add(new RichTextSizePreset("large", 24));
+ SizePresets.Add(new RichTextSizePreset("huge", 32));
+
+ BuildCache();
+ }
+
+ ///
+ /// 构建缓存
+ ///
+ private void BuildCache()
+ {
+ _tagCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var tag in SupportedTags)
+ {
+ if (tag.Enabled && !_tagCache.ContainsKey(tag.TagName))
+ {
+ _tagCache[tag.TagName] = tag;
+ }
+ }
+
+ _colorCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var color in ColorPresets)
+ {
+ if (!_colorCache.ContainsKey(color.ColorName))
+ {
+ _colorCache[color.ColorName] = color;
+ }
+ if (!_colorCache.ContainsKey(color.ColorCode))
+ {
+ _colorCache[color.ColorCode] = color;
+ }
+ }
+
+ _sizeCache = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ foreach (var size in SizePresets)
+ {
+ if (!_sizeCache.ContainsKey(size.SizeName))
+ {
+ _sizeCache[size.SizeName] = size;
+ }
+ _sizeCache[size.SizeValue.ToString()] = size;
+ }
+ }
+
+ ///
+ /// 检查标签是否支持
+ ///
+ public bool IsTagSupported(string tagName)
+ {
+ if (_tagCache == null) BuildCache();
+ return _tagCache.ContainsKey(tagName);
+ }
+
+ ///
+ /// 获取标签定义
+ ///
+ public RichTextTagDefinition GetTagDefinition(string tagName)
+ {
+ if (_tagCache == null) BuildCache();
+ _tagCache.TryGetValue(tagName, out var definition);
+ return definition;
+ }
+
+ ///
+ /// 获取标签定义(通过类型)
+ ///
+ public RichTextTagDefinition GetTagDefinition(RichTextTag tagType)
+ {
+ return SupportedTags.Find(t => t.TagType == tagType && t.Enabled);
+ }
+
+ ///
+ /// 检查颜色是否有效
+ ///
+ public bool IsValidColor(string colorValue)
+ {
+ if (_colorCache == null) BuildCache();
+
+ // 检查预设
+ if (_colorCache.ContainsKey(colorValue))
+ return true;
+
+ // 检查十六进制格式
+ if (colorValue.StartsWith("#") && (colorValue.Length == 7 || colorValue.Length == 9))
+ {
+ return System.Text.RegularExpressions.Regex.IsMatch(colorValue, "^#[0-9A-Fa-f]{6}$") ||
+ System.Text.RegularExpressions.Regex.IsMatch(colorValue, "^#[0-9A-Fa-f]{8}$");
+ }
+
+ return false;
+ }
+
+ ///
+ /// 获取颜色值
+ ///
+ public Color GetColor(string colorValue)
+ {
+ if (_colorCache == null) BuildCache();
+
+ if (_colorCache.TryGetValue(colorValue, out var preset))
+ return preset.Color;
+
+ // 解析十六进制
+ if (ColorUtility.TryParseHtmlString(colorValue, out var color))
+ return color;
+
+ return Color.white;
+ }
+
+ ///
+ /// 检查字号是否有效
+ ///
+ public bool IsValidSize(string sizeValue)
+ {
+ if (_sizeCache == null) BuildCache();
+
+ // 检查预设
+ if (_sizeCache.ContainsKey(sizeValue))
+ return true;
+
+ // 检查数字
+ if (int.TryParse(sizeValue, out var size))
+ return size > 0 && size <= 200;
+
+ // 检查相对值(如+2、-3)
+ if (sizeValue.StartsWith("+") || sizeValue.StartsWith("-"))
+ {
+ return int.TryParse(sizeValue, out _);
+ }
+
+ return false;
+ }
+
+ ///
+ /// 获取所有支持的标签名称
+ ///
+ public List GetSupportedTagNames()
+ {
+ if (_tagCache == null) BuildCache();
+ return new List(_tagCache.Keys);
+ }
+
+ ///
+ /// 启用/禁用标签
+ ///
+ public void SetTagEnabled(RichTextTag tagType, bool enabled)
+ {
+ var tag = SupportedTags.Find(t => t.TagType == tagType);
+ if (tag != null)
+ {
+ tag.Enabled = enabled;
+ BuildCache();
+ }
+ }
+
+ ///
+ /// 创建验证器
+ ///
+ public Utils.RichTextValidator CreateValidator()
+ {
+ return new Utils.RichTextValidator(this);
+ }
+ }
+
+ ///
+ /// 非法标签处理方式
+ ///
+ public enum InvalidTagHandling
+ {
+ Strip, // 移除非法标签
+ Escape, // 转义为普通文本
+ Warning, // 保留但警告
+ Error // 报错
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs.meta b/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs.meta
new file mode 100644
index 0000000..6abdf7d
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Config/RichTextConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 34f0a47fe2e1c4d4795b5bc22032d96c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Core.meta b/Assets/BP_Scripts/GameplayEditor/Core.meta
new file mode 100644
index 0000000..fd40b66
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Core.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a1a9b6f3bf5e07344b1a1a522f1c9b5a
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs b/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs
new file mode 100644
index 0000000..6f06f58
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using NodeCanvas.Framework;
+using ParadoxNotion;
+using UnityEngine;
+
+namespace GameplayEditor.Core
+{
+ ///
+ /// 所有活动节点类型的抽象基类
+ /// 每个子类对应一张配置表,有独立的字段结构和导出逻辑
+ ///
+ [Serializable]
+ public abstract class ActivityNodeBase : Node
+ {
+ /// 表格名称,用于路由到对应 CSV 文件
+ public abstract string TableName { get; }
+
+ /// 字段名(CSV 第1行)
+ public abstract string[] FieldNames { get; }
+
+ /// 字段类型(CSV 第2行)
+ public abstract string[] FieldTypes { get; }
+
+ /// 字段说明(CSV 第3行)
+ public abstract string[] FieldDocs { get; }
+
+ /// 将节点数据序列化为一行 CSV 数据
+ public abstract Dictionary ToExcelRow();
+
+ /// 从 CSV 一行数据反序列化到节点字段
+ public abstract void FromExcelRow(Dictionary data);
+
+ // 所有子类共用的 Node 抽象属性
+ public override int maxInConnections => -1;
+ public override int maxOutConnections => -1;
+ public override Type outConnectionType => typeof(GameplayConnection);
+ public override bool allowAsPrime => true;
+ public override bool canSelfConnect => false;
+ public override Alignment2x2 commentsAlignment => Alignment2x2.Bottom;
+ public override Alignment2x2 iconAlignment => Alignment2x2.Default;
+
+ protected override Status OnExecute(Component agent, IBlackboard blackboard)
+ => Status.Success;
+ }
+}
diff --git a/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs.meta b/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs.meta
new file mode 100644
index 0000000..51a0940
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Core/ActivityNodeBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7d04cbc7dec380c4a80a1cf00bcab601
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/BP_Scripts/GameplayEditor/Core/BTDebugger.cs b/Assets/BP_Scripts/GameplayEditor/Core/BTDebugger.cs
new file mode 100644
index 0000000..18a595d
--- /dev/null
+++ b/Assets/BP_Scripts/GameplayEditor/Core/BTDebugger.cs
@@ -0,0 +1,627 @@
+using System.Collections.Generic;
+using NodeCanvas.BehaviourTrees;
+using NodeCanvas.Framework;
+using UnityEngine;
+
+namespace GameplayEditor.Core
+{
+ ///
+ /// 行为树调试器
+ /// 支持断点、单步执行、变量查看
+ ///
+ public class BTDebugger : MonoBehaviour
+ {
+ [Header("调试设置")]
+ [Tooltip("启用调试")]
+ public bool EnableDebug = true;
+
+ [Tooltip("自动断点(异常时)")]
+ public bool AutoBreakOnError = true;
+
+ // 当前调试的行为树
+ private BehaviourTree _targetTree;
+ private BehaviourTreeOwner _treeOwner;
+
+ // 断点列表
+ private HashSet _breakpoints = new HashSet();
+ private Dictionary _conditionBreakpoints = new Dictionary();
+
+ // 调试状态
+ private bool _isPaused = false;
+ // private bool _stepMode = false; // 暂不使用
+ private Node _currentNode;
+ private string _lastStatus;
+
+ // 执行历史
+ private Queue _executionHistory = new Queue(100);
+
+ // 节点状态缓存
+ private Dictionary _nodeDebugInfo = new Dictionary();
+
+ // 调试事件
+ public System.Action OnBreakpointHit;
+ public System.Action OnNodeExecuted;
+ public System.Action OnDebugPaused;
+ public System.Action OnDebugResumed;
+
+ // 执行记录
+ public class ExecutionRecord
+ {
+ public float Time;
+ public string NodeName;
+ public string NodeType;
+ public Status Result;
+ public string Message;
+ }
+
+ // 节点调试信息
+ public class NodeDebugInfo
+ {
+ public string NodeId;
+ public string NodeName;
+ public int ExecutionCount;
+ public Status LastStatus;
+ public float LastExecutionTime;
+ public bool IsBreakpoint;
+ public string Condition;
+ }
+
+ private static BTDebugger _instance;
+ public static BTDebugger Instance
+ {
+ get
+ {
+ if (_instance == null)
+ {
+ _instance = FindObjectOfType();
+ }
+ return _instance;
+ }
+ }
+
+ private void Awake()
+ {
+ if (_instance != null && _instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+ _instance = this;
+ }
+
+ #region 目标设置
+
+ ///
+ /// 设置调试目标
+ ///
+ public void SetTarget(BehaviourTree tree)
+ {
+ _targetTree = tree;
+ _nodeDebugInfo.Clear();
+
+ // 扫描所有节点
+ if (tree?.primeNode != null)
+ {
+ ScanNode(tree.primeNode);
+ }
+
+ Debug.Log($"[BTDebugger] 设置调试目标: {tree?.name}");
+ }
+
+ ///
+ /// 递归扫描节点
+ ///
+ private void ScanNode(Node node)
+ {
+ if (node == null) return;
+
+ string nodeId = GetNodeId(node);
+ if (!_nodeDebugInfo.ContainsKey(nodeId))
+ {
+ _nodeDebugInfo[nodeId] = new NodeDebugInfo
+ {
+ NodeId = nodeId,
+ NodeName = node.name,
+ IsBreakpoint = _breakpoints.Contains(nodeId)
+ };
+ }
+
+ // 递归扫描子节点
+ if (node.outConnections != null)
+ {
+ foreach (var conn in node.outConnections)
+ {
+ ScanNode(conn.targetNode);
+ }
+ }
+ }
+
+ #endregion
+
+ #region 断点管理
+
+ ///
+ /// 添加断点
+ ///
+ public void AddBreakpoint(Node node)
+ {
+ string nodeId = GetNodeId(node);
+ _breakpoints.Add(nodeId);
+
+ if (_nodeDebugInfo.TryGetValue(nodeId, out var info))
+ {
+ info.IsBreakpoint = true;
+ }
+ }
+
+ ///
+ /// 移除断点
+ ///
+ public void RemoveBreakpoint(Node node)
+ {
+ string nodeId = GetNodeId(node);
+ _breakpoints.Remove(nodeId);
+ _conditionBreakpoints.Remove(nodeId);
+
+ if (_nodeDebugInfo.TryGetValue(nodeId, out var info))
+ {
+ info.IsBreakpoint = false;
+ info.Condition = null;
+ }
+ }
+
+ ///
+ /// 添加条件断点
+ ///
+ public void AddConditionBreakpoint(Node node, string condition)
+ {
+ string nodeId = GetNodeId(node);
+ _breakpoints.Add(nodeId);
+ _conditionBreakpoints[nodeId] = condition;
+
+ if (_nodeDebugInfo.TryGetValue(nodeId, out var info))
+ {
+ info.IsBreakpoint = true;
+ info.Condition = condition;
+ }
+ }
+
+ ///
+ /// 清除所有断点
+ ///
+ public void ClearBreakpoints()
+ {
+ _breakpoints.Clear();
+ _conditionBreakpoints.Clear();
+
+ foreach (var info in _nodeDebugInfo.Values)
+ {
+ info.IsBreakpoint = false;
+ info.Condition = null;
+ }
+ }
+
+ ///
+ /// 检查是否是断点
+ ///
+ public bool IsBreakpoint(Node node)
+ {
+ return _breakpoints.Contains(GetNodeId(node));
+ }
+
+ #endregion
+
+ #region 执行控制
+
+ ///
+ /// 暂停调试
+ ///
+ public void Pause()
+ {
+ _isPaused = true;
+ OnDebugPaused?.Invoke();
+ Debug.Log("[BTDebugger] 调试暂停");
+ }
+
+ ///
+ /// 继续执行
+ ///
+ public void Resume()
+ {
+ _isPaused = false;
+ // _stepMode = false;
+ OnDebugResumed?.Invoke();
+ Debug.Log("[BTDebugger] 调试继续");
+ }
+
+ ///
+ /// 单步执行
+ ///
+ public void Step()
+ {
+ _isPaused = false;
+ // _stepMode = true;
+
+ // 执行一帧后暂停
+ StartCoroutine(StepCoroutine());
+ }
+
+ private System.Collections.IEnumerator StepCoroutine()
+ {
+ yield return null; // 等待一帧
+ Pause();
+ }
+
+ ///
+ /// 检查是否可以执行
+ ///
+ public bool CanExecute(Node node)
+ {
+ if (!EnableDebug)
+ return true;
+
+ // 检查是否暂停
+ if (_isPaused)
+ return false;
+
+ // 检查断点
+ string nodeId = GetNodeId(node);
+ if (_breakpoints.Contains(nodeId))
+ {
+ // 检查条件
+ if (_conditionBreakpoints.TryGetValue(nodeId, out var condition))
+ {
+ if (EvaluateCondition(condition, node))
+ {
+ HitBreakpoint(node);
+ return false;
+ }
+ }
+ else
+ {
+ HitBreakpoint(node);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// 触发断点
+ ///
+ private void HitBreakpoint(Node node)
+ {
+ _currentNode = node;
+ _isPaused = true;
+
+ Debug.Log($"[BTDebugger] 触发断点: {node.name}");
+ OnBreakpointHit?.Invoke(node);
+ }
+
+ ///
+ /// 评估条件
+ /// 支持黑板变量、比较运算符、逻辑运算符
+ ///
+ private bool EvaluateCondition(string condition, Node node)
+ {
+ if (string.IsNullOrWhiteSpace(condition))
+ return true;
+
+ try
+ {
+ var blackboard = GetBlackboard(node);
+ var evaluator = new ConditionEvaluator(blackboard);
+ return evaluator.Evaluate(condition);
+ }
+ catch (System.Exception ex)
+ {
+ Debug.LogWarning($"[BTDebugger] 条件求值错误 '{condition}': {ex.Message}");
+ return true; // 求值失败时默认触发断点
+ }
+ }
+
+ ///
+ /// 获取节点关联的黑板
+ ///
+ private IBlackboard GetBlackboard(Node node)
+ {
+ if (node?.graph is BehaviourTree bt)
+ return bt.blackboard;
+ return _targetTree?.blackboard;
+ }
+
+ ///
+ /// 轻量级条件表达式求值器
+ ///
+ private class ConditionEvaluator
+ {
+ private IBlackboard _blackboard;
+
+ public ConditionEvaluator(IBlackboard blackboard)
+ {
+ _blackboard = blackboard;
+ }
+
+ public bool Evaluate(string expression)
+ {
+ expression = expression.Trim();
+
+ // 处理括号(仅支持最外层一对括号)
+ if (expression.StartsWith("(") && expression.EndsWith(")"))
+ {
+ expression = expression.Substring(1, expression.Length - 2).Trim();
+ }
+
+ // 处理 ||
+ var orParts = SplitTopLevel(expression, "||");
+ if (orParts.Count > 1)
+ {
+ foreach (var part in orParts)
+ {
+ if (Evaluate(part.Trim())) return true;
+ }
+ return false;
+ }
+
+ // 处理 &&
+ var andParts = SplitTopLevel(expression, "&&");
+ if (andParts.Count > 1)
+ {
+ foreach (var part in andParts)
+ {
+ if (!Evaluate(part.Trim())) return false;
+ }
+ return true;
+ }
+
+ // 处理 !
+ if (expression.StartsWith("!"))
+ {
+ return !Evaluate(expression.Substring(1).Trim());
+ }
+
+ // 处理比较
+ return EvaluateComparison(expression);
+ }
+
+ private bool EvaluateComparison(string expression)
+ {
+ var ops = new[] { "==", "!=", "<=", ">=", "<", ">" };
+ foreach (var op in ops)
+ {
+ int idx = FindTopLevelOperator(expression, op);
+ if (idx >= 0)
+ {
+ var left = expression.Substring(0, idx).Trim();
+ var right = expression.Substring(idx + op.Length).Trim();
+ return Compare(left, op, right);
+ }
+ }
+
+ // 没有比较运算符,视为布尔值
+ return ToBool(ResolveValue(expression));
+ }
+
+ private bool Compare(string leftExpr, string op, string rightExpr)
+ {
+ var left = ResolveValue(leftExpr);
+ var right = ResolveValue(rightExpr);
+
+ // 尝试数值比较
+ if (double.TryParse(left, System.Globalization.NumberStyles.Any,
+ System.Globalization.CultureInfo.InvariantCulture, out var leftNum) &&
+ double.TryParse(right, System.Globalization.NumberStyles.Any,
+ System.Globalization.CultureInfo.InvariantCulture, out var rightNum))
+ {
+ return op switch
+ {
+ "==" => Mathf.Approximately((float)leftNum, (float)rightNum),
+ "!=" => !Mathf.Approximately((float)leftNum, (float)rightNum),
+ "<" => leftNum < rightNum,
+ "<=" => leftNum <= rightNum,
+ ">" => leftNum > rightNum,
+ ">=" => leftNum >= rightNum,
+ _ => false
+ };
+ }
+
+ // 字符串/其他比较
+ return op switch
+ {
+ "==" => string.Equals(left, right, System.StringComparison.OrdinalIgnoreCase),
+ "!=" => !string.Equals(left, right, System.StringComparison.OrdinalIgnoreCase),
+ _ => false
+ };
+ }
+
+ private string ResolveValue(string expr)
+ {
+ expr = expr.Trim();
+
+ // 字符串字面量
+ if ((expr.StartsWith("\"") && expr.EndsWith("\"")) ||
+ (expr.StartsWith("'") && expr.EndsWith("'")))
+ {
+ return expr.Substring(1, expr.Length - 2);
+ }
+
+ // 布尔字面量
+ if (bool.TryParse(expr, out _)) return expr;
+
+ // 数字字面量
+ if (double.TryParse(expr, System.Globalization.NumberStyles.Any,
+ System.Globalization.CultureInfo.InvariantCulture, out _)) return expr;
+
+ // 尝试从黑板获取变量
+ if (_blackboard != null && _blackboard.variables != null &&
+ _blackboard.variables.ContainsKey(expr))
+ {
+ var value = _blackboard.GetVariableValue