diff --git a/Assets/ActivityGraph.asset b/Assets/ActivityGraph.asset new file mode 100644 index 0000000..34df036 --- /dev/null +++ b/Assets/ActivityGraph.asset @@ -0,0 +1,24 @@ +%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: b8409462a7f3e4d46a886d68e7626d94, type: 3} + m_Name: ActivityGraph + m_EditorClassIdentifier: + _serializedGraph: '{"type":"GameplayEditor.Core.GameplayGraph","nodes":[{"levelID":1,"nodeID":1,"nodeLayer":1,"eventTypeGroup":"1","priorityWeight":"1","eventMappingID":1,"_position":{"x":660.0},"$type":"GameplayEditor.Core.GameplayNode"}],"connections":[],"canvasGroups":[],"localBlackboard":{"_variables":{}}}' + _objectReferences: [] + _graphSource: + _version: 3.33 + _category: + _comments: + _translation: {x: -281, y: 182} + _zoomFactor: 1 + _haltSerialization: 0 + _externalSerializationFile: {fileID: 0} diff --git a/Assets/ActivityGraph.asset.meta b/Assets/ActivityGraph.asset.meta new file mode 100644 index 0000000..8763454 --- /dev/null +++ b/Assets/ActivityGraph.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a0bc0a969202f49498e8d63e4da11d62 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BP_Scripts/BT_test.asset b/Assets/BP_Scripts/BT_test.asset index d526cb6..be471f1 100644 --- a/Assets/BP_Scripts/BT_test.asset +++ b/Assets/BP_Scripts/BT_test.asset @@ -13,8 +13,8 @@ MonoBehaviour: m_Name: BT_test m_EditorClassIdentifier: _serializedGraph: '{"type":"NodeCanvas.BehaviourTrees.BehaviourTree","nodes":[{"dynamic":true,"_tag":"\u521d\u59cb\u5316","_position":{"x":560.0,"y":280.0},"_comment":"\u8fd9\u662f\u5173\u5361\u521d\u59cb\u5316\uff0c\u5173\u5361\u5f00\u59cb\u5fc5\u6709","$type":"NodeCanvas.BehaviourTrees.Sequencer","$id":"0"},{"dynamic":true,"_position":{"x":420.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.Sequencer","$id":"1"},{"_action":{"actions":[{"dictionary":{"_name":"Dictionary","_targetVariableID":"34aa1bb3-2cc8-4d06-9fb0-9d751d56ba17"},"key":{"_value":"1"},"value":{},"$type":"NodeCanvas.Tasks.Actions.AddElementToDictionary`1[[UnityEngine.Camera, - UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},{"parent":{},"clonePosition":{},"cloneRotation":{},"saveCloneAs":{"_name":""},"$type":"NodeCanvas.Tasks.Actions.InstantiateGameObject"}],"$type":"NodeCanvas.Framework.ActionList"},"_position":{"x":120.0,"y":540.0},"_comment":"\u6dfb\u52a0\u76f8\u673a\u5230\u5217\u8868","$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"2"},{"_action":{"dictionary":{"_name":"Dictionary","_targetVariableID":"34aa1bb3-2cc8-4d06-9fb0-9d751d56ba17"},"key":{"_value":"1"},"saveAs":{"_name":"Main","_targetVariableID":"dc6ba0ab-ee9b-4bac-b4a9-8084a2f509b0"},"$type":"NodeCanvas.Tasks.Actions.GetDictionaryElement`1[[UnityEngine.Camera, - UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"_position":{"x":320.0,"y":540.0},"_comment":"\u4ece\u5217\u8868\u62ff\u76f8\u673a","$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"3"},{"dynamic":true,"_position":{"x":560.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.Selector","$id":"4"},{"_position":{"x":700.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.Parallel","$id":"5"},{"_position":{"x":700.0,"y":540.0},"$type":"NodeCanvas.BehaviourTrees.ConditionNode"},{"dynamic":true,"desires":[{"considerations":[]},{"considerations":[]},{"considerations":[]}],"_position":{"x":1040.0,"y":280.0},"$type":"NodeCanvas.BehaviourTrees.PrioritySelector","$version":1,"$id":"7"},{"_position":{"x":900.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"8"},{"_position":{"x":1040.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"9"},{"_position":{"x":1180.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"10"},{"_position":{"x":560.0,"y":540.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode"},{"_condition":{"conditions":[{"valueA":{"_name":""},"valueB":{},"$type":"NodeCanvas.Tasks.Conditions.CheckInt"},{"valueA":{"_name":"Dictionary","_targetVariableID":"34aa1bb3-2cc8-4d06-9fb0-9d751d56ba17"},"valueB":{"_value":{"":null}},"$type":"NodeCanvas.Tasks.Conditions.CheckVariable`1[[System.Collections.Generic.Dictionary`2[[System.String, + UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},{"parent":{},"clonePosition":{},"cloneRotation":{},"saveCloneAs":{"_name":""},"$type":"NodeCanvas.Tasks.Actions.InstantiateGameObject"}],"$type":"NodeCanvas.Framework.ActionList"},"_position":{"x":31.53383,"y":683.4225},"_comment":"\u6dfb\u52a0\u76f8\u673a\u5230\u5217\u8868","$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"2"},{"_action":{"dictionary":{"_name":"Dictionary","_targetVariableID":"34aa1bb3-2cc8-4d06-9fb0-9d751d56ba17"},"key":{"_value":"1"},"saveAs":{"_name":"Main","_targetVariableID":"dc6ba0ab-ee9b-4bac-b4a9-8084a2f509b0"},"$type":"NodeCanvas.Tasks.Actions.GetDictionaryElement`1[[UnityEngine.Camera, + UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]"},"_position":{"x":388.3602,"y":604.3391},"_comment":"\u4ece\u5217\u8868\u62ff\u76f8\u673a","$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"3"},{"dynamic":true,"_position":{"x":560.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.Selector","$id":"4"},{"_position":{"x":700.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.Parallel","$id":"5"},{"_position":{"x":700.0,"y":540.0},"$type":"NodeCanvas.BehaviourTrees.ConditionNode"},{"dynamic":true,"desires":[{"considerations":[]},{"considerations":[]},{"considerations":[]}],"_position":{"x":1040.0,"y":280.0},"$type":"NodeCanvas.BehaviourTrees.PrioritySelector","$version":1,"$id":"7"},{"_position":{"x":900.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"8"},{"_position":{"x":1040.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"9"},{"_position":{"x":1180.0,"y":420.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode","$id":"10"},{"_position":{"x":560.0,"y":540.0},"$type":"NodeCanvas.BehaviourTrees.ActionNode"},{"_condition":{"conditions":[{"valueA":{"_name":""},"valueB":{},"$type":"NodeCanvas.Tasks.Conditions.CheckInt"},{"valueA":{"_name":"Dictionary","_targetVariableID":"34aa1bb3-2cc8-4d06-9fb0-9d751d56ba17"},"valueB":{"_value":{"":null}},"$type":"NodeCanvas.Tasks.Conditions.CheckVariable`1[[System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[UnityEngine.Camera, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}],"$type":"NodeCanvas.Framework.ConditionList"},"_position":{"x":480.0,"y":760.0},"$type":"NodeCanvas.BehaviourTrees.ConditionNode"}],"connections":[{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"1"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"0"},"_targetNode":{"$ref":"4"},"$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":"1"},"_targetNode":{"$ref":"3"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"7","$version":1},"_targetNode":{"$ref":"8"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"7","$version":1},"_targetNode":{"$ref":"9"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"},{"_sourceNode":{"$ref":"7","$version":1},"_targetNode":{"$ref":"10"},"$type":"NodeCanvas.BehaviourTrees.BTConnection"}],"canvasGroups":[],"localBlackboard":{"_variables":{"myBoolean":{"_name":"myBoolean","_id":"597a5d7c-3bd9-4688-839b-d14d91ff6172","$type":"NodeCanvas.Framework.Variable`1[[System.Boolean, @@ -28,7 +28,7 @@ MonoBehaviour: _version: 3.33 _category: _comments: - _translation: {x: -8, y: 216} - _zoomFactor: 1 + _translation: {x: 275, y: -62} + _zoomFactor: 0.59610087 _haltSerialization: 0 _externalSerializationFile: {fileID: 0} 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/ActivityStageNode.cs.meta b/Assets/BP_Scripts/GameplayEditor/Core/ActivityStageNode.cs.meta new file mode 100644 index 0000000..2cdc2a0 --- /dev/null +++ b/Assets/BP_Scripts/GameplayEditor/Core/ActivityStageNode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e498e185f67a544479d637626a1f98a7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BP_Scripts/GameplayEditor/Core/BehaviourDocNodes.cs.meta b/Assets/BP_Scripts/GameplayEditor/Core/BehaviourDocNodes.cs.meta new file mode 100644 index 0000000..8328052 --- /dev/null +++ b/Assets/BP_Scripts/GameplayEditor/Core/BehaviourDocNodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f68ed535e8fe19d43ba9ae227e07c76d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BP_Scripts/GameplayEditor/Core/BehaviourGraph.cs b/Assets/BP_Scripts/GameplayEditor/Core/BehaviourGraph.cs new file mode 100644 index 0000000..245b610 --- /dev/null +++ b/Assets/BP_Scripts/GameplayEditor/Core/BehaviourGraph.cs @@ -0,0 +1,49 @@ +using NodeCanvas.BehaviourTrees; +using ParadoxNotion; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace GameplayEditor.Core +{ + /// + /// 行为树 Graph - 支持可执行的行为逻辑 + 表格编辑 + /// 继承 BehaviourTree,额外支持 ActivityNodeBase 节点的导出/导入 + /// + [System.Serializable] + [CreateAssetMenu(menuName = "NodeCanvas/Behaviour Graph")] + public class BehaviourGraph : BehaviourTree + { + public void ExportToExcel(string path) + { + Export.ExcelExporter.Export(this, path); + } + + public void ImportFromExcel(string path) + { + Export.ExcelImporter.Import(path, this); + } + + public List GetNodesByType() where T : Node + { + return allNodes.OfType().ToList(); + } + + public List GetAllActivityNodes() + { + return allNodes.OfType().ToList(); + } + + public Dictionary> GetNodesByTableName() + { + var result = new Dictionary>(); + foreach (var node in GetAllActivityNodes()) + { + if (!result.ContainsKey(node.TableName)) + result[node.TableName] = new List(); + result[node.TableName].Add(node); + } + return result; + } + } +} diff --git a/Assets/BP_Scripts/GameplayEditor/Core/DataNodes.cs.meta b/Assets/BP_Scripts/GameplayEditor/Core/DataNodes.cs.meta new file mode 100644 index 0000000..0a3c56e --- /dev/null +++ b/Assets/BP_Scripts/GameplayEditor/Core/DataNodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25cde69a219386b44bccc0c08271bdc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BP_Scripts/GameplayEditor/Core/LutNodes.cs.meta b/Assets/BP_Scripts/GameplayEditor/Core/LutNodes.cs.meta new file mode 100644 index 0000000..3b9ab6c --- /dev/null +++ b/Assets/BP_Scripts/GameplayEditor/Core/LutNodes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 758081b0b34f2e942b68f9fa35170ba8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/BP_Scripts/GameplayEditor/Export/ExcelExporter.cs b/Assets/BP_Scripts/GameplayEditor/Export/ExcelExporter.cs index f0bdeda..28584d6 100644 --- a/Assets/BP_Scripts/GameplayEditor/Export/ExcelExporter.cs +++ b/Assets/BP_Scripts/GameplayEditor/Export/ExcelExporter.cs @@ -3,28 +3,41 @@ using System.Collections.Generic; using System.IO; using System.Linq; using GameplayEditor.Core; +using NodeCanvas.Framework; using UnityEngine; namespace GameplayEditor.Export { public static class ExcelExporter { - public static void Export(GameplayGraph graph, string basePath) + public static void Export(Graph graph, string basePath) { - var nodesByTable = graph.GetNodesByTableName(); + var activityNodes = graph.allNodes.OfType().ToList(); + if (activityNodes.Count == 0) + { + Debug.LogWarning("No ActivityNodeBase nodes found in graph"); + return; + } + + var nodesByTable = new Dictionary>(); + foreach (var node in activityNodes) + { + if (!nodesByTable.ContainsKey(node.TableName)) + nodesByTable[node.TableName] = new List(); + nodesByTable[node.TableName].Add(node); + } foreach (var kvp in nodesByTable) { var tableName = kvp.Key; var nodes = kvp.Value; - if (nodes.Count == 0) continue; var excelPath = Path.Combine(Path.GetDirectoryName(basePath), $"{tableName}.csv"); ExportTable(nodes, excelPath); } - Debug.Log($"Exported {nodesByTable.Count} tables from {basePath}"); + Debug.Log($"Exported {nodesByTable.Count} tables from graph"); } private static void ExportTable(List nodes, string filePath) diff --git a/Assets/BP_Scripts/GameplayEditor/Export/ExcelImporter.cs b/Assets/BP_Scripts/GameplayEditor/Export/ExcelImporter.cs index 5b4fffc..cd3f064 100644 --- a/Assets/BP_Scripts/GameplayEditor/Export/ExcelImporter.cs +++ b/Assets/BP_Scripts/GameplayEditor/Export/ExcelImporter.cs @@ -25,13 +25,13 @@ namespace GameplayEditor.Export { "DialogInfo", () => new DialogInfoNode() }, }; - public static void Import(string filePath, GameplayGraph graph) + public static void Import(string filePath, Graph graph) { var tableName = Path.GetFileNameWithoutExtension(filePath); ImportTable(filePath, graph, tableName); } - public static void ImportTable(string filePath, GameplayGraph graph, string tableName) + public static void ImportTable(string filePath, Graph graph, string tableName) { if (!File.Exists(filePath)) { diff --git a/Assets/BP_Scripts/GameplayEditor/GameplayEditorWindow.cs b/Assets/BP_Scripts/GameplayEditor/GameplayEditorWindow.cs index d23846a..9010a1c 100644 --- a/Assets/BP_Scripts/GameplayEditor/GameplayEditorWindow.cs +++ b/Assets/BP_Scripts/GameplayEditor/GameplayEditorWindow.cs @@ -68,15 +68,23 @@ namespace GameplayEditor if (currentGraph == null) { - EditorGUILayout.HelpBox("请选择一个 Graph", MessageType.Info); + EditorGUILayout.HelpBox("请选择一个 Graph(GameplayGraph 或 BehaviourGraph)", MessageType.Info); return; } canvasScroll = EditorGUILayout.BeginScrollView(canvasScroll); - var nodesByTable = (currentGraph as Core.GameplayGraph)?.GetNodesByTableName(); - if (nodesByTable != null) + var activityNodes = currentGraph.allNodes.OfType().ToList(); + if (activityNodes.Count > 0) { + var nodesByTable = new Dictionary>(); + foreach (var node in activityNodes) + { + if (!nodesByTable.ContainsKey(node.TableName)) + nodesByTable[node.TableName] = new List(); + nodesByTable[node.TableName].Add(node); + } + foreach (var kvp in nodesByTable.OrderBy(x => x.Key)) { EditorGUILayout.LabelField($"{kvp.Key} ({kvp.Value.Count})", EditorStyles.boldLabel); @@ -89,6 +97,10 @@ namespace GameplayEditor } } } + else + { + EditorGUILayout.HelpBox("Canvas 中没有 ActivityNodeBase 节点", MessageType.Info); + } EditorGUILayout.EndScrollView(); } @@ -125,7 +137,7 @@ namespace GameplayEditor } var basePath = "Assets/玩法编辑器.csv"; - (currentGraph as Core.GameplayGraph)?.ExportToExcel(basePath); + Export.ExcelExporter.Export(currentGraph, basePath); EditorUtility.DisplayDialog("成功", $"已导出所有表到 Assets/ 目录", "确定"); } @@ -142,7 +154,7 @@ namespace GameplayEditor { var filePath = System.IO.Path.Combine(basePath, $"{tableName}.csv"); if (System.IO.File.Exists(filePath)) - Export.ExcelImporter.ImportTable(filePath, currentGraph as Core.GameplayGraph, tableName); + Export.ExcelImporter.ImportTable(filePath, currentGraph, tableName); } EditorUtility.DisplayDialog("成功", "已导入所有 CSV 表", "确定"); diff --git a/Assets/BehaviourTree.csv b/Assets/BehaviourTree.csv new file mode 100644 index 0000000..ca50c0a --- /dev/null +++ b/Assets/BehaviourTree.csv @@ -0,0 +1,4 @@ +LevelID,NodeID,NodeLayer,EventTypeGroup,Priority,EventMappingID,InteractVisible,NodeState,ParentNodeID +int,int,int,string,string,int,bool,bool,int +关卡编号ID,节点ID(唯一),节点层级,节点事件类型组(|分隔),优先级权重(|分隔),存储事件ID映射,交互是否可见,节点是否可进入,父节点ID(-1表示根节点) +1,1,1,1,1,1,True,True,-1 diff --git a/Assets/BehaviourTree.csv.meta b/Assets/BehaviourTree.csv.meta new file mode 100644 index 0000000..e58fa66 --- /dev/null +++ b/Assets/BehaviourTree.csv.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 89e2f58b96cb09245a29a89b64f4239d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/New Graph Data Schema.asset b/Assets/New Graph Data Schema.asset new file mode 100644 index 0000000..aff94cc --- /dev/null +++ b/Assets/New Graph Data Schema.asset @@ -0,0 +1,26 @@ +%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: fd283cf39e183a04d814b09c20b2a810, type: 3} + m_Name: New Graph Data Schema + m_EditorClassIdentifier: + sheetName: "\u6280\u80FD" + fields: + - fieldName: ID + excelColumnName: NodeID + fieldType: 1 + - fieldName: name + excelColumnName: NodeName + fieldType: 0 + - fieldName: comment + excelColumnName: "\u63CF\u8FF0" + fieldType: 0 + nodeTypeFilter: [] diff --git a/Assets/New Graph Data Schema.asset.meta b/Assets/New Graph Data Schema.asset.meta new file mode 100644 index 0000000..f1068bd --- /dev/null +++ b/Assets/New Graph Data Schema.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0de0e36ee42ae394a8ac5b18dc6303ed +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 1aadda9..ee0c0c6 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -382,6 +382,103 @@ PlayerSettings: m_Height: 36 m_Kind: 0 m_SubKind: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store m_BuildTargetBatching: - m_BuildTarget: WebGL m_StaticBatching: 1 diff --git a/设计文档/玩法编辑器使用手册.md b/设计文档/玩法编辑器使用手册.md index 331275d..cee30ae 100644 --- a/设计文档/玩法编辑器使用手册.md +++ b/设计文档/玩法编辑器使用手册.md @@ -177,92 +177,218 @@ UI 资源。填写需要使用的关卡和映射 ID,可以填 String。 ``` Assets/BP_Scripts/GameplayEditor/ ├── Core/ -│ ├── GameplayGraph.cs ← 自定义 Graph 类型(Asset 本体) -│ ├── GameplayNode.cs ← 节点数据模型(含9个业务字段) -│ └── GameplayConnection.cs ← 节点连线类型 +│ ├── ActivityNodeBase.cs ← 所有节点的抽象基类 +│ ├── GameplayNode.cs ← 行为树逻辑节点(工作表2) +│ ├── ActivityStageNode.cs ← 关卡配置节点(工作表1) +│ ├── BehaviourDocNodes.cs ← 节点说明 + 组合节点(工作表3/4) +│ ├── LutNodes.cs ← UI/Camera/FX资源映射(3.2/3.3/3.4) +│ ├── DataNodes.cs ← 地图/对话配置节点(3.5/3.6/3.7) +│ ├── GameplayGraph.cs ← 自定义 Graph 类型 +│ └── GameplayConnection.cs ← 节点连线类型 ├── Export/ -│ ├── ExcelExporter.cs ← Canvas → CSV 导出逻辑 -│ └── ExcelImporter.cs ← CSV → Canvas 导入逻辑 -├── GameplayEditorWindow.cs ← 编辑器窗口 UI -├── GraphDataSchema.cs ← 字段映射配置(ScriptableObject) -├── GraphExcelSyncManager.cs ← 运行时同步组件 -└── ExcelDataProvider.cs ← CSV 读写基础工具 +│ ├── ExcelExporter.cs ← Canvas → 多个 CSV 导出 +│ └── ExcelImporter.cs ← 多个 CSV → Canvas 导入 +├── GameplayEditorWindow.cs ← 编辑器窗口 UI(支持表格选择) +├── GraphDataSchema.cs ← 字段映射配置(ScriptableObject) +├── GraphExcelSyncManager.cs ← 运行时同步组件 +└── ExcelDataProvider.cs ← CSV 读写基础工具 ``` --- -### 5.2 快速开始 +### 5.2 快速开始(完整流程) **第一步:创建 GameplayGraph Asset** -在 Project 窗口中右键 → **Create → NodeCanvas → Gameplay Graph**,命名为关卡名,例如 `Level01Graph`。 +在 Project 窗口中右键 → **Create → NodeCanvas → Gameplay Graph**,命名为 `ActivityGraph`。 -**第二步:创建 GraphDataSchema 配置** - -右键 → **Create → GameplayEditor → GraphDataSchema**,命名为 `Level01Schema`,在 Inspector 中可查看字段映射关系(用于编辑器窗口右侧展示)。 - -**第三步:打开玩法编辑器窗口** +**第二步:打开玩法编辑器窗口** 菜单栏 → **Window → Gameplay Editor** ``` ┌──────────────────────────────────────────────────────────────┐ -│ [Graph 拖槽] [Schema 拖槽] [导出 Canvas→CSV] [导入 CSV→Canvas] │ ← 工具栏 +│ [Graph 拖槽] [Schema 拖槽] [导出所有表→CSV] [导入CSV→Canvas] │ ← 工具栏 ├─────────────────────┬────────────────────────────────────────┤ -│ Canvas 可视化编辑 │ CSV 字段映射 │ -│ 显示所有节点列表 │ 显示 Schema 中配置的字段映射关系 │ +│ Canvas 节点列表 │ 表格选择 + 字段映射 │ +│ 按表格分组显示所有 │ 下拉菜单选择要查看的表格 │ +│ 节点(10种类型) │ 显示该表的字段结构 │ └─────────────────────┴────────────────────────────────────────┘ ``` -将 `Level01Graph` 和 `Level01Schema` 分别拖入工具栏对应拖槽。 +将 `ActivityGraph` 拖入工具栏左侧的 **Graph 拖槽**。 -**第四步:在 Canvas 中编辑节点** +**第三步:在 Canvas 中添加节点** -双击 `Level01Graph.asset` 打开 NodeCanvas 编辑器,右键画布 → **Add Node → GameplayEditor → GameplayNode** 添加节点。 +双击 `ActivityGraph.asset` 打开 NodeCanvas 编辑器,右键画布 → **Add Node → GameplayEditor → [节点类型]** -点击节点在 Inspector 中编辑以下字段: +支持的节点类型(10种): -| 字段名 | 类型 | 说明 | -|-------------------|--------|---------------------------------------------------| -| Level ID | int | 关卡编号 ID(对应 ActivityStageID) | -| Node ID | int | 节点唯一 ID(**同一 Graph 内不能重复**) | -| Node Layer | int | 节点层级(0 为顶层根节点) | -| Event Type Group | string | 事件类型组,多个用 `\|` 分隔,如 `1\|2\|3` | -| Priority | string | 优先级权重,多个用 `\|` 分隔,如 `10\|20` | -| Event Mapping ID | int | 事件 ID 映射值(对应各 Lut 表中的映射 ID) | -| Interact Visible | bool | 交互时是否显示该节点 | -| Node State | bool | 节点是否处于可进入状态 | -| Parent Node ID | int | 父节点的 Node ID,**-1 表示根节点(无父节点)** | +| 节点类型 | 对应表格 | 用途 | +|---------|---------|------| +| GameplayNode | BehaviourTree | 行为树逻辑节点 | +| ActivityStageNode | ActivityStage | 关卡配置(场景、地图、相机) | +| BehaviourNodeDocNode | BehaviourNodeDoc | 节点类型说明(注释用) | +| ActivityGroupNode | ActivityGroup | 组合节点说明 | +| UiLutNode | ActivityUiLut | UI资源映射 | +| CameraLutNode | ActivityCameraLut | 相机资源映射 | +| FXLutNode | ActivityFXLut | 特效资源映射 | +| MapInfoNode | ActivityMapInfoLut | 地图打点(支持8个点) | +| DialogByStageNode | ActivityDialogInfoByStage | 对话索引 | +| DialogInfoNode | DialogInfo | 对话详细配置 | -拖拽节点端口建立连线,连线关系在导入时会根据 `ParentNodeID` 字段自动重建。 +**第四步:编辑节点字段** -**第五步:导出 Canvas → CSV** +点击节点在 Inspector 中填写对应字段。每种节点的字段不同,参考下方字段说明。 -工具栏点击 **"导出 Canvas → CSV"**,输出到 `Assets/玩法编辑器.csv`。 +**第五步:导出所有表 → CSV** -生成格式(行1=字段名,行2=类型,行3=说明,行4起=节点数据): +工具栏点击 **"导出所有表 → CSV"**,自动生成 10 个 CSV 文件到 `Assets/` 目录: + +``` +Assets/ +├── BehaviourTree.csv +├── ActivityStage.csv +├── BehaviourNodeDoc.csv +├── ActivityGroup.csv +├── ActivityUiLut.csv +├── ActivityCameraLut.csv +├── ActivityFXLut.csv +├── ActivityMapInfoLut.csv +├── ActivityDialogInfoByStage.csv +└── DialogInfo.csv +``` + +每个 CSV 格式(行1=字段名,行2=类型,行3=说明,行4起=数据): ``` LevelID,NodeID,NodeLayer,EventTypeGroup,Priority,EventMappingID,InteractVisible,NodeState,ParentNodeID int,int,int,string,string,int,bool,bool,int -关卡编号ID,节点ID(唯一),节点层级,节点事件类型组(|分隔),优先级权重(|分隔),存储事件ID映射,交互是否可见,节点是否可进入,父节点ID(-1表示无父节点) +关卡编号ID,节点ID(唯一),节点层级,节点事件类型组(|分隔),优先级权重(|分隔),存储事件ID映射,交互是否可见,节点是否可进入,父节点ID(-1表示根节点) 1,101,0,1|2,10|20,5001,True,True,-1 1,102,1,3,30,5002,True,True,101 ``` -> 生成的 `.csv` 可直接用 Excel 打开(打开时选 UTF-8 编码)。 +**第六步:用 Excel 批量编辑** -**第六步:批量编辑后导入 CSV → Canvas** +1. 用 Excel 打开任意 CSV(打开时选 UTF-8 编码) +2. 修改数据(**不要删除前3行表头**) +3. 保存文件 -1. 用 Excel 修改 `Assets/玩法编辑器.csv`(**不要删除前3行表头**) -2. 保存文件 -3. 回到 Unity,点击 **"导入 CSV → Canvas"** +**第七步:导入 CSV → Canvas** -> **注意:导入会清空当前 Canvas 中的所有节点后重建。** 导入后自动根据 `ParentNodeID` 重建连线,`-1` 表示根节点不连线。 +回到 Unity,点击 **"导入 CSV → Canvas"** + +> **注意:导入会清空当前 Canvas 中的所有节点后重建。** 导入后自动根据 `ParentNodeID` 重建 GameplayNode 连线。 --- -### 5.3 嵌套节点示例 +### 5.3 各表字段说明 + +#### 工作表1:ActivityStage(关卡配置) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ActivityStageID | int | 行为树ID(全局唯一) | +| Doc | string | 关卡备注 | +| SceneName | string | 场景名称 | +| MapInfo | string | 地图打点组ID | +| CloseLoadingDelaySeconds | float | 关闭加载画面延迟(秒) | +| CameraID | int | 默认相机ID | + +#### 工作表2:BehaviourTree(行为树逻辑) + +| 字段 | 类型 | 说明 | +|------|------|------| +| LevelID | int | 关卡编号ID | +| NodeID | int | 节点唯一ID(**同一Graph内不重复**) | +| NodeLayer | int | 节点层级(0=顶层) | +| EventTypeGroup | string | 事件类型组(`\|`分隔,如 `1\|2\|3`) | +| Priority | string | 优先级权重(`\|`分隔,如 `10\|20`) | +| EventMappingID | int | 事件ID映射值 | +| InteractVisible | bool | 交互时是否显示 | +| NodeState | bool | 节点是否可进入 | +| ParentNodeID | int | 父节点ID(**-1=根节点**) | + +#### 工作表3:BehaviourNodeDoc(节点说明) + +| 字段 | 类型 | 说明 | +|------|------|------| +| NodeType | string | 节点类型(如 DoGetTime) | +| Description | string | 节点描述 | +| ChineseMapping | string | 中文节点映射 | +| Param1~5 | string | 参数1~5说明 | + +#### 工作表4:ActivityGroup(组合节点) + +| 字段 | 类型 | 说明 | +|------|------|------| +| GroupTreeID | string | 组合树ID | +| GroupTreeType | string | 组合树类型/节点名称 | +| Comment | string | 注释 | + +#### 3.2:ActivityUiLut(UI资源映射) + +| 字段 | 类型 | 说明 | +|------|------|------| +| StageID | int | 关卡ID | +| UIMappingID | int | UI映射ID | +| PrefabPath | string | Prefab路径 | + +#### 3.3:ActivityCameraLut(相机资源映射) + +| 字段 | 类型 | 说明 | +|------|------|------| +| StageID | int | 关卡ID | +| CamMappingID | int | 相机映射ID | +| PrefabPath | string | Prefab路径 | + +#### 3.4:ActivityFXLut(特效资源映射) + +| 字段 | 类型 | 说明 | +|------|------|------| +| StageID | int | 关卡ID | +| FXMappingID | int | 特效映射ID | +| PrefabPath | string | Prefab路径 | + +#### 3.5:ActivityMapInfoLut(地图打点) + +支持最多8个打点,每个打点包含 ID 和 Vector3 坐标(格式:`x\|y\|z`) + +| 字段 | 类型 | 说明 | +|------|------|------| +| InfoID | int | 地图信息ID | +| PointID_1~8 | string | 打点ID | +| Coord_1~8 | Vector3 | 坐标(x\|y\|z) | + +#### 3.6:ActivityDialogInfoByStage(对话索引) + +| 字段 | 类型 | 说明 | +|------|------|------| +| StageID | int | 玩法关卡ID | +| DialogConfigTable | string | 对应文本配置表名称 | + +#### 3.7:DialogInfo(对话详细配置) + +行为树对应节点:`DoShowDialog(DialogInfoID)` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | int | 配置ID | +| isMask | bool | 是否开启蒙层 | +| MaskTarget | string | 蒙层位置(万分比坐标 x,y) | +| ClickKey | int | 关闭蒙层键值 | +| DialogType | int | 战中类型 | +| UIType | int | UI样式 | +| PrefabPath | string | UI预制体路径(默认空) | +| Name | string | 显示角色名 | +| Text | string | 正文(支持富文本) | +| Duration | float | 显示时间 | +| Params1~5 | string | 特殊参数1~5 | + +--- + +### 5.4 嵌套节点示例(仅 GameplayNode) **CSV 数据:** @@ -287,14 +413,14 @@ int,int,int,...,int --- -### 5.4 运行时自动同步(可选) +### 5.5 运行时自动同步(可选) 如需在运行时自动同步,将 `GraphExcelSyncManager` 组件挂到场景中的 GameObject 上: 1. **Add Component → GraphExcelSyncManager** 2. Inspector 中绑定 `Target Graph` 和 `Schema` -之后每次 Graph 触发序列化事件时会自动写入 CSV。 +之后每�� Graph 触发序列化事件时会自动写入 CSV。 --- @@ -305,6 +431,8 @@ int,int,int,...,int | 导入后节点全部重叠 | 导入时节点默认位置为 Vector2.zero | 在 Canvas 中手动排布节点位置 | | CSV 用 Excel 打开乱码 | 编码不一致 | Excel → 数据 → 从文本/CSV 导入 → 选 UTF-8 | | 导入报错 "CSV file format invalid" | 缺少前3行表头 | 保留 NAME/TYPE/DOC 三行,数据从第4行开始 | -| 点击导出提示"请选择 Graph 和 Schema" | 工具栏拖槽为空 | Graph 和 Schema 两个拖槽都需要赋值 | -| Create 菜单找不到 Gameplay Graph | 正常,菜单已注册 | 右键 → Create → NodeCanvas → Gameplay Graph | -| QA 发现流程卡死 | 可能是配置问题也可能是程序问题 | 先策划自检行为树节点报错,确认非配置问题再提程序单 | +| 点击导出提示"请选择 Graph" | Graph 拖槽为空 | Graph 拖槽必须赋值 | +| 导入时提示"Unknown table name" | CSV 文件名不匹配 | 确保 CSV 文件名为标准表名(BehaviourTree.csv 等) | +| 某个表导入失败 | 该表的 CSV 格式错误 | 检查前3行表头是否完整,数据行是否有缺失 | +| 导出后找不到 CSV 文件 | 导出路径不对 | CSV 文件生成在 Assets/ 目录,刷新 Project 窗口 | +| ParentNodeID 连线没有重建 | 只有 GameplayNode 支持嵌套 | 其他节点类型不支持父子关系 |