Initialer Upload neues Unity-Projekt
This commit is contained in:
@ -0,0 +1,141 @@
|
||||
using System.IO;
|
||||
using Convai.Scripts.Utils;
|
||||
using Convai.Scripts.Utils.LipSync;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Convai.Scripts.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window for managing Convai NPC components.
|
||||
/// </summary>
|
||||
public class ConvaiNPCComponentSettingsWindow : EditorWindow
|
||||
{
|
||||
private ConvaiNPC _convaiNPC;
|
||||
|
||||
/// <summary>
|
||||
/// Handles GUI events for the window.
|
||||
/// </summary>
|
||||
private void OnGUI()
|
||||
{
|
||||
titleContent = new GUIContent("Convai NPC Components");
|
||||
Vector2 windowSize = new(300, 180);
|
||||
minSize = windowSize;
|
||||
maxSize = windowSize;
|
||||
if (_convaiNPC == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No ConvaiNPC selected");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
EditorGUIUtility.labelWidth = 200f; // Set a custom label width
|
||||
|
||||
_convaiNPC.IncludeActionsHandler = EditorGUILayout.Toggle(new GUIContent("NPC Actions", "Decides if Actions Handler is included"), _convaiNPC.IncludeActionsHandler);
|
||||
_convaiNPC.LipSync = EditorGUILayout.Toggle(new GUIContent("Lip Sync", "Decides if Lip Sync is enabled"), _convaiNPC.LipSync);
|
||||
_convaiNPC.HeadEyeTracking = EditorGUILayout.Toggle(new GUIContent("Head & Eye Tracking", "Decides if Head & Eye tracking is enabled"), _convaiNPC.HeadEyeTracking);
|
||||
_convaiNPC.EyeBlinking = EditorGUILayout.Toggle(new GUIContent("Eye Blinking", "Decides if Eye Blinking is enabled"), _convaiNPC.EyeBlinking);
|
||||
_convaiNPC.NarrativeDesignManager = EditorGUILayout.Toggle(new GUIContent("Narrative Design Manager", "Decides if Narrative Design Manager is enabled"), _convaiNPC.NarrativeDesignManager);
|
||||
_convaiNPC.ConvaiGroupNPCController = EditorGUILayout.Toggle(new GUIContent("Group NPC Controller", "Decides if this NPC can be a part of Convai NPC to NPC Conversation"), _convaiNPC.ConvaiGroupNPCController);
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Apply Changes", GUILayout.Height(40)))
|
||||
{
|
||||
ApplyChanges();
|
||||
EditorUtility.SetDirty(_convaiNPC);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the component states when the window gains focus.
|
||||
/// </summary>
|
||||
private void OnFocus()
|
||||
{
|
||||
RefreshComponentStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the Convai NPC Component Settings window.
|
||||
/// </summary>
|
||||
/// <param name="convaiNPC">The Convai NPC to manage.</param>
|
||||
public static void Open(ConvaiNPC convaiNPC)
|
||||
{
|
||||
ConvaiNPCComponentSettingsWindow window = GetWindow<ConvaiNPCComponentSettingsWindow>();
|
||||
window.titleContent = new GUIContent("Convai NPC Component Settings");
|
||||
window._convaiNPC = convaiNPC;
|
||||
window.RefreshComponentStates();
|
||||
window.Show();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the states of the components.
|
||||
/// </summary>
|
||||
private void RefreshComponentStates()
|
||||
{
|
||||
if (_convaiNPC != null)
|
||||
{
|
||||
_convaiNPC.IncludeActionsHandler = _convaiNPC.GetComponent<ConvaiActionsHandler>() is not null;
|
||||
_convaiNPC.LipSync = _convaiNPC.GetComponent<ConvaiLipSync>() is not null;
|
||||
_convaiNPC.HeadEyeTracking = _convaiNPC.GetComponent<ConvaiHeadTracking>() is not null;
|
||||
_convaiNPC.EyeBlinking = _convaiNPC.GetComponent<ConvaiBlinkingHandler>() is not null;
|
||||
_convaiNPC.NarrativeDesignManager = _convaiNPC.GetComponent<NarrativeDesignManager>() is not null;
|
||||
_convaiNPC.ConvaiGroupNPCController = _convaiNPC.GetComponent<ConvaiGroupNPCController>() is not null;
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies changes based on the user's selection in the inspector.
|
||||
/// </summary>
|
||||
private void ApplyChanges()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Confirm Apply Changes",
|
||||
"Do you want to apply the following changes?", "Yes", "No"))
|
||||
{
|
||||
ApplyComponent<ConvaiActionsHandler>(_convaiNPC.IncludeActionsHandler);
|
||||
ApplyComponent<ConvaiLipSync>(_convaiNPC.LipSync);
|
||||
ApplyComponent<ConvaiHeadTracking>(_convaiNPC.HeadEyeTracking);
|
||||
ApplyComponent<ConvaiBlinkingHandler>(_convaiNPC.EyeBlinking);
|
||||
ApplyComponent<NarrativeDesignManager>(_convaiNPC.NarrativeDesignManager);
|
||||
ApplyComponent<ConvaiGroupNPCController>(_convaiNPC.ConvaiGroupNPCController);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies or removes a component based on the specified condition.
|
||||
/// If the component is to be removed, its state is saved. If it's added, its state is restored if previously saved.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the component.</typeparam>
|
||||
/// <param name="includeComponent">Whether to include the component.</param>
|
||||
private void ApplyComponent<T>(bool includeComponent) where T : Component
|
||||
{
|
||||
T component = _convaiNPC.GetComponent<T>();
|
||||
|
||||
string savedDataFileName = Path.Combine(StateSaver.ROOT_DIRECTORY, _convaiNPC.characterID,
|
||||
$"{SceneManager.GetActiveScene().name}_{_convaiNPC.characterID}_{nameof(T)}_State.data");
|
||||
|
||||
if (includeComponent)
|
||||
{
|
||||
if (component == null)
|
||||
{
|
||||
component = _convaiNPC.gameObject.AddComponentSafe<T>();
|
||||
|
||||
if (File.Exists(savedDataFileName))
|
||||
component.RestoreStateFromFile(savedDataFileName);
|
||||
}
|
||||
}
|
||||
else if (component != null)
|
||||
{
|
||||
component.SaveStateToFile(savedDataFileName);
|
||||
DestroyImmediate(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f84be99651944b69262e8060b8ac451
|
||||
timeCreated: 1696842374
|
||||
254
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs
Normal file
254
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs
Normal file
@ -0,0 +1,254 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Convai.Scripts.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for the ConvaiNPC component.
|
||||
/// Provides functionalities to cache and restore states of all convai scripts whenever a scene is saved.
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(ConvaiNPC))]
|
||||
[HelpURL("https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/scripts-overview")]
|
||||
public class ConvaiNPCEditor : UnityEditor.Editor
|
||||
{
|
||||
private ConvaiNPC _convaiNPC;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_convaiNPC = (ConvaiNPC)target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the default inspector GUI to add custom buttons and functionality.
|
||||
/// </summary>
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawDefaultInspector();
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
// Add Components button to add necessary components and assign a random color to the character.
|
||||
if (GUILayout.Button(new GUIContent(
|
||||
"Add Components",
|
||||
"Adds necessary components to the NPC and assigns a random color to the character's text"
|
||||
),
|
||||
GUILayout.Width(120)
|
||||
)
|
||||
) AddComponentsToNPC();
|
||||
|
||||
if (GUILayout.Button(new GUIContent(
|
||||
"Copy Debug",
|
||||
"Copies the session id and other essential properties to clipboard for easier debugging"
|
||||
),
|
||||
GUILayout.Width(120)
|
||||
)
|
||||
) CopyToClipboard();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void CopyToClipboard()
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
stringBuilder.AppendLine($"Endpoint: {_convaiNPC.GetEndPointURL}");
|
||||
stringBuilder.AppendLine($"Character ID: {_convaiNPC.characterID}");
|
||||
stringBuilder.AppendLine($"Session ID: {_convaiNPC.sessionID}");
|
||||
|
||||
GUIUtility.systemCopyBuffer = stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds components to the NPC and assigns a random color to the character's text.
|
||||
/// </summary>
|
||||
private void AddComponentsToNPC()
|
||||
{
|
||||
try
|
||||
{
|
||||
ConvaiNPCComponentSettingsWindow.Open(_convaiNPC);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Unexpected error occurred when applying changes. Error: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility class to save the state of Convai scripts.
|
||||
/// </summary>
|
||||
public abstract class StateSaver
|
||||
{
|
||||
public const string ROOT_DIRECTORY = "Assets/Convai/Settings/Script State/";
|
||||
|
||||
/// <summary>
|
||||
/// Save the state of all Convai scripts in the current scene.
|
||||
/// </summary>
|
||||
[MenuItem("Convai/Save Script State", false, 100)]
|
||||
public static void SaveScriptState()
|
||||
{
|
||||
Scene scene = SceneManager.GetActiveScene();
|
||||
ConvaiNPC[] convaiObjects = Object.FindObjectsOfType<ConvaiNPC>();
|
||||
|
||||
foreach (ConvaiNPC convaiNPC in convaiObjects)
|
||||
{
|
||||
Debug.Log($"Saving state for character: {convaiNPC.characterName}");
|
||||
MonoBehaviour[] scripts = convaiNPC.GetComponentsInChildren<MonoBehaviour>();
|
||||
|
||||
string characterFolder = Path.Combine(ROOT_DIRECTORY, convaiNPC.characterID);
|
||||
if (!Directory.Exists(characterFolder)) Directory.CreateDirectory(characterFolder);
|
||||
|
||||
foreach (MonoBehaviour script in scripts)
|
||||
{
|
||||
string fullName = script.GetType().FullName;
|
||||
if (fullName != null && !fullName.StartsWith("Convai.Scripts")) continue;
|
||||
|
||||
string assetPath = script.GetSavePath(characterFolder, scene.name, convaiNPC.characterID);
|
||||
File.WriteAllText(assetPath, JsonUtility.ToJson(script));
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
#region Nested type: SaveSceneHook
|
||||
|
||||
/// <summary>
|
||||
/// Restore the state of all Convai scripts in the current scene.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public class SaveSceneHook
|
||||
{
|
||||
static SaveSceneHook()
|
||||
{
|
||||
EditorSceneManager.sceneSaved += SceneSaved;
|
||||
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
|
||||
}
|
||||
|
||||
private static void OnBeforeAssemblyReload()
|
||||
{
|
||||
// Unsubscribe from the sceneSaved event to prevent memory leaks.
|
||||
EditorSceneManager.sceneSaved -= SceneSaved;
|
||||
// Unsubscribe from the beforeAssemblyReload event.
|
||||
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
|
||||
}
|
||||
|
||||
private static void SceneSaved(Scene scene)
|
||||
{
|
||||
SaveScriptState();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for Unity editor components to facilitate saving and restoring state, as well as safely
|
||||
/// adding components.
|
||||
/// </summary>
|
||||
public static class EditorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves the state of a component to a file in JSON format.
|
||||
/// </summary>
|
||||
/// <param name="component">The component whose state is to be saved.</param>
|
||||
/// <param name="path">The file path where the state will be saved.</param>
|
||||
/// <typeparam name="T">The type of the component derived from UnityEngine.Component.</typeparam>
|
||||
public static void SaveStateToFile<T>(this T component, string path) where T : Component
|
||||
{
|
||||
try
|
||||
{
|
||||
string serializedComponentData = JsonUtility.ToJson(component);
|
||||
File.WriteAllText(path, serializedComponentData);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
Debug.LogError($"Access to the path '{path}' is denied. Error: {ex.Message}");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Debug.LogError($"An I/O error occurred while writing to the file at '{path}'. Error: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to save component state for {typeof(T).Name}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores the state of a component from a file containing JSON data.
|
||||
/// </summary>
|
||||
/// <param name="component">The component whose state is to be restored.</param>
|
||||
/// <param name="path">The file path from which the state will be restored.</param>
|
||||
/// <typeparam name="T">The type of the component derived from UnityEngine.Component.</typeparam>
|
||||
public static void RestoreStateFromFile<T>(this T component, string path) where T : Component
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
Debug.LogWarning($"No saved state file found at '{path}' for component {typeof(T).Name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
string savedData = File.ReadAllText(path);
|
||||
JsonUtility.FromJsonOverwrite(savedData, component);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
Debug.LogError($"Access to the path '{path}' is denied. Error: {ex.Message}");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Debug.LogError($"An I/O error occurred while reading from the file at '{path}'. Error: {ex.Message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to restore component data for {typeof(T).Name}. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a component to the GameObject safely, catching any exceptions that occur during the process.
|
||||
/// </summary>
|
||||
/// <param name="go">The GameObject to which the component will be added.</param>
|
||||
/// <typeparam name="T">The type of the component to be added, derived from UnityEngine.Component.</typeparam>
|
||||
/// <returns>The newly added component, or null if the operation failed.</returns>
|
||||
public static T AddComponentSafe<T>(this GameObject go) where T : Component
|
||||
{
|
||||
try
|
||||
{
|
||||
return go.AddComponent<T>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Failed to add component of type {typeof(T).Name}, Error: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a file path for saving the state of a MonoBehaviour script, based on the character folder, scene name,
|
||||
/// and character ID.
|
||||
/// </summary>
|
||||
/// <param name="script">The MonoBehaviour script for which the save path is being constructed.</param>
|
||||
/// <param name="characterFolder">The folder path associated with the character.</param>
|
||||
/// <param name="sceneName">The name of the current scene.</param>
|
||||
/// <param name="characterID">The unique identifier of the character from Convai Playground.</param>
|
||||
/// <returns>A string representing the constructed file path for saving the script's state.</returns>
|
||||
public static string GetSavePath(this MonoBehaviour script, string characterFolder, string sceneName,
|
||||
string characterID)
|
||||
{
|
||||
return Path.Combine(characterFolder, $"{sceneName}_{characterID}_{script.GetType().FullName}_State.data");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs.meta
Normal file
11
Assets/Convai/Scripts/Editor/NPC/ConvaiNPCEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f2ab5d11b144b3aa5d8c371ea664c44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user