Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release/v7.4.0 #331

Merged
merged 21 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5bf7023
Merge branch 'release/v7.3.0' into develop
HarrisonHough Oct 23, 2024
298b9d7
Merge branch 'hotfix/v7.3.1' into develop
HarrisonHough Oct 30, 2024
7aabadf
chore: default to no blendshapes
HarrisonHough Nov 4, 2024
e3abca2
chore: make blendshapes default to none if empty
HarrisonHough Nov 20, 2024
168363e
feat: add texture format to prevent webp usage
HarrisonHough Dec 6, 2024
6117328
Fix VoiceHandler memory access out of bounds on WebGL (#324)
mennetorelli Dec 6, 2024
422661a
chore: small cleanup
HarrisonHough Dec 16, 2024
ebfa6ed
feat(colors): mark avatar's equipped colors as selected (#327)
sander-rpm Dec 16, 2024
7ffc543
Merge branch 'develop' into feature/small-fixes
HarrisonHough Dec 16, 2024
e725b7d
Merge branch 'feature/small-fixes' into develop
HarrisonHough Dec 16, 2024
6d1302e
feat: added support for equipping multiple assets
HarrisonHough Jan 7, 2025
d5a685d
feat: changed editor avatar storage location
HarrisonHough Jan 10, 2025
f0d5de4
Merge remote-tracking branch 'origin/develop' into release/v7.4.0
HarrisonHough Jan 10, 2025
ee7fa5f
Merge branch 'feature/headwear-equip-fix' into develop
HarrisonHough Jan 10, 2025
028e472
Merge branch 'develop' into release/v7.4.0
HarrisonHough Jan 10, 2025
a334e20
chore: version updates and editor caching path update
HarrisonHough Jan 10, 2025
b6a872f
chore: test updates
HarrisonHough Jan 10, 2025
231b447
chore: re-remove samples
HarrisonHough Jan 10, 2025
95b58b6
chore: fix hair selection issue
HarrisonHough Jan 10, 2025
3320f0b
chore: remove texture format param
HarrisonHough Jan 13, 2025
2f6bb86
chore: update date
HarrisonHough Jan 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [7.4.0] - 2025.01.15

## Updated
- Avatar caching location is now changed when running in the Unity Editor it will now be stored in the `Application.persistentDataPath` directory as it already did for builds. However when loading avatars from the Avatar Loader Editor window it will still store them in the `Assets/Ready Player Me/Avatars folder`.
- AvatarManager and AvatarHandler classes updated so that in the Avatar Creator Elements sample it will re-equip hair when headwear is removed. [#330](https://github.com/readyplayerme/rpm-unity-sdk-core/pull/330)
- AvatarConfigProcessor updated so that by default if morph targets are not set, it will set it to None to improve file size. [#326](https://github.com/readyplayerme/rpm-unity-sdk-core/pull/326)

## [7.3.1] - 2024.10.30

## Updated
Expand Down
125 changes: 125 additions & 0 deletions Editor/Core/Scripts/EditorAvatarLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using ReadyPlayerMe.Core;
using ReadyPlayerMe.Loader;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
using Object = UnityEngine.Object;

public class EditorAvatarLoader
{
private const string TAG = nameof(EditorAvatarLoader);

private readonly bool avatarCachingEnabled;

/// Scriptable Object Avatar API request parameters configuration
public AvatarConfig AvatarConfig;

/// Importer to use to import glTF
public IImporter Importer;

private string avatarUrl;
private OperationExecutor<AvatarContext> executor;
private float startTime;

public Action<AvatarContext> OnCompleted;

/// <summary>
/// This class constructor is used to any required fields.
/// </summary>
/// <param name="useDefaultGLTFDeferAgent">Use default defer agent</param>
public EditorAvatarLoader()
{
AvatarLoaderSettings loaderSettings = AvatarLoaderSettings.LoadSettings();
Importer = new GltFastAvatarImporter();
AvatarConfig = loaderSettings.AvatarConfig != null ? loaderSettings.AvatarConfig : null;
}

/// Set the timeout for download requests
public int Timeout { get; set; } = 20;

/// <summary>
/// Runs through the process of loading the avatar and creating a game object via the <c>OperationExecutor</c>.
/// </summary>
/// <param name="url">The URL to the avatars .glb file.</param>
public async Task<AvatarContext> Load(string url)
{
var context = new AvatarContext();
context.Url = url;
context.AvatarCachingEnabled = false;
context.AvatarConfig = AvatarConfig;
context.ParametersHash = AvatarCache.GetAvatarConfigurationHash(AvatarConfig);

// process url
var urlProcessor = new UrlProcessor();
context = await urlProcessor.Execute(context, CancellationToken.None);
// get metadata
var metadataDownloader = new MetadataDownloader();
context = await metadataDownloader.Execute(context, CancellationToken.None);
//download avatar into asset folder
context.AvatarUri.LocalModelPath = await DownloadAvatarModel(context.AvatarUri);
if (string.IsNullOrEmpty(context.AvatarUri.LocalModelPath))
{
Debug.LogError($"Failed to download avatar model from {context.AvatarUri.ModelUrl}");
return null;
}
// import model
context.Bytes = await File.ReadAllBytesAsync(context.AvatarUri.LocalModelPath);
context = await Importer.Execute(context, CancellationToken.None);
// Process the avatar
var avatarProcessor = new AvatarProcessor();
context = await avatarProcessor.Execute(context, CancellationToken.None);

var avatar = (GameObject) context.Data;
avatar.SetActive(true);

var avatarData = avatar.AddComponent<AvatarData>();
avatarData.AvatarId = avatar.name;
avatarData.AvatarMetadata = context.Metadata;
OnCompleted?.Invoke(context);
return context;
}

private static async Task<string> DownloadAvatarModel(AvatarUri avatarUri)
{
var folderPath = Path.Combine(Application.dataPath, $"Ready Player Me/Avatars/{avatarUri.Guid}");
// Ensure the folder exists
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}

// Create the full file path
var fullPath = Path.Combine(folderPath, avatarUri.Guid + ".glb");

// Start the download
using (UnityWebRequest request = UnityWebRequest.Get(avatarUri.ModelUrl))
{
Debug.Log($"Downloading {avatarUri.ModelUrl}...");
var operation = request.SendWebRequest();

while (!operation.isDone)
{
await Task.Yield(); // Await completion of the web request
}

if (request.result == UnityWebRequest.Result.Success)
{
// Write the downloaded data to the file
await File.WriteAllBytesAsync(fullPath, request.downloadHandler.data);
Debug.Log($"File saved to: {fullPath}");

// Refresh the AssetDatabase to recognize the new file
AssetDatabase.Refresh();
Debug.Log("AssetDatabase refreshed.");
return fullPath;
}
Debug.LogError($"Failed to download file: {request.error}");
return null;
}
}

}
11 changes: 11 additions & 0 deletions Editor/Core/Scripts/EditorAvatarLoader.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@

namespace ReadyPlayerMe.Core.Editor
{
public class AvatarLoaderEditor : EditorWindow
public class AvatarLoaderWindow : EditorWindow
{
private const string TAG = nameof(AvatarLoaderEditor);
private const string AVATAR_LOADER = "Avatar Loader";
private const string LOAD_AVATAR_BUTTON = "LoadAvatarButton";
private const string HEADER_LABEL = "HeaderLabel";
Expand All @@ -25,11 +24,12 @@ public class AvatarLoaderEditor : EditorWindow

private bool useEyeAnimations;
private bool useVoiceToAnim;
private EditorAvatarLoader editorAvatarLoader;

[MenuItem("Tools/Ready Player Me/Avatar Loader", priority = 1)]
public static void ShowWindow()
{
var window = GetWindow<AvatarLoaderEditor>();
var window = GetWindow<AvatarLoaderWindow>();
window.titleContent = new GUIContent(AVATAR_LOADER);
window.minSize = new Vector2(500, 300);
}
Expand Down Expand Up @@ -82,53 +82,23 @@ private void LoadAvatar(string url)
{
avatarLoaderSettings = AvatarLoaderSettings.LoadSettings();
}
var avatarLoader = new AvatarObjectLoader();
avatarLoader.OnFailed += Failed;
avatarLoader.OnCompleted += Completed;
avatarLoader.OperationCompleted += OnOperationCompleted;
if (avatarLoaderSettings != null)
{
avatarLoader.AvatarConfig = avatarLoaderSettings.AvatarConfig;
if (avatarLoaderSettings.GLTFDeferAgent != null)
{
avatarLoader.GLTFDeferAgent = avatarLoaderSettings.GLTFDeferAgent;
}
}
avatarLoader.LoadAvatar(url);
editorAvatarLoader = new EditorAvatarLoader();
editorAvatarLoader.OnCompleted += Completed;
editorAvatarLoader.Load(url);
}

private void OnOperationCompleted(object sender, IOperation<AvatarContext> e)
{
if (e.GetType() == typeof(MetadataDownloader))
{
AnalyticsEditorLogger.EventLogger.LogMetadataDownloaded(EditorApplication.timeSinceStartup - startTime);
}
}

private void Failed(object sender, FailureEventArgs args)
{
Debug.LogError($"{args.Type} - {args.Message}");
}

private void Completed(object sender, CompletionEventArgs args)
private void Completed(AvatarContext context)
{
AnalyticsEditorLogger.EventLogger.LogAvatarLoaded(EditorApplication.timeSinceStartup - startTime);

if (avatarLoaderSettings == null)
{
avatarLoaderSettings = AvatarLoaderSettings.LoadSettings();
}
var paramHash = AvatarCache.GetAvatarConfigurationHash(avatarLoaderSettings.AvatarConfig);
var path = $"{DirectoryUtility.GetRelativeProjectPath(args.Avatar.name, paramHash)}/{args.Avatar.name}";
if (!avatarLoaderSettings.AvatarCachingEnabled)
{
SDKLogger.LogWarning(TAG, "Enable Avatar Caching to generate a prefab in the project folder.");
return;
}
var avatar = PrefabHelper.CreateAvatarPrefab(args.Metadata, path, avatarConfig: avatarLoaderSettings.AvatarConfig);
var path = $@"Assets\Ready Player Me\Avatars\{context.AvatarUri.Guid}";
var avatar = PrefabHelper.CreateAvatarPrefab(context.Metadata, path, avatarConfig: avatarLoaderSettings.AvatarConfig);
if (useEyeAnimations) avatar.AddComponent<EyeAnimationHandler>();
if (useVoiceToAnim) avatar.AddComponent<VoiceHandler>();
DestroyImmediate(args.Avatar, true);
DestroyImmediate((GameObject) context.Data, true);
Selection.activeObject = avatar;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using ReadyPlayerMe.Core.Analytics;
using ReadyPlayerMe.Core.Data;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
Expand Down
5 changes: 3 additions & 2 deletions Editor/Core/Scripts/Utilities/PrefabHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace ReadyPlayerMe.Core.Editor
public static class PrefabHelper
{
private const string TAG = nameof(PrefabHelper);

public static void TransferPrefabByGuid(string guid, string newPath)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
Expand All @@ -18,7 +19,7 @@ public static void TransferPrefabByGuid(string guid, string newPath)
AssetDatabase.Refresh();
Selection.activeObject = AssetDatabase.LoadAssetAtPath(newPath, typeof(GameObject));
}

public static GameObject CreateAvatarPrefab(AvatarMetadata avatarMetadata, string path, string prefabPath = null, AvatarConfig avatarConfig = null)
{
var modelFilePath = $"{path}.glb";
Expand All @@ -34,7 +35,7 @@ public static GameObject CreateAvatarPrefab(AvatarMetadata avatarMetadata, strin
CreatePrefab(newAvatar, prefabPath ?? $"{path}.prefab");
return newAvatar;
}

public static void CreatePrefab(GameObject source, string path)
{
PrefabUtility.SaveAsPrefabAssetAndConnect(source, path, InteractionMode.AutomatedAction, out var success);
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ Steps for trying out avatar creator sample can be found [here.](Documentation~/A
A guide for customizing avatar creator can be found [here.](Documentation~/CustomizationGuide.md)

### Note
- [*]Camera support is only provided for Windows and WebGL, using Unity’s webcam native API.
- Camera support is only provided for Windows and WebGL, using Unity’s webcam native API.
- Unity does not have a native file picker, so we have discontinued support for this feature.
- To add support for file picker (for selfies) you have to implement it yourself
44 changes: 44 additions & 0 deletions Runtime/AvatarCreator/Scripts/Managers/AvatarManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,50 @@ public async Task<GameObject> UpdateAsset(AssetType assetType, object assetId)

return await inCreatorAvatarLoader.Load(avatarId, gender, data);
}

public async Task<GameObject> UpdateAssets(Dictionary<AssetType, object> assetIdByType)
{
var payload = new AvatarProperties
{
Assets = new Dictionary<AssetType, object>()
};
// if it contains top, bottom or footwear, remove outfit
if (assetIdByType.ContainsKey(AssetType.Top) || assetIdByType.ContainsKey(AssetType.Bottom) || assetIdByType.ContainsKey(AssetType.Footwear))
{
payload.Assets.Add(AssetType.Outfit, string.Empty);
}

// Convert costume to outfit
foreach (var assetType in assetIdByType.Keys)
{
payload.Assets.Add(assetType == AssetType.Costume ? AssetType.Outfit : assetType, assetIdByType[assetType]);
}

byte[] data;
try
{
data = await avatarAPIRequests.UpdateAvatar(avatarId, payload, avatarConfigParameters);
}
catch (Exception e)
{
HandleException(e);
return null;
}

if (ctxSource.IsCancellationRequested)
{
return null;
}
foreach (var assetType in assetIdByType)
{
if (assetType.Key != AssetType.BodyShape)
{
await ValidateBodyShapeUpdate(assetType.Key, assetType.Value);
}
}

return await inCreatorAvatarLoader.Load(avatarId, gender, data);
}

/// <summary>
/// Function that checks if body shapes are enabled in the studio. This validation is performed only in the editor.
Expand Down
6 changes: 3 additions & 3 deletions Runtime/Core/Scripts/Animation/VoiceHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using UnityEngine.Android;
#endif


namespace ReadyPlayerMe.Core
{
/// <summary>
Expand Down Expand Up @@ -111,9 +110,10 @@ public void InitializeAudio()
{
try
{

if (AudioSource == null)
{
AudioSource = gameObject.AddComponent<AudioSource>();
AudioSource = GetComponent<AudioSource>() ?? gameObject.AddComponent<AudioSource>();
}

switch (AudioProvider)
Expand Down Expand Up @@ -169,7 +169,7 @@ private float GetAmplitude()
{
var currentPosition = AudioSource.timeSamples;
var remaining = AudioSource.clip.samples - currentPosition;
if (remaining > 0 && remaining < AUDIO_SAMPLE_LENGTH)
if (remaining >= 0 && remaining < AUDIO_SAMPLE_LENGTH)
{
return 0f;
}
Expand Down
Loading
Loading