-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use UniTask to update state. Fix AudioFileAssist index. Add an editor window to edit statemachine. (may change to graph editor if more features are added)
- Loading branch information
Showing
40 changed files
with
1,298 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using UnityEditor; | ||
using UnityEditor.Experimental.GraphView; | ||
using UnityEngine; | ||
namespace Kurisu.UniChat.StateMachine.Editor | ||
{ | ||
[CustomPropertyDrawer(typeof(ChatStateMachineGraph.BehaviorNode))] | ||
public class BehaviorNodeDrawer : PropertyDrawer | ||
{ | ||
private const string NullType = "Null"; | ||
public override float GetPropertyHeight(SerializedProperty property, GUIContent label) | ||
{ | ||
return EditorGUIUtility.singleLineHeight | ||
+ GenericBehaviorWrapperDrawer.CalculatePropertyHeight(property.FindPropertyRelative("container")) | ||
+ EditorGUIUtility.standardVerticalSpacing; | ||
} | ||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) | ||
{ | ||
EditorGUI.BeginProperty(position, label, property); | ||
var totalHeight = position.height; | ||
position.height = EditorGUIUtility.singleLineHeight; | ||
var reference = property.FindPropertyRelative("serializedType"); | ||
var container = property.FindPropertyRelative("container"); | ||
var type = SerializedType.FromString(reference.stringValue); | ||
string id = type != null ? type.Name : NullType; | ||
if (type != null && container.objectReferenceValue == null) | ||
{ | ||
container.objectReferenceValue = SerializedBehaviorUtils.Wrap(Activator.CreateInstance(type)); | ||
property.serializedObject.ApplyModifiedProperties(); | ||
} | ||
if (EditorGUI.DropdownButton(position, new GUIContent(id), FocusType.Keyboard)) | ||
{ | ||
var provider = ScriptableObject.CreateInstance<StateMachineBehaviorSearchWindow>(); | ||
provider.Initialize((selectType) => | ||
{ | ||
reference.stringValue = selectType != null ? SerializedType.ToString(selectType) : NullType; | ||
if (selectType != null) | ||
{ | ||
var wrapper = SerializedBehaviorUtils.Wrap(Activator.CreateInstance(selectType)); | ||
container.objectReferenceValue = wrapper; | ||
} | ||
else | ||
{ | ||
container.objectReferenceValue = null; | ||
} | ||
property.serializedObject.ApplyModifiedProperties(); | ||
}); | ||
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), provider); | ||
} | ||
position.y += position.height + EditorGUIUtility.standardVerticalSpacing; | ||
position.height = totalHeight - position.height - EditorGUIUtility.standardVerticalSpacing; | ||
EditorGUI.PropertyField(position, container, true); | ||
EditorGUI.EndProperty(); | ||
} | ||
} | ||
public class StateMachineBehaviorSearchWindow : ScriptableObject, ISearchWindowProvider | ||
{ | ||
private Texture2D _indentationIcon; | ||
private Action<Type> typeSelectCallBack; | ||
public void Initialize(Action<Type> typeSelectCallBack) | ||
{ | ||
this.typeSelectCallBack = typeSelectCallBack; | ||
_indentationIcon = new Texture2D(1, 1); | ||
_indentationIcon.SetPixel(0, 0, new Color(0, 0, 0, 0)); | ||
_indentationIcon.Apply(); | ||
} | ||
List<SearchTreeEntry> ISearchWindowProvider.CreateSearchTree(SearchWindowContext context) | ||
{ | ||
var entries = new List<SearchTreeEntry> | ||
{ | ||
new SearchTreeGroupEntry(new GUIContent("Select StateMachineBehavior"), 0), | ||
new(new GUIContent("<Null>", _indentationIcon)) { level = 1, userData = null } | ||
}; | ||
List<Type> nodeTypes = FindSubClasses(typeof(ChatStateMachineBehavior)) | ||
.Where(x => x != typeof(InvalidStateMachineBehavior)) | ||
.ToList(); | ||
var groups = nodeTypes.GroupBy(t => t.Assembly); | ||
foreach (var group in groups) | ||
{ | ||
entries.Add(new SearchTreeGroupEntry(new GUIContent($"Select {group.Key.GetName().Name}"), 1)); | ||
var subGroups = group.GroupBy(x => x.Namespace); | ||
foreach (var subGroup in subGroups) | ||
{ | ||
entries.Add(new SearchTreeGroupEntry(new GUIContent($"Select {subGroup.Key}"), 2)); | ||
foreach (var type in subGroup) | ||
{ | ||
entries.Add(new SearchTreeEntry(new GUIContent(type.Name, _indentationIcon)) { level = 3, userData = type }); | ||
} | ||
} | ||
} | ||
return entries; | ||
} | ||
bool ISearchWindowProvider.OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context) | ||
{ | ||
var type = searchTreeEntry.userData as Type; | ||
typeSelectCallBack?.Invoke(type); | ||
return true; | ||
} | ||
private static IEnumerable<Type> FindSubClasses(Type father) | ||
{ | ||
return AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.IsSubclassOf(father) && !t.IsAbstract); | ||
} | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
Runtime/Interfaces/IEmbeddingWriter.cs.meta → ...r/StateMachine/BehaviorNodeDrawer.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using UnityEditor; | ||
using UnityEngine; | ||
namespace Kurisu.UniChat.StateMachine.Editor | ||
{ | ||
public class ChatStateMachineEditorWindow : EditorWindow | ||
{ | ||
private SerializedObject targetObject; | ||
private ChatStateMachineGraphEditorCtrl graphCtrl; | ||
public delegate Vector2 BeginVerticalScrollViewFunc(Vector2 scrollPosition, bool alwaysShowVertical, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options); | ||
private static BeginVerticalScrollViewFunc s_func; | ||
private Vector2 m_ScrollPosition; | ||
private string path; | ||
public string fileName = "NewChatFSM"; | ||
private static BeginVerticalScrollViewFunc BeginVerticalScrollView | ||
{ | ||
get | ||
{ | ||
if (s_func == null) | ||
{ | ||
var methods = typeof(EditorGUILayout).GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Where(x => x.Name == "BeginVerticalScrollView").ToArray(); | ||
var method = methods.First(x => x.GetParameters()[1].ParameterType == typeof(bool)); | ||
s_func = (BeginVerticalScrollViewFunc)method.CreateDelegate(typeof(BeginVerticalScrollViewFunc)); | ||
} | ||
return s_func; | ||
} | ||
} | ||
[MenuItem("Tools/UniChat/Chat StateMachine Editor")] | ||
private static void ShowEditorWindow() | ||
{ | ||
GetWindow<ChatStateMachineEditorWindow>("Chat StateMachine Editor"); | ||
} | ||
private void OnEnable() | ||
{ | ||
graphCtrl = CreateInstance<ChatStateMachineGraphEditorCtrl>(); | ||
targetObject = new(graphCtrl); | ||
} | ||
private void OnGUI() | ||
{ | ||
fileName = EditorGUILayout.TextField(new GUIContent("File Name"), fileName); | ||
m_ScrollPosition = BeginVerticalScrollView(m_ScrollPosition, false, GUI.skin.verticalScrollbar, "OL Box"); | ||
DrawModel(); | ||
EditorGUILayout.EndScrollView(); | ||
GUILayout.FlexibleSpace(); | ||
EditorGUILayout.BeginHorizontal(); | ||
if (GUILayout.Button("Load")) | ||
{ | ||
path = EditorUtility.OpenFilePanel("Select fsm bytes file", PathUtil.UserDataPath, "bytes"); | ||
if (string.IsNullOrEmpty(path)) return; | ||
fileName = Path.GetFileNameWithoutExtension(path); | ||
graphCtrl.Load(path); | ||
targetObject.Update(); | ||
} | ||
if (GUILayout.Button("New")) | ||
{ | ||
graphCtrl.Reset(); | ||
targetObject.Update(); | ||
} | ||
if (GUILayout.Button("Save")) | ||
{ | ||
path = EditorUtility.OpenFolderPanel("Select folder", PathUtil.UserDataPath, ""); | ||
if (string.IsNullOrEmpty(path)) return; | ||
graphCtrl.Save(Path.Combine(path, $"{fileName}.bytes")); | ||
} | ||
EditorGUILayout.EndHorizontal(); | ||
} | ||
private void DrawModel() | ||
{ | ||
EditorGUI.BeginChangeCheck(); | ||
SerializedProperty prop = targetObject.GetIterator(); | ||
prop.NextVisible(true); | ||
while (prop.NextVisible(false)) | ||
{ | ||
if (prop.name == "graph") continue; | ||
EditorGUILayout.PropertyField(prop, true); | ||
} | ||
if (EditorGUI.EndChangeCheck()) | ||
{ | ||
targetObject.ApplyModifiedProperties(); | ||
} | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
using System.IO; | ||
using System.Linq; | ||
using Newtonsoft.Json; | ||
using UnityEngine; | ||
namespace Kurisu.UniChat.StateMachine.Editor | ||
{ | ||
public class ChatStateMachineGraphEditorCtrl : ScriptableObject | ||
{ | ||
public ChatStateMachineGraph.Layer[] layers; | ||
private ChatStateMachineGraph graph = new(); | ||
public void Save(string path) | ||
{ | ||
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write); | ||
using var bw = new BinaryWriter(stream); | ||
graph.layers = layers; | ||
graph.layers.SelectMany(x => x.states).SelectMany(x => x.behaviors) | ||
.ForEach(x => | ||
{ | ||
x.jsonData = JsonConvert.SerializeObject(x.container.Value); | ||
}); | ||
bw.Write(JsonConvert.SerializeObject(graph)); | ||
} | ||
public void Load(string path) | ||
{ | ||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); | ||
using var br = new BinaryReader(stream); | ||
string json = br.ReadString(); | ||
graph = JsonConvert.DeserializeObject<ChatStateMachineGraph>(json); | ||
graph.layers.SelectMany(x => x.states).SelectMany(x => x.behaviors) | ||
.ForEach(x => | ||
{ | ||
x.container = SerializedBehaviorUtils.Wrap(x.Deserialize()); | ||
}); | ||
layers = graph.layers; | ||
} | ||
|
||
public void Reset() | ||
{ | ||
layers = new ChatStateMachineGraph.Layer[1] { new(){ | ||
states = new ChatStateMachineGraph.StateNode[1]{new() | ||
{ | ||
name="Start" | ||
}} | ||
}}; | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Editor/StateMachine/ChatStateMachineGraphEditorCtrl.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Reflection; | ||
using System.Reflection.Emit; | ||
namespace Kurisu.UniChat.StateMachine.Editor | ||
{ | ||
public static class DynamicTypeBuilder | ||
{ | ||
private const string kDynamicTypeBuilderAssemblyName = "Kurisu.UniChat.Emit"; | ||
private static ModuleBuilder m_ModuleBuilder; | ||
|
||
private static ModuleBuilder CreateModuleBuilder() | ||
{ | ||
if (m_ModuleBuilder != null) | ||
return m_ModuleBuilder; | ||
|
||
var appDomain = AppDomain.CurrentDomain; | ||
var assemblyName = new AssemblyName(kDynamicTypeBuilderAssemblyName); | ||
var assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); | ||
m_ModuleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name); | ||
return m_ModuleBuilder; | ||
} | ||
|
||
public static Type MakeDerivedType(Type baseClass, Type parameterType) | ||
{ | ||
ModuleBuilder moduleBuilder = CreateModuleBuilder(); | ||
string tAssemblyName = parameterType.Assembly.GetName().Name; | ||
string typeName = $"{baseClass.Namespace}_{baseClass.Name}"; | ||
|
||
string currentAssemblyName = typeof(DynamicTypeBuilder).Assembly.GetName().Name.Replace('.', '_'); | ||
|
||
if (baseClass.IsGenericType) | ||
{ | ||
//Get rid of the '`N' after the class name for the # of generic args | ||
//TODO: If there are >= 10 args (highly unlikely) this will break (: | ||
typeName = typeName[..^2]; | ||
typeName += $"_{string.Join("_", baseClass.GetGenericArguments().Select(t => t.FullName))}"; | ||
} | ||
|
||
string typeNameWithoutAssembly = typeName.Replace('.', '_'); | ||
|
||
typeName = $"{tAssemblyName}_{typeName}"; | ||
typeName = typeName.Replace('.', '_'); | ||
Type[] moduleBuilderTypes = moduleBuilder.GetTypes(); | ||
|
||
|
||
var existingType = moduleBuilderTypes.SingleOrDefault(t => t.Name.EndsWith(typeNameWithoutAssembly) && !t.Name.StartsWith($"{currentAssemblyName}")); | ||
if (existingType != null) | ||
{ | ||
return existingType; | ||
} | ||
|
||
|
||
existingType = moduleBuilderTypes.SingleOrDefault(t => t.Name == typeName); | ||
if (existingType != null) | ||
{ | ||
return existingType; | ||
} | ||
var baseConstructor = baseClass.GetConstructor(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance, null, new Type[0], null); | ||
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public, baseClass); | ||
var constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, null); | ||
var ilGenerator = constructor.GetILGenerator(); | ||
|
||
if (baseConstructor != null) | ||
{ | ||
ilGenerator.Emit(OpCodes.Ldarg_0); | ||
ilGenerator.Emit(OpCodes.Call, baseConstructor); | ||
} | ||
|
||
ilGenerator.Emit(OpCodes.Nop); | ||
ilGenerator.Emit(OpCodes.Nop); | ||
ilGenerator.Emit(OpCodes.Ret); | ||
return typeBuilder.CreateType(); | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.