Initialer Upload neues Unity-Projekt

This commit is contained in:
Daniel Ocks
2025-07-21 09:11:14 +02:00
commit eeca72985b
14558 changed files with 1508140 additions and 0 deletions

View File

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

View File

@ -0,0 +1,367 @@
#if READY_PLAYER_ME
using ReadyPlayerMe.Core;
using ReadyPlayerMe.Core.Editor;
#endif
using System;
using System.IO;
using System.Net;
using System.Text;
using Convai.Scripts.Runtime.Utils;
using Convai.Scripts.Utils;
using Convai.Scripts.Utils.LipSync;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Random = UnityEngine.Random;
namespace Convai.Scripts.Editor.Character
{
public class ConvaiCharacterImporter : EditorWindow
{
/// <summary>
/// The color palette used for the character text.
/// </summary>
private static readonly Color[] ColorPalette =
{
new(1f, 0f, 0f), new(0f, 1f, 0f), new(0f, 0f, 1f),
new(1f, 1f, 0f), new(0f, 1f, 1f), new(1f, 0f, 1f),
new(1f, 0.5f, 0f), new(0.5f, 0f, 0.5f), new(0f, 0.5f, 0f),
new(0.5f, 0.5f, 0.5f), new(1f, 0.8f, 0.6f), new(0.6f, 0.8f, 1f),
new(0.8f, 0.6f, 1f), new(1f, 0.6f, 0.8f), new(0.7f, 0.4f, 0f),
new(0f, 0.7f, 0.7f), new(0.7f, 0.7f, 0f), new(0f, 0.7f, 0.4f),
new(0.7f, 0f, 0.2f), new(0.9f, 0.9f, 0.9f)
};
/// <summary>
/// Creates the GUI for the Character Importer window.
/// </summary>
public void CreateGUI()
{
VisualElement root = rootVisualElement;
ScrollView page2 = new();
root.Add(new Label(""));
Image convaiLogoImage = new()
{
image = AssetDatabase.LoadAssetAtPath<Texture>(ConvaiImagesDirectory.CONVAI_LOGO_PATH),
style =
{
height = 100,
paddingBottom = 10,
paddingTop = 10,
paddingRight = 10,
paddingLeft = 10
}
};
root.Add(convaiLogoImage);
Label convaiCharacterIDLabel = new("Enter your Character ID: ")
{
style = { fontSize = 16 }
};
TextField characterIDTextField = new();
Button downloadButton = new(() => DownloadCharacter(characterIDTextField.text))
{
text = "Import!",
style =
{
fontSize = 16,
unityFontStyleAndWeight = FontStyle.Bold,
alignSelf = Align.Center,
paddingBottom = 10,
paddingLeft = 30,
paddingRight = 30,
paddingTop = 10
}
};
Button docsLink = new(() => Application.OpenURL(
"https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/importing-a-character-from-convai-playground"))
{
text = "How do I create a character?",
style =
{
alignSelf = Align.Center,
paddingBottom = 5,
paddingLeft = 50,
paddingRight = 50,
paddingTop = 5
}
};
page2.Add(convaiCharacterIDLabel);
page2.Add(new Label(""));
page2.Add(characterIDTextField);
page2.Add(new Label(""));
page2.Add(downloadButton);
page2.Add(new Label(""));
page2.Add(docsLink);
page2.style.marginBottom = 20;
page2.style.marginLeft = 20;
page2.style.marginRight = 20;
page2.style.marginTop = 20;
root.Add(page2);
}
/// <summary>
/// Opens the Character Importer window.
/// </summary>
[MenuItem("Convai/Character Importer", false, 5)]
public static void CharacterImporter()
{
ConvaiCharacterImporter wnd = GetWindow<ConvaiCharacterImporter>();
wnd.titleContent = new GUIContent("Character Importer");
}
/// <summary>
/// Downloads the character from the Convai API and sets up the character in the scene.
/// </summary>
/// <param name="characterID"> The characterID of the character to download.</param>
private async void DownloadCharacter(string characterID)
{
#if READY_PLAYER_ME
if (!ConvaiAPIKeySetup.GetAPIKey(out string apiKey)) return;
GetRequest getRequest = new(characterID);
string stringGetRequest = JsonConvert.SerializeObject(getRequest);
WebRequest request = WebRequest.Create("https://api.convai.com/character/get");
EditorUtility.DisplayProgressBar("Connecting", "Collecting resources...", 0f);
request.Method = "post";
request.ContentType = "application/json";
request.Headers.Add("CONVAI-API-KEY", apiKey);
byte[] jsonBytes = Encoding.UTF8.GetBytes(stringGetRequest);
await using Stream requestStream = await request.GetRequestStreamAsync();
await requestStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
try
{
using HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
await using Stream streamResponse = response.GetResponseStream();
if (streamResponse == null) return;
using StreamReader reader = new(streamResponse);
string responseContent = await reader.ReadToEndAsync();
GetResponse getResponseContent = JsonConvert.DeserializeObject<GetResponse>(responseContent);
string modelLink = getResponseContent.ModelDetail.ModelLink;
string characterName = getResponseContent.CharacterName.Trim();
AvatarObjectLoader avatarLoader = new()
{
AvatarConfig = Resources.Load<AvatarConfig>("ConvaiRPMAvatarConfig")
};
DirectoryUtility.DefaultAvatarFolder = $"Convai/Characters/Mesh Data/{characterName}";
EditorUtility.DisplayProgressBar("Downloading Character", "Initializing download...", 0f);
avatarLoader.OnProgressChanged += (_, progressArgs) =>
EditorUtility.DisplayProgressBar("Downloading Character", $"Downloading character model {characterName}: {progressArgs.Progress * 100f}%",
progressArgs.Progress);
avatarLoader.OnCompleted += (_, args) =>
{
EditorUtility.ClearProgressBar();
AvatarLoaderSettings avatarLoaderSettings = Resources.Load<AvatarLoaderSettings>("ConvaiAvatarLoaderSettings");
string path =
$"{DirectoryUtility.GetRelativeProjectPath(args.Avatar.name, AvatarCache.GetAvatarConfigurationHash(avatarLoaderSettings.AvatarConfig))}/{args.Avatar.name}";
GameObject avatar = PrefabHelper.CreateAvatarPrefab(args.Metadata, path, avatarConfig: avatarLoaderSettings.AvatarConfig);
SetupCharacter(characterID, characterName, avatar, args);
Debug.Log($"Character '{characterName}' downloaded and set up successfully.");
};
avatarLoader.OnFailed += (_, error) =>
{
EditorUtility.ClearProgressBar();
Debug.LogError($"Failed to download character: {error}");
};
avatarLoader.LoadAvatar(modelLink);
}
catch (WebException e)
{
EditorUtility.ClearProgressBar();
Debug.LogError(e.Message + "\nPlease check if Character ID is correct.");
}
catch (Exception e)
{
EditorUtility.ClearProgressBar();
Debug.LogError(e);
}
#endif
}
#if READY_PLAYER_ME
/// <summary>
/// Sets up the character in the scene with the downloaded character model.
/// </summary>
/// <param name="characterID"> The character ID.</param>
/// <param name="characterName"> The name of the character.</param>
/// <param name="avatar"> The avatar GameObject to set up.</param>
/// <param name="args"> The completion event arguments.</param>
private void SetupCharacter(string characterID, string characterName, GameObject avatar, CompletionEventArgs args)
{
SetupCharacterMetadata(characterName, avatar);
SetupCollision(avatar);
avatar.AddComponent<AudioSource>();
SetupAnimator(args, avatar);
ConvaiNPC convaiNPCComponent = SetupConvaiComponents(characterID, characterName, avatar);
SetupLipsync(avatar);
SetupChatUI(convaiNPCComponent);
PrefabUtility.SaveAsPrefabAsset(avatar, $"Assets/Convai/Characters/Prefabs/{avatar.name}.prefab");
DestroyImmediate(args.Avatar, true);
Selection.activeObject = avatar;
}
#endif
/// <summary>Setups the lipsync.</summary>
/// <param name="avatar">The avatar.</param>
private static void SetupLipsync(GameObject avatar)
{
ConvaiLipSync convaiLipSync = avatar.AddComponent<ConvaiLipSync>();
convaiLipSync.BlendshapeType = ConvaiLipSync.LipSyncBlendshapeType.ARKit;
}
/// <summary>
/// Sets up the metadata for the character.
/// </summary>
/// <param name="characterName">The name of the character.</param>
/// <param name="avatar">The avatar GameObject.</param>
private static void SetupCharacterMetadata(string characterName, GameObject avatar)
{
avatar.tag = "Character";
avatar.name = $"Convai NPC {characterName}";
}
/// <summary>
/// Sets up the chat UI for the character.
/// </summary>
/// <param name="convaiNPCComponent">The ConvaiNPC component.</param>
private void SetupChatUI(ConvaiNPC convaiNPCComponent)
{
ConvaiChatUIHandler chatUIHandler = FindObjectOfType<ConvaiChatUIHandler>();
if (chatUIHandler != null && convaiNPCComponent.characterName != null &&
!chatUIHandler.HasCharacter(convaiNPCComponent.characterName))
{
Utils.Character newCharacter = new()
{
characterGameObject = convaiNPCComponent,
characterName = convaiNPCComponent.characterName,
CharacterTextColor = GetRandomColor()
};
chatUIHandler.AddCharacter(newCharacter);
EditorUtility.SetDirty(chatUIHandler);
}
}
/// <summary>
/// Sets up the Convai components for the character.
/// </summary>
/// <param name="characterID">The character ID.</param>
/// <param name="characterName">The name of the character.</param>
/// <param name="avatar">The avatar GameObject.</param>
/// <returns>The ConvaiNPC component.</returns>
private static ConvaiNPC SetupConvaiComponents(string characterID, string characterName, GameObject avatar)
{
ConvaiNPC convaiNPCComponent = avatar.AddComponent<ConvaiNPC>();
convaiNPCComponent.sessionID = "-1";
convaiNPCComponent.characterID = characterID;
convaiNPCComponent.characterName = characterName;
avatar.AddComponent<ConvaiHeadTracking>();
return convaiNPCComponent;
}
#if READY_PLAYER_ME
/// <summary>
/// Sets up the animator for the character.
/// </summary>
/// <param name="args">The completion event arguments.</param>
/// <param name="avatar">The avatar GameObject.</param>
private static void SetupAnimator(CompletionEventArgs args, GameObject avatar)
{
AvatarAnimationHelper.SetupAnimator(args.Metadata, avatar);
Animator animator = avatar.GetComponent<Animator>();
// Determine avatar type based on Avatar field in Animator component
bool isMasculine = animator.avatar.name.Contains("Masculine");
// Set the appropriate animator controller
string animatorPath = isMasculine ? "Masculine NPC Animator" : "Feminine NPC Animator";
animator.runtimeAnimatorController = Resources.Load<RuntimeAnimatorController>(animatorPath);
}
#endif
/// <summary>
/// Sets up the collision for the character.
/// </summary>
/// <param name="avatar">The avatar GameObject.</param>
private static void SetupCollision(GameObject avatar)
{
CapsuleCollider capsuleColliderComponent = avatar.AddComponent<CapsuleCollider>();
capsuleColliderComponent.center = new Vector3(0, 0.9f, 0);
capsuleColliderComponent.radius = 0.3f;
capsuleColliderComponent.height = 1.8f;
capsuleColliderComponent.isTrigger = true;
}
/// <summary>
/// Returns a random color from the predefined palette.
/// </summary>
/// <returns>A random color from the predefined palette.</returns>
private Color GetRandomColor()
{
return ColorPalette[Random.Range(0, ColorPalette.Length)];
}
private class GetRequest
{
[JsonProperty("charID")] public string CharID;
public GetRequest(string charID)
{
CharID = charID;
}
}
private class GetResponse
{
[JsonProperty("backstory")] public string Backstory;
[JsonProperty("character_actions")] public string[] CharacterActions;
[JsonProperty("character_emotions")] public string[] CharacterEmotions;
[JsonProperty("character_id")] public string CharacterID;
[JsonProperty("character_name")] public string CharacterName;
[JsonProperty("model_details")] public ModelDetails ModelDetail;
[JsonProperty("timestamp")] public string Timestamp;
[JsonProperty("user_id")] public string UserID;
[JsonProperty("voice_type")] public string VoiceType;
#region Nested type: ModelDetails
internal class ModelDetails
{
[JsonProperty("modelLink")] public string ModelLink;
[JsonProperty("modelPlaceholder")] public string ModelPlaceholder;
[JsonProperty("modelType")] public string ModelType;
}
#endregion
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,588 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
namespace Convai.Scripts.Editor.Custom_Package
{
/// <summary>
/// Custom package installer for Convai's Custom Packages in Unity Editor.
/// </summary>
public class ConvaiCustomPackageInstaller : EditorWindow, IActiveBuildTargetChanged
{
// Enum to represent different setup types
private enum SetupTypes
{
None,
ARAndroid,
ARiOS,
VR,
Uninstaller
}
// Paths to different Convai packages
private const string AR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiARUpgrader.unitypackage";
private const string IOS_BUILD_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiiOSBuild.unitypackage";
private const string TMP_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiCustomTMP.unitypackage";
private const string URP_CONVERTER_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiURPConverter.unitypackage";
private const string VR_PACKAGE_PATH = "Assets/Convai/Custom Packages/ConvaiVRUpgrader.unitypackage";
// Index to keep track of the current package installation step
private int _currentPackageInstallIndex;
// Current setup type
private SetupTypes _currentSetup;
// Request object for package installations/uninstallations
private Request _request;
/// <summary>
/// GUI method to display the window and buttons.
/// </summary>
private void OnGUI()
{
// Loading Convai logo
Texture2D convaiLogo = AssetDatabase.LoadAssetAtPath<Texture2D>(ConvaiImagesDirectory.CONVAI_LOGO_PATH);
GUI.DrawTexture(new Rect(115, 0, 256, 80), convaiLogo);
GUILayout.BeginArea(new Rect(165, 100, Screen.width, Screen.height));
GUILayout.BeginVertical();
// Button to install AR package
if (GUILayout.Button("Install AR Package", GUILayout.Width(170), GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog("Which Platform",
"Which platform do you want to install AR package for?", "Android", "iOS"))
{
// Display confirmation dialog before installation
if (EditorUtility.DisplayDialog("Confirm Android AR Package Installation",
"This step will install AR-related packages and integrate Convai's AR package into your project. " +
"This process will affect your project. Do you want to proceed?\n\n" +
"The following operations will be performed:\n" +
"- Universal Render Pipeline (URP)\n" +
"- ARCore Plugin\n" +
"- Convai Custom AR Package\n" +
"- Convai URP Converter\n\n" +
"* If these packages are not present in your project, they will be installed.\n" +
"* If the target build platform is not Android, it will be switched to Android.", "Yes, Proceed", "No, Cancel"))
{
EditorApplication.LockReloadAssemblies();
StartPackageInstallation(SetupTypes.ARAndroid);
}
}
else
{
// Display confirmation dialog before installation
if (EditorUtility.DisplayDialog("Confirm iOS AR Package Installation",
"This step will install AR-related packages and integrate Convai's AR package into your project. " +
"This process will affect your project. Do you want to proceed?\n\n" +
"The following operations will be performed:\n" +
"- Universal Render Pipeline (URP)\n" +
"- ARKit Plugin\n" +
"- Convai Custom AR Package\n" +
"- Convai URP Converter\n\n" +
"* If these packages are not present in your project, they will be installed.\n" +
"* If the target build platform is not iOS, it will be switched to iOS.", "Yes, Proceed", "No, Cancel"))
{
EditorApplication.LockReloadAssemblies();
StartPackageInstallation(SetupTypes.ARiOS);
}
}
}
GUILayout.Space(10);
// Button to install VR package
if (GUILayout.Button("Install VR Package", GUILayout.Width(170), GUILayout.Height(30)))
// Display confirmation dialog before installation
if (EditorUtility.DisplayDialog("Confirm VR Package Installation",
"This step will install VR-related packages and integrate Convai's VR package into your project. " +
"This process will affect your project. Do you want to proceed?\n\n" +
"The following operations will be performed:\n" +
"- Universal Render Pipeline (URP)\n" +
"- OpenXR Plugin\n" +
"- XR Interaction Toolkit\n" +
"- Convai Custom VR Package\n" +
"- Convai URP Converter\n\n" +
"* If these packages are not present in your project, they will be installed.\n" +
"* If the target build platform is not Android, it will be switched to Android.", "Yes, Proceed", "No, Cancel"))
{
EditorApplication.LockReloadAssemblies();
StartPackageInstallation(SetupTypes.VR);
}
GUILayout.Space(10);
// Button to uninstall XR package
if (GUILayout.Button("Uninstall XR Package", GUILayout.Width(170), GUILayout.Height(30)))
// Display confirmation dialog before uninstallation
if (EditorUtility.DisplayDialog("Confirm Package Uninstallation",
"This process will uninstall the Convai package and revert changes made by AR or VR setups in your project. " +
"It may affect your project. Are you sure you want to proceed?\n\n" +
"The following packages will be uninstalled.\n" +
"- ARCore Plugin or ARKit\n" +
"- OpenXR Plugin\n" +
"- XR Interaction Toolkit\n" +
"- Convai Custom AR or VR Package\n\n" +
"* The Convai Uninstaller Package will be installed. This process will revert scripts modified for XR to their default states.",
"Yes, Uninstall", "No, Cancel"))
{
_currentSetup = SetupTypes.Uninstaller;
EditorApplication.update += Progress;
EditorApplication.LockReloadAssemblies();
HandleUninstallPackage();
}
GUILayout.Space(10);
if (GUILayout.Button("Install iOS Build Package", GUILayout.Width(170), GUILayout.Height(30)))
{
InstallConvaiUnityPackage(IOS_BUILD_PACKAGE_PATH);
TryToDownloadiOSDLL();
}
GUILayout.Space(10);
if (GUILayout.Button("Install URP Converter", GUILayout.Width(170), GUILayout.Height(30))) InstallConvaiUnityPackage(URP_CONVERTER_PACKAGE_PATH);
GUILayout.Space(10);
if (GUILayout.Button("Install TMP Package", GUILayout.Width(170), GUILayout.Height(30))) InstallConvaiUnityPackage(TMP_PACKAGE_PATH);
GUILayout.EndVertical();
GUILayout.EndArea();
}
// IActiveBuildTargetChanged callback
public int callbackOrder { get; }
/// <summary>
/// Called when the active build target is changed.
/// </summary>
/// <param name="previousTarget">The previous build target.</param>
/// <param name="newTarget">The new build target.</param>
public void OnActiveBuildTargetChanged(BuildTarget previousTarget, BuildTarget newTarget)
{
// Check if the new build target is iOS and trigger the download of iOS DLL.
if (newTarget == BuildTarget.iOS) TryToDownloadiOSDLL();
}
/// <summary>
/// Shows the Convai Custom Package Installer window.
/// </summary>
[MenuItem("Convai/Custom Package Installer", false, 10)]
public static void ShowWindow()
{
ConvaiCustomPackageInstaller window = GetWindow<ConvaiCustomPackageInstaller>("Convai Custom Package Installer", true);
window.minSize = new Vector2(500, 370);
window.maxSize = window.minSize;
window.titleContent.text = "Custom Package Installer";
window.Show();
}
/// <summary>
/// Progress method to handle the installation/uninstallation progress.
/// </summary>
private void Progress()
{
Debug.Log("<color=cyan>Process in progress... Please wait.</color>");
// Check if the request object is initialized
if (_request == null) return;
if (_request.IsCompleted)
{
switch (_request.Status)
{
case StatusCode.InProgress:
// Do nothing while the request is still in progress
break;
case StatusCode.Success:
// Handle the successful completion of the package request
HandlePackageRequest();
break;
case StatusCode.Failure:
// Log an error message in case of failure
Debug.LogError("Error: " + _request.Error.message);
break;
}
// Remove the Progress method from the update event
EditorApplication.UnlockReloadAssemblies();
EditorApplication.update -= Progress;
}
}
/// <summary>
/// Method to handle the completion of the package request.
/// </summary>
private void HandlePackageRequest()
{
switch (_currentSetup)
{
case SetupTypes.None:
// Do nothing for SetupTypes.None
break;
case SetupTypes.ARiOS:
// Handle iOS AR package installation
HandleARPackageInstall();
Debug.Log("<color=lime>The request for package installation from the Package Manager has been successfully completed.</color>");
break;
case SetupTypes.ARAndroid:
// Handle Android AR package installation
HandleARPackageInstall();
Debug.Log("<color=lime>The request for package installation from the Package Manager has been successfully completed.</color>");
break;
case SetupTypes.VR:
// Handle VR package installation
HandleVRPackageInstall();
Debug.Log("<color=lime>The request for package installation from the Package Manager has been successfully completed.</color>");
break;
case SetupTypes.Uninstaller:
// Handle uninstallation package completion
HandleUninstallPackage();
Debug.Log("<color=lime>The request for package uninstallation from the Package Manager has been successfully completed.</color>");
break;
}
// Add the Progress method back to the update event
EditorApplication.update += Progress;
}
/// <summary>
/// Method to handle the uninstallation of packages.
/// </summary>
private void HandleUninstallPackage()
{
// Check if the request object is not initialized
if (_request == null)
{
// Define asset paths to delete
string[] deleteAssetPaths =
{
"Assets/Samples",
"Assets/Convai/ConvaiAR",
"Assets/Convai/ConvaiVR",
"Assets/XR",
"Assets/XRI"
};
List<string> outFailedPaths = new();
// Delete specified asset paths
AssetDatabase.DeleteAssets(deleteAssetPaths, outFailedPaths);
// Log errors if any deletion fails
if (outFailedPaths.Count > 0)
foreach (string failedPath in outFailedPaths)
Debug.LogError("Failed to delete : " + failedPath);
}
// Define package names for uninstallation
string ARCorePackageName = "com.unity.xr.arcore";
string ARKitPackageName = "com.unity.xr.arkit";
string OpenXRPackageName = "com.unity.xr.openxr";
string XRInteractionToolkitPackageName = "com.unity.xr.interaction.toolkit";
// Check if ARCore is installed and initiate removal
if (IsPackageInstalled(ARCorePackageName)) _request = Client.Remove(ARCorePackageName);
// Check if ARKit is installed and initiate removal
if (IsPackageInstalled(ARKitPackageName))
{
_request = Client.Remove(ARKitPackageName);
}
// Check if OpenXR is installed and initiate removal
else if (IsPackageInstalled(OpenXRPackageName))
{
_request = Client.Remove(OpenXRPackageName);
}
// Check if XR Interaction Toolkit is installed and initiate removal
else if (IsPackageInstalled(XRInteractionToolkitPackageName))
{
_request = Client.Remove(XRInteractionToolkitPackageName);
}
else
{
// Stop the update event if the request is not initialized
EditorApplication.update -= Progress;
EditorApplication.UnlockReloadAssemblies();
}
// Remove the Progress method from the update event if the request is not initialized
if (_request == null) EditorApplication.update -= Progress;
}
/// <summary>
/// Method to start the installation of a specific package setup.
/// </summary>
private void StartPackageInstallation(SetupTypes setupType)
{
// Log a message indicating the start of the package installation
Debug.Log($"<color=cyan>Installation of {setupType} package has started... This process may take 3-5 minutes.</color>");
// Warn the user about the possibility of 'Failed to Resolve Packages' error
Debug.LogWarning("<color=yellow>If you encounter with 'Failed to Resolve Packages' error, there's no need to be concerned.</color>");
// Reset the package installation index
_currentPackageInstallIndex = 0;
// Set the current setup type
_currentSetup = setupType;
// Initialize the Universal Render Pipeline (URP) setup
InitializeURPSetup();
}
/// <summary>
/// Method to handle the installation of AR-related packages.
/// </summary>
private void HandleARPackageInstall()
{
// Check the current package installation index
if (_currentPackageInstallIndex == 0)
{
switch (_currentSetup)
{
case SetupTypes.ARAndroid:
// Initialize the ARCore setup
InitializeARCoreSetup();
break;
case SetupTypes.ARiOS:
// Initialize the ARKit setup
InitializeARKitSetup();
break;
}
}
else
{
// Install AR-related packages and perform necessary setup
InstallConvaiUnityPackage(AR_PACKAGE_PATH);
InstallConvaiUnityPackage(URP_CONVERTER_PACKAGE_PATH);
switch (_currentSetup)
{
case SetupTypes.ARAndroid:
TryToChangeEditorBuildTargetToAndroid();
break;
case SetupTypes.ARiOS:
TryToChangeEditorBuildTargetToiOS();
break;
}
}
}
/// <summary>
/// Method to handle the installation of VR-related packages.
/// </summary>
private void HandleVRPackageInstall()
{
// Check the current package installation index
if (_currentPackageInstallIndex == 0)
{
// Initialize the OpenXR setup
InitializeOpenXRSetup();
}
else if (_currentPackageInstallIndex == 1)
{
// Initialize the XR Interaction Toolkit setup
InitializeXRInteractionToolkitSetup();
}
else
{
// Install VR-related packages and perform necessary setup
InstallConvaiUnityPackage(VR_PACKAGE_PATH);
InstallConvaiUnityPackage(URP_CONVERTER_PACKAGE_PATH);
TryToChangeEditorBuildTargetToAndroid();
}
}
/// <summary>
/// Method to initialize the URP setup.
/// </summary>
private void InitializeURPSetup()
{
// Define the URP package name
const string URPPackageName = "com.unity.render-pipelines.universal@14.0.11";
// Check if the URP package is already installed
if (IsPackageInstalled(URPPackageName))
{
// If installed, handle the successful package request
HandlePackageRequest();
return;
}
// If not installed, send a request to the Package Manager to add the URP package
_request = Client.Add(URPPackageName);
Debug.Log($"<color=orange>{URPPackageName} Package Installation Request Sent to Package Manager.</color>");
// Add the Progress method to the update event to monitor the installation progress
EditorApplication.update += Progress;
}
/// <summary>
/// Method to initialize the ARCore setup.
/// </summary>
private void InitializeARCoreSetup()
{
// Set the current package installation index for ARCore
_currentPackageInstallIndex = 1;
// Define the ARCore package name
string ARCorePackageName = "com.unity.xr.arcore@5.1.4";
// Check if the ARCore package is already installed
if (IsPackageInstalled(ARCorePackageName))
{
// If installed, handle the AR package installation
HandleARPackageInstall();
return;
}
// If not installed, send a request to the Package Manager to add the ARCore package
_request = Client.Add(ARCorePackageName);
Debug.Log($"<color=orange>{ARCorePackageName} Package Installation Request sent to Package Manager.</color>");
}
/// <summary>
/// Method to initialize the ARKit setup.
/// </summary>
private void InitializeARKitSetup()
{
// Set the current package installation index for AR Setup
_currentPackageInstallIndex = 1;
// Define the ARKit package name
string ARKitPackageName = "com.unity.xr.arkit@5.1.4";
// Check if the ARKit package is already installed
if (IsPackageInstalled(ARKitPackageName))
{
// If installed, handle the AR package installation
HandleARPackageInstall();
return;
}
// If not installed, send a request to the Package Manager to add the ARKit package
_request = Client.Add(ARKitPackageName);
Debug.Log($"<color=orange>{ARKitPackageName} Package Installation Request sent to Package Manager.</color>");
}
/// <summary>
/// Method to initialize the OpenXR setup.
/// </summary>
private void InitializeOpenXRSetup()
{
// Set the current package installation index for OpenXR
_currentPackageInstallIndex = 1;
// Define the OpenXR package name
string OpenXRPackageName = "com.unity.xr.openxr@1.10.0";
// Check if the OpenXR package is already installed
if (IsPackageInstalled(OpenXRPackageName))
{
// If installed, handle the VR package installation
HandleVRPackageInstall();
return;
}
// If not installed, send a request to the Package Manager to add the OpenXR package
_request = Client.Add(OpenXRPackageName);
Debug.Log($"<color=orange>{OpenXRPackageName} Package Installation Request sent to Package Manager.</color>");
}
/// <summary>
/// Method to initialize the XR Interaction Toolkit setup.
/// </summary>
private void InitializeXRInteractionToolkitSetup()
{
// Set the current package installation index for XR Interaction Toolkit
_currentPackageInstallIndex = 2;
// Define the XR Interaction Toolkit package name
string XRInteractionToolkitPackageName = "com.unity.xr.interaction.toolkit@2.5.4";
// Check if the XR Interaction Toolkit package is already installed
if (IsPackageInstalled(XRInteractionToolkitPackageName))
{
// If installed, handle the VR package installation
HandleVRPackageInstall();
return;
}
// If not installed, send a request to the Package Manager to add the XR Interaction Toolkit package
_request = Client.Add(XRInteractionToolkitPackageName);
Debug.Log($"<color=orange>{XRInteractionToolkitPackageName} Package Installation Request sent to Package Manager.</color>");
}
/// <summary>
/// Method to install a custom Convai Unity package.
/// </summary>
private void InstallConvaiUnityPackage(string packagePath)
{
// Import the Unity package
AssetDatabase.ImportPackage(packagePath, false);
// Get the package name without extension
string packageName = Path.GetFileNameWithoutExtension(packagePath);
Debug.Log($"<color=lime>{packageName} Custom Unity Package Installation Completed.</color>");
}
/// <summary>
/// Method to check if a package is already installed.
/// </summary>
private bool IsPackageInstalled(string packageName)
{
// Iterate through all registered packages
foreach (PackageInfo packageInfo in PackageInfo.GetAllRegisteredPackages())
// Check if the package name matches
if (packageInfo.name == packageName)
// Return true if the package is installed
return true;
// Return false if the package is not installed
return false;
}
/// <summary>
/// Try changing the editor build target to Android.
/// </summary>
private void TryToChangeEditorBuildTargetToAndroid()
{
// Check if the current build target is not Android
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
{
// Switch the active build target to Android
EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Android, BuildTarget.Android);
Debug.Log("<color=lime>Build Target Platform is being Changed to Android...</color>");
}
}
/// <summary>
/// Try changing the editor build target to iOS.
/// </summary>
private void TryToChangeEditorBuildTargetToiOS()
{
// Check if the current build target is not iOS
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.iOS)
{
// Switch the active build target to iOS
EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.iOS, BuildTarget.iOS);
Debug.Log("<color=lime>Build Target Platform is being Changed to iOS...</color>");
}
}
/// <summary>
/// Attempts to download the iOS DLL using the IOSDLLDownloader class.
/// </summary>
private void TryToDownloadiOSDLL()
{
// Call the TryToDownload method from the IOSDLLDownloader class.
iOSDLLDownloader.TryToDownload();
}
}
}
#endif

View File

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

View File

@ -0,0 +1,173 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Net;
using Convai.Scripts.Runtime.Utils;
using Convai.Scripts.Utils;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
namespace Convai.Scripts.Editor
{
/// <summary>
/// Editor window for downloading and extracting iOS DLL from a specified URL.
/// </summary>
public class iOSDLLDownloader
{
private const string DOWNLOAD_ENDPOINT_URL = "https://api.convai.com/user/downloadAsset";
private const string RELATIVE_PATH = "Convai/Plugins/Grpc.Core/runtimes";
private static string _targetDirectory;
/// <summary>
/// Attempts to download the iOS DLL if it doesn't already exist.
/// </summary>
public static void TryToDownload()
{
if (CheckFileExistence()) return;
Debug.Log("<color=lime>The iOS DLL download has started...</color>");
DownloadAndExtract(GetTargetDirectory());
}
/// <summary>
/// Coroutine to download and extract the ZIP file from the specified URL.
/// </summary>
/// <param name="url">URL of the ZIP file to download.</param>
/// <param name="outputPath">Directory to extract the contents to.</param>
/// <returns></returns>
private static void DownloadAndExtract(string outputPath)
{
try
{
string downloadURL = GetDownloadURL();
if (downloadURL == null) Debug.LogError("Failed to get download URL. Please check the API key and try again.");
using UnityWebRequest webRequest = UnityWebRequest.Get(downloadURL);
webRequest.SendWebRequest();
while (!webRequest.isDone)
{
float progress = webRequest.downloadProgress;
EditorUtility.DisplayProgressBar("Downloading required iOS DLL...",
"Please wait for the download to finish and do not close Unity. " + (int)(progress * 100) + "%", progress);
}
EditorUtility.ClearProgressBar();
if (webRequest.result is UnityWebRequest.Result.ConnectionError or UnityWebRequest.Result.ProtocolError)
{
Debug.LogError($"Error downloading file: {webRequest.error}");
}
else
{
byte[] results = webRequest.downloadHandler.data;
string zipPath = Path.Combine(Path.GetTempPath(), "downloaded.zip");
File.WriteAllBytes(zipPath, results);
ExtractZipFile(zipPath, outputPath);
File.Delete(zipPath);
Debug.Log($"Downloaded and extracted to {outputPath}" + "/ios/libgrpc.a");
// Refresh the asset database to make sure the new files are recognized
AssetDatabase.Refresh();
}
}
catch (Exception e)
{
Debug.Log(e.Message);
throw;
}
}
/// <summary>
/// Retrieves the download URL from Convai API.
/// </summary>
/// <returns>The download URL or null.</returns>
private static string GetDownloadURL()
{
if(!ConvaiAPIKeySetup.GetAPIKey(out string apiKey)) return null;
string body = @"{""service_name"": ""unity-builds"",""version"":""ios""}";
WebRequest request = WebRequest.Create(DOWNLOAD_ENDPOINT_URL);
request.Method = "POST";
request.ContentType = "application/json";
request.Headers.Add("CONVAI-API-KEY", apiKey);
using (StreamWriter streamWriter = new StreamWriter(request.GetRequestStream()))
{
streamWriter.Write(body);
}
using (WebResponse response = request.GetResponse())
using (Stream dataStream = response.GetResponseStream())
using (StreamReader reader = new(dataStream))
{
JObject responseJson = JObject.Parse(reader.ReadToEnd());
return (string)responseJson["download_link"];
}
}
/// <summary>
/// Extracts the contents of a ZIP file to the specified output folder.
/// </summary>
/// <param name="zipFilePath">Path to the ZIP file.</param>
/// <param name="outputFolder">Directory to extract the contents to.</param>
private static void ExtractZipFile(string zipFilePath, string outputFolder)
{
if (!Directory.Exists(outputFolder)) Directory.CreateDirectory(outputFolder);
using (ZipArchive archive = ZipFile.OpenRead(zipFilePath))
{
float totalEntries = archive.Entries.Count;
float currentEntry = 0;
foreach (ZipArchiveEntry entry in archive.Entries)
{
string fullPath = Path.Combine(outputFolder, entry.FullName);
// Ensure the directory exists
string directoryName = Path.GetDirectoryName(fullPath);
if (!Directory.Exists(directoryName))
if (directoryName != null)
Directory.CreateDirectory(directoryName);
// Extract the entry to the output directory
entry.ExtractToFile(fullPath, true);
// Update the progress bar
currentEntry++;
float progress = currentEntry / totalEntries;
EditorUtility.DisplayProgressBar("Extracting", $"Extracting file {entry.Name}...", progress);
}
}
EditorUtility.ClearProgressBar();
}
/// <summary>
/// Gets the target directory for extracting the files.
/// </summary>
/// <returns>Target directory path.</returns>
private static string GetTargetDirectory()
{
_targetDirectory = Path.Combine(Application.dataPath, RELATIVE_PATH);
if (!Directory.Exists(_targetDirectory)) Directory.CreateDirectory(_targetDirectory);
return _targetDirectory;
}
/// <summary>
/// Checks if the iOS DLL file already exists.
/// </summary>
/// <returns>True if the file exists, otherwise false.</returns>
private static bool CheckFileExistence()
{
string fullPath = Path.Combine(Application.dataPath, RELATIVE_PATH + "/ios/libgrpc.a");
bool fileExists = File.Exists(fullPath);
if (fileExists) Debug.Log("<color=orange>iOS DLL already exists. No need to download.</color>");
return fileExists;
}
}
}

View File

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

View File

@ -0,0 +1,47 @@
using UnityEditor;
using UnityEditor.PackageManager.Requests;
using UnityEditor.PackageManager;
#if !READY_PLAYER_ME
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
#endif
namespace Convai.Scripts.Editor.Custom_Package
{
[InitializeOnLoad]
public class ReadyPlayerMeImporter
{
static AddRequest _request;
static ReadyPlayerMeImporter()
{
#if !READY_PLAYER_ME
Debug.Log("Ready Player Me is not installed, importing it");
_request = Client.Add("https://github.com/readyplayerme/rpm-unity-sdk-core.git");
EditorUtility.DisplayProgressBar("Importing Ready Player Me", "Importing.....", Random.Range(0,1f));
EditorApplication.update += UnityEditorUpdateCallback;
#endif
}
#if !READY_PLAYER_ME
private static void UnityEditorUpdateCallback()
{
if (_request == null) return;
if (!_request.IsCompleted) return;
switch (_request.Status)
{
case StatusCode.Success:
Debug.Log($"Successfully installed: {_request.Result.name}");
break;
case StatusCode.Failure:
Debug.Log($"Failure: {_request.Error.message}");
break;
}
EditorApplication.update -= UnityEditorUpdateCallback;
EditorUtility.ClearProgressBar();
}
#endif
}
}

View File

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

View File

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

View File

@ -0,0 +1,211 @@
using System.Collections.Generic;
using System.Reflection;
using Convai.Scripts.Utils;
using UnityEditor;
using UnityEngine;
using Logger = Convai.Scripts.Utils.Logger;
namespace Convai.Scripts.Editor
{
/// <summary>
/// Manages the settings for the Logger, including loading, creating, and modifying LoggerSettings.
/// </summary>
public class LoggerSettingsManager
{
// Path to the LoggerSettings asset
private const string SETTINGS_PATH = "Assets/Convai/Resources/LoggerSettings.asset";
/// <summary>
/// Mapping between the row names and the field names in the LoggerSettings class.
/// </summary>
private static readonly Dictionary<string, string> CategoryMapping = new()
{
{ "Character", "characterResponse" },
{ "LipSync", "lipSync" },
{ "Actions", "actions" }
};
/// <summary>
/// The LoggerSettings instance.
/// </summary>
private LoggerSettings _loggerSettings;
/// <summary>
/// Property accessor for _loggerSettings. If _loggerSettings is null, it attempts to load it from the asset path.
/// If the asset does not exist, it creates a new LoggerSettings instance.
/// </summary>
public LoggerSettings loggerSettings
{
get
{
if (_loggerSettings == null)
{
_loggerSettings = AssetDatabase.LoadAssetAtPath<LoggerSettings>(SETTINGS_PATH);
if (_loggerSettings == null)
{
CreateLoggerSettings();
Logger.Warn("LoggerSettings ScriptableObject not found. Creating one...",
Logger.LogCategory.Character);
}
}
return _loggerSettings;
}
}
/// <summary>
/// Creates a new LoggerSettings instance with default values and saves it as an asset
/// </summary>
private void CreateLoggerSettings()
{
_loggerSettings = ScriptableObject.CreateInstance<LoggerSettings>();
// Set default values for Character
_loggerSettings.characterResponseDebug = true;
_loggerSettings.characterResponseInfo = true;
_loggerSettings.characterResponseWarning = true;
_loggerSettings.characterResponseError = true;
_loggerSettings.characterResponseException = true;
// Set default values for LipSync
_loggerSettings.lipSyncDebug = true;
_loggerSettings.lipSyncInfo = true;
_loggerSettings.lipSyncWarning = true;
_loggerSettings.lipSyncError = true;
_loggerSettings.lipSyncException = true;
// Set default values for Actions
_loggerSettings.actionsDebug = true;
_loggerSettings.actionsInfo = true;
_loggerSettings.actionsWarning = true;
_loggerSettings.actionsError = true;
_loggerSettings.actionsException = true;
// Check if the Convai folder exists and create if not
if (!AssetDatabase.IsValidFolder("Assets/Convai/Resources"))
AssetDatabase.CreateFolder("Assets/Convai", "Resources");
// Check if the Settings folder exists and create if not
if (!AssetDatabase.IsValidFolder("Assets/Convai/Resources/Settings"))
AssetDatabase.CreateFolder("Assets/Convai/Resources", "Settings");
AssetDatabase.CreateAsset(_loggerSettings, SETTINGS_PATH);
AssetDatabase.SaveAssets();
}
/// <summary>
/// Checks if all flags for a given row are set.
/// </summary>
/// <param name="rowName">The name of the row to check.</param>
/// <returns>True if all flags for the given row are set, false otherwise.</returns>
public bool GetAllFlagsForRow(string rowName)
{
bool allSelected = true;
foreach (string logType in new[] { "Debug", "Error", "Exception", "Info", "Warning" })
{
string baseFieldName = CategoryMapping.TryGetValue(rowName, out string value) ? value : string.Empty;
if (string.IsNullOrEmpty(baseFieldName))
{
Debug.LogError($"No mapping found for row {rowName}");
return false;
}
string fieldName = $"{baseFieldName}{logType}";
FieldInfo field = _loggerSettings.GetType().GetField(fieldName);
if (field != null)
{
bool currentValue = (bool)field.GetValue(_loggerSettings);
allSelected &= currentValue;
}
else
{
Debug.LogError($"Field {fieldName} does not exist in LoggerSettings");
return false;
}
}
return allSelected;
}
/// <summary>
/// Renders a checkbox for a given log type and handles changes to its value.
/// </summary>
/// <param name="rowName">The name of the row to render the checkbox for.</param>
/// <param name="logType">The type of log to handle.</param>
public void RenderAndHandleCheckbox(string rowName, string logType)
{
// Using the mapping to get the base name for the fields
string baseFieldName = CategoryMapping.TryGetValue(rowName, out string value) ? value : string.Empty;
if (string.IsNullOrEmpty(baseFieldName))
{
Debug.LogError($"No mapping found for row {rowName}");
return;
}
string fieldName = $"{baseFieldName}{logType}";
FieldInfo field = _loggerSettings.GetType().GetField(fieldName);
if (field != null)
{
bool currentValue = (bool)field.GetValue(_loggerSettings);
bool newValue = EditorGUILayout.Toggle(currentValue, GUILayout.Width(100));
if (currentValue != newValue) field.SetValue(_loggerSettings, newValue);
}
else
{
Debug.LogError($"Field {fieldName} does not exist in LoggerSettings");
}
}
/// <summary>
/// Sets all flags for a given row to the provided value.
/// </summary>
/// <param name="rowName">The name of the row to set the flags for.</param>
/// <param name="value">The value to set all flags to.</param>
public void SetAllFlagsForRow(string rowName, bool value)
{
foreach (string logType in new[] { "Debug", "Error", "Exception", "Info", "Warning" })
{
string baseFieldName = CategoryMapping.TryGetValue(rowName, out string value1) ? value1 : string.Empty;
if (string.IsNullOrEmpty(baseFieldName))
{
Debug.LogError($"No mapping found for row {rowName}");
return;
}
string fieldName = $"{baseFieldName}{logType}";
FieldInfo field = _loggerSettings.GetType().GetField(fieldName);
if (field != null)
field.SetValue(_loggerSettings, value);
else
Debug.LogError($"Field {fieldName} does not exist in LoggerSettings");
}
}
/// <summary>
/// Sets all flags to the provided value.
/// </summary>
/// <param name="value"> The value to set all flags to.</param>
public void SetAllFlags(bool value)
{
string[] categories = { "characterResponse", "lipSync", "actions" };
string[] logTypes = { "Debug", "Info", "Error", "Exception", "Warning" };
foreach (string category in categories)
foreach (string logType in logTypes)
{
string fieldName = $"{category}{logType}";
FieldInfo field = _loggerSettings.GetType().GetField(fieldName);
if (field != null && field.FieldType == typeof(bool))
field.SetValue(_loggerSettings, value);
else
Debug.LogWarning($"Field {fieldName} not found or not boolean.");
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b6aa5925780d46299eacd79ab8115332
timeCreated: 1701853621

View File

@ -0,0 +1,87 @@
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Editor
{
public class LoggerSettingsWindow : EditorWindow
{
private readonly LoggerSettingsManager _loggerSettingsManager = new();
private void OnEnable()
{
_ = _loggerSettingsManager.loggerSettings;
}
private void OnGUI()
{
// Setting window size
minSize = new Vector2(850, 250);
maxSize = minSize;
if (_loggerSettingsManager.loggerSettings == null) return;
EditorGUILayout.Space(20);
// Create a custom GUIStyle based on EditorStyles.wordWrappedLabel
GUIStyle customLabelStyle = new(EditorStyles.wordWrappedLabel)
{
fontSize = 15,
normal = { textColor = Color.grey }
};
// Display a label with a custom style
GUILayout.Label(
"These loggerSettings only affect log loggerSettings related to the Convai plugin. Changes made here will not affect other parts of your project.",
customLabelStyle);
EditorGUILayout.Space(20);
// Headers for the table
string[] headers =
{ "Select All", "Category", "Debug", "Info", "Error", "Exception", "Warning" };
// Names of the rows in the table
string[] rowNames = { "Character", "LipSync", "Actions" };
// Style for the headers
GUIStyle headerStyle = new(GUI.skin.label)
{
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleLeft
};
// Draw the headers
EditorGUILayout.BeginHorizontal();
foreach (string header in headers) GUILayout.Label(header, headerStyle, GUILayout.Width(95));
EditorGUILayout.EndHorizontal();
// Slightly increased spacing between rows
EditorGUILayout.Space(5);
// Draw the rows
foreach (string row in rowNames)
{
EditorGUILayout.BeginHorizontal();
// 'Select All' checkbox for each row
bool allSelectedForRow = _loggerSettingsManager.GetAllFlagsForRow(row);
bool newAllSelectedForRow = EditorGUILayout.Toggle(allSelectedForRow, GUILayout.Width(100));
if (newAllSelectedForRow != allSelectedForRow)
_loggerSettingsManager.SetAllFlagsForRow(row, newAllSelectedForRow);
GUILayout.Label(row, GUILayout.Width(100));
// Individual checkboxes for each log type
foreach (string logType in new[] { "Debug", "Info", "Error", "Exception", "Warning" })
_loggerSettingsManager.RenderAndHandleCheckbox(row, logType);
EditorGUILayout.EndHorizontal();
}
// Increased spacing before global actions
EditorGUILayout.Space(20);
// Global actions
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Select All", GUILayout.Width(150), GUILayout.Height(30))) // Slightly bigger button
_loggerSettingsManager.SetAllFlags(true);
if (GUILayout.Button("Clear All", GUILayout.Width(150), GUILayout.Height(30))) // Slightly bigger button
_loggerSettingsManager.SetAllFlags(false);
EditorGUILayout.EndHorizontal();
// Additional space at the end for cleaner look
EditorGUILayout.Space(20);
// If the GUI has changed, mark _settings as dirty so it gets saved
if (GUI.changed) EditorUtility.SetDirty(_loggerSettingsManager.loggerSettings);
}
[MenuItem("Convai/Logger Settings")]
public static void ShowWindow()
{
GetWindow<LoggerSettingsWindow>("Logger Settings");
}
}
}

View File

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

View File

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

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1f84be99651944b69262e8060b8ac451
timeCreated: 1696842374

View 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

View File

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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 25397e28c8a1443e8ed01b939b22a538
timeCreated: 1715597900

View File

@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Convai.Scripts.Narrative_Design.Models;
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Narrative_Design.Editor
{
[CustomEditor(typeof(NarrativeDesignManager))]
public class NarrativeDesignManagerEditor : UnityEditor.Editor
{
/// Dictionary to keep track of which sections are expanded in the editor
private readonly Dictionary<string, bool> _sectionIdExpanded = new();
/// Reference to the NarrativeDesignManager that this editor is modifying
private NarrativeDesignManager _narrativeDesignManager;
/// SerializedProperty for the section change events in the NarrativeDesignManager
private SerializedProperty _sectionChangeEvents;
/// Whether the section events are expanded in the editor
private bool _sectionEventsExpanded = true;
/// SerializedObject for the target object
private SerializedObject _serializedObject;
private void OnEnable()
{
_serializedObject = new SerializedObject(target);
_narrativeDesignManager = target as NarrativeDesignManager;
if (_narrativeDesignManager != null) FindProperties();
}
public override void OnInspectorGUI()
{
_serializedObject.Update();
if (GUILayout.Button("Check for Updates")) OnUpdateNarrativeDesignButtonClicked();
GUILayout.Space(10);
if (_narrativeDesignManager.sectionDataList.Count > 0)
{
_sectionEventsExpanded = EditorGUILayout.Foldout(_sectionEventsExpanded, "Section Events", true, EditorStyles.foldoutHeader);
if (_sectionEventsExpanded)
{
EditorGUI.indentLevel++;
EditorGUI.BeginChangeCheck();
for (int i = 0; i < _narrativeDesignManager.sectionDataList.Count; i++)
{
SectionData sectionData = _narrativeDesignManager.sectionDataList[i];
string sectionId = sectionData.sectionId;
SectionChangeEventsData sectionChangeEventsData = _narrativeDesignManager.sectionChangeEventsDataList.Find(x => x.id == sectionId);
if (sectionChangeEventsData == null)
{
sectionChangeEventsData = new SectionChangeEventsData { id = sectionId };
_narrativeDesignManager.sectionChangeEventsDataList.Add(sectionChangeEventsData);
}
_sectionIdExpanded.TryAdd(sectionId, false);
GUIStyle sectionIdStyle = new(EditorStyles.foldoutHeader)
{
fontStyle = FontStyle.Bold,
fontSize = 14
};
string sectionIdText = $"{sectionData.sectionName} - {sectionId}";
_sectionIdExpanded[sectionId] = EditorGUILayout.Foldout(_sectionIdExpanded[sectionId], sectionIdText, true, sectionIdStyle);
if (_sectionIdExpanded[sectionId])
{
EditorGUI.indentLevel++;
SerializedProperty sectionChangeEventsProperty = _sectionChangeEvents.GetArrayElementAtIndex(i);
EditorGUILayout.PropertyField(sectionChangeEventsProperty, GUIContent.none, true);
EditorGUI.indentLevel--;
}
}
if (EditorGUI.EndChangeCheck())
{
_serializedObject.ApplyModifiedProperties();
_narrativeDesignManager.OnSectionEventListChange();
}
EditorGUI.indentLevel--;
}
}
_serializedObject.ApplyModifiedProperties();
}
private async void OnUpdateNarrativeDesignButtonClicked()
{
await Task.WhenAll(_narrativeDesignManager.UpdateSectionListAsync(), _narrativeDesignManager.UpdateTriggerListAsync());
_serializedObject.ApplyModifiedProperties();
_narrativeDesignManager.OnSectionEventListChange();
}
private void FindProperties()
{
_serializedObject.FindProperty(nameof(NarrativeDesignManager.sectionDataList));
_serializedObject.FindProperty(nameof(NarrativeDesignManager.triggerDataList));
_sectionChangeEvents = _serializedObject.FindProperty(nameof(NarrativeDesignManager.sectionChangeEventsDataList));
}
}
}

View File

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

View File

@ -0,0 +1,43 @@
using Convai.Scripts.Narrative_Design.Models;
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Narrative_Design.Editor
{
[CustomPropertyDrawer(typeof(SectionChangeEventsData))]
public class NarrativeDesignSectionChangeEventsDataPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
SerializedProperty sectionIdProperty = property.FindPropertyRelative("id");
SerializedProperty onSectionStartProperty = property.FindPropertyRelative("onSectionStart");
SerializedProperty onSectionEndProperty = property.FindPropertyRelative("onSectionEnd");
Rect sectionIdRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
EditorGUI.LabelField(sectionIdRect, "Section ID", sectionIdProperty.stringValue);
position.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(position, onSectionStartProperty, true);
position.y += EditorGUI.GetPropertyHeight(onSectionStartProperty) + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(position, onSectionEndProperty, true);
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
SerializedProperty onSectionStartProperty = property.FindPropertyRelative("onSectionStart");
SerializedProperty onSectionEndProperty = property.FindPropertyRelative("onSectionEnd");
float height = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
height += EditorGUI.GetPropertyHeight(onSectionStartProperty) + EditorGUIUtility.standardVerticalSpacing;
height += EditorGUI.GetPropertyHeight(onSectionEndProperty);
return height;
}
}
}

View File

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

View File

@ -0,0 +1,19 @@
using UnityEditor;
namespace Convai.Scripts.Narrative_Design.Editor
{
[CustomEditor(typeof(NarrativeDesignTrigger))]
public class NarrativeDesignTriggerEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
NarrativeDesignTrigger narrativeDesignTrigger = (NarrativeDesignTrigger)target;
DrawDefaultInspector();
if (narrativeDesignTrigger.availableTriggers is { Count: > 0 })
narrativeDesignTrigger.selectedTriggerIndex =
EditorGUILayout.Popup("Trigger", narrativeDesignTrigger.selectedTriggerIndex, narrativeDesignTrigger.availableTriggers.ToArray());
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cdf68c1817bd4102879052a18d773fec
timeCreated: 1706873993

View File

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

View File

@ -0,0 +1,406 @@
#if UNITY_EDITOR
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor.VSAttribution;
using Newtonsoft.Json;
using System.IO;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System;
using System.Threading.Tasks;
using UnityEditor;
using Convai.Scripts.Utils;
using Convai.Scripts.Runtime.Utils;
public class ConvaiSetup : EditorWindow
{
private const string API_KEY_PATH = "Assets/Resources/ConvaiAPIKey.asset";
private const string API_URL = "https://api.convai.com/user/referral-source-status";
[MenuItem("Convai/Convai Setup", false, 1)]
public static void ShowConvaiSetupWindow()
{
ConvaiSetup wnd = GetWindow<ConvaiSetup>();
}
[MenuItem("Convai/Documentation")]
public static void OpenDocumentation()
{
Application.OpenURL("https://docs.convai.com/plugins-and-integrations/unity-plugin");
}
public class UpdateSource
{
[JsonProperty("referral_source")] public string referral_source;
public UpdateSource(string referral_source)
{
this.referral_source = referral_source;
}
}
public class referralSourceStatus
{
[JsonProperty("referral_source_status")] public string referral_source_status;
[JsonProperty("status")] public string status;
}
async Task<string> CheckReferralStatus(string url, string apiKey)
{
// Create a new HttpWebRequest object
var request = WebRequest.Create(url);
request.Method = "post";
// Set the request headers
request.ContentType = "application/json";
string bodyJsonString = "{}";
// Convert the json string to bytes
byte[] jsonBytes = Encoding.UTF8.GetBytes(bodyJsonString);
referralSourceStatus referralStatus;
request.Headers.Add("CONVAI-API-KEY", apiKey);
// Write the data to the request stream
using (Stream requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
}
// Get the response from the server
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream streamResponse = response.GetResponseStream())
{
using (StreamReader reader = new(streamResponse))
{
string responseContent = reader.ReadToEnd();
referralStatus = JsonConvert.DeserializeObject<referralSourceStatus>(responseContent);
}
}
return referralStatus.referral_source_status;
}
}
catch (WebException e)
{
Debug.LogError(e.Message + "\nPlease check if API Key is correct.");
return null;
}
catch (Exception e)
{
Debug.LogError(e);
return null;
}
}
async Task<bool> SendReferralRequest(string url, string bodyJsonString, string apiKey)
{
// Create a new HttpWebRequest object
var request = WebRequest.Create(url);
request.Method = "post";
// Set the request headers
request.ContentType = "application/json";
// Convert the json string to bytes
byte[] jsonBytes = Encoding.UTF8.GetBytes(bodyJsonString);
request.Headers.Add("CONVAI-API-KEY", apiKey);
// Write the data to the request stream
using (Stream requestStream = await request.GetRequestStreamAsync())
{
await requestStream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
}
// Get the response from the server
try
{
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{
using (Stream streamResponse = response.GetResponseStream())
{
using (StreamReader reader = new(streamResponse))
{
string responseContent = reader.ReadToEnd();
}
}
if ((int)response.StatusCode == 200)
{
return true;
}
}
}
catch (WebException e)
{
Debug.LogError(e.Message + "\nPlease check if API Key is correct.");
return false;
}
catch (Exception e)
{
Debug.LogError(e);
return false;
}
return false;
}
private async Task<bool> BeginButtonTask(string apiKey)
{
ConvaiAPIKeySetup aPIKeySetup = CreateInstance<ConvaiAPIKeySetup>();
aPIKeySetup.APIKey = apiKey;
if (!string.IsNullOrEmpty(apiKey))
{
string referralStatus =
await CheckReferralStatus(API_URL, apiKey);
if (referralStatus != null)
{
CreateOrUpdateAPIKeyAsset(aPIKeySetup);
if (referralStatus.Trim().ToLower() == "undefined" || referralStatus.Trim().ToLower() == "")
{
EditorUtility.DisplayDialog("Warning", "[Step 1/2] API Key loaded successfully!",
"OK");
return true;
}
EditorUtility.DisplayDialog("Success", "API Key loaded successfully!", "OK");
// if the status is already set, do not show the referral dialog
return false;
}
else
{
EditorUtility.DisplayDialog("Error", "Something went wrong. Please check your API Key. Contact support@convai.com for more help. ", "OK");
return false;
}
}
EditorUtility.DisplayDialog("Error", "Please enter a valid API Key.", "OK");
return false;
}
private void CreateOrUpdateAPIKeyAsset(ConvaiAPIKeySetup aPIKeySetup)
{
string assetPath = "Assets/Resources/ConvaiAPIKey.asset";
if (!File.Exists(assetPath))
{
if (!AssetDatabase.IsValidFolder("Assets/Resources"))
AssetDatabase.CreateFolder("Assets", "Resources");
AssetDatabase.CreateAsset(aPIKeySetup, assetPath);
}
else
{
AssetDatabase.DeleteAsset(assetPath);
AssetDatabase.CreateAsset(aPIKeySetup, assetPath);
}
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
public void CreateGUI()
{
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;
VisualElement page1 = new ScrollView();
VisualElement page2 = new ScrollView();
root.Add(new Label(""));
Image convaiLogoImage = new()
{
image = AssetDatabase.LoadAssetAtPath<Texture>(ConvaiImagesDirectory.CONVAI_LOGO_PATH)
};
convaiLogoImage.style.height = 100;
convaiLogoImage.style.paddingBottom = 10;
convaiLogoImage.style.paddingTop = 10;
convaiLogoImage.style.paddingRight = 10;
convaiLogoImage.style.paddingLeft = 10;
root.Add(convaiLogoImage);
Label convaiSetupLabel = new("Enter your API Key:");
convaiSetupLabel.style.fontSize = 16;
TextField APIKeyTextField = new("", -1, false, true, '*');
Button beginButton = new(async () =>
{
bool isPage2 = await BeginButtonTask(APIKeyTextField.text);
if (isPage2)
{
root.Remove(page1);
root.Add(page2);
}
else
{
Close();
}
})
{
text = "Begin!"
};
beginButton.style.fontSize = 16;
beginButton.style.unityFontStyleAndWeight = FontStyle.Bold;
beginButton.style.alignSelf = Align.Center;
beginButton.style.paddingBottom = 10;
beginButton.style.paddingLeft = 30;
beginButton.style.paddingRight = 30;
beginButton.style.paddingTop = 10;
Button docsLink = new(() =>
{
Application.OpenURL("https://docs.convai.com/api-docs/plugins-and-integrations/unity-plugin/setting-up-unity-plugin");
})
{
text = "How do I find my API key?"
};
docsLink.style.alignSelf = Align.Center;
docsLink.style.paddingBottom = 5;
docsLink.style.paddingLeft = 50;
docsLink.style.paddingRight = 50;
docsLink.style.paddingTop = 5;
page1.Add(convaiSetupLabel);
page1.Add(new Label("\n"));
page1.Add(APIKeyTextField);
page1.Add(new Label("\n"));
page1.Add(beginButton);
page1.Add(new Label("\n"));
page1.Add(docsLink);
page1.style.marginBottom = 20;
page1.style.marginLeft = 20;
page1.style.marginRight = 20;
page1.style.marginTop = 20;
Label attributionSourceLabel = new("[Step 2/2] Where did you discover Convai?");
attributionSourceLabel.style.fontSize = 14;
attributionSourceLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
List<string> attributionSourceOptions = new()
{
"Search Engine (Google, Bing, etc.)",
"Youtube",
"Social Media (Facebook, Instagram, TikTok, etc.)",
"Friend Referral",
"Unity Asset Store",
"Others"
};
TextField otherOptionTextField = new();
string currentChoice = "";
int currentChoiceIndex = -1;
DropdownMenu dropdownMenu = new();
ToolbarMenu toolbarMenu = new() { text = "Click here to select option..." };
foreach (string choice in attributionSourceOptions)
{
toolbarMenu.menu.AppendAction(choice,
action =>
{
currentChoice = choice;
toolbarMenu.text = choice;
});
}
toolbarMenu.style.paddingBottom = 10;
toolbarMenu.style.paddingLeft = 30;
toolbarMenu.style.paddingRight = 30;
toolbarMenu.style.paddingTop = 10;
Button continueButton = new(async () =>
{
UpdateSource updateSource;
currentChoiceIndex = attributionSourceOptions.IndexOf(toolbarMenu.text);
if (currentChoiceIndex < 0)
{
EditorUtility.DisplayDialog("Error", "Please select a valid referral source!", "OK");
}
else
{
updateSource = new UpdateSource(attributionSourceOptions[currentChoiceIndex]);
if (attributionSourceOptions[currentChoiceIndex] == "Others")
{
updateSource.referral_source = otherOptionTextField.text;
}
ConvaiAPIKeySetup apiKeyObject = AssetDatabase.LoadAssetAtPath<ConvaiAPIKeySetup>("Assets/Resources/ConvaiAPIKey.Asset");
await SendReferralRequest("https://api.convai.com/user/update-source", JsonConvert.SerializeObject(updateSource), apiKeyObject.APIKey);
if (attributionSourceOptions[currentChoiceIndex] == "Unity Asset Store")
{
// VS Attribution
VSAttribution.SendAttributionEvent("Initial Setup", "Convai Technologies, Inc.", apiKeyObject.APIKey);
}
Close();
}
})
{
text = "Continue"
};
continueButton.style.fontSize = 16;
continueButton.style.unityFontStyleAndWeight = FontStyle.Bold;
continueButton.style.alignSelf = Align.Center;
continueButton.style.paddingBottom = 5;
continueButton.style.paddingLeft = 30;
continueButton.style.paddingRight = 30;
continueButton.style.paddingTop = 5;
page2.Add(new Label("\n"));
page2.Add(attributionSourceLabel);
page2.Add(new Label("\n"));
page2.Add(toolbarMenu);
page2.Add(new Label("\nIf selected Others above, please specifiy from where: "));
page2.Add(otherOptionTextField);
page2.Add(new Label("\n"));
page2.Add(continueButton);
page2.style.marginBottom = 20;
page2.style.marginLeft = 20;
page2.style.marginRight = 20;
page2.style.marginTop = 20;
root.Add(page1);
}
}
#endif

View File

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

View File

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

View File

@ -0,0 +1 @@
TutorialInitialized

View File

@ -0,0 +1,29 @@
using System.IO;
using UnityEditor;
using UnityEditor.PackageManager;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
public class RemoveTutorialAssets : EditorWindow
{
private const string TUTORIAL_ASSETS_PATH = "Assets/Convai/Tutorials";
private const string TUTORIAL_PACKAGE_NAME = "com.unity.learn.iet-framework";
[MenuItem("Convai/Remove Tutorial Assets")]
private static void StartUninstallProcess()
{
if (Directory.Exists(TUTORIAL_ASSETS_PATH))
{
AssetDatabase.DeleteAsset(TUTORIAL_ASSETS_PATH);
AssetDatabase.Refresh();
foreach (PackageInfo packageInfo in PackageInfo.GetAllRegisteredPackages())
{
// Check if the package name matches
if (packageInfo.name == TUTORIAL_PACKAGE_NAME)
{
Client.Remove(TUTORIAL_PACKAGE_NAME);
}
}
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,4 @@
public struct ConvaiImagesDirectory
{
public const string CONVAI_LOGO_PATH = "Assets/Convai/Art/UI/Logos/Convai Logo.png";
}

View File

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

View File

@ -0,0 +1,16 @@
using UnityEditor;
using UnityEngine;
namespace Convai.Scripts.Editor
{
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false; // Disable the property field
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true; // Re-enable the property field
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2b829967cd7e4a4880a40bb6653e26d9
timeCreated: 1701082874