从旧版中移植,Prefab未确认

This commit is contained in:
2025-07-24 15:41:28 +08:00
parent 86842492ea
commit 43b824b722
300 changed files with 101926 additions and 14 deletions

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
using UnityEngine.Events;
namespace Convention.Workflow
{
public class GraphInputWindow : MonoSingleton<GraphInputWindow>, ITitle, IText
{
[Resources, SerializeField, OnlyNotNullMode] private Text Title;
[Resources, SerializeField, OnlyNotNullMode] private Text Description;
[Resources, SerializeField, OnlyNotNullMode] private ModernUIButton AddContentInput;
[Resources, SerializeField, HopeNotNull, Header(nameof(HierarchyWindow))] private HierarchyWindow MyHierarchyWindow;
private PropertiesWindow.ItemEntry StartNodeInputsTab, FunctionsTab, EndNodeOutputsTab;
public string title { get => ((ITitle)this.Title).title; set => ((ITitle)this.Title).title = value; }
public string text { get => ((IText)this.Description).text; set => ((IText)this.Description).text = value; }
private class TitleClass : IHierarchyItemTitle
{
[InspectorDraw(InspectorDrawType.Text, false, false)]
public string title;
public TitleClass(string title)
{
this.title = title;
}
public string HierarchyItemTitle => title;
}
private void Start()
{
AddContentInput.AddListener(() =>
{
SharedModule.instance.OpenCustomMenu(this.transform as RectTransform, new SharedModule.CallbackData("test", go => { }));
});
Architecture.RegisterWithDuplicateAllow(typeof(GraphInputWindow), this, () =>
{
StartNodeInputsTab = MyHierarchyWindow.CreateRootItemEntryWithBinders(new TitleClass(nameof(StartNodeInputsTab)))[0];
StartNodeInputsTab.GetHierarchyItem().title = WorkflowManager.Transformer("StartNodes");
FunctionsTab = MyHierarchyWindow.CreateRootItemEntryWithBinders(new TitleClass(nameof(FunctionsTab)))[0];
FunctionsTab.GetHierarchyItem().title = WorkflowManager.Transformer("Functions");
EndNodeOutputsTab = MyHierarchyWindow.CreateRootItemEntryWithBinders(new TitleClass(nameof(EndNodeOutputsTab)))[0];
EndNodeOutputsTab.GetHierarchyItem().title = WorkflowManager.Transformer("EndNodes");
}, typeof(HierarchyWindow));
}
private void Reset()
{
MyHierarchyWindow = GetComponent<HierarchyWindow>();
}
public PropertiesWindow.ItemEntry RegisterOnHierarchyWindow(NodeInfo info)
{
PropertiesWindow.ItemEntry item = null;
if (info is StartNodeInfo)
{
item = StartNodeInputsTab.GetHierarchyItem().CreateSubPropertyItemWithBinders(info)[0];
}
else if (info is EndNodeInfo)
{
item = EndNodeOutputsTab.GetHierarchyItem().CreateSubPropertyItemWithBinders(info)[0];
}
else if (info is StepNodeInfo sNode)
{
var parentItem = FunctionsTab.GetHierarchyItem();
var menuEntry = parentItem.Entry.GetChilds().Find(x => (x.GetHierarchyItem().target as TitleClass).title == sNode.funcname);
if (menuEntry == null)
{
menuEntry = parentItem.CreateSubPropertyItemWithBinders(new TitleClass(sNode.funcname))[0];
}
item = menuEntry.GetHierarchyItem().CreateSubPropertyItemWithBinders(info)[0];
}
return item;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 628e8952d5d14ea46b193b11d1d15fee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
using UnityEngine.Events;
namespace Convention.Workflow
{
public class GraphInspector : MonoSingleton<GraphInspector>, ITitle, IText
{
[Resources, SerializeField, OnlyNotNullMode] private Text Title;
public string title { get => ((ITitle)this.Title).title; set => ((ITitle)this.Title).title = value; }
public string text { get => ((IText)this.Title).text; set => ((IText)this.Title).text = value; }
private void Start()
{
Architecture.RegisterWithDuplicateAllow(typeof(GraphInspector), this, () => { });
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: facb72df493019b48ba11ef9789a1fae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,408 @@
using System;
using System.Collections.Generic;
using System.IO;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class NodeInfo : IHierarchyItemTitle
{
/// <summary>
/// 节点
/// </summary>
[Setting, Ignore, NonSerialized] public Node node = null;
/// <summary>
/// 节点ID
/// </summary>
[Setting] public int nodeID = -1;
/// <summary>
/// 节点类型
/// </summary>
[InspectorDraw(InspectorDrawType.Text, false, false, name: "GraphNodeType")]
[Setting] public string typename;
/// <summary>
/// 节点标题
/// </summary>
[Content] public string title = "";
/// <summary>
/// 输入映射
/// </summary>
[Setting] public Dictionary<string, NodeSlotInfo> inmapping = new();
/// <summary>
/// 输出映射
/// </summary>
[Setting] public Dictionary<string, NodeSlotInfo> outmapping = new();
/// <summary>
/// 节点位置
/// </summary>
[Content] public Vector2 position = Vector2.zero;
[InspectorDraw(InspectorDrawType.Text, name: "GraphNodeTitle")]
public string GraphNodeTitle
{
get => this.title;
set => this.title = node.title = value;
}
string IHierarchyItemTitle.HierarchyItemTitle => title;
public NodeInfo()
{
WorkflowManager.Transformer(typename = this.GetType().Name[..^4]);
}
protected virtual NodeInfo CreateTemplate()
{
return new NodeInfo();
}
protected virtual void CloneValues([In] NodeInfo clonen)
{
clonen.nodeID = nodeID;
clonen.typename = typename;
clonen.title = string.IsNullOrEmpty(title) ? WorkflowManager.Transformer(this.GetType().Name[..^4]) : title;
clonen.position = position;
foreach (var (key, value) in inmapping)
{
clonen.inmapping[key] = value.TemplateClone();
}
foreach (var (key, value) in outmapping)
{
clonen.outmapping[key] = value.TemplateClone();
}
}
public NodeInfo TemplateClone()
{
NodeInfo result = CreateTemplate();
CloneValues(result);
return result;
}
public static Vector2 GetPosition(Transform transform)
{
Vector3 result = transform.position - WorkflowManager.instance.ContentPlane.transform.position;
return new(result.x, result.y);
}
public virtual void CopyFromNode([In] Node node)
{
nodeID = WorkflowManager.instance.GetGraphNodeID(node);
title = node.title;
position = GetPosition(node.transform);
foreach (var (key, inslot) in node.m_Inmapping)
{
inmapping[key] = inslot.info;
}
foreach (var (key, outslot) in node.m_Outmapping)
{
outmapping[key] = outslot.info;
}
}
[return: IsInstantiated(true)]
public virtual Node Instantiate()
{
string key = this.GetType().Name;
if (key.EndsWith("Info"))
key = key[..^4];
var node = GameObject.Instantiate(WorkflowManager.instance.GraphNodePrefabs.FindItem<GameObject>(key)).GetComponent<Node>();
return node;
}
public override string ToString()
{
return $"{title}<in.c={(inmapping == null ? 0 : inmapping.Count)}, out.c={(outmapping == null ? 0 : outmapping.Count)}>";
}
}
public class Node : WindowsComponent, IOnlyFocusThisOnInspector, ITitle
{
#if UNITY_EDITOR
[Content]
public void DebugLogNodeInfo()
{
Debug.Log(this.info);
}
#endif
public PropertiesWindow.ItemEntry MyNodeTab;
private BehaviourContextManager Context;
[Resources, OnlyNotNullMode, SerializeField] private Text Title;
[Setting]
public int SlotHeight = 40, TitleHeight = 50, ExtensionHeight = 0;
public bool IsStartNode => this.GetType().IsSubclassOf(typeof(StartNode));
public bool IsEndNode => this.GetType().IsSubclassOf(typeof(EndNode));
[Resources, SerializeField, WhenAttribute.Is(nameof(IsStartNode), false), OnlyNotNullMode]
private PropertiesWindow InSlotPropertiesWindow;
[Resources, SerializeField, WhenAttribute.Is(nameof(IsEndNode), false), OnlyNotNullMode]
private PropertiesWindow OutSlotPropertiesWindow;
private List<PropertiesWindow.ItemEntry> InSlots = new(), OutSlots = new();
internal Dictionary<string, NodeSlot> m_Inmapping = new();
internal Dictionary<string, NodeSlot> m_Outmapping = new();
[Resources, SerializeField, OnlyNotNullMode] protected BaseWindowPlane InoutContainerPlane;
[Content, OnlyPlayMode, SerializeField] private string rawTitle;
public string title
{
get => rawTitle;
set
{
rawTitle = value;
this.Title.title = WorkflowManager.Transformer(rawTitle);
}
}
[Setting, SerializeField] private NodeInfo m_info;
public NodeInfo info { get => m_info; private set => m_info = value; }
protected virtual void Start()
{
if (Context == null)
Context = this.GetOrAddComponent<BehaviourContextManager>();
Context.OnPointerDownEvent = BehaviourContextManager.InitializeContextSingleEvent(Context.OnPointerDownEvent, OnPointerDown);
Context.OnDragEvent = BehaviourContextManager.InitializeContextSingleEvent(Context.OnDragEvent, OnDrag);
Context.OnPointerClickEvent = BehaviourContextManager.InitializeContextSingleEvent(Context.OnPointerClickEvent, PointerRightClickAndOpenMenu);
Context.OnEndDragEvent = BehaviourContextManager.InitializeContextSingleEvent(Context.OnEndDragEvent, EndDrag);
}
protected virtual void OnDestroy()
{
if (InspectorWindow.instance.GetTarget() == this.info)
{
InspectorWindow.instance.ClearWindow();
}
MyNodeTab?.Release();
}
public virtual void PointerRightClickAndOpenMenu(PointerEventData pointer)
{
if (pointer.button == PointerEventData.InputButton.Right)
{
List<SharedModule.CallbackData> callbacks = new()
{
new (WorkflowManager.Transformer("Delete"), x =>
{
WorkflowManager.instance.DestroyNode(this);
})
};
SharedModule.instance.OpenCustomMenu(WorkflowManager.instance.UIFocusObject, callbacks.ToArray());
}
}
public void OnPointerDown(PointerEventData _)
{
if (this.info != null)
InspectorWindow.instance.SetTarget(this.info, null);
else
Debug.LogError($"GraphNode<{this.GetType()}>={this}'s info is not setup", this);
}
public void OnDrag(PointerEventData _)
{
RefreshImmediate();
}
public void EndDrag(PointerEventData _)
{
if (Keyboard.current[Key.LeftCtrl].isPressed)
{
var vec = this.transform.localPosition;
float x = Mathf.Round(vec.x / 100);
float y = Mathf.Round(vec.y / 100);
transform.localPosition = new Vector3(x * 100, y * 100, 0);
RefreshImmediate();
}
}
protected virtual void WhenSetup(NodeInfo info)
{
}
public void SetupFromInfo([In] NodeInfo value, bool isRefresh = true)
{
if (value != info)
{
ClearLink();
ClearSlots();
info = value;
this.title = value.title;
int nodeID = WorkflowManager.instance.GetGraphNodeID(this);
value.nodeID = nodeID;
value.node = this;
if (isRefresh)
{
BuildSlots();
BuildLink();
RefreshPosition();
}
WhenSetup(info);
}
}
public void RefreshImmediate()
{
foreach (var (_, slot) in m_Inmapping)
{
if (slot != null)
slot.SetDirty();
}
foreach (var (_, slot) in m_Outmapping)
{
if (slot != null)
{
foreach (var targetSlot in slot.info.targetSlots)
{
targetSlot.SetDirty();
}
}
}
}
public void RefreshPosition()
{
this.transform.position = new Vector3(info.position.x, info.position.y) + (WorkflowManager.instance.ContentPlane.transform.position);
}
public void RefreshRectTransform()
{
this.rectTransform.sizeDelta = new(this.rectTransform.sizeDelta.x, TitleHeight + Mathf.Max(m_Inmapping.Count, m_Outmapping.Count) * SlotHeight + ExtensionHeight);
}
public virtual void ClearLink()
{
if (InSlotPropertiesWindow == true)
foreach (var (name, slot) in this.m_Inmapping)
{
NodeSlot.UnlinkAll(slot);
slot.SetDirty();
}
if (OutSlotPropertiesWindow == true)
foreach (var (name, slot) in m_Outmapping)
{
NodeSlot.UnlinkAll(slot);
slot.SetDirty();
}
}
public virtual void ClearSlots()
{
if (this.info == null)
{
return;
}
if (InSlotPropertiesWindow == true)
{
foreach (var slot in this.InSlots)
slot.Release();
this.m_Inmapping.Clear();
}
if (OutSlotPropertiesWindow == true)
{
foreach (var slot in this.OutSlots)
slot.Release();
OutSlots.Clear();
this.m_Outmapping.Clear();
}
}
protected List<PropertiesWindow.ItemEntry> CreateGraphNodeInSlots(int count)
{
if (InSlotPropertiesWindow == null)
throw new InvalidOperationException($"this node is not using {nameof(InSlotPropertiesWindow)}");
return InSlotPropertiesWindow.CreateRootItemEntries(count);
}
protected List<PropertiesWindow.ItemEntry> CreateGraphNodeOutSlots(int count)
{
if (OutSlotPropertiesWindow == null)
throw new InvalidOperationException($"this node is not using {nameof(OutSlotPropertiesWindow)}");
return OutSlotPropertiesWindow.CreateRootItemEntries(count);
}
public virtual void BuildSlots()
{
if (InSlotPropertiesWindow == true)
{
int InSlotCount = info.inmapping.Count;
InSlots = CreateGraphNodeInSlots(InSlotCount);
foreach (var (key, slotInfo) in info.inmapping)
{
InSlotCount--;
var slot = InSlots[InSlotCount].ref_value.GetComponent<NodeSlot>();
// 这样真的好吗
m_Inmapping[key] = slot;
var info = slotInfo.TemplateClone();
info.parentNode = this;
slot.SetupFromInfo(info);
}
}
if (OutSlotPropertiesWindow == true)
{
int OutSlotCount = info.outmapping.Count;
OutSlots = CreateGraphNodeOutSlots(OutSlotCount);
foreach (var (key, slotInfo) in info.outmapping)
{
OutSlotCount--;
var slot = OutSlots[OutSlotCount].ref_value.GetComponent<NodeSlot>();
// 通过这种方法规避了重复键, 但是也存在一些特殊的问题
m_Outmapping[key] = slot;
var info = slotInfo.TemplateClone();
info.parentNode = this;
slot.SetupFromInfo(info);
}
}
RefreshRectTransform();
}
public virtual void BuildLink()
{
if (InSlotPropertiesWindow != null)
{
foreach (var (slot_name, slot_info) in info.inmapping)
{
var targetNode = WorkflowManager.instance.GetGraphNode(slot_info.targetNodeID);
if (targetNode != null)
{
NodeSlot.Link(m_Inmapping[slot_name], targetNode.m_Outmapping[slot_info.targetSlotName]);
}
}
}
}
public void LinkInslotToOtherNodeOutslot(
[In, IsInstantiated(true)] Node other,
[In] string slotName,
[In] string targetSlotName)
{
NodeSlot.Link(this.m_Inmapping[slotName], other.m_Outmapping[targetSlotName]);
}
public void LinkOutslotToOtherNodeInslot(
[In, IsInstantiated(true)] Node other,
[In] string slotName,
[In] string targetSlotName)
{
NodeSlot.Link(this.m_Outmapping[slotName], other.m_Inmapping[targetSlotName]);
}
public void UnlinkInslot([In] string slotName)
{
NodeSlot.UnlinkAll(this.m_Inmapping[slotName]);
}
public void UnlinkOutslot([In] string slotName)
{
NodeSlot.UnlinkAll(this.m_Outmapping[slotName]);
}
}
[Serializable, ArgPackage]
public class DynamicNodeInfo : NodeInfo
{
protected override NodeInfo CreateTemplate()
{
return new DynamicNodeInfo();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93f89afbfd02c054aa2e5c7141eae234
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using Convention.WindowsUI;
using UnityEngine;
using UnityEngine.EventSystems;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class NodeSlotInfo
{
/// <summary>
/// 所属的父节点
/// </summary>
[Ignore, NonSerialized] public Node parentNode = null;
/// <summary>
/// 所属的插槽
/// </summary>
[Ignore, NonSerialized] public NodeSlot slot = null;
/// <summary>
/// 插槽名称
/// </summary>
public string slotName = "unknown";
/// <summary>
/// 目标节点
/// </summary>
[Ignore, NonSerialized]
public List<Node> targetNodes = new();
/// <summary>
/// 目标槽
/// </summary>
[Ignore, NonSerialized]
public List<NodeSlot> targetSlots = new();
/// <summary>
/// 目标节点ID, 输出节点无效
/// </summary>
public int targetNodeID = -1;
/// <summary>
/// 目标插槽名称, 输出节点无效
/// </summary>
public string targetSlotName = "unknown";
/// <summary>
/// 类型指示器
/// </summary>
public string typeIndicator;
/// <summary>
/// 是否为输入映射插槽
/// </summary>
public bool IsInmappingSlot;
public NodeSlotInfo TemplateClone(bool isClearn = true)
{
NodeSlotInfo result = new()
{
slotName = slotName,
targetNodeID = isClearn ? -1 : this.targetNodeID,
targetSlotName = isClearn ? "" : this.targetSlotName,
typeIndicator = typeIndicator,
IsInmappingSlot = IsInmappingSlot
};
return result;
}
}
public interface INodeSlotLinkable
{
public bool LinkTo([In, Opt] NodeSlot other);
public bool Linkable([In]NodeSlot other);
}
public class NodeSlot : WindowUIModule, ITitle, INodeSlotLinkable
{
[Resources, Setting, Tooltip("挂载额外的组件")] public GameObject ExtensionModule;
//这个缩放因子是最顶层Canvas的变形
public const float ScaleFactor = 100;
public bool Linkable([In] NodeSlot other)
{
if (this.info.IsInmappingSlot == other.info.IsInmappingSlot)
{
throw new InvalidOperationException($"{this} and {other} has same mapping type");
}
if (this.info.typeIndicator != other.info.typeIndicator)
{
if (!((this.info.typeIndicator == "string" && other.info.typeIndicator == "str") ||
(this.info.typeIndicator == "str" && other.info.typeIndicator == "string") ||
this.info.typeIndicator.StartsWith("Any", StringComparison.CurrentCultureIgnoreCase) ||
other.info.typeIndicator.StartsWith("Any", StringComparison.CurrentCultureIgnoreCase
)))
throw new InvalidOperationException($"{this}<{this.info.typeIndicator}> and {other}<{other.info.typeIndicator}> has different type indicator");
}
if (this.info.parentNode == other.info.parentNode)
{
throw new InvalidOperationException($"{this} and {other} has same parent node<{this.info.parentNode}>");
}
return true;
}
public static void Link([In] NodeSlot left, [In] NodeSlot right)
{
if(left.info.IsInmappingSlot)
{
(left, right) = (right, left);
}
left.Linkable(right);
// left一定是输出端
if (left.info.targetSlots.Contains(right) == false)
{
UnlinkAll(right);
left.info.targetSlots.Add(right);
left.info.targetSlotName = right.info.slotName;
left.info.targetNodes.Add(right.info.parentNode);
left.info.targetNodeID = WorkflowManager.instance.GetGraphNodeID(right.info.parentNode);
right.info.targetSlots.Clear();
right.info.targetSlots.Add(left);
right.info.targetNodes.Clear();
right.info.targetNodes.Add(left.info.parentNode);
right.info.targetNodeID = WorkflowManager.instance.GetGraphNodeID(left.info.parentNode);
right.info.targetSlotName = left.info.slotName;
left.SetDirty();
right.SetDirty();
}
}
public static void Unlink([In] NodeSlot slot, NodeSlot targetSlot)
{
int index = slot.info.targetSlots.IndexOf(targetSlot);
if (index != -1)
Unlink(slot, index);
}
public static void Unlink([In] NodeSlot slot, int slotIndex)
{
var targetSlot = slot.info.targetSlots[slotIndex];
slot.info.targetSlots.RemoveAt(slotIndex);
slot.info.targetNodes.RemoveAt(slotIndex);
if (slot.info.targetSlots.Count == 0)
{
slot.info.targetNodeID = -1;
slot.info.targetSlotName = "";
}
int r_slotIndex = targetSlot.info.targetSlots.IndexOf(slot);
if (targetSlot != null && r_slotIndex != -1)
{
targetSlot.info.targetSlots.RemoveAt(r_slotIndex);
targetSlot.info.targetNodes.RemoveAt(r_slotIndex);
if (targetSlot.info.targetSlots.Count == 0)
{
targetSlot.info.targetNodeID = -1;
targetSlot.info.targetSlotName = "";
}
targetSlot.SetDirty();
}
slot.SetDirty();
}
public static void UnlinkAll([In] NodeSlot slot)
{
foreach (var otherSlot in slot.info.targetSlots)
{
int index = otherSlot.info.targetSlots.IndexOf(slot);
otherSlot.info.targetSlots.RemoveAt(index);
otherSlot.info.targetNodes.RemoveAt(index);
if (otherSlot.info.targetSlots.Count == 0)
{
otherSlot.info.targetNodeID = -1;
otherSlot.info.targetSlotName = "";
}
otherSlot.SetDirty();
}
slot.info.targetSlots.Clear();
slot.info.targetNodes.Clear();
slot.info.targetNodeID = -1;
slot.info.targetSlotName = "";
slot.SetDirty();
}
public bool LinkTo([In, Opt] NodeSlot slot)
{
if (slot != null)
{
Link(this, slot);
return true;
}
return false;
}
public static NodeSlot CurrentHighLightSlot { get; private set; }
public static INodeSlotLinkable CurrentLinkTarget;
public static void EnableHighLight(NodeSlot slot)
{
if (CurrentHighLightSlot != null)
{
CurrentHighLightSlot.HighLight.SetActive(false);
}
CurrentHighLightSlot = slot;
CurrentHighLightSlot.HighLight.SetActive(true);
CurrentLinkTarget = slot;
}
public static void DisableAllHighLight()
{
if (CurrentHighLightSlot != null)
{
CurrentHighLightSlot.HighLight.SetActive(false);
CurrentHighLightSlot = null;
}
}
public static void DisableHighLight(NodeSlot slot)
{
if (CurrentHighLightSlot == slot)
{
CurrentHighLightSlot.HighLight.SetActive(false);
CurrentHighLightSlot = null;
CurrentLinkTarget = null;
}
}
public static readonly Vector3[] zeroVecs = new Vector3[0];
public Vector3[] GetCurrentLinkingVectors()
{
if (this.info == null)
return zeroVecs;
if (info.targetSlots.Count > 0 && info.IsInmappingSlot)
{
Vector3 position = this.Anchor.position;
Vector3 localPosition = this.Anchor.localPosition;
Vector3 targetPosition = info.targetSlots[0].Anchor.position;
float offset = Mathf.Clamp((targetPosition - position).magnitude * ScaleFactor, 0, 30);
return new Vector3[]{
localPosition,
localPosition+Vector3.left*offset,
(targetPosition-position)*ScaleFactor+ localPosition+Vector3.right*offset,
(targetPosition-position)*ScaleFactor+ localPosition
};
}
else return zeroVecs;
}
[Content, OnlyPlayMode, Ignore] private NodeSlotInfo m_info;
public NodeSlotInfo info { get => m_info; private set => m_info = value; }
public void SetupFromInfo(NodeSlotInfo value)
{
if (info != value)
{
info = value;
info.slot = this;
SetDirty();
}
}
[Resources, OnlyNotNullMode, SerializeField] private Text Title;
[Resources, OnlyNotNullMode, SerializeField] private LineRenderer LineRenderer;
[Resources, OnlyNotNullMode, SerializeField] private Transform Anchor;
[Resources, OnlyNotNullMode, SerializeField] private GameObject HighLight;
[Setting] public float Offset = 1;
[Content, SerializeField] private Vector3[] Points = new Vector3[0];
public string title { get => ((ITitle)this.Title).title; set => ((ITitle)this.Title).title = value; }
private void OnDestroy()
{
UnlinkAll(this);
}
private void Start()
{
BehaviourContextManager contextManager = this.GetOrAddComponent<BehaviourContextManager>();
contextManager.OnBeginDragEvent = BehaviourContextManager.InitializeContextSingleEvent(contextManager.OnBeginDragEvent, BeginDragLine);
contextManager.OnDragEvent = BehaviourContextManager.InitializeContextSingleEvent(contextManager.OnDragEvent, DragLine);
contextManager.OnEndDragEvent = BehaviourContextManager.InitializeContextSingleEvent(contextManager.OnEndDragEvent, EndDragLine);
contextManager.OnPointerEnterEvent = BehaviourContextManager.InitializeContextSingleEvent(contextManager.OnPointerEnterEvent, _ =>
{
if (
CurrentHighLightSlot == null ||
(CurrentHighLightSlot.info.IsInmappingSlot != this.info.IsInmappingSlot &&
CurrentHighLightSlot.info.typeIndicator == this.info.typeIndicator)
)
EnableHighLight(this);
});
contextManager.OnPointerExitEvent = BehaviourContextManager.InitializeContextSingleEvent(contextManager.OnPointerExitEvent, _ =>
{
DisableHighLight(this);
});
}
[Content, Ignore, SerializeField] private bool IsKeepDrag = false;
[Content, Ignore, SerializeField] private bool IsDirty = false;
private void Update()
{
if (IsKeepDrag == false && IsDirty)
{
Points = GetCurrentLinkingVectors();
UpdateLineImmediate();
}
else if (IsDirty)
{
RebuildLine();
}
}
public void SetDirty()
{
IsDirty = true;
}
public void RebuildLine()
{
LineRenderer.positionCount = Points.Length;
LineRenderer.SetPositions(Points);
}
public void UpdateLineImmediate()
{
RebuildLine();
title = $"{WorkflowManager.Transformer(info.slotName)}({WorkflowManager.Transformer(info.typeIndicator)})";
IsDirty = false;
}
public void BeginDragLine(PointerEventData _)
{
if (this.info.IsInmappingSlot)
UnlinkAll(this);
IsKeepDrag = true;
SetDirty();
}
public void DragLine(PointerEventData pointer)
{
Points = new Vector3[] { Anchor.localPosition, (pointer.pointerCurrentRaycast.worldPosition - Anchor.position) * ScaleFactor + Anchor.localPosition };
SetDirty();
}
public void EndDragLine(PointerEventData _)
{
IsKeepDrag = false;
if (CurrentHighLightSlot != null && CurrentHighLightSlot.info.IsInmappingSlot != this.info.IsInmappingSlot)
{
Link(this, CurrentHighLightSlot);
}
SetDirty();
DisableAllHighLight();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd0126d735f2225428b21f2121ecc526
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Convention.Workflow
{
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05e37045826234d469d8aa11593c65f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,162 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
using UnityEngine.EventSystems;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class EndNodeInfo : NodeInfo
{
protected override NodeInfo CreateTemplate()
{
return new EndNodeInfo();
}
}
public class EndNode : Node, INodeSlotLinkable
{
internal static List<EndNode> AllEndNodes = new();
// ContextBehaviour
public object end_result;
protected override void Start()
{
base.Start();
end_result = null;
AllEndNodes.Add(this);
var context = gameObject.GetOrAddComponent<BehaviourContextManager>();
context.OnPointerClickEvent = BehaviourContextManager.InitializeContextSingleEvent(context.OnPointerClickEvent, PointerRightClickAndOpenMenu);
}
protected override void OnDestroy()
{
base.OnDestroy();
AllEndNodes.Remove(this);
}
private Dictionary<string, PropertiesWindow.ItemEntry> m_dynamicSlots = new();
public override void PointerRightClickAndOpenMenu(PointerEventData pointer)
{
if (pointer.button == PointerEventData.InputButton.Right)
{
List<SharedModule.CallbackData> callbacks = new()
{
new (WorkflowManager.Transformer("Create New Slot"), x =>
{
SharedModule.instance.SingleEditString(
WorkflowManager.Transformer("SlotName"),
WorkflowManager.Transformer("SlotName"),
y => AddSlot(y,"string"));
}),
new (WorkflowManager.Transformer("Delete"), x =>
{
WorkflowManager.instance.DestroyNode(this);
})
};
SharedModule.instance.OpenCustomMenu(WorkflowManager.instance.UIFocusObject, callbacks.ToArray());
}
}
public bool AddSlot(string name, string typeIndicator)
{
if (this.m_Inmapping.ContainsKey(name))
return false;
var entry = CreateGraphNodeInSlots(1)[0];
RectTransform curEntryRect = entry.ref_value.transform as RectTransform;
this.m_Inmapping[name] = entry.ref_value.GetComponent<NodeSlot>();
this.m_Inmapping[name].SetupFromInfo(new NodeSlotInfo()
{
parentNode = this,
slotName = name,
typeIndicator = typeIndicator,
IsInmappingSlot = true
});
m_dynamicSlots.Add(name, entry);
this.rectTransform.sizeDelta = new Vector2(this.rectTransform.sizeDelta.x, this.rectTransform.sizeDelta.y + curEntryRect.rect.height);
ConventionUtility.CreateSteps().Wait(1f, () =>
{
foreach (var (key, slot) in this.m_Inmapping)
{
slot.SetDirty();
}
}).Invoke();
return true;
}
public bool RemoveSlot(string name)
{
if (this.m_Inmapping.ContainsKey(name) == false)
return false;
this.m_Inmapping.Remove(name);
RectTransform curEntryRect = m_dynamicSlots[name].ref_value.transform as RectTransform;
this.rectTransform.sizeDelta = new Vector2(this.rectTransform.sizeDelta.x, this.rectTransform.sizeDelta.y - curEntryRect.rect.height);
m_dynamicSlots[name].Release();
m_dynamicSlots.Remove(name);
ConventionUtility.CreateSteps().Next(() =>
{
foreach (var (key, slot) in this.m_Inmapping)
{
slot.SetDirty();
}
}).Invoke();
return true;
}
public bool LinkTo([In, Opt] NodeSlot other)
{
if (Linkable(other))
{
AddSlot(other.info.slotName, other.info.typeIndicator);
var slot = m_dynamicSlots[other.info.slotName].ref_value.GetComponent<NodeSlot>();
if (slot.Linkable(other))
{
slot.LinkTo(other);
return true;
}
}
return false;
}
public bool Linkable([In] NodeSlot other)
{
return other != null && other.info.IsInmappingSlot == false;
}
[return: ReturnMayNull]
public GameObject GetExtensionModule(string slotName)
{
if (this.m_Inmapping.ContainsKey(slotName))
{
return this.m_Inmapping[slotName].ExtensionModule;
}
return null;
}
[return: ReturnMayNull]
public T GetExtensionModule<T>(string slotName) where T : Component
{
if (this.m_Inmapping.ContainsKey(slotName))
{
var go = this.m_Inmapping[slotName].ExtensionModule;
if (go != null)
{
return ConventionUtility.SeekComponent<T>(go);
}
}
return null;
}
public List<string> GetAllInslotNames()
{
return m_Inmapping.Keys.ToList();
}
public bool ContainsInslot(string slotName)
{
return m_Inmapping.ContainsKey(slotName);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 227064b71f46ce54aba01ed5a07a6da9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,75 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class ResourceNodeInfo : StartNodeInfo
{
[NonSerialized] private string l_resource = WorkflowManager.Transformer(nameof(resource));
[InspectorDraw(InspectorDrawType.Text, true, true, nameGenerater: nameof(l_resource))]
public string resource = "unknown";
public ResourceNodeInfo() : this("") { }
public ResourceNodeInfo(string resource, string outmappingName = "value")
{
this.resource = resource;
this.outmapping = new()
{
{
outmappingName, new NodeSlotInfo()
{
slotName = outmappingName,
typeIndicator = "string",
IsInmappingSlot = false,
}
}
};
this.inmapping = new();
this.title = "Resource";
}
protected override NodeInfo CreateTemplate()
{
return new ResourceNodeInfo();
}
protected override void CloneValues([In] NodeInfo clonen)
{
((ResourceNodeInfo)clonen).resource = this.resource;
base.CloneValues(clonen);
}
}
public class ResourceNode : StartNode, IText
{
[Resources, OnlyNotNullMode] public ModernUIInputField InputField;
[Content, OnlyPlayMode] public bool isEditing = false;
public ResourceNodeInfo MyResourceNodeInfo => this.info as ResourceNodeInfo;
public string text { get => ((IText)this.InputField).text; set => ((IText)this.InputField).text = value; }
protected override void Start()
{
base.Start();
InputField.InputFieldSource.Source.onSelect.AddListener(_ => isEditing = true);
InputField.InputFieldSource.Source.onEndEdit.AddListener(str =>
{
MyResourceNodeInfo.resource = str;
isEditing = false;
});
}
private void LateUpdate()
{
if (info != null && this.isEditing == false && RectTransformExtension.IsVisible(this.rectTransform))
{
this.text = this.MyResourceNodeInfo.resource;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24bfe76a1dd2f914c8222f30cf7e61b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
namespace Convention.Workflow
{
public class SelectorNodeInfo : StartNodeInfo
{
public virtual IEnumerable<string> EnumNamesGenerater()
{
return new List<string>()
{
WorkflowManager.Transformer("unknown")
};
}
[NonSerialized] private string l_select = WorkflowManager.Transformer(nameof(select));
[InspectorDraw(InspectorDrawType.Enum, true, false, nameGenerater: nameof(l_select), enumGenerater: nameof(EnumNamesGenerater))]
public string select = "";
public SelectorNodeInfo() : this("") { }
public SelectorNodeInfo(string select, string outputName = "select")
{
this.select = select;
this.outmapping = new()
{
{
outputName, new NodeSlotInfo()
{
slotName = outputName,
typeIndicator="string",
IsInmappingSlot=false
}
}
};
this.inmapping = new();
this.title = "Selector";
}
protected override NodeInfo CreateTemplate()
{
return new SelectorNodeInfo();
}
protected override void CloneValues([In] NodeInfo clonen)
{
var info = (SelectorNodeInfo)clonen;
info.select = select;
info.outmapping = new(outmapping);
base.CloneValues(clonen);
}
}
public class SelectorNode : StartNode
{
[Resources, OnlyNotNullMode,SerializeField] private ModernUIDropdown DropDown;
public SelectorNodeInfo MySelectorInfo => info as SelectorNodeInfo;
protected override void Start()
{
base.Start();
DropDown.AddListener(x => MySelectorInfo.select = x);
}
protected virtual void RebuildDropDown(ModernUIDropdown dropdown, SelectorNodeInfo info)
{
foreach (var name in info.EnumNamesGenerater())
{
dropdown.CreateOption(name);
}
dropdown.RefreshImmediate();
if (string.IsNullOrEmpty(info.select) == false)
dropdown.Select(info.select);
}
protected override void WhenSetup(NodeInfo info)
{
RebuildDropDown(DropDown, info as SelectorNodeInfo);
base.WhenSetup(info);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3f9707e651688bd4f89ece4047a68b95
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
using System.Linq;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
namespace Convention.Workflow
{
public class TextNodeInfo : StartNodeInfo
{
private string l_text => WorkflowManager.Transformer(nameof(text));
[InspectorDraw(InspectorDrawType.Text, nameGenerater: nameof(l_text))]
public string text;
public TextNodeInfo() : this("") { }
public TextNodeInfo(string text, string outmappingName = "text")
{
this.text = text;
this.outmapping = new()
{
{
outmappingName, new NodeSlotInfo()
{
slotName = outmappingName,
typeIndicator = "string",
IsInmappingSlot = false,
}
}
};
this.inmapping = new();
this.title = "Text";
}
protected override NodeInfo CreateTemplate()
{
return new TextNodeInfo();
}
protected override void CloneValues([In] NodeInfo clonen)
{
((TextNodeInfo)clonen).text = text;
base.CloneValues(clonen);
}
}
public class TextNode : StartNode, IText
{
[Resources, OnlyNotNullMode] public ModernUIInputField InputField;
[Content, OnlyPlayMode] public bool isEditing = false;
public TextNodeInfo MyTextNodeInfo => this.info as TextNodeInfo;
public string text { get => ((IText)this.InputField).text; set => ((IText)this.InputField).text = value; }
protected override void Start()
{
base.Start();
InputField.InputFieldSource.Source.onSelect.AddListener(_ => isEditing = true);
InputField.InputFieldSource.Source.onEndEdit.AddListener(str =>
{
MyTextNodeInfo.text = str;
isEditing = false;
});
}
private void LateUpdate()
{
if (info != null && this.isEditing == false && RectTransformExtension.IsVisible(this.rectTransform))
{
this.text = this.MyTextNodeInfo.text;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eaab2d787a0d66642a89d8b957606de5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System;
using System.Linq;
using Convention.WindowsUI;
using Convention.WindowsUI.Variant;
using UnityEngine;
namespace Convention.Workflow
{
public class ValueNodeInfo : StartNodeInfo
{
[NonSerialized] private string l_value = WorkflowManager.Transformer(nameof(value));
[InspectorDraw(InspectorDrawType.Text, true, true, nameGenerater: nameof(l_value))]
public float value = 0;
[NonSerialized] private string l_min = WorkflowManager.Transformer(nameof(min));
[InspectorDraw(InspectorDrawType.Auto, true, true, nameGenerater: nameof(l_min))]
public float min = 0;
[NonSerialized]private string l_max = WorkflowManager.Transformer(nameof(max));
[InspectorDraw(InspectorDrawType.Auto, true, true, nameGenerater: nameof(l_max))]
public float max = 1;
public ValueNodeInfo() : this(0) { }
public ValueNodeInfo(float value, string outmappingName = "value")
{
this.value = value;
this.outmapping = new()
{
{
outmappingName, new NodeSlotInfo()
{
slotName = outmappingName,
typeIndicator = "float",
IsInmappingSlot = false,
}
}
};
this.inmapping = new();
this.title = "Value";
}
protected override NodeInfo CreateTemplate()
{
return new ValueNodeInfo();
}
protected override void CloneValues([In] NodeInfo clonen)
{
var info = ((ValueNodeInfo)clonen);
info.value = value;
info.min = min;
info.max = max;
base.CloneValues(clonen);
}
}
public class ValueNode : StartNode, IText
{
[Resources, OnlyNotNullMode] public ModernUIInputField InputField;
[Resources, OnlyNotNullMode] public ModernUIFillBar RangeBar;
[Content, OnlyPlayMode] public bool isEditing = false;
public ValueNodeInfo MyValueNodeInfo => this.info as ValueNodeInfo;
public string text { get => ((IText)this.InputField).text; set => ((IText)this.InputField).text = value; }
protected override void Start()
{
base.Start();
InputField.InputFieldSource.Source.onSelect.AddListener(_ => isEditing = true);
InputField.InputFieldSource.Source.onEndEdit.AddListener(str =>
{
if (float.TryParse(str, out float value))
MyValueNodeInfo.value = value;
else
MyValueNodeInfo.value = 0;
isEditing = false;
});
}
protected override void WhenSetup(NodeInfo info)
{
base.WhenSetup(info);
RangeBar.minValue = MyValueNodeInfo.min;
RangeBar.maxValue = MyValueNodeInfo.max;
RangeBar.SetValue(MyValueNodeInfo.value);
}
private void LateUpdate()
{
if (info != null && this.isEditing == false && RectTransformExtension.IsVisible(this.rectTransform))
{
RangeBar.minValue = MyValueNodeInfo.min;
RangeBar.maxValue = MyValueNodeInfo.max;
RangeBar.SetValue(MyValueNodeInfo.value);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8404795928101e843ad4d836cea5c1f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,332 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: OnEntryRunWorkflow
serializedVersion: 7
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves:
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.083333336
value: 0.5
inSlope: 3.6
outSlope: 3.6
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.25
value: 0.9
inSlope: 0.43636376
outSlope: 0.43636376
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_Alpha
path: UI Canvas/AnimationPlane
classID: 225
script: {fileID: 0}
flags: 0
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.25
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.41666666
value: 100
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_SizeDelta.y
path: UI Canvas/AnimationPlane/Shadow/Image
classID: 224
script: {fileID: 0}
flags: 0
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 0.016666668
value: 1
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_BlocksRaycasts
path: UI Canvas/AnimationPlane
classID: 225
script: {fileID: 0}
flags: 0
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 4103470125
attribute: 1574349066
script: {fileID: 0}
typeID: 225
customType: 0
isPPtrCurve: 0
isIntCurve: 0
isSerializeReferenceCurve: 0
- serializedVersion: 2
path: 355489497
attribute: 38095219
script: {fileID: 0}
typeID: 224
customType: 28
isPPtrCurve: 0
isIntCurve: 0
isSerializeReferenceCurve: 0
- serializedVersion: 2
path: 4103470125
attribute: 3739863151
script: {fileID: 0}
typeID: 225
customType: 0
isPPtrCurve: 0
isIntCurve: 0
isSerializeReferenceCurve: 0
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves:
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.083333336
value: 0.5
inSlope: 3.6
outSlope: 3.6
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.25
value: 0.9
inSlope: 0.43636376
outSlope: 0.43636376
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 1
value: 1
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_Alpha
path: UI Canvas/AnimationPlane
classID: 225
script: {fileID: 0}
flags: 0
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.25
value: 0
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
- serializedVersion: 3
time: 0.41666666
value: 100
inSlope: 0
outSlope: 0
tangentMode: 136
weightedMode: 0
inWeight: 0.33333334
outWeight: 0.33333334
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_SizeDelta.y
path: UI Canvas/AnimationPlane/Shadow/Image
classID: 224
script: {fileID: 0}
flags: 0
- serializedVersion: 2
curve:
serializedVersion: 2
m_Curve:
- serializedVersion: 3
time: 0
value: 0
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 0.016666668
value: 1
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
- serializedVersion: 3
time: 1
value: 1
inSlope: Infinity
outSlope: Infinity
tangentMode: 103
weightedMode: 0
inWeight: 0
outWeight: 0
m_PreInfinity: 2
m_PostInfinity: 2
m_RotationOrder: 4
attribute: m_BlocksRaycasts
path: UI Canvas/AnimationPlane
classID: 225
script: {fileID: 0}
flags: 0
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cbeef9f55988bc645a8bcf751e8779f4
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using System;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class StartNodeInfo : NodeInfo
{
protected override NodeInfo CreateTemplate()
{
return new StartNodeInfo();
}
}
public class StartNode : Node
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea9e2ae1ef3fbf441aaf9fe79a3639c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Convention.WindowsUI;
using UnityEngine;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class StepNodeInfo : NodeInfo
{
public string module = "global";
public string funcname = "";
protected override NodeInfo CreateTemplate()
{
return new StepNodeInfo();
}
protected override void CloneValues([In] NodeInfo clonen)
{
var info = (StepNodeInfo)clonen;
info.module = module;
info.funcname = funcname;
base.CloneValues(clonen);
}
}
public class StepNode : Node
{
[Resources, OnlyNotNullMode] public ModernUIDropdown FunctionSelector;
public StepNodeInfo MyStepInfo => info as StepNodeInfo;
private void ClearSelector()
{
FunctionSelector.ClearOptions();
}
private void OnEnable()
{
if (WorkflowManager.instance == null)
return;
ClearSelector();
var names = WorkflowManager.instance.GetAllModuleName();
if (names.Count > 0)
{
SelectModel(names);
}
else
{
FunctionSelector.CreateOption(WorkflowManager.Transformer("No Module Registered"));
}
}
private void SelectModel(List<string> names)
{
foreach (var moduleName in names)
{
this.FunctionSelector.CreateOption(WorkflowManager.Transformer(moduleName)).toggleEvents.AddListener(x =>
{
if (x)
{
ClearSelector();
SelectFunctionModel(moduleName);
}
});
}
this.FunctionSelector.RefreshImmediate();
}
private void SelectFunctionModel(string moduleName)
{
foreach (var funcModel in WorkflowManager.instance.GetAllFunctionModel(moduleName))
{
this.FunctionSelector.CreateOption(WorkflowManager.Transformer(funcModel.name)).toggleEvents.AddListener(y =>
{
if (y)
{
SetupWhenFunctionNameCatch(funcModel);
}
});
}
this.FunctionSelector.RefreshImmediate();
}
public void SetupWhenFunctionNameCatch(FunctionModel funcModel)
{
var oriExtensionHeight = this.ExtensionHeight;
this.ExtensionHeight = 0;
this.MyStepInfo.module = funcModel.module;
this.MyStepInfo.funcname = funcModel.name;
this.MyStepInfo.inmapping = new();
if (this.MyStepInfo.title == this.MyStepInfo.GetType().Name[..^4])
this.MyStepInfo.title = funcModel.name;
foreach (var (name, type) in funcModel.parameters)
{
this.MyStepInfo.inmapping[name] = new NodeSlotInfo()
{
slotName = name,
typeIndicator = type,
IsInmappingSlot = true
};
}
this.MyStepInfo.outmapping = new();
foreach (var (name, type) in funcModel.returns)
{
this.MyStepInfo.outmapping[name] = new NodeSlotInfo()
{
slotName = name,
typeIndicator = type,
IsInmappingSlot = false
};
}
this.FunctionSelector.gameObject.SetActive(false);
this.ExtensionHeight = 0;
this.ClearLink();
this.ClearSlots();
this.BuildSlots();
this.BuildLink();
this.InoutContainerPlane.rectTransform.sizeDelta = new Vector2(
this.InoutContainerPlane.rectTransform.sizeDelta.x,
this.InoutContainerPlane.rectTransform.sizeDelta.y + oriExtensionHeight
);
this.RefreshRectTransform();
}
protected override void WhenSetup(NodeInfo info)
{
base.WhenSetup(info);
if (string.IsNullOrEmpty(MyStepInfo.funcname) == false)
{
SetupWhenFunctionNameCatch(WorkflowManager.instance.GetFunctionModel(MyStepInfo.module, MyStepInfo.funcname));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c3a9d5b6db27ae4bb28554ecc681b6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,130 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1107 &-8138631149079467525
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: -4332790445053841364}
m_Position: {x: 400, y: 70, z: 0}
- serializedVersion: 1
m_State: {fileID: -2159031066727738487}
m_Position: {x: 50, y: 170, z: 0}
- serializedVersion: 1
m_State: {fileID: -5526377917478383796}
m_Position: {x: 400, y: 120, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 60, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: -2159031066727738487}
--- !u!1102 &-5526377917478383796
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: OnQuitRunWorkflow
m_Speed: -1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: cbeef9f55988bc645a8bcf751e8779f4, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-4332790445053841364
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: OnEntryRunWorkflow
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: cbeef9f55988bc645a8bcf751e8779f4, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-2159031066727738487
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: New State
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 0}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: WorkflowGraph
serializedVersion: 5
m_AnimatorParameters: []
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -8138631149079467525}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4891df624d2e69241bb634ec11c36645
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,383 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Convention.WindowsUI.Variant;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace Convention.Workflow
{
[Serializable, ArgPackage]
public class NodeResult
{
public int nodeID;
public string nodeTitle;
public Dictionary<string, string> result;
}
[Serializable, ArgPackage]
public class ContextResult
{
public string hashID;
public List<NodeResult> results;
public float progress;
public int task_count;
public List<int> current_running_nodes;
}
[Serializable, ArgPackage]
public class FunctionModel
{
public string name = "unknown";
public string description = "unknown";
public Dictionary<string, string> parameters = new();
public Dictionary<string, string> returns = new();
public string module = "global";
}
[Serializable, ArgPackage]
public class Workflow
{
public List<NodeInfo> Datas = new();
[NonSerialized] public List<Node> Nodes = new();
public List<FunctionModel> Functions = new();
}
public class WorkflowManager : MonoSingleton<WorkflowManager>
{
public static float CameraZ = 0;
[Content] public Workflow m_workflow;
public Workflow workflow
{
get
{
return m_workflow;
}
//set
//{
// if (m_workflow == value)
// return;
// ClearWorkflowGraph();
// m_workflow = value;
// BuildWorkflowGraph();
//}
}
[Resources, OnlyNotNullMode, SerializeField] private Transform m_CameraTransform;
[Setting] public float ScrollSpeed = 1;
[Setting, OnlyNotNullMode] public ScriptableObject GraphNodePrefabs;
[Setting, HopeNotNull] public ScriptableObject TextLabels;
//[Resources, SerializeField, OnlyNotNullMode, Header("Prefabs")]
//private GameObject GraphNodePrefab;
[Resources, SerializeField, OnlyNotNullMode, Header("Content")]
public RectTransform ContentPlane;
[Resources, SerializeField, OnlyNotNullMode, Header("Mouse Click")]
public RectTransform focusObject;
[SerializeField, OnlyNotNullMode]
public RectTransform UIFocusObject;
private List<SharedModule.CallbackData> callbackDatas = new();
private HashSet<Type> registeredCallbackNodeType = new();
public Dictionary<string, List<FunctionModel>> CallableFunctionModels = new();
public void RegisterFunctionModel([In] FunctionModel func)
{
if (!CallableFunctionModels.ContainsKey(func.module))
CallableFunctionModels[func.module] = new();
CallableFunctionModels[func.module].Add(func);
Debug.Log($"[{String.Join(", ", func.returns.ToList().ConvertAll(x => $"{x.Key}: {x.Value}"))}] {func.name}" +
$"({String.Join(", ", func.parameters.ToList().ConvertAll(x => $"{x.Key}: {x.Value}"))}): {func.description}");
}
public void UnregisterFunctionModel([In] FunctionModel func)
{
CallableFunctionModels[func.module].Remove(func);
}
public List<string> GetAllModuleName()
{
return CallableFunctionModels.Keys.ToList();
}
public List<string> GetAllFunctionName(string module)
{
return CallableFunctionModels[module].ConvertAll(x => x.name);
}
public bool ContainsModule(string module)
{
return CallableFunctionModels.ContainsKey(module);
}
public bool ContainsFunctionModel(string module, string functionName)
{
return ContainsModule(module) && CallableFunctionModels[module].Any(y => y.name == functionName);
}
public List<FunctionModel> GetAllFunctionModel(string module)
{
return CallableFunctionModels[module];
}
[return: ReturnMayNull]
public FunctionModel GetFunctionModel(string module,string functionName)
{
if (ContainsModule(module))
return CallableFunctionModels[module].FirstOrDefault(x => x.name == functionName);
return null;
}
public delegate bool CustomTransformer([In] string word, out string late);
public List<CustomTransformer> customTransformers = new();
public static string Transformer([In] string str)
{
if (string.IsNullOrEmpty(str))
return str;
if (instance != null)
{
foreach (var customTransformer in instance.customTransformers)
{
if (customTransformer(str, out var result))
{
var menuButton = instance.callbackDatas.Find(x => x.name == str);
if (menuButton != null)
{
menuButton.name = result;
}
return result;
}
}
if (instance.TextLabels == null)
return str;
if (instance.TextLabels.symbols.ContainsKey(str))
return instance.TextLabels.symbols[str];
}
return str;
}
public void SetupWorkflowGraphNodeType([In] SharedModule.CallbackData callback)
{
callbackDatas.Add(callback);
}
public void SetupWorkflowGraphNodeType(params NodeInfo[] templates)
{
foreach (NodeInfo nodeInfo in templates)
{
if (registeredCallbackNodeType.Contains(nodeInfo.GetType()))
continue;
registeredCallbackNodeType.Add(nodeInfo.GetType());
string label = nodeInfo.GetType().Name;
if (label.EndsWith("Info"))
label = label[..^4];
SetupWorkflowGraphNodeType(new SharedModule.CallbackData(Transformer(label), x =>
{
var info = nodeInfo.TemplateClone();
var node = this.CreateGraphNode(info);
var pos = focusObject.position;
node.transform.position = new Vector2(pos.x, pos.y);
}));
}
}
private void Start()
{
callbackDatas = new();
Architecture.RegisterWithDuplicateAllow(typeof(WorkflowManager), this, () =>
{
Debug.Log($"{nameof(WorkflowManager)} registered");
if (GraphNodePrefabs == null)
GraphNodePrefabs = Resources.Load<ScriptableObject>("Workflow/Nodes");
SetupWorkflowGraphNodeType(
new TextNodeInfo(),
new ValueNodeInfo(),
new ResourceNodeInfo(),
new StepNodeInfo(),
new EndNodeInfo());
}, typeof(GraphInputWindow), typeof(GraphInspector));
}
private void Update()
{
if (Keyboard.current[Key.LeftCtrl].isPressed)
{
var t = -Mouse.current.scroll.y.ReadValue() * ScrollSpeed * 0.001f;
var z = m_CameraTransform.transform.localPosition.z;
if (z - t > -100 && z - t < -5)
m_CameraTransform.transform.Translate(new Vector3(0, 0, -t), Space.Self);
}
UIFocusObject.position = Mouse.current.position.ReadValue();
}
private void LateUpdate()
{
CameraZ = Camera.main.transform.position.z;
}
public void RefreshImmediate()
{
foreach (var node in workflow.Nodes)
{
node.RefreshImmediate();
}
}
public void ClearWorkflowGraph()
{
foreach (var node in workflow.Nodes)
{
GameObject.Destroy(node.gameObject);
}
workflow.Nodes.Clear();
workflow.Datas.Clear();
}
public void BuildWorkflowGraph()
{
foreach (var info in workflow.Datas)
{
CreateGraphNode(info);
}
}
public Node CreateGraphNode([In] NodeInfo info, bool isRefresh = true)
{
var node = info.Instantiate();
node.gameObject.SetActive(true);
node.transform.SetParent(ContentPlane);
node.transform.localScale = Vector3.one;
node.transform.eulerAngles = Vector3.zero;
node.SetupFromInfo(info.TemplateClone(), isRefresh);
workflow.Nodes.Add(node);
node.MyNodeTab = GraphInputWindow.instance.RegisterOnHierarchyWindow(node.info);
return node;
}
public void DestroyNode(Node node)
{
if (!workflow.Nodes.Remove(node))
{
throw new InvalidOperationException("node is not in current workflow");
}
GameObject.Destroy(node.gameObject);
}
public bool ContainsNode(int id)
{
if (id < 0)
return false;
return workflow.Nodes.Count < id;
}
public Node GetGraphNode(int id)
{
if (id < 0)
return null;
return workflow.Nodes[id];
}
public int GetGraphNodeID(Node node)
{
if (node == null)
return -1;
return workflow.Nodes.IndexOf(node);
}
public void SaveWorkflowWithSystemPlugin()
{
var str = PluginExtenion.SaveFile("工作流|*.workflow;*.json", "保存工作流");
if (string.IsNullOrEmpty(str) == false)
SaveWorkflow(str);
}
public void LoadWorkflowWithSystemPlugin()
{
var str = PluginExtenion.SelectFile("工作流|*.workflow;*.json", "加载工作流");
if (string.IsNullOrEmpty(str) == false)
LoadWorkflow(str);
}
[Content, OnlyPlayMode] public string LastSavePath = null;
public static string WorkflowFileKey = "workflow";
public ToolFile SaveWorkflow(string workflowPath)
{
LastSavePath = workflowPath;
ToolFile local = new(workflowPath);
ToolFile parent = local.GetParentDir();
if (parent.Exists() == false)
throw new FileNotFoundException($"{parent} is not exist");
workflow.Datas.Clear();
Debug.Log(workflow.Nodes.Count);
foreach (var node in workflow.Nodes)
{
node.info.CopyFromNode(node);
workflow.Datas.Add(node.info);
}
local.SaveAsJson(workflow, WorkflowFileKey);
return local;
}
public Workflow LoadWorkflow(Workflow workflow)
{
ClearWorkflowGraph();
workflow.Datas.Sort((x, y) => x.nodeID.CompareTo(y.nodeID));
for (int i = 0; i < workflow.Datas.Count; i++)
{
if (workflow.Datas[i].nodeID != i)
throw new InvalidOperationException("Bad workflow: nodeID != node index");
}
this.m_workflow = new();
foreach (var info in workflow.Datas)
{
var node = CreateGraphNode(info, false);
this.workflow.Datas.Add(node.info);
node.ClearSlots();
node.BuildSlots();
}
ConventionUtility.CreateSteps().Next(() =>
{
for (int i = 0; i < workflow.Datas.Count; i++)
{
var info = workflow.Datas[i];
var node = GetGraphNode(i);
foreach (var (key, slot) in info.inmapping)
{
if (slot.targetNodeID != -1)
this.workflow.Nodes[i].LinkInslotToOtherNodeOutslot(GetGraphNode(slot.targetNodeID), slot.slotName, slot.targetSlotName);
}
node.RefreshRectTransform();
node.RefreshPosition();
}
}).Wait(0.1f, () =>
{
foreach (var node in this.workflow.Nodes)
{
node.RefreshImmediate();
}
}).Wait(1f, () =>
{
foreach (var node in this.workflow.Nodes)
{
node.RefreshImmediate();
}
}).Invoke();
Debug.Log($"Current Workflow Nodes: count={this.workflow.Nodes.Count}");
return this.workflow;
}
public Workflow LoadWorkflow(string workflowPath)
{
ToolFile local = new(workflowPath);
if (local.Exists() == false)
throw new FileNotFoundException($"{local} is not exist");
LastSavePath = workflowPath;
var loadedWorkflow = local.LoadAsJson<Workflow>(WorkflowFileKey);
return LoadWorkflow(loadedWorkflow);
}
public void OpenMenu(PointerEventData data)
{
focusObject.position = data.pointerCurrentRaycast.worldPosition;
#if UNITY_EDITOR
if (callbackDatas.Count == 0)
SharedModule.instance.OpenCustomMenu(UIFocusObject, new SharedModule.CallbackData("Empty", x => Debug.Log(x)));
else
#endif
{
foreach (var callbackData in callbackDatas)
{
Transformer(callbackData.name);
}
SharedModule.instance.OpenCustomMenu(UIFocusObject, callbackDatas.ToArray());
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd45ab603848a8147a3b02553e5bc1ba
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: