Added defualt graph properties inspector;

Added sticky note;

Changed the name of BaseNode to SlotContainerNode in case we need other type of nodes in the future;
This commit is contained in:
Misaki
2024-11-04 01:02:30 +09:00
parent 5a9d8b9420
commit 7eec130b39
53 changed files with 517 additions and 436 deletions

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a6eb17dcf06e45741bbfd074d511d694
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,117 +0,0 @@
fileFormatVersion: 2
guid: d79e8e2fe01e368469036dfb319e17ae
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,117 +0,0 @@
fileFormatVersion: 2
guid: a6e9b2e48ab99064fb9ae034ef931082
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -48,8 +48,8 @@ namespace Misaki.GraphView.Editor
_searchContextElements.Clear(); _searchContextElements.Clear();
var types = string.IsNullOrEmpty(_owner.GraphViewConfig.searchNamespace) ? var types = string.IsNullOrEmpty(_owner.GraphViewConfig.searchNamespace) ?
TypeCache.GetTypesDerivedFrom<BaseNode>().ToArray() : TypeCache.GetTypesDerivedFrom<SlotContainerNode>().ToArray() :
TypeCache.GetTypesDerivedFrom<BaseNode>().Where(t => !string.IsNullOrEmpty(t.Namespace) && t.Namespace.StartsWith(_owner.GraphViewConfig.searchNamespace)).ToArray(); TypeCache.GetTypesDerivedFrom<SlotContainerNode>().Where(t => !string.IsNullOrEmpty(t.Namespace) && t.Namespace.StartsWith(_owner.GraphViewConfig.searchNamespace)).ToArray();
foreach (var type in types) foreach (var type in types)
{ {
@@ -142,14 +142,14 @@ namespace Misaki.GraphView.Editor
var element = (SearchContextElement)searchTreeEntry.userData; var element = (SearchContextElement)searchTreeEntry.userData;
BaseNode node = null; SlotContainerNode node = null;
if (element.Target is ExposedProperty property) if (element.Target is ExposedProperty property)
{ {
node = new PropertyInputNode(property); node = new PropertyInputNode(property);
} }
else if (element.Target is Type nodeType) else if (element.Target is Type nodeType)
{ {
node = Activator.CreateInstance(nodeType) as BaseNode; node = Activator.CreateInstance(nodeType) as SlotContainerNode;
} }
if (node == null) if (node == null)

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 51c9ebcd3b3149aeb61c479e1898561b
timeCreated: 1730603598

View File

@@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
[CustomEditor(typeof(GraphObject))]
public class GraphObjectEditor : UnityEditor.Editor
{
private readonly Dictionary<string, List<SerializedProperty>> _inspectorPropertyMap = new ();
private GraphObject _graphObject;
private void OnEnable()
{
_graphObject = target as GraphObject;
if (_graphObject == null)
{
return;
}
GetSerializedProperty();
}
protected virtual void GetSerializedProperty()
{
foreach (var property in _graphObject.ExposedProperties)
{
var showInInspectorField = property.GetType().GetField(nameof(ExposedProperty.showInInspector));
if (showInInspectorField == null)
{
continue;
}
var showInInspectorValue = showInInspectorField.GetValue(property);
if (showInInspectorValue is not bool or bool and false)
{
continue;
}
var fields = property.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
var i = _graphObject.ExposedProperties.IndexOf(property);
foreach (var field in fields)
{
var serializedProperty = serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null)
{
continue;
}
if (!_inspectorPropertyMap.ContainsKey(property.propertyName))
{
_inspectorPropertyMap[property.propertyName] = new ();
}
_inspectorPropertyMap[property.propertyName].Add(serializedProperty);
}
}
}
protected virtual VisualElement CreateInspectorProperty()
{
var graphPropertyFoldout = new Foldout()
{
text = "Graph Properties",
value = true
};
foreach (var property in _inspectorPropertyMap)
{
var label = new Label(property.Key)
{
style =
{
unityFontStyleAndWeight = FontStyle.Bold,
marginTop = 4,
marginBottom = 2,
}
};
var propertyContainer = new VisualElement()
{
style =
{
marginLeft = 15
}
};
foreach (var serializedProperty in property.Value)
{
var inputField = new PropertyField(serializedProperty);
inputField.Bind(serializedObject);
propertyContainer.Add(inputField);
}
graphPropertyFoldout.Add(label);
graphPropertyFoldout.Add(propertyContainer);
}
return graphPropertyFoldout;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 032ca62a690e4cb4be9c91fe2f4a955a
timeCreated: 1730603608

View File

@@ -78,6 +78,29 @@ namespace Misaki.GraphView.Editor
graphViewChanged += OnGraphViewChanged; graphViewChanged += OnGraphViewChanged;
} }
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
base.BuildContextualMenu(evt);
var mousePosition = (evt.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, evt.localMousePosition);
if (selection.Count == 0)
{
evt.menu.InsertAction(1, "Create Sticky Note", e =>
{
var stickyNote = new StickyNoteData
{
title = "Sticky Note",
contents = "Contents",
theme = StickyNoteTheme.Classic,
fontSize = StickyNoteFontSize.Medium,
position = new Rect(mousePosition, new Vector2(200, 200))
};
AddStickyNote(stickyNote);
}, DropdownMenuAction.AlwaysEnabled);
}
}
private void InitializeAssetElements() private void InitializeAssetElements()
{ {
var searchProvider = ScriptableObject.CreateInstance<NodeSearchProvider>(); var searchProvider = ScriptableObject.CreateInstance<NodeSearchProvider>();
@@ -88,12 +111,19 @@ namespace Misaki.GraphView.Editor
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider); SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), searchProvider);
}; };
if (_graphObject.Nodes.Count > 0) foreach (var node in _graphObject.Nodes)
{ {
foreach (var node in _graphObject.Nodes) AddNodeView(node); AddNodeView(node);
}
foreach (var connection in _graphObject.Connections) foreach (var noteData in _graphObject.StickyNotes)
AddConnectionView(connection.InputSlotData, connection.OutputSlotData); {
AddStickyNoteView(noteData);
}
foreach (var connection in _graphObject.Connections)
{
AddConnectionView(connection);
} }
RegisterCallback<DragPerformEvent>(OnDragPerform); RegisterCallback<DragPerformEvent>(OnDragPerform);
@@ -110,11 +140,16 @@ namespace Misaki.GraphView.Editor
for (var i = removedElements.Count - 1; i >= 0; i--) for (var i = removedElements.Count - 1; i >= 0; i--)
{ {
if (removedElements[i] is Node { userData: BaseNode node }) if (removedElements[i] is Node { userData: SlotContainerNode node })
{ {
RemoveNode(node); RemoveNode(node);
} }
if (removedElements[i] is StickyNote {userData : StickyNoteData stickyNote})
{
RemoveStickyNote(stickyNote);
}
if (removedElements[i] is Edge edge) if (removedElements[i] is Edge edge)
{ {
if (_slotConnections.Remove(edge, out var connection)) if (_slotConnections.Remove(edge, out var connection))
@@ -133,19 +168,16 @@ namespace Misaki.GraphView.Editor
} }
} }
if (graphViewChange.movedElements != null) // if (graphViewChange.movedElements != null)
{ // {
var movedElements = graphViewChange.movedElements; // var movedElements = graphViewChange.movedElements;
Undo.RecordObject(_graphObject, $"Move {movedElements.FirstOrDefault()?.GetType().Name}"); // Undo.RecordObject(_graphObject, $"Move {movedElements.FirstOrDefault()?.GetType().Name}");
//
foreach (var element in graphViewChange.movedElements) // foreach (var element in graphViewChange.movedElements)
{ // {
if (element is Node node) // element.SetPosition(element.GetPosition());
{ // }
node.SetPosition(node.GetPosition()); // }
}
}
}
if (graphViewChange.edgesToCreate != null) if (graphViewChange.edgesToCreate != null)
{ {
@@ -163,10 +195,10 @@ namespace Misaki.GraphView.Editor
} }
} }
_graphObject.SetTransform(viewTransform); _graphObject.SetGraphTransform(viewTransform);
EditorUtility.SetDirty(_graphObject);
_graphViewConfig.serializedObject.Update(); _graphViewConfig.serializedObject.Update();
EditorUtility.SetDirty(_graphObject);
return graphViewChange; return graphViewChange;
} }
@@ -210,24 +242,24 @@ namespace Misaki.GraphView.Editor
} }
} }
public void AddNode(BaseNode baseNode) public void AddNode(SlotContainerNode slotContainerNode)
{ {
Undo.RecordObject(_graphObject, $"Add {baseNode.GetType().Name}"); Undo.RecordObject(_graphObject, $"Add {slotContainerNode.GetType().Name}");
_graphObject.AddNode(baseNode); _graphObject.AddNode(slotContainerNode);
AddNodeView(baseNode); AddNodeView(slotContainerNode);
EditorUtility.SetDirty(_graphObject); EditorUtility.SetDirty(_graphObject);
} }
public virtual void AddNodeView(BaseNode baseNode) public virtual void AddNodeView(SlotContainerNode slotContainerNode)
{ {
Node nodeView; Node nodeView;
var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>(); var types = TypeCache.GetTypesWithAttribute<CustomInspectorAttribute>();
var type = types.FirstOrDefault(t => var type = types.FirstOrDefault(t =>
t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == baseNode.GetType()); t.GetCustomAttribute<CustomInspectorAttribute>().InspectorType == slotContainerNode.GetType());
if (baseNode is PropertyInputNode propertyInputNode) if (slotContainerNode is PropertyInputNode propertyInputNode)
{ {
// type ??= typeof(PropertyInputNodeView); // type ??= typeof(PropertyInputNodeView);
// var slot = propertyInputNode.GetSlot(0, SlotDirection.Output); // var slot = propertyInputNode.GetSlot(0, SlotDirection.Output);
@@ -237,7 +269,7 @@ namespace Misaki.GraphView.Editor
else else
{ {
type ??= typeof(EditorNodeView); type ??= typeof(EditorNodeView);
nodeView = Activator.CreateInstance(type, baseNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as EditorNodeView; nodeView = Activator.CreateInstance(type, slotContainerNode, _graphViewConfig.serializedObject, _graphViewConfig.portColorManager, _graphObject.Logger) as EditorNodeView;
} }
@@ -246,7 +278,7 @@ namespace Misaki.GraphView.Editor
return; return;
} }
nodeView.SetPosition(baseNode.position); nodeView.SetPosition(slotContainerNode.position);
if (nodeView is IInspectable inspectable) if (nodeView is IInspectable inspectable)
{ {
@@ -254,22 +286,22 @@ namespace Misaki.GraphView.Editor
} }
AddElement(nodeView); AddElement(nodeView);
_nodeViewsMap.Add(baseNode.Id, nodeView); _nodeViewsMap.Add(slotContainerNode.Id, nodeView);
} }
private void RemoveNode(BaseNode baseNode) private void RemoveNode(SlotContainerNode slotContainerNode)
{ {
Undo.RecordObject(_graphObject, $"Remove {baseNode.GetType().Name}"); Undo.RecordObject(_graphObject, $"Remove {slotContainerNode.GetType().Name}");
_graphObject.RemoveNode(baseNode); _graphObject.RemoveNode(slotContainerNode);
RemoveNodeView(baseNode); RemoveNodeView(slotContainerNode);
EditorUtility.SetDirty(_graphObject); EditorUtility.SetDirty(_graphObject);
} }
private void RemoveNodeView(BaseNode baseNode) private void RemoveNodeView(SlotContainerNode slotContainerNode)
{ {
if (_nodeViewsMap.Remove(baseNode.Id, out var nodeView)) if (_nodeViewsMap.Remove(slotContainerNode.Id, out var nodeView))
{ {
RemoveElement(nodeView); RemoveElement(nodeView);
@@ -280,6 +312,82 @@ namespace Misaki.GraphView.Editor
} }
} }
public void AddStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Add {stickyNote.title}");
_graphObject.AddStickyNote(stickyNote);
AddStickyNoteView(stickyNote);
}
private void AddStickyNoteView(StickyNoteData stickyNote)
{
var stickyNoteView = new StickyNoteView(stickyNote);
stickyNoteView.SetPosition(stickyNote.position);
AddElement(stickyNoteView);
}
public void RemoveStickyNote(StickyNoteData stickyNote)
{
Undo.RecordObject(_graphObject, $"Remove {stickyNote.title}");
_graphObject.RemoveStickyNote(stickyNote);
RemoveStickyNoteView(stickyNote);
}
private void RemoveStickyNoteView(StickyNoteData stickyNote)
{
// var stickyNoteView = GetStickyNoteView(stickyNote);
// if (stickyNoteView != null)
// {
// RemoveElement(stickyNoteView);
// }
}
public void AddConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Add {connection.GetType().Name}");
_graphObject.AddConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Remove {connection.GetType().Name}");
_graphObject.RemoveConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void AddConnectionView(SlotConnection connection)
{
var inputSlotData = connection.InputSlotData;
var outputSlotData = connection.OutputSlotData;
if (!_nodeViewsMap.TryGetValue(inputSlotData.nodeID, out var inputNodeView) ||
!_nodeViewsMap.TryGetValue(outputSlotData.nodeID, out var outputNodeView))
{
return;
}
if (inputNodeView is not IPortContainer inputPortContainer || outputNodeView is not IPortContainer outputPortContainer)
{
return;
}
var inputPort = inputPortContainer.InputPorts[inputSlotData.slotIndex];
var outputPort = outputPortContainer.OutputPorts[outputSlotData.slotIndex];
var edge = inputPort.ConnectTo(outputPort);
AddElement(edge);
_slotConnections.Add(edge, connection);
}
public void ToggleBlackboardViewVisibility() public void ToggleBlackboardViewVisibility()
{ {
if (_blackboardView == null) if (_blackboardView == null)
@@ -313,42 +421,5 @@ namespace Misaki.GraphView.Editor
? Visibility.Visible ? Visibility.Visible
: Visibility.Hidden; : Visibility.Hidden;
} }
public void AddConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Add {connection.GetType().Name}");
_graphObject.AddConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void RemoveConnection(SlotConnection connection)
{
Undo.RecordObject(_graphObject, $"Remove {connection.GetType().Name}");
_graphObject.RemoveConnection(connection);
EditorUtility.SetDirty(_graphObject);
}
private void AddConnectionView(SlotData a, SlotData b)
{
if (_nodeViewsMap.TryGetValue(a.nodeID, out var inputNodeView) &&
_nodeViewsMap.TryGetValue(b.nodeID, out var outputNodeView))
{
if (inputNodeView is not IPortContainer inputPortContainer || outputNodeView is not IPortContainer outputPortContainer)
{
return;
}
var inputPort = inputPortContainer.InputPorts[a.slotIndex];
var outputPort = outputPortContainer.OutputPorts[b.slotIndex];
var edge = inputPort.ConnectTo(outputPort);
AddElement(edge);
_slotConnections.Add(edge, new SlotConnection(a, b));
}
}
} }
} }

