This commit is contained in:
陈思海 2025-02-07 16:05:13 +08:00
commit 11e5744b46
113 changed files with 3841 additions and 0 deletions

8
Editor.meta Normal file
View File

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

View File

@ -0,0 +1,18 @@
{
"name": "AlicizaX.AnimationFlow.Editor",
"rootNamespace": "AlicizaX.AnimationFlow.Editor",
"references": [
"GUID:189d55e03d78888459720d730f4d2424"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: da025bffb2b9d1344b59cf03601655ea
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Editor/Data.meta Normal file
View File

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

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
using AlicizaX.AnimationFlow.Runtime;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Editor {
public class AnimationFlowSerialize {
public string flag;
[SerializeReference]
public List<ActionNode> nodes = new();
}
}

View File

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

8
Editor/Graph.meta Normal file
View File

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

367
Editor/Graph/GraphView.cs Normal file
View File

@ -0,0 +1,367 @@
using System;
using System.Collections.Generic;
using AlicizaX.AnimationFlow.Runtime;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.AnimationFlow.Editor
{
public class GraphView : UnityEditor.Experimental.GraphView.GraphView
{
private readonly SearchWindowProvider searchWindowProvider;
private readonly GraphWindow graphWindow;
private readonly Dictionary<string, NodeView> dicNodeView = new();
private Runtime.AnimationFlow animationFlow;
private const string applicationFlag = "@AnimationFlow";
private readonly Dictionary<string, ActionNode> pendingNodes = new();
public GraphView(GraphWindow graphWindow) : base()
{
this.graphWindow = graphWindow;
InitView();
SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());
this.AddManipulator(new ContentDragger());
searchWindowProvider = ScriptableObject.CreateInstance<SearchWindowProvider>();
searchWindowProvider.Initialize(graphWindow, this);
SetCopyAndPaste();
graphViewChanged += OnGraphViewChanged;
}
private void InitView()
{
this.StretchToParentSize();
Insert(0, new GridBackground());
AddStyles();
MiniMap miniMap = new();
Add(miniMap);
miniMap.style.backgroundColor = new Color(0.12f, 0.12f, 0.12f, 1f);
miniMap.style.borderBottomColor = new Color(0.2f, 0.2f, 0.2f, 1f);
miniMap.style.borderRightColor = new Color(0.2f, 0.2f, 0.2f, 1f);
miniMap.style.borderTopColor = new Color(0.2f, 0.2f, 0.2f, 1f);
miniMap.style.borderLeftColor = new Color(0.2f, 0.2f, 0.2f, 1f);
}
private void SetCopyAndPaste()
{
serializeGraphElements += (IEnumerable<GraphElement> elements) =>
{
AnimationFlowSerialize serialize = new();
serialize.flag = applicationFlag;
foreach (var ele in elements)
{
if (ele is NodeView nodeView)
{
serialize.nodes.Add(nodeView.actionNode);
}
}
return JsonUtility.ToJson(serialize);
};
canPasteSerializedData += (string data) => { return data.Contains(applicationFlag); };
unserializeAndPaste += (string operationName, string data) =>
{
ClearSelection();
Undo.RecordObject(animationFlow, operationName);
Vector2 mousePos = contentViewContainer.WorldToLocal(contentRect.center);
try
{
AnimationFlowSerialize serialize = JsonUtility.FromJson<AnimationFlowSerialize>(data);
List<ActionNode> newDatas = new();
Dictionary<string, ActionNode> dicUid = new();
foreach (ActionNode node in serialize.nodes)
{
node.nodePos = node.nodePos + mousePos;
string uuid = node.uuid;
node.uuid = Guid.NewGuid().ToString();
NodeView nodeView = AddNodeView(node);
AddToSelection(nodeView);
newDatas.Add(node);
dicUid.Add(uuid, node);
}
foreach (ActionNode copyNode in newDatas)
{
for (int i = copyNode.Childs.Count - 1; i >= 0; i--)
{
if (dicUid.TryGetValue(copyNode.Childs[i].uuid, out ActionNode newNode))
{
copyNode.Childs[i] = newNode;
if (dicNodeView.TryGetValue(copyNode.uuid, out NodeView inNode) && dicNodeView.TryGetValue(newNode.uuid, out NodeView outNode))
{
AddElement(inNode.portOut.ConnectTo(outNode.portIn));
}
}
else
{
copyNode.Childs.RemoveAt(i);
}
}
}
}
catch (Exception)
{
}
};
}
private void ResetEdge()
{
List<ActionNode> nodes = new List<ActionNode>();
foreach (var item in animationFlow.AnimationNodes)
{
nodes.Add(item);
}
List<ActionNode> allNodes = new List<ActionNode>();
LoadAllChildNode(nodes, allNodes);
foreach (ActionNode data in allNodes)
{
foreach (ActionNode node in allNodes)
{
if (data.Childs.Count > 0 && data.Childs.Find(a => a.uuid == node.uuid) != null)
{
if (dicNodeView.TryGetValue(data.uuid, out NodeView inNode) && dicNodeView.TryGetValue(node.uuid, out NodeView outNode))
{
AddElement(inNode.portOut.ConnectTo(outNode.portIn));
}
}
}
}
}
private NodeView ResetNodeView(ActionNode data)
{
NodeView nodeView = new(data, graphWindow);
AddElement(nodeView);
nodeView.SetPosition(new Rect(data.nodePos, Vector2.zero));
dicNodeView.Add(data.uuid, nodeView);
return nodeView;
}
private void RemoveNodeView(NodeView nodeView)
{
dicNodeView.Remove(nodeView.actionNode.uuid);
pendingNodes.Remove(nodeView.actionNode.uuid);
if (nodeView.actionNode is EntryNode)
{
animationFlow.AnimationNodes.Remove(nodeView.actionNode as EntryNode);
}
}
private void AddStyles()
{
StyleSheet styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(EditorResourceTool.editorAssets + "/AnimationFlowStyles.uss");
styleSheets.Add(styleSheet);
}
private GraphViewChange OnGraphViewChanged(GraphViewChange change)
{
change.edgesToCreate?.ForEach(edge =>
{
if (animationFlow == null)
{
ClearGraph();
return;
}
Undo.RecordObject(animationFlow, "Create Edge");
NodeView nodeIn = edge.input.node as NodeView;
NodeView nodeOut = edge.output.node as NodeView;
nodeOut.actionNode.Childs.Add(nodeIn.actionNode);
EditorUtility.SetDirty(animationFlow);
});
change.elementsToRemove?.ForEach(elem =>
{
if (animationFlow == null)
{
ClearGraph();
return;
}
if (elem is Edge edge)
{
Undo.RecordObject(animationFlow, "Rmove Edge");
NodeView nodeIn = edge.input.node as NodeView;
NodeView nodeOut = edge.output.node as NodeView;
nodeOut.actionNode.Childs.Remove(nodeIn.actionNode);
}
if (elem is NodeView node)
{
Undo.RecordObject(animationFlow, "Remove Node");
RemoveNodeView(node);
graphWindow.OnNodeRemove();
}
EditorUtility.SetDirty(animationFlow);
});
change.movedElements?.ForEach(elem =>
{
if (animationFlow == null)
{
ClearGraph();
return;
}
if (elem is NodeView nodeView)
{
Undo.RecordObject(animationFlow, "Move Node");
nodeView.actionNode.nodePos = nodeView.GetPosition().position;
}
EditorUtility.SetDirty(animationFlow);
});
return change;
}
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(evt.mousePosition)), searchWindowProvider);
}
public override List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)
{
List<Port> compatiblePorts = new();
ports.ForEach(port =>
{
if (startPort == port)
{
return;
}
if (startPort.node == port.node)
{
return;
}
if (startPort.direction == port.direction)
{
return;
}
if (startPort.portType != port.portType)
{
return;
}
compatiblePorts.Add(port);
});
return compatiblePorts;
}
protected override bool canDeleteSelection => base.canDeleteSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying;
protected override bool canCopySelection => base.canCopySelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying;
protected override bool canCutSelection => base.canCutSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying;
protected override bool canDuplicateSelection => base.canDuplicateSelection && !Application.isPlaying && animationFlow != null && !animationFlow.InPlaying;
protected override bool canPaste => base.canPaste && !Application.isPlaying && animationFlow != null;
public NodeView AddNodeView(ActionNode node)
{
if (animationFlow == null)
{
return null;
}
Undo.RecordObject(animationFlow, "Create Node");
if (node is EntryNode)
{
animationFlow.AnimationNodes.Add(node as EntryNode);
}
else
{
pendingNodes.Add(node.uuid, node);
}
return ResetNodeView(node);
}
public void ClearGraph()
{
graphElements.ForEach(g => RemoveElement(g));
dicNodeView.Clear();
animationFlow = null;
}
public void SetAnimationFlow(Runtime.AnimationFlow animationFlow)
{
this.animationFlow = animationFlow;
List<ActionNode> nodes = new List<ActionNode>();
foreach (var VARIABLE in animationFlow.AnimationNodes)
{
nodes.Add(VARIABLE);
}
List<ActionNode> allNodes = new List<ActionNode>();
LoadAllChildNode(nodes, allNodes);
foreach (ActionNode data in allNodes)
{
ResetNodeView(data);
}
ResetEdge();
}
private void LoadAllChildNode(List<ActionNode> nodes, List<ActionNode> collector)
{
foreach (var item in nodes)
{
collector.Add(item);
if (item.Childs.Count > 0) LoadAllChildNode(item.Childs, collector);
}
}
public void ResetView()
{
foreach (NodeView nodeView in dicNodeView.Values)
{
nodeView.RemoveProgress();
nodeView.RemoveComplete();
}
}
public void OnNodeEnter(ActionNode actionNode)
{
if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView))
{
nodeView.AddProgress();
}
}
public void OnNodeExit(ActionNode actionNode)
{
if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView))
{
nodeView.RemoveProgress();
nodeView.AddComplete();
}
}
public void OnNodeUpdate(ActionNode actionNode)
{
if (dicNodeView.TryGetValue(actionNode.uuid, out NodeView nodeView))
{
nodeView.UpdateProgress();
}
}
}
}

View File

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

123
Editor/Graph/NodeView.cs Normal file
View File

@ -0,0 +1,123 @@
using System;
using System.Linq;
using AlicizaX.AnimationFlow.Runtime;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
using static UnityEditor.Experimental.GraphView.Port;
namespace AlicizaX.AnimationFlow.Editor {
public class NodeView : Node {
public readonly ActionNode actionNode;
public Port portIn;
public Port portOut;
private readonly GraphWindow graphWindow;
private ProgressBar progressPlaying;
private Image imageComplete;
private Image imageWarning;
private Label sub_title;
public NodeView(ActionNode node, GraphWindow graphWindow) {
this.actionNode = node;
this.graphWindow = graphWindow;
Draw();
}
private void Draw() {
Type type = actionNode.GetType();
NameAttribute nameAttribute = type.GetCustomAttributes(typeof(NameAttribute), true).FirstOrDefault() as NameAttribute;
title = nameAttribute != null && !string.IsNullOrEmpty(nameAttribute.name) ? nameAttribute.name : type.Name;
if (actionNode.HasInputPort()) {
portIn = InstantiatePort(Orientation.Horizontal, Direction.Input, actionNode.MultiInputPort() ? Capacity.Multi : Capacity.Single, typeof(bool));
inputContainer.Add(portIn);
portIn.portName = "in";
}
if (actionNode.HasOutPort()) {
portOut = InstantiatePort(Orientation.Horizontal, Direction.Output, actionNode.MultiOutPort() ? Capacity.Multi : Capacity.Single, typeof(bool));
outputContainer.Add(portOut);
portOut.portName = "out";
}
titleContainer.Remove(titleButtonContainer);
imageComplete = new() {
image = AssetDatabase.LoadAssetAtPath<Texture2D>(EditorResourceTool.editorAssets + "/Complete.png"),
tintColor = new Color(0.4f, 0.7f, 0.2f),
style = { marginLeft = 3, marginRight = 10, visibility = Visibility.Hidden }
};
titleContainer.Add(imageComplete);
imageWarning = new() {
image = AssetDatabase.LoadAssetAtPath<Texture2D>(EditorResourceTool.editorAssets + "/Warning.png"),
tintColor = Color.red,
style = { position = Position.Absolute, right = 0, top = 5 }
};
titleContainer.Add(imageWarning);
RefreshState();
if (actionNode.HasSubTitle()) {
sub_title = new(actionNode.SubTitle()) {
style = { fontSize = 12, paddingLeft = 8, paddingBottom = 5, backgroundColor = new Color(0.23f, 0.23f, 0.23f, 1f) }
};
this.Q<VisualElement>("node-border").Insert(1, sub_title);
}
this.Q<VisualElement>("title").style.height = 25;
}
public void RefreshState() {
imageWarning.style.visibility = actionNode.Valid() ? Visibility.Hidden : Visibility.Visible;
if (sub_title != null) {
if (string.IsNullOrEmpty(actionNode.SubTitle())) {
sub_title.text = "<Empty>";
sub_title.style.color = Color.red;
} else {
sub_title.text = actionNode.SubTitle();
sub_title.style.color = new Color(0.82f, 0.82f, 0.82f, 1f);
}
}
}
public void AddComplete() {
imageComplete.style.visibility = Visibility.Visible;
}
public void RemoveComplete() {
imageComplete.style.visibility = Visibility.Hidden;
}
public void AddProgress() {
if (actionNode.Duration() == 0) {
return;
}
progressPlaying = new();
progressPlaying.style.height = 10;
contentContainer.Add(progressPlaying);
progressPlaying.highValue = actionNode.Duration();
progressPlaying.value = 0;
RefreshExpandedState();
}
public void UpdateProgress() {
if (actionNode.Duration() == 0) {
return;
}
if (progressPlaying == null) {
AddProgress();
}
progressPlaying.value = actionNode.elapsedTime;
}
public void RemoveProgress() {
if (progressPlaying == null) {
return;
}
contentContainer.Remove(progressPlaying);
progressPlaying = null;
RefreshExpandedState();
}
public override void OnSelected() {
base.OnSelected();
graphWindow.OnSelect(this);
}
}
}

View File

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

View File

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.AnimationFlow.Runtime;
using UnityEditor.Experimental.GraphView;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.AnimationFlow.Editor {
public class SearchWindowProvider : ScriptableObject, ISearchWindowProvider {
private GraphWindow graphWindow;
private GraphView graphView;
private Texture2D icon;
public void Initialize(GraphWindow graphWindow, GraphView graphView) {
this.graphWindow = graphWindow;
this.graphView = graphView;
icon = new Texture2D(1, 1);
icon.SetPixel(0, 0, Color.clear);
icon.Apply();
}
List<SearchTreeEntry> ISearchWindowProvider.CreateSearchTree(SearchWindowContext context) {
List<SearchTreeEntry> entries = new() {
new SearchTreeGroupEntry(new GUIContent("Create Node",icon))
};
Dictionary<string, List<Type>> dicType = new();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
foreach (var type in assembly.GetTypes()) {
if (type.IsClass && !type.IsAbstract && type.IsSubclassOf(typeof(ActionNode))) {
CategoryAttribute categoryAttribute = type.GetCustomAttributes(typeof(CategoryAttribute), true).FirstOrDefault() as CategoryAttribute;
if (categoryAttribute == null || string.IsNullOrEmpty(categoryAttribute.category)) {
NameAttribute nameAttribute = type.GetCustomAttributes(typeof(NameAttribute), true).FirstOrDefault() as NameAttribute;
entries.Add(new SearchTreeEntry(new GUIContent(nameAttribute != null && !string.IsNullOrEmpty(nameAttribute.name) ? nameAttribute.name : type.Name, icon)) { level = 1, userData = type });
} else {
if (dicType.TryGetValue(categoryAttribute.category, out var list)) {
list.Add(type);
} else {
dicType.Add(categoryAttribute.category, new List<Type>() { type });
}
}
}
}
}
foreach (var kv in dicType) {
entries.Add(new SearchTreeGroupEntry(new GUIContent(kv.Key), 1));
foreach (Type type in kv.Value) {
NameAttribute nameAttribute = type.GetCustomAttributes(typeof(NameAttribute), true).FirstOrDefault() as NameAttribute;
entries.Add(new SearchTreeEntry(new GUIContent(nameAttribute != null && !string.IsNullOrEmpty(nameAttribute.name) ? nameAttribute.name : type.Name, icon)) { level = 2, userData = type });
}
}
return entries;
}
bool ISearchWindowProvider.OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) {
var type = searchTreeEntry.userData as Type;
var data = Activator.CreateInstance(type) as ActionNode;
data.uuid = Guid.NewGuid().ToString();
data.nodePos = graphView.contentViewContainer.WorldToLocal(context.screenMousePosition - graphWindow.position.position);
graphView.AddNodeView(data);
return true;
}
}
}

View File

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

8
Editor/Inspector.meta Normal file
View File

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

View File

@ -0,0 +1,45 @@
using System;
using System.Linq;
using AlicizaX.AnimationFlow.Runtime;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.AnimationFlow.Editor
{
public class GraphInspector : VisualElement
{
private PropertyTree propertyTree;
public void DrawNode(NodeView nodeView)
{
Clear();
if (nodeView.actionNode == null)
{
return;
}
// 使用 Odin PropertyTree 来处理非 UnityEngine.Object 对象
propertyTree = PropertyTree.Create(nodeView.actionNode);
var imguiContainer = new IMGUIContainer(() =>
{
if (propertyTree == null)
{
return;
}
propertyTree.Draw(false);
// 手动调用 Odin 的 Apply处理数据更新后的逻辑
if (propertyTree.ApplyChanges())
{
nodeView.RefreshState();
}
});
Add(imguiContainer);
propertyTree.Dispose();
}
}
}

View File

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

8
Editor/Misc.meta Normal file
View File

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

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Editor {
public static class EditorResourceTool {
public static string editorAssets;
public static bool LocateEditorAssets() {
string projectPath = Application.dataPath;
if (projectPath.EndsWith("/Assets")) {
projectPath = projectPath.Remove(projectPath.Length - ("Assets".Length));
}
editorAssets = "Packages/com.yuliuren.alicizaframework/ThirdParty/AnimationFlow/Editor/Styles";
if (!System.IO.File.Exists(projectPath + editorAssets + "/AnimationFlowStyles.uss")) {
var sdir = new System.IO.DirectoryInfo(Application.dataPath);
var dirQueue = new Queue<System.IO.DirectoryInfo>();
dirQueue.Enqueue(sdir);
bool found = false;
while (dirQueue.Count > 0) {
System.IO.DirectoryInfo dir = dirQueue.Dequeue();
if (System.IO.File.Exists(dir.FullName + "/AnimationFlowStyles.uss")) {
string path = dir.FullName.Replace('\\', '/');
found = true;
path = path.Replace(projectPath, "");
if (path.StartsWith("/")) {
path = path.Remove(0, 1);
}
editorAssets = path;
return true;
}
var dirs = dir.GetDirectories();
for (int i = 0; i < dirs.Length; i++) {
dirQueue.Enqueue(dirs[i]);
}
}
if (!found) {
Debug.LogWarning("Could not locate editor assets folder. Make sure you have imported the package correctly.");
return false;
}
}
return true;
}
}
}

View File

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

8
Editor/Styles.meta Normal file
View File

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

View File

@ -0,0 +1,7 @@
GridBackground
{
--grid-background-color:#2b2b2b;
--line-color:rgba(55,55,55,1);
--thick-line-color:rgba(55,55,55,1);
--spacing:40;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f82e3630fa0ecb41b029ea6ce3a5cbb
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

BIN
Editor/Styles/Complete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,98 @@
fileFormatVersion: 2
guid: 675b0173b4bfbcb4ebd0ec1d0fdc3293
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 1
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 1024
textureSettings:
filterMode: -1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Windows Store Apps
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
using UnityEngine.UIElements;
namespace AlicizaX.AnimationFlow.Editor {
public class SplitView : TwoPaneSplitView {
public new class UxmlFactory : UxmlFactory<SplitView, UxmlTraits> { }
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a794fdf26eb8e9c4aac599b04c03fa37
timeCreated: 1675978413

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 B

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 3a8e09bf61f38fe42beb76e9adc5921d
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
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: 0
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 0
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: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 1
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 32
resizeAlgorithm: 1
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 32
resizeAlgorithm: 1
textureFormat: -1
textureCompression: 0
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: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,6 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<AlicizaX.AnimationFlow.Editor.SplitView fixed-pane-index="0" fixed-pane-initial-dimension="250" tabindex="0">
<ui:VisualElement name="Inspector" />
<ui:VisualElement name="Graph" style="flex-grow: 1;" />
</AlicizaX.AnimationFlow.Editor.SplitView>
</ui:UXML>

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 43465dc65d73bca49a224ece1d2b91d8
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

BIN
Editor/Styles/Warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,98 @@
fileFormatVersion: 2
guid: 90b2d00dd1c22fe4b8edb550dabd9dc5
TextureImporter:
fileIDToRecycleName: {}
serializedVersion: 4
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 0
linearTexture: 1
fadeOut: 0
borderMipMap: 0
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: -3
maxTextureSize: 1024
textureSettings:
filterMode: 1
aniso: 1
mipBias: -1
wrapMode: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spritePixelsToUnits: 100
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Standalone
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Android
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: Windows Store Apps
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
- buildTarget: WebGL
maxTextureSize: 1024
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
spritePackingTag:
userData:
assetBundleName:
assetBundleVariant:

8
Editor/Window.meta Normal file
View File

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

View File

@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AlicizaX.AnimationFlow.Runtime;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace AlicizaX.AnimationFlow.Editor
{
public class GraphWindow : EditorWindow
{
private GraphInspector graphInspector;
private GraphView graphContentView;
private Runtime.AnimationFlow animationFlowComponent;
private float editorPreviousTime;
private bool _subScribeEditorEventState;
[MenuItem("Window/AnimationGraph")]
public static void Open()
{
GetWindow<GraphWindow>("AnimationFlow");
}
private void Update()
{
if (graphContentView == null || animationFlowComponent == null || Application.isPlaying)
{
return;
}
if (animationFlowComponent.InPlaying)
{
float delta = (Time.realtimeSinceStartup - editorPreviousTime) * Time.timeScale;
editorPreviousTime = Time.realtimeSinceStartup;
animationFlowComponent.Tick(delta);
}
List<ActionNode> nodes = new List<ActionNode>();
foreach (var VARIABLE in animationFlowComponent.AnimationNodes)
{
nodes.Add(VARIABLE);
}
UpdateNodeState(nodes);
}
private void UpdateNodeState(List<ActionNode> nodes)
{
foreach (var node in nodes)
{
switch (node.State)
{
case EState.Enter:
graphContentView?.OnNodeEnter(node);
break;
case EState.Running:
graphContentView?.OnNodeUpdate(node);
break;
case EState.Exit:
graphContentView?.OnNodeExit(node);
break;
;
}
if (node.Childs.Count > 0)
{
UpdateNodeState(node.Childs);
}
}
}
private void OnEnable()
{
animationFlowComponent = null;
BuildView();
CheckSelectionAnimationFlow();
Selection.selectionChanged -= OnSelectionChanged;
Selection.selectionChanged += OnSelectionChanged;
Undo.undoRedoPerformed -= UndoRedoPerformed;
Undo.undoRedoPerformed += UndoRedoPerformed;
EditorApplication.playModeStateChanged -= OnModeStateChanged;
EditorApplication.playModeStateChanged += OnModeStateChanged;
}
private void OnDisable()
{
Selection.selectionChanged -= OnSelectionChanged;
Undo.undoRedoPerformed -= UndoRedoPerformed;
EditorApplication.playModeStateChanged -= OnModeStateChanged;
if (animationFlowComponent != null)
{
animationFlowComponent.ResetNode();
}
}
/// <summary>
/// 生成View
/// </summary>
private void BuildView()
{
if (!EditorResourceTool.LocateEditorAssets())
{
return;
}
Texture2D btnPlayIcon = EditorGUIUtility.FindTexture("d_PlayButton");
Texture2D btnPauseIcon = EditorGUIUtility.FindTexture("d_PauseButton");
Texture2D btnStepIcon = EditorGUIUtility.FindTexture("d_StepButton");
Texture2D btnSaveIcon = EditorGUIUtility.FindTexture("SaveActive");
VisualTreeAsset visual_tree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(EditorResourceTool.editorAssets + "/UIGraphWindow.uxml");
visual_tree.CloneTree(rootVisualElement);
graphInspector = new GraphInspector();
rootVisualElement.Q<VisualElement>("Inspector").Add(graphInspector);
VisualElement graph_parent = rootVisualElement.Q<VisualElement>("Graph");
GUIStyle gui_style = new();
Texture2D texture = new(1, 1);
texture.SetPixel(0, 0, new Color(0.25f, 0.25f, 0.25f, 1f));
texture.Apply();
gui_style.normal.background = texture;
gui_style.padding = new RectOffset(5, 5, 4, 4);
int index_name = 0;
IMGUIContainer imgui_container = new(() =>
{
if (animationFlowComponent == null)
{
graphContentView?.ClearGraph();
return;
}
GUILayout.BeginHorizontal(gui_style);
List<string> keys = new();
foreach (ActionNode baseData in animationFlowComponent.AnimationNodes)
{
if (baseData is EntryNode root && !string.IsNullOrEmpty(root.Name))
{
keys.Add(root.Name);
}
}
GUI.color = !animationFlowComponent.InPlaying && keys.Count > 0 ? Color.white : Color.gray;
index_name = EditorGUILayout.Popup(index_name, keys.ToArray(), GUILayout.Width(100));
if (GUILayout.Button(btnPlayIcon))
{
if (keys.Count > 0)
{
if (animationFlowComponent.InPlaying)
{
animationFlowComponent.Stop();
return;
}
OnBtnClickPlay(keys[index_name]);
}
}
GUI.color = animationFlowComponent.InPlaying ? Color.white : Color.gray;
if (GUILayout.Button(btnPauseIcon))
{
OnBtnClickPause();
}
GUI.color = animationFlowComponent.InPlaying || animationFlowComponent.InPause ? Color.white : Color.gray;
if (GUILayout.Button(btnStepIcon))
{
OnBtnClickStep();
}
GUI.color = Color.white;
GUILayout.EndHorizontal();
});
graph_parent.Add(imgui_container);
imgui_container.style.position = Position.Absolute;
imgui_container.style.bottom = 0;
imgui_container.style.alignSelf = Align.Center;
imgui_container.style.height = 40;
graphContentView = new(this);
graph_parent.Insert(0, graphContentView);
}
private void OnFocus()
{
CheckSelectionAnimationFlow();
}
private void OnSelectionChanged()
{
CheckSelectionAnimationFlow();
}
private void UndoRedoPerformed()
{
RePaintGraph();
}
private void OnModeStateChanged(PlayModeStateChange _)
{
if (TryGetPlayerFromSelection())
{
RePaintGraph();
}
else
{
graphInspector.Clear();
graphContentView.ClearGraph();
}
}
/// <summary>
/// 检查当前选中的物体是否存在AnimationFlow组件
/// </summary>
private void CheckSelectionAnimationFlow()
{
if (TryGetPlayerFromSelection())
{
RePaintGraph();
}
}
private void RePaintGraph()
{
List<ActionNode> nodes = new List<ActionNode>();
foreach (var VARIABLE in animationFlowComponent.AnimationNodes)
{
nodes.Add(VARIABLE);
}
CheckEmptyNode(nodes);
graphInspector.Clear();
graphContentView.ClearGraph();
graphContentView.SetAnimationFlow(animationFlowComponent);
}
private void CheckEmptyNode(List<ActionNode> nodes)
{
for (int i = nodes.Count - 1; i >= 0; i--)
{
if (nodes[i] == null)
{
nodes.RemoveAt(i);
}
if (nodes[i].Childs.Count > 0)
{
CheckEmptyNode(nodes[i].Childs);
}
}
}
private bool TryGetPlayerFromSelection()
{
if (Selection.activeGameObject != null)
{
Runtime.AnimationFlow new_animation_flow = Selection.activeGameObject.GetComponent<Runtime.AnimationFlow>();
if (new_animation_flow != null && (animationFlowComponent == null || new_animation_flow != animationFlowComponent))
{
if (animationFlowComponent != null)
{
// DeleteAction();
}
animationFlowComponent = new_animation_flow;
return true;
}
}
return false;
}
#region
private void OnBtnClickPlay(string name)
{
if (graphContentView == null || animationFlowComponent == null)
{
return;
}
if (animationFlowComponent.AnimationNodes.Exists(v => !v.Valid()))
{
EditorUtility.DisplayDialog("Warning", "Please make sure the required parameters are set.", "ok");
return;
}
editorPreviousTime = Time.realtimeSinceStartup;
ResetView();
animationFlowComponent.Play(name);
}
private void OnBtnClickPause()
{
if (graphContentView == null || animationFlowComponent == null || !animationFlowComponent.InPlaying)
{
return;
}
animationFlowComponent.AnimationFlowPlayebleType = EAnimationFlowPlayebleType.Pause;
}
private void OnBtnClickStep()
{
if (graphContentView == null || animationFlowComponent == null || !animationFlowComponent.InPlaying || !animationFlowComponent.InPause)
{
return;
}
animationFlowComponent.Tick(0.016f);
}
#endregion
public void OnSelect(NodeView node_view)
{
graphInspector.DrawNode(node_view);
}
public void OnNodeRemove()
{
graphInspector.Clear();
}
private void ResetView()
{
graphContentView?.ResetView();
}
}
}

View File

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

201
LICENSE.md Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [2023] [ALianBlank of copyright owner][alianblank@outlook.com][https://alianblank.com/]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

7
LICENSE.md.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5a68ca6cac9f6bf4d879d41d6eed3744
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime.meta Normal file
View File

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

View File

@ -0,0 +1,14 @@
{
"name": "AlicizaX.AnimationFlow.Runtime",
"rootNamespace": "AlicizaX.AnimationFlow.Runtime",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 189d55e03d78888459720d730f4d2424
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

265
Runtime/AnimationFlow.cs Normal file
View File

@ -0,0 +1,265 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime
{
public class AnimationFlow : MonoBehaviour
{
#region Editor相关
#if UNITY_EDITOR
IEnumerable GetAllAnimationClips
{
get
{
List<string> keys = new();
foreach (EntryNode root in AnimationNodes)
{
if (!string.IsNullOrEmpty(root.Name))
{
keys.Add(root.Name);
}
}
keys.Insert(0, "None");
return keys;
}
}
[Button("节点编辑器", ButtonSizes.Large), GUIColor(0, 1, 0)]
private void OpenGraphWindow()
{
UnityEditor.EditorApplication.ExecuteMenuItem("AlicizaFramework/Window/AnimationGraph");
}
#endif
#endregion
[HideInInspector] [SerializeReference] public List<EntryNode> AnimationNodes = new List<EntryNode>();
[LabelText("动画片段")] [BoxGroup("基础设置", true)] [ValueDropdown("GetAllAnimationClips", ExpandAllMenuItems = true)] [SerializeField]
private string _defaultPlayName = "None";
[LabelText("自动播放")] [BoxGroup("基础设置", true)] [SerializeField]
private bool _enableAutoPlay;
private List<ActionNode> _runningNodes = new List<ActionNode>();
private List<ActionNode> _resetNodes = new List<ActionNode>();
private ActionNode _curEntryNode;
private int _loopCount = 0;
/// <summary>
/// 节点播放回调
/// </summary>
private Action _playFinishEvent;
/// <summary>
/// 时间缩放系数 受Time.deltatime影响
/// </summary>
private float _timeScale = 1f;
public float TimeScale
{
get => _timeScale;
set => _timeScale = value;
}
/// <summary>
/// 当前节点状态
/// </summary>
private EAnimationFlowPlayebleType _playebleType = EAnimationFlowPlayebleType.Stop;
public EAnimationFlowPlayebleType AnimationFlowPlayebleType
{
get => _playebleType;
set => _playebleType = value;
}
/// <summary>
/// 是否播放中
/// </summary>
public bool InPlaying => _playebleType == EAnimationFlowPlayebleType.Play;
/// <summary>
/// 是否暂停中
/// </summary>
public bool InPause => _playebleType == EAnimationFlowPlayebleType.Pause;
private void Start()
{
if (_enableAutoPlay)
{
Play(_defaultPlayName);
}
}
private void Update()
{
if (_playebleType == EAnimationFlowPlayebleType.Play)
{
Tick(Time.deltaTime * _timeScale);
}
}
public void Tick(float deltaTime)
{
List<ActionNode> runNodes = _runningNodes;
int runCount = runNodes.Count;
if (runCount == 0)
{
if (_loopCount > 0)
{
_loopCount--;
ResetNode();
PushNode(_curEntryNode);
}
else
{
StopFlow();
}
}
if (runCount > 0)
{
for (int i = runCount - 1; i >= 0; i--)
{
ActionNode node = _runningNodes[i];
if (node.Condition())
{
PopNode(node);
}
else
{
node.elapsedTime = Mathf.Min(node.elapsedTime + deltaTime, node.Duration());
node.OnUpdate(deltaTime);
node.State = EState.Running;
}
}
}
}
public void ResetNode()
{
for (int i = _resetNodes.Count - 1; i >= 0; i--)
{
ActionNode node = _resetNodes[i];
node.Reset();
node.OnReset();
}
_resetNodes.Clear();
}
private void StopFlow(bool isInterrupt = false)
{
_playebleType = EAnimationFlowPlayebleType.Stop;
_curEntryNode = null;
_loopCount = 0;
for (int i = _resetNodes.Count - 1; i >= 0; i--)
{
ActionNode node = _resetNodes[i];
if (!Application.isPlaying)
{
node.OnReset();
}
node.Reset();
}
if (isInterrupt)
{
for (int i = _runningNodes.Count - 1; i >= 0; i--)
{
ActionNode node = _runningNodes[i];
node.OnInterrupt();
}
}
if (_playFinishEvent != null) _playFinishEvent();
_resetNodes.Clear();
_runningNodes.Clear();
}
private void PopNode(ActionNode node)
{
node.OnExit();
node.State = EState.Exit;
_runningNodes.Remove(node);
PushChildNode(node);
}
private void PushChildNode(ActionNode node)
{
List<ActionNode> childNode = node.Childs;
if (childNode.Count > 0)
{
foreach (var child in childNode)
{
PushNode(child);
}
}
}
private void PushNode(ActionNode node)
{
node.OnInit();
node.OnEnter();
node.State = EState.Enter;
_runningNodes.Add(node);
_resetNodes.Add(node);
}
public void Play(string clipName = "", Action actionComplete = null)
{
if (_playebleType == EAnimationFlowPlayebleType.Play)
{
Debug.LogWarning($"animation flow is playing!");
return;
}
if (string.IsNullOrEmpty(clipName) || clipName == "None")
{
Debug.LogWarning($"animation name is empty!");
return;
}
_playFinishEvent = actionComplete;
EntryNode entryNode = AnimationNodes.Find(a => a.Name == clipName);
if (entryNode == null)
{
Debug.LogWarning($"Can not find this Clip {clipName}");
return;
}
_loopCount = entryNode.LoopCount;
_curEntryNode = entryNode;
_playebleType = EAnimationFlowPlayebleType.Play;
}
public void Stop()
{
if (_playebleType == EAnimationFlowPlayebleType.Play)
{
StopFlow(true);
}
}
}
}

View File

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

8
Runtime/Attribute.meta Normal file
View File

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

View File

@ -0,0 +1,12 @@
using System;
using System.Diagnostics;
namespace AlicizaX.AnimationFlow.Runtime {
[AttributeUsage(AttributeTargets.Class), Conditional("UNITY_EDITOR")]
public class CategoryAttribute : Attribute {
public readonly string category;
public CategoryAttribute(string category) {
this.category = category;
}
}
}

View File

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

View File

@ -0,0 +1,12 @@
using System;
using System.Diagnostics;
namespace AlicizaX.AnimationFlow.Runtime {
[AttributeUsage(AttributeTargets.Class), Conditional("UNITY_EDITOR")]
public class NameAttribute : Attribute {
public readonly string name;
public NameAttribute(string name) {
this.name = name;
}
}
}

View File

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

8
Runtime/Core.meta Normal file
View File

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

View File

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

View File

@ -0,0 +1,54 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class CanvasGroupAlphaTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public CanvasGroup target;
public bool setFrom;
public float from;
public float to;
protected float orgValue;
protected float enterValue;
public override void OnInit()
{
orgValue = target.alpha;
}
public override void OnReset()
{
target.alpha = orgValue;
}
public override void OnEnter() {
if (setFrom) {
target.alpha = from;
}
enterValue = target.alpha;
}
public override void OnUpdate(float dt) {
target.alpha = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,54 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class GraphicAlphaTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public Graphic target;
public bool setFrom;
public float from;
public float to;
protected float orgValue;
protected float enterValue;
public override void OnInit() {
orgValue = target.color.a;
}
public override void OnReset() {
target.color = new Color(target.color.r, target.color.g, target.color.b, orgValue);
}
public override void OnEnter() {
if (setFrom) {
target.color = new Color(target.color.r, target.color.g, target.color.b, from);
}
enterValue = target.color.a;
}
public override void OnUpdate(float dt) {
float a = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
target.color = new Color(target.color.r, target.color.g, target.color.b, a);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,64 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime
{
[Category("Animation")]
public class GraphicColorTo : ActionNode
{
public float duration = 1f;
public EaseType easyType;
public Graphic target;
public bool setFrom;
public Color from = Color.white;
public Color to = Color.white;
protected Color orgValue;
protected Color enterValue;
public override void OnInit()
{
orgValue = target.color;
}
public override void OnReset()
{
target.color = orgValue;
}
public override void OnEnter()
{
if (setFrom)
{
target.color = from;
}
enterValue = target.color;
}
public override void OnUpdate(float dt)
{
target.color = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid()
{
return target != null && duration > 0;
}
public override float Duration()
{
return duration;
}
public override bool HasSubTitle()
{
return true;
}
public override string SubTitle()
{
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
namespace AlicizaX.AnimationFlow.Runtime {
public class RotateBy : RotateTo {
public override void OnUpdate(float dt) {
target.localEulerAngles = Easing.Ease(easyType, enterValue, enterValue + to, elapsedTime / duration);
}
}
}

View File

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

View File

@ -0,0 +1,53 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class RotateTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public Transform target;
public bool setFrom;
public Vector3 from;
public Vector3 to;
protected Vector3 orgValue;
protected Vector3 enterValue;
public override void OnInit() {
orgValue = target.localEulerAngles;
}
public override void OnReset() {
target.localEulerAngles = orgValue;
}
public override void OnEnter() {
if (setFrom) {
target.localEulerAngles = from;
}
enterValue = target.localEulerAngles;
}
public override void OnUpdate(float dt) {
target.localEulerAngles = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
namespace AlicizaX.AnimationFlow.Runtime {
public class ScaleBy : ScaleTo {
public override void OnUpdate(float dt) {
target.localScale = Easing.Ease(easyType, enterValue, enterValue + to, elapsedTime / duration);
}
}
}

View File

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

View File

@ -0,0 +1,53 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class ScaleTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public Transform target;
public bool setFrom;
public Vector3 from;
public Vector3 to = Vector3.one;
protected Vector3 orgValue;
protected Vector3 enterValue;
public override void OnInit() {
orgValue = target.localScale;
}
public override void OnReset() {
target.localScale = orgValue;
}
public override void OnEnter() {
if (setFrom) {
target.localScale = from;
}
enterValue = target.localScale;
}
public override void OnUpdate(float dt) {
target.localScale = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,54 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class SpriteRendererAlphaTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public SpriteRenderer target;
public bool setFrom;
public float from;
public float to;
protected float orgValue;
protected float enterValue;
public override void OnInit() {
orgValue = target.color.a;
}
public override void OnReset() {
target.color = new Color(target.color.r, target.color.g, target.color.b, orgValue);
}
public override void OnEnter() {
if (setFrom) {
target.color = target.color = new Color(target.color.r, target.color.g, target.color.b, from);
}
enterValue = target.color.a;
}
public override void OnUpdate(float dt) {
float a = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
target.color = new Color(target.color.r, target.color.g, target.color.b, a);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,54 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Animation")]
public class SpriteRendererColorTo : ActionNode {
public float duration = 1f;
public EaseType easyType;
public SpriteRenderer target;
public bool setFrom;
public Color from = Color.white;
public Color to = Color.white;
protected Color orgValue;
protected Color enterValue;
public override void OnInit() {
orgValue = target.color;
}
public override void OnReset() {
target.color = orgValue;
}
public override void OnEnter() {
if (setFrom) {
target.color = from;
}
enterValue = target.color;
}
public override void OnUpdate(float dt) {
target.color = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid() {
return target != null && duration > 0;
}
public override float Duration() {
return duration;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,7 @@
namespace AlicizaX.AnimationFlow.Runtime {
public class TranslateBy : TranslateTo {
public override void OnUpdate(float dt) {
target.localPosition = Easing.Ease(easyType, enterValue, enterValue + to, elapsedTime / duration);
}
}
}

View File

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

View File

@ -0,0 +1,73 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime
{
[Category("Animation")]
[System.Serializable]
public class TranslateTo : ActionNode
{
[LabelText("持续时间")] public float duration = 1f;
[LabelText("过渡类型")] public EaseType easyType;
[ChildGameObjectsOnly] [LabelText("对象")]
public Transform target;
[LabelText("设置起点")] public bool setFrom;
[LabelText("开始")] [ShowIf("setFrom", true)]
public Vector3 from;
[LabelText("结束")] public Vector3 to;
protected Vector3 orgValue;
protected Vector3 enterValue;
public override void OnInit()
{
orgValue = target.localPosition;
}
public override void OnReset()
{
target.localPosition = orgValue;
}
public override void OnEnter()
{
if (setFrom)
{
target.localPosition = from;
}
enterValue = target.localPosition;
}
public override void OnUpdate(float dt)
{
target.localPosition = Easing.Ease(easyType, enterValue, to, elapsedTime / duration);
}
public override bool Valid()
{
Debug.Log(target != null && duration > 0);
return target != null && duration > 0;
}
public override float Duration()
{
return duration;
}
public override bool HasSubTitle()
{
return true;
}
public override string SubTitle()
{
return target != null ? target.name : null;
}
}
}

View File

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

247
Runtime/Core/Easing.cs Normal file
View File

@ -0,0 +1,247 @@
using System;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
public enum EaseType {
Linear,
QuadraticIn,
QuadraticOut,
QuadraticInOut,
QuarticIn,
QuarticOut,
QuarticInOut,
QuinticIn,
QuinticOut,
QuinticInOut,
CubicIn,
CubicOut,
CubicInOut,
ExponentialIn,
ExponentialOut,
ExponentialInOut,
CircularIn,
CircularOut,
CircularInOut,
SinusoidalIn,
SinusoidalOut,
SinusoidalInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BounceIn,
BounceOut,
BounceInOut,
BackIn,
BackOut,
BackInOut
}
public static class Easing {
public static float Ease(EaseType type, float from, float to, float t) {
if (t <= 0) { return from; }
if (t >= 1) { return to; }
return Mathf.LerpUnclamped(from, to, Function(type)(t));
}
public static Vector3 Ease(EaseType type, Vector3 from, Vector3 to, float t) {
if (t <= 0) { return from; }
if (t >= 1) { return to; }
return Vector3.LerpUnclamped(from, to, Function(type)(t));
}
public static Quaternion Ease(EaseType type, Quaternion from, Quaternion to, float t) {
if (t <= 0) { return from; }
if (t >= 1) { return to; }
return Quaternion.LerpUnclamped(from, to, Function(type)(t));
}
public static Color Ease(EaseType type, Color from, Color to, float t) {
if (t <= 0) { return from; }
if (t >= 1) { return to; }
return Color.LerpUnclamped(from, to, Function(type)(t));
}
public static Func<float, float> Function(EaseType type) {
return type switch {
EaseType.Linear => Linear,
EaseType.QuadraticIn => QuadraticIn,
EaseType.QuadraticOut => QuadraticOut,
EaseType.QuadraticInOut => QuadraticInOut,
EaseType.QuarticIn => QuarticIn,
EaseType.QuarticOut => QuarticOut,
EaseType.QuarticInOut => QuarticInOut,
EaseType.QuinticIn => QuinticIn,
EaseType.QuinticOut => QuinticOut,
EaseType.QuinticInOut => QuinticInOut,
EaseType.CubicIn => CubicIn,
EaseType.CubicOut => CubicOut,
EaseType.CubicInOut => CubicInOut,
EaseType.ExponentialIn => ExponentialIn,
EaseType.ExponentialOut => ExponentialOut,
EaseType.ExponentialInOut => ExponentialInOut,
EaseType.CircularIn => CircularIn,
EaseType.CircularOut => CircularOut,
EaseType.CircularInOut => CircularInOut,
EaseType.SinusoidalIn => SinusoidalIn,
EaseType.SinusoidalOut => SinusoidalOut,
EaseType.SinusoidalInOut => SinusoidalInOut,
EaseType.ElasticIn => ElasticIn,
EaseType.ElasticOut => ElasticOut,
EaseType.ElasticInOut => ElasticInOut,
EaseType.BounceIn => BounceIn,
EaseType.BounceOut => BounceOut,
EaseType.BounceInOut => BounceInOut,
EaseType.BackIn => BackIn,
EaseType.BackOut => BackOut,
EaseType.BackInOut => BackInOut,
_ => throw new ArgumentOutOfRangeException(),
};
}
public static float Linear(float t) {
return t;
}
public static float QuadraticIn(float t) {
return t * t;
}
public static float QuadraticOut(float t) {
return 1f - (1f - t) * (1f - t);
}
public static float QuadraticInOut(float t) {
return t < 0.5f ? 2f * t * t : 1f - Mathf.Pow(-2f * t + 2f, 2f) / 2f;
}
public static float QuarticIn(float t) {
return t * t * t * t;
}
public static float QuarticOut(float t) {
return 1f - (--t * t * t * t);
}
public static float QuarticInOut(float t) {
if ((t *= 2f) < 1f)
return 0.5f * t * t * t * t;
return -0.5f * ((t -= 2f) * t * t * t - 2f);
}
public static float QuinticIn(float t) {
return t * t * t * t * t;
}
public static float QuinticOut(float t) {
return --t * t * t * t * t + 1f;
}
public static float QuinticInOut(float t) {
if ((t *= 2f) < 1)
return 0.5f * t * t * t * t * t;
return 0.5f * ((t -= 2f) * t * t * t * t + 2f);
}
public static float CubicIn(float t) {
return t * t * t;
}
public static float CubicOut(float t) {
return --t * t * t + 1f;
}
public static float CubicInOut(float t) {
return t < 0.5 ? 4f * t * t * t : 1f - Mathf.Pow(-2f * t + 2f, 3f) / 2f;
}
public static float SinusoidalIn(float t) {
return 1f - Mathf.Cos(t * Mathf.PI / 2f);
}
public static float SinusoidalOut(float t) {
return Mathf.Sin(t * Mathf.PI / 2f);
}
public static float SinusoidalInOut(float t) {
return 0.5f * (1f - Mathf.Cos(Mathf.PI * t));
}
public static float ExponentialIn(float t) {
return t == 0f ? 0f : Mathf.Pow(2f, 10f * t - 10f);
}
public static float ExponentialOut(float t) {
return t == 1f ? 1f : 1f - Mathf.Pow(2f, -10f * t);
}
public static float ExponentialInOut(float t) {
return t < 0.5f ? Mathf.Pow(2f, 20f * t - 10f) / 2f : (2f - Mathf.Pow(2f, -20f * t + 10f)) / 2f;
}
public static float CircularIn(float t) {
return 1f - Mathf.Sqrt(1f - t * t);
}
public static float CircularOut(float t) {
return Mathf.Sqrt(1f - (--t * t));
}
public static float CircularInOut(float t) {
return t < 0.5f ? (Mathf.Sqrt(1f - t * t) - 1f) / 2 : (Mathf.Sqrt(1f - (t -= 2f) * t) + 1f) / 2;
}
public static float ElasticIn(float t) {
float x = (2f * Mathf.PI) / 3f;
return -Mathf.Pow(2f, 10f * t - 10f) * Mathf.Sin((t * 10f - 10.75f) * x);
}
public static float ElasticOut(float t) {
float x = (2f * Mathf.PI) / 3f;
return Mathf.Pow(2f, -10f * t) * Mathf.Sin((t * 10f - 0.75f) * x) + 1f;
}
public static float ElasticInOut(float t) {
float x = (2f * Mathf.PI) / 4.5f;
if (t < 0.5f)
return -(Mathf.Pow(2f, 20f * t - 10f) * Mathf.Sin((20f * t - 11.125f) * x)) / 2f;
return (Mathf.Pow(2f, -20f * t + 10f) * Mathf.Sin((20f * t - 11.125f) * x)) / 2f + 1f;
}
public static float BounceIn(float t) {
return 1f - BounceOut(1f - t);
}
public static float BounceOut(float t) {
if (t < (1f / 2.75f)) {
return 7.5625f * t * t;
} else if (t < (2f / 2.75f)) {
return 7.5625f * (t -= (1.5f / 2.75f)) * t + 0.75f;
} else if (t < (2.5f / 2.75f)) {
return 7.5625f * (t -= (2.25f / 2.75f)) * t + 0.9375f;
} else {
return 7.5625f * (t -= (2.625f / 2.75f)) * t + 0.984375f;
}
}
public static float BounceInOut(float t) {
return t < 0.5f ? BounceIn(t * 2f) * 0.5f : BounceOut(t * 2f - 1f) * 0.5f + 0.5f;
}
public static float BackIn(float t) {
float s = 1.70158f;
return t * t * ((s + 1f) * t - s);
}
public static float BackOut(float t) {
float s = 1.70158f;
return --t * t * ((s + 1f) * t + s) + 1f;
}
public static float BackInOut(float t) {
float s = 1.70158f * 1.525f;
if ((t *= 2f) < 1f)
return 0.5f * (t * t * ((s + 1) * t - s));
return 0.5f * ((t -= 2f) * t * ((s + 1f) * t + s) + 2f);
}
}
}

View File

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

8
Runtime/Core/Event.meta Normal file
View File

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

View File

@ -0,0 +1,12 @@
using UnityEngine.Events;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Event"), Name("UnityEvent")]
public class UnityEventNode : ActionNode {
public UnityEvent unityEvent;
public override void OnEnter() {
unityEvent?.Invoke();
}
}
}

View File

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

3
Runtime/Core/Node.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2bd5293bdf6fd52439390bafec491b0e
timeCreated: 1731292591

View File

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Sirenix.OdinInspector;
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime
{
public enum EState
{
None,
Enter,
Running,
Exit,
}
[System.Serializable]
public class EntryNode : ActionNode
{
[LabelText("循环次数")] public int LoopCount = 1; // 控制流程循环的次数
[LabelText("入口名称")] public string Name; // 节点名称(可以是技能名称、关卡名称等)
}
[System.Serializable]
public abstract partial class ActionNode
{
[HideInInspector] public float elapsedTime;
[HideInInspector] [SerializeReference] [SerializeField]
public List<ActionNode> Childs = new List<ActionNode>(); // 邻接节点(图中的边)
[HideInInspector] public EState State;
/// <summary>
/// 该类型是否允许接受输入口
/// </summary>
/// <returns></returns>
public virtual bool HasInputPort()
{
return true;
}
/// <summary>
/// 是否有多输入口
/// </summary>
/// <returns></returns>
public virtual bool MultiInputPort()
{
return true;
}
/// <summary>
/// 是否有输出口
/// </summary>
/// <returns></returns>
public virtual bool HasOutPort()
{
return true;
}
/// <summary>
/// 是否有多输出口
/// </summary>
/// <returns></returns>
public virtual bool MultiOutPort()
{
return true;
}
/// <summary>
/// 持续时间
/// </summary>
/// <returns></returns>
public virtual float Duration()
{
return 0;
}
/// <summary>
/// 前置条件 如果满足 则进入下一分支
/// </summary>
/// <returns></returns>
public virtual bool Condition()
{
return elapsedTime >= Duration();
}
public virtual bool HasSubTitle()
{
return false;
}
public virtual string SubTitle()
{
return null;
}
/// <summary>
/// 效验通过 编辑器使用
/// </summary>
/// <returns></returns>
public virtual bool Valid()
{
return true;
}
public virtual void OnInit()
{
}
public virtual void OnEnter()
{
}
public virtual void OnExit()
{
}
public virtual void OnUpdate(float dt)
{
}
public virtual void OnInterrupt()
{
}
public virtual void OnReset()
{
}
public void Reset()
{
elapsedTime = 0;
State = EState.None;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1329507424583a6498dd62a900edaf76
timeCreated: 1731065028

View File

@ -0,0 +1,17 @@

using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime
{
#if UNITY_EDITOR
public abstract partial class ActionNode
{
[HideInInspector]
public Vector2 nodePos; //GraphView使用
[HideInInspector]
public string uuid; //GraphView映射的Id
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b3a49f0f1e2c1e942a472ab2d6574c10
timeCreated: 1731292612

View File

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

View File

@ -0,0 +1,35 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class ButtonInteractable : ActionNode {
public Button target;
public bool interactable;
protected bool orgValue;
public override void OnInit() {
orgValue = target.interactable;
}
public override void OnReset() {
target.interactable = orgValue;
}
public override void OnEnter() {
target.interactable = interactable;
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,59 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property"), Name("CanvasGroup")]
public class CanvasGroupProperty : ActionNode {
public CanvasGroup target;
public bool setAlpha = true;
public float alpha;
public bool setInteractable;
public bool interactable;
public bool setBlocksRaycasts;
public bool blocksRaycasts;
private float orgAlpha;
private bool orgInteractable;
private bool orgBlocksRaycasts;
public override void OnInit() {
orgAlpha = target.alpha;
orgInteractable = target.interactable;
orgBlocksRaycasts = target.blocksRaycasts;
}
public override void OnReset() {
if (setAlpha) {
target.alpha = orgAlpha;
}
if (setInteractable) {
target.interactable = orgInteractable;
}
if (setBlocksRaycasts) {
target.blocksRaycasts = orgBlocksRaycasts;
}
}
public override void OnEnter() {
if (setAlpha) {
target.alpha = alpha;
}
if (setInteractable) {
target.interactable = interactable;
}
if (setBlocksRaycasts) {
target.blocksRaycasts = blocksRaycasts;
}
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,34 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class GameObjectActive : ActionNode {
public GameObject target;
public bool active;
protected bool orgValue;
public override void OnInit() {
orgValue = target.activeSelf;
}
public override void OnReset() {
target.SetActive(orgValue);
}
public override void OnEnter() {
target.SetActive(active);
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,36 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class GraphicAlpha : ActionNode {
public Graphic target;
public float alpha;
protected float orgValue;
public override void OnInit() {
orgValue = target.color.a;
}
public override void OnReset() {
target.color = new Color(target.color.r, target.color.g, target.color.b, orgValue);
}
public override void OnEnter() {
target.color = new Color(target.color.r, target.color.g, target.color.b, alpha);
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,37 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class GraphicColor : ActionNode {
public Graphic target;
public Color color = Color.white;
protected Color orgValue;
public override void OnInit() {
orgValue = target.color;
}
public override void OnReset() {
target.color = orgValue;
}
public override void OnEnter() {
target.color = color;
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,37 @@
using UnityEngine;
using UnityEngine.UI;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class ImageSprite : ActionNode {
public Image target;
public Sprite sprite;
protected Sprite orgValue;
public override void OnInit() {
orgValue = target.sprite;
}
public override void OnReset() {
target.sprite = orgValue;
}
public override void OnEnter() {
target.sprite = sprite;
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,35 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class SiblingIndex : ActionNode {
public Transform target;
public int index;
protected int orgIndex;
public override void OnInit() {
orgIndex = target.GetSiblingIndex();
}
public override void OnReset() {
target.SetSiblingIndex(orgIndex);
}
public override void OnEnter() {
target.SetSiblingIndex(index);
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,60 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property")]
public class SpriteRendererProperty : ActionNode {
public SpriteRenderer target;
public bool setSprite = true;
public Sprite sprite;
public bool setColor;
public Color color;
public bool setLayer;
public int layer;
protected Sprite orgSprite;
protected Color orgColor;
protected int orgLayer;
public override void OnInit() {
orgSprite = target.sprite;
orgColor = target.color;
orgLayer = target.sortingLayerID;
}
public override void OnReset() {
if (setSprite) {
target.sprite = orgSprite;
}
if (setColor) {
target.color = orgColor;
}
if (setLayer) {
target.sortingLayerID = orgLayer;
}
}
public override void OnEnter() {
if (setSprite) {
target.sprite = sprite;
}
if (setColor) {
target.color = color;
}
if (setLayer) {
target.sortingLayerID = layer;
}
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

View File

@ -0,0 +1,59 @@
using UnityEngine;
namespace AlicizaX.AnimationFlow.Runtime {
[Category("Property"), Name("Transform")]
public class TransformProperty : ActionNode {
public Transform target;
public bool setPosition = true;
public Vector3 position;
public bool setScale;
public Vector3 scale;
public bool setRotation;
public Vector3 rotation;
private Vector3 orgPosition;
private Vector3 orgScale;
private Vector3 orgRotation;
public override void OnInit() {
orgPosition = target.localPosition;
orgScale = target.localScale;
orgRotation = target.eulerAngles;
}
public override void OnReset() {
if (setPosition) {
target.localPosition = orgPosition;
}
if (setScale) {
target.localScale = orgScale;
}
if (setRotation) {
target.eulerAngles = orgRotation;
}
}
public override void OnEnter() {
if (setPosition) {
target.localPosition = position;
}
if (setScale) {
target.localScale = scale;
}
if (setRotation) {
target.eulerAngles = rotation;
}
}
public override bool Valid() {
return target != null;
}
public override bool HasSubTitle() {
return true;
}
public override string SubTitle() {
return target != null ? target.name : null;
}
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More