Add BehaviourGraph for executable behavior trees + refactor Excel export/import to support both graph types
parent
4fefe78bb8
commit
6557e9dfcf
|
|
@ -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}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a0bc0a969202f49498e8d63e4da11d62
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7d04cbc7dec380c4a80a1cf00bcab601
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e498e185f67a544479d637626a1f98a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f68ed535e8fe19d43ba9ae227e07c76d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
using NodeCanvas.BehaviourTrees;
|
||||
using ParadoxNotion;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GameplayEditor.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 行为树 Graph - 支持可执行的行为逻辑 + 表格编辑
|
||||
/// 继承 BehaviourTree,额外支持 ActivityNodeBase 节点的导出/导入
|
||||
/// </summary>
|
||||
[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<T> GetNodesByType<T>() where T : Node
|
||||
{
|
||||
return allNodes.OfType<T>().ToList();
|
||||
}
|
||||
|
||||
public List<ActivityNodeBase> GetAllActivityNodes()
|
||||
{
|
||||
return allNodes.OfType<ActivityNodeBase>().ToList();
|
||||
}
|
||||
|
||||
public Dictionary<string, List<ActivityNodeBase>> GetNodesByTableName()
|
||||
{
|
||||
var result = new Dictionary<string, List<ActivityNodeBase>>();
|
||||
foreach (var node in GetAllActivityNodes())
|
||||
{
|
||||
if (!result.ContainsKey(node.TableName))
|
||||
result[node.TableName] = new List<ActivityNodeBase>();
|
||||
result[node.TableName].Add(node);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 25cde69a219386b44bccc0c08271bdc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 758081b0b34f2e942b68f9fa35170ba8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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<ActivityNodeBase>().ToList();
|
||||
if (activityNodes.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("No ActivityNodeBase nodes found in graph");
|
||||
return;
|
||||
}
|
||||
|
||||
var nodesByTable = new Dictionary<string, List<ActivityNodeBase>>();
|
||||
foreach (var node in activityNodes)
|
||||
{
|
||||
if (!nodesByTable.ContainsKey(node.TableName))
|
||||
nodesByTable[node.TableName] = new List<ActivityNodeBase>();
|
||||
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<ActivityNodeBase> nodes, string filePath)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<Core.ActivityNodeBase>().ToList();
|
||||
if (activityNodes.Count > 0)
|
||||
{
|
||||
var nodesByTable = new Dictionary<string, List<Core.ActivityNodeBase>>();
|
||||
foreach (var node in activityNodes)
|
||||
{
|
||||
if (!nodesByTable.ContainsKey(node.TableName))
|
||||
nodesByTable[node.TableName] = new List<Core.ActivityNodeBase>();
|
||||
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 表", "确定");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 89e2f58b96cb09245a29a89b64f4239d
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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: []
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0de0e36ee42ae394a8ac5b18dc6303ed
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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。
|
||||
之后每<EFBFBD><EFBFBD> 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 支持嵌套 | 其他节点类型不支持父子关系 |
|
||||
|
|
|
|||
Loading…
Reference in New Issue