View File

@@ -12,7 +12,7 @@ namespace Misaki.GraphView.Editor
{ {
public class EditorNodeView : Node, IInspectable, IPortContainer public class EditorNodeView : Node, IInspectable, IPortContainer
{ {
private readonly BaseNode _dataNode; private readonly SlotContainerNode _dataNode;
private readonly Type _nodeType; private readonly Type _nodeType;
private readonly NodeInfoAttribute _nodeInfo; private readonly NodeInfoAttribute _nodeInfo;
@@ -24,7 +24,7 @@ namespace Misaki.GraphView.Editor
private readonly VisualElement _logContainer = new(); private readonly VisualElement _logContainer = new();
public BaseNode DataNode => _dataNode; public SlotContainerNode DataNode => _dataNode;
public List<Port> InputPorts => _inputPorts; public List<Port> InputPorts => _inputPorts;
public List<Port> OutputPorts => _outputPorts; public List<Port> OutputPorts => _outputPorts;
@@ -32,7 +32,7 @@ namespace Misaki.GraphView.Editor
public string InspectorName => _nodeInfo.Name ?? _nodeType.Name; public string InspectorName => _nodeInfo.Name ?? _nodeType.Name;
public EditorNodeView(BaseNode dataNode, SerializedObject serializedObject, IPortColorManager portColorManager, ILogger logger) public EditorNodeView(SlotContainerNode dataNode, SerializedObject serializedObject, IPortColorManager portColorManager, ILogger logger)
{ {
if (dataNode == null) if (dataNode == null)
{ {
@@ -59,7 +59,7 @@ namespace Misaki.GraphView.Editor
AddToClassList(depth.ToLower().Replace(" ", "-")); AddToClassList(depth.ToLower().Replace(" ", "-"));
} }
var inputs = _nodeType.GetProperty(nameof(BaseNode.Inputs)); var inputs = _nodeType.GetProperty(nameof(SlotContainerNode.Inputs));
if (inputs != null) if (inputs != null)
{ {
@@ -78,7 +78,7 @@ namespace Misaki.GraphView.Editor
} }
} }
var outputs = _nodeType.GetProperty(nameof(BaseNode.Outputs)); var outputs = _nodeType.GetProperty(nameof(SlotContainerNode.Outputs));
if (outputs != null) if (outputs != null)
{ {
@@ -108,7 +108,7 @@ namespace Misaki.GraphView.Editor
} }
} }
private void CreateLogElement(BaseNode node, string message, LogType type) private void CreateLogElement(SlotContainerNode node, string message, LogType type)
{ {
if (node.Id != _dataNode.Id) if (node.Id != _dataNode.Id)
{ {
@@ -236,9 +236,9 @@ namespace Misaki.GraphView.Editor
return root; return root;
} }
var i = graphObject.Nodes.IndexOf(_dataNode);
foreach (var field in fields) foreach (var field in fields)
{ {
var i = graphObject.Nodes.IndexOf(_dataNode);
var serializedProperty = _serializedObject.FindProperty("_nodes")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name); var serializedProperty = _serializedObject.FindProperty("_nodes")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null) if (serializedProperty == null)

View File

@@ -0,0 +1,51 @@
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Editor
{
public class StickyNoteView : StickyNote
{
private readonly StickyNoteData _data;
public StickyNoteView(StickyNoteData data)
{
_data = data;
userData = data;
title = data.title;
contents = data.contents;
theme = data.theme;
fontSize = data.fontSize;
this.Q<TextField>("title-field").RegisterCallback<ChangeEvent<string>>(e => {
_data.title = e.newValue;
});
this.Q<TextField>("contents-field").RegisterCallback<ChangeEvent<string>>(e => {
_data.contents = e.newValue;
});
}
public override void SetPosition(Rect rect)
{
base.SetPosition(rect);
if (_data == null)
{
return;
}
_data.position = rect;
}
public override void OnResized()
{
base.OnResized();
if (_data == null)
{
return;
}
_data.position = layout;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2d669992c46c4ca497549fa31f567db1
timeCreated: 1730597994

View File

@@ -37,9 +37,9 @@ namespace Misaki.GraphView.Editor
return root; return root;
} }
var i = graphObject.ExposedProperties.IndexOf(_property);
foreach (var field in fields) foreach (var field in fields)
{ {
var i = graphObject.ExposedProperties.IndexOf(_property);
var serializedProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name); var serializedProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative(field.Name);
if (serializedProperty == null) if (serializedProperty == null)
@@ -54,6 +54,15 @@ namespace Misaki.GraphView.Editor
root.Add(inputField); root.Add(inputField);
} }
var showInInspectorProperty = _serializedObject.FindProperty("_exposedProperties")?.GetArrayElementAtIndex(i)?.FindPropertyRelative("showInInspector");
if (showInInspectorProperty != null)
{
var showInInspectorField = new PropertyField(showInInspectorProperty, "Show In Inspector");
showInInspectorField.Bind(_serializedObject);
root.Add(showInInspectorField);
}
return root; return root;
} }
} }

View File

@@ -9,7 +9,7 @@ namespace Misaki.GraphView
public void UpdateComputeOrder(); public void UpdateComputeOrder();
public void Execute(ReadOnlyCollection<BaseNode> nodes); public void Execute(ReadOnlyCollection<SlotContainerNode> nodes);
public void Break(); public void Break();
} }

View File

@@ -5,10 +5,12 @@ namespace Misaki.GraphView
{ {
public interface ILogger public interface ILogger
{ {
public Action<BaseNode, string, LogType> OnLog { get; set; } public Action<SlotContainerNode, string, LogType> OnLog { get; set; }
public void LogInfo(BaseNode node, string message); public void LogInfo(SlotContainerNode node, string message);
public void LogWarning(BaseNode node, string message); public void LogWarning(SlotContainerNode node, string message);
public void LogError(BaseNode node, string message); public void LogError(SlotContainerNode node, string message);
public void ClearLogs();
} }
} }

View File

@@ -4,7 +4,7 @@ namespace Misaki.GraphView
{ {
public static class BaseNodeExtension public static class BaseNodeExtension
{ {
public static void ClearAllExecuteFlag(this IList<BaseNode> nodes) public static void ClearAllExecuteFlag(this IList<SlotContainerNode> nodes)
{ {
foreach (var node in nodes) foreach (var node in nodes)
{ {

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using UnityEditor.Experimental.GraphView;
using UnityEngine; using UnityEngine;
using UnityEngine.UIElements; using UnityEngine.UIElements;
@@ -9,15 +10,18 @@ namespace Misaki.GraphView
public abstract class GraphObject : ScriptableObject public abstract class GraphObject : ScriptableObject
{ {
[SerializeReference] [SerializeReference]
private List<BaseNode> _nodes = new(); private List<SlotContainerNode> _nodes = new();
[SerializeField]
private List<StickyNoteData> _stickyNotes = new();
[SerializeField] [SerializeField]
private List<SlotConnection> _connections = new(); private List<SlotConnection> _connections = new();
[SerializeReference] [SerializeReference]
private List<ExposedProperty> _exposedProperties = new(); private List<ExposedProperty> _exposedProperties = new();
private readonly Dictionary<string, BaseNode> _nodeMap = new(); private readonly Dictionary<string, SlotContainerNode> _nodeMap = new();
public ReadOnlyCollection<BaseNode> Nodes => _nodes.AsReadOnly(); public ReadOnlyCollection<SlotContainerNode> Nodes => _nodes.AsReadOnly();
public ReadOnlyCollection<StickyNoteData> StickyNotes => _stickyNotes.AsReadOnly();
public ReadOnlyCollection<SlotConnection> Connections => _connections.AsReadOnly(); public ReadOnlyCollection<SlotConnection> Connections => _connections.AsReadOnly();
public ReadOnlyCollection<ExposedProperty> ExposedProperties => _exposedProperties.AsReadOnly(); public ReadOnlyCollection<ExposedProperty> ExposedProperties => _exposedProperties.AsReadOnly();
@@ -37,39 +41,49 @@ namespace Misaki.GraphView
} }
} }
public void AddNode(BaseNode baseNode) public void AddNode(SlotContainerNode slotContainerNode)
{ {
_nodes.Add(baseNode); _nodes.Add(slotContainerNode);
TryAddNodeToMap(baseNode); TryAddNodeToMap(slotContainerNode);
baseNode.Initialize(this); slotContainerNode.Initialize(this);
} }
public void RemoveNode(BaseNode baseNode) public void RemoveNode(SlotContainerNode slotContainerNode)
{ {
_nodes.Remove(baseNode); _nodes.Remove(slotContainerNode);
RemoveNodeFromMap(baseNode); RemoveNodeFromMap(slotContainerNode);
baseNode.UnLoad(); slotContainerNode.UnLoad();
baseNode.UnlinkAllSlots(); slotContainerNode.UnlinkAllSlots();
} }
public bool TryAddNodeToMap(BaseNode baseNode) public bool TryAddNodeToMap(SlotContainerNode slotContainerNode)
{ {
return _nodeMap.TryAdd(baseNode.Id, baseNode); return _nodeMap.TryAdd(slotContainerNode.Id, slotContainerNode);
} }
public void RemoveNodeFromMap(BaseNode baseNode) public void RemoveNodeFromMap(SlotContainerNode slotContainerNode)
{ {
_nodeMap.Remove(baseNode.Id); _nodeMap.Remove(slotContainerNode.Id);
} }
public BaseNode GetNode(string id) public SlotContainerNode GetNode(string id)
{ {
return _nodeMap.GetValueOrDefault(id); return _nodeMap.GetValueOrDefault(id);
} }
public bool TryGetNode(string id, out BaseNode baseNode) public bool TryGetNode(string id, out SlotContainerNode slotContainerNode)
{ {
return _nodeMap.TryGetValue(id, out baseNode); return _nodeMap.TryGetValue(id, out slotContainerNode);
}
public void AddStickyNote(StickyNoteData stickyNote)
{
_stickyNotes.Add(stickyNote);
}
public void RemoveStickyNote(StickyNoteData stickyNote)
{
_stickyNotes.Remove(stickyNote);
} }
public void AddConnection(SlotConnection connection) public void AddConnection(SlotConnection connection)
@@ -104,7 +118,7 @@ namespace Misaki.GraphView
_exposedProperties.Remove(property); _exposedProperties.Remove(property);
} }
public void SetTransform(ITransform transform) public void SetGraphTransform(ITransform transform)
{ {
graphPosition = transform.position; graphPosition = transform.position;
graphScale = transform.scale; graphScale = transform.scale;

View File

@@ -3,7 +3,7 @@ using UnityEngine;
namespace Misaki.GraphView namespace Misaki.GraphView
{ {
public class PropertyInputNode : BaseNode public class PropertyInputNode : SlotContainerNode
{ {
[SerializeReference] [SerializeReference]
private ExposedProperty _property; private ExposedProperty _property;

View File

@@ -5,7 +5,7 @@ using UnityEngine;
namespace Misaki.GraphView namespace Misaki.GraphView
{ {
[Serializable] [Serializable]
public abstract class BaseNode : SlotContainer public abstract class SlotContainerNode : SlotContainer
{ {
[SerializeField] [SerializeField]
private GraphObject _graphObject; private GraphObject _graphObject;
@@ -20,16 +20,21 @@ namespace Misaki.GraphView
public string Id => _id; public string Id => _id;
public Action OnExecutionCompleted; public Action OnExecutionCompleted;
public Action<BaseNode> OnExecutionFailed; public Action<SlotContainerNode> OnExecutionFailed;
public Action OnExecuteFlagCleared; public Action OnExecuteFlagCleared;
/// <summary> /// <summary>
/// Initialize the node with the graph object, this method is called when the node is added to the graph. /// Initialize the node with the graph object, this method is called when the node is added to the graph.
/// </summary> /// </summary>
public virtual void Initialize(GraphObject graph) public void Initialize(GraphObject graph)
{ {
_graphObject = graph; _graphObject = graph;
InitializeSlot();
}
public virtual void InitializeSlot()
{
var type = GetType(); var type = GetType();
var fields = type.GetFields(ConstResource.NODE_FIELD_BINDING_FLAGS); var fields = type.GetFields(ConstResource.NODE_FIELD_BINDING_FLAGS);

View File

@@ -8,6 +8,7 @@ namespace Misaki.GraphView
public string id = Guid.NewGuid().ToString(); public string id = Guid.NewGuid().ToString();
public string propertyName; public string propertyName;
public string propertyType; public string propertyType;
public bool showInInspector = true;
public virtual object Value { get; set; } public virtual object Value { get; set; }
public virtual Type GetValueType() => Value == null ? typeof(object) : Value.GetType(); public virtual Type GetValueType() => Value == null ? typeof(object) : Value.GetType();

View File

@@ -50,12 +50,12 @@ namespace Misaki.GraphView
public ReadOnlyCollection<SlotData> LinkedSlotData => _linkedSlotData.AsReadOnly(); public ReadOnlyCollection<SlotData> LinkedSlotData => _linkedSlotData.AsReadOnly();
[SerializeReference] [SerializeReference]
public BaseNode owner; public SlotContainerNode owner;
public SlotData slotData; public SlotData slotData;
public object value; public object value;
public Slot(BaseNode owner, SlotData slotData) public Slot(SlotContainerNode owner, SlotData slotData)
{ {
this.owner = owner; this.owner = owner;
this.slotData = slotData; this.slotData = slotData;

View File

@@ -0,0 +1,22 @@
using System;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
namespace Misaki.GraphView
{
[Serializable]
public class StickyNoteData
{
[SerializeField]
private string _id = Guid.NewGuid().ToString();
public string title;
public string contents;
public Rect position;
public StickyNoteTheme theme;
public StickyNoteFontSize fontSize;
public string Id => _id;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3b18c70789034fef8f94030eefab46bb
timeCreated: 1730597117

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 82d6ed7c6c994e4caf44d9ed05403636
timeCreated: 1730300084

View File

@@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace Misaki.GraphView
{
[System.Serializable]
public class SerializationData
{
public List<string> serializedFields = new ();
public List<string> serializedValues = new ();
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c495f3d017e443e3a78a54a3e418dec8
timeCreated: 1730300091

View File

@@ -1,33 +0,0 @@
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
namespace Misaki.GraphView.Sample.Editor
{
[CustomEditor(typeof(SampleGraphAsset))]
public class SampleGraphAssetEditor : UnityEditor.Editor
{
[OnOpenAsset]
public static bool OnOpenAsset(int instanceID, int line)
{
var asset = EditorUtility.InstanceIDToObject(instanceID) as SampleGraphAsset;
if (asset != null)
{
SampleGraphEditor.Open(asset);
return true;
}
return false;
}
public override void OnInspectorGUI()
{
if (GUILayout.Button("Execute"))
{
var asset = target as SampleGraphAsset;
asset?.Execute();
}
}
}
}

3
Sample/Editor/View.meta Normal file
View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6196bfe4913b43c48a128bc5d8a94a51
timeCreated: 1730603519

View File

@@ -0,0 +1,45 @@
using Misaki.GraphView.Editor;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;
using UnityEngine.UIElements;
namespace Misaki.GraphView.Sample.Editor
{
[CustomEditor(typeof(SampleGraphAsset))]
public class SampleGraphAssetEditor : GraphObjectEditor
{
[OnOpenAsset]
public static bool OnOpenAsset(int instanceID, int line)
{
var asset = EditorUtility.InstanceIDToObject(instanceID) as SampleGraphAsset;
if (asset != null)
{
SampleGraphEditor.Open(asset);
return true;
}
return false;
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
var graphProperties = CreateInspectorProperty();
var executeButton = new Button(() =>
{
var graph = target as SampleGraphAsset;
graph?.Execute();
})
{
text = "Execute"
};
root.Add(graphProperties);
root.Add(executeButton);
return root;
}
}
}

View File

@@ -8,6 +8,8 @@ namespace Misaki.GraphView.Sample.Editor
{ {
public class SampleGraphEditor : EditorWindow public class SampleGraphEditor : EditorWindow
{ {
private const string Icon_Path = "Packages/com.misaki.graph-view/Sample/Icon/icons8-workflow-96.png";
[SerializeField] [SerializeField]
private StyleSheet _styleSheet; private StyleSheet _styleSheet;
@@ -21,15 +23,15 @@ namespace Misaki.GraphView.Sample.Editor
private static void Open() private static void Open()
{ {
var window = CreateWindow<SampleGraphEditor>(typeof(SceneView)); var window = CreateWindow<SampleGraphEditor>(typeof(SceneView));
window.titleContent = new GUIContent("Sample Graph Editor"); window.titleContent = new GUIContent("Sample Graph Editor", EditorGUIUtility.IconContent(Icon_Path).image);
} }
public static void Open(GraphObject asset) public static void Open(GraphObject asset)
{ {
var window = GetWindow<SampleGraphEditor>(typeof(SceneView)); var window = GetWindow<SampleGraphEditor>(typeof(SceneView));
window.titleContent = new GUIContent(asset.name, EditorGUIUtility.IconContent(Icon_Path).image);
window.Clear(); window.Clear();
window.LoadAsset(asset); window.LoadAsset(asset);
window.titleContent = new GUIContent(asset.name);
window.DrawGraph(); window.DrawGraph();
window.Focus(); window.Focus();
} }

View File

@@ -17,6 +17,18 @@ MonoBehaviour:
- rid: 299037523270959197 - rid: 299037523270959197
- rid: 299037523270959199 - rid: 299037523270959199
- rid: 299037535202443351 - rid: 299037535202443351
_stickyNotes:
- _id: 940a3025-e571-4b33-8d7c-c86761a95017
title: Title
contents: Test
position:
serializedVersion: 2
x: 764.2733
y: 223.30664
width: 164
height: 156.66666
theme: 0
fontSize: 1
_connections: _connections:
- _inputSlotData: - _inputSlotData:
slotName: a slotName: a
@@ -56,8 +68,8 @@ MonoBehaviour:
valueType: System.Single valueType: System.Single
_exposedProperties: _exposedProperties:
- rid: 299037523270959195 - rid: 299037523270959195
graphPosition: {x: -76.666664, y: -113.333336, z: 0} graphPosition: {x: 72, y: 28.666666, z: 0}
graphScale: {x: 1.21, y: 1.21, z: 1} graphScale: {x: 1, y: 1, z: 1}
references: references:
version: 2 version: 2
RefIds: RefIds:
@@ -67,6 +79,7 @@ MonoBehaviour:
id: facf43a4-c260-4e6a-a767-85cdbe63505e id: facf43a4-c260-4e6a-a767-85cdbe63505e
propertyName: Float Field propertyName: Float Field
propertyType: Misaki.GraphView.Sample.FloatProperty propertyType: Misaki.GraphView.Sample.FloatProperty
showInInspector: 1
value: 1 value: 1
- rid: 299037523270959196 - rid: 299037523270959196
type: {class: PropertyInputNode, ns: Misaki.GraphView, asm: GraphView} type: {class: PropertyInputNode, ns: Misaki.GraphView, asm: GraphView}
@@ -120,8 +133,8 @@ MonoBehaviour:
_id: e630425e-5b42-4839-9e4a-9b134c1e497c _id: e630425e-5b42-4839-9e4a-9b134c1e497c
position: position:
serializedVersion: 2 serializedVersion: 2
x: 840.6667 x: 847.9999
y: 437.3333 y: 437.3332
width: 124.66669 width: 124.66669
height: 78.66666 height: 78.66666
- rid: 299037523270959199 - rid: 299037523270959199
@@ -170,10 +183,10 @@ MonoBehaviour:
_id: b055be5b-5e72-4715-8981-d38913599762 _id: b055be5b-5e72-4715-8981-d38913599762
position: position:
serializedVersion: 2 serializedVersion: 2
x: 507.88882 x: 495.33328
y: 437.76617 y: 437.3333
width: 0 width: 116.66666
height: 0 height: 102.66666
a: 1 a: 1
b: 0 b: 0
- rid: 299037535202443351 - rid: 299037535202443351
@@ -222,9 +235,9 @@ MonoBehaviour:
_id: c45162b3-f131-44ac-a36f-26b579c4626c _id: c45162b3-f131-44ac-a36f-26b579c4626c
position: position:
serializedVersion: 2 serializedVersion: 2
x: 673.3334 x: 679.3334
y: 437.3333 y: 437.3333
width: 116.666626 width: 116.66669
height: 102.66666 height: 102.66666
a: 0 a: 1
b: 0 b: 0

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c84f34f806197ed4ca31c5998b73d78c guid: 6437de69d83b04b44b43fb449f5cbf8e
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: bd79539447defc842ae6917ff9164721 guid: 63281bb8d4b34874296d5ab5ba173840
TextureImporter: TextureImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 72f536fdc43abda46a1f44e0a7413280 guid: ac32d6575dcf0a643a0b1cd3b2b24740
TextureImporter: TextureImporter:
internalIDToNameTable: [] internalIDToNameTable: []
externalObjects: {} externalObjects: {}

View File

@@ -13,7 +13,7 @@ namespace Misaki.GraphView.Sample
{ {
} }
public void Execute(ReadOnlyCollection<BaseNode> nodes) public void Execute(ReadOnlyCollection<SlotContainerNode> nodes)
{ {
_isRunning = true; _isRunning = true;
nodes.ClearAllExecuteFlag(); nodes.ClearAllExecuteFlag();

View File

@@ -7,24 +7,29 @@ namespace Misaki.GraphView.Sample
{ {
private readonly List<string> _logs = new (); private readonly List<string> _logs = new ();
public Action<BaseNode, string, LogType> OnLog { get; set; } public Action<SlotContainerNode, string, LogType> OnLog { get; set; }
public void LogInfo(BaseNode node, string message) public void LogInfo(SlotContainerNode node, string message)
{ {
_logs.Add($"Log Info from node {node.GetType().Name}: {message}"); _logs.Add($"Log Info from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Info); OnLog?.Invoke(node, message, LogType.Info);
} }
public void LogWarning(BaseNode node, string message) public void LogWarning(SlotContainerNode node, string message)
{ {
_logs.Add($"Log Warning from node {node.GetType().Name}: {message}"); _logs.Add($"Log Warning from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Warning); OnLog?.Invoke(node, message, LogType.Warning);
} }
public void LogError(BaseNode node, string message) public void LogError(SlotContainerNode node, string message)
{ {
_logs.Add($"Log Error from node {node.GetType().Name}: {message}"); _logs.Add($"Log Error from node {node.GetType().Name}: {message}");
OnLog?.Invoke(node, message, LogType.Error); OnLog?.Invoke(node, message, LogType.Error);
} }
public void ClearLogs()
{
_logs.Clear();
}
} }
} }

View File

@@ -5,7 +5,7 @@ using Misaki.GraphView.Editor;
namespace Misaki.GraphView.Sample namespace Misaki.GraphView.Sample
{ {
[NodeInfo("Add", "Math")] [NodeInfo("Add", "Math")]
public class AddNode : BackTraceBaseNode public class AddNode : BackTraceNode
{ {
[NodeInput] [NodeInput]
#if UNITY_EDITOR #if UNITY_EDITOR

View File

@@ -1,6 +1,6 @@
namespace Misaki.GraphView.Sample namespace Misaki.GraphView.Sample
{ {
public abstract class BackTraceBaseNode : BaseNode public abstract class BackTraceNode : SlotContainerNode
{ {
protected override void OnPullData(Slot input) protected override void OnPullData(Slot input)
{ {

View File

@@ -3,7 +3,7 @@ using UnityEngine;
namespace Misaki.GraphView.Sample namespace Misaki.GraphView.Sample
{ {
[NodeInfo("Output Node", "Output")] [NodeInfo("Output Node", "Output")]
public class OutputNode : BackTraceBaseNode public class OutputNode : BackTraceNode
{ {
[NodeInput] [NodeInput]
private float _input; private float _input;

View File

@@ -1,2 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: a6ff09617d9e7ad4db1c77355ecb6ee1 guid: a6ff09617d9e7ad4db1c77355ecb6ee1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 63281bb8d4b34874296d5ab5ba173840, type: 3}
userData:
assetBundleName:
assetBundleVariant: