From 0810f42cf4cf660361b8b39bcdf2f28090db7c45 Mon Sep 17 00:00:00 2001
From: Robin <1121080+rYuuk@users.noreply.github.com>
Date: Fri, 9 Jun 2023 11:02:53 +0200
Subject: [PATCH 1/3] [SDK-142] Add avatar template requests for default
avatars (#28)
## [SDK-142](https://ready-player-me.atlassian.net/browse/SDK-142)
## Changes
#### Added
- Avatar template API and requests
#### Updated
- Replace hardcoded id with avatar templates in default avatar selection
- Fixed blocked loading screen on error
---
Runtime/AvatarManager.cs | 30 +++++++
Runtime/WebRequests/AvatarAPIRequests.cs | 79 +++++++++++++++++--
Samples~/Prefabs/DefaultAvatarButton.prefab | 6 +-
Samples~/Scripts/Data/AvatarCreatorData.cs | 1 +
Samples~/Scripts/UI/AssetTypeUICreator.cs | 6 +-
Samples~/Scripts/UI/PanelSwitcher.cs | 13 +--
.../AvatarCreatorSelection.cs | 16 +++-
.../UI/SelectionScreens/AvatarSelection.cs | 3 +-
.../SelectionScreens/CameraPhotoSelection.cs | 3 +-
.../DefaultAvatarSelection.cs | 74 ++++++-----------
10 files changed, 161 insertions(+), 70 deletions(-)
diff --git a/Runtime/AvatarManager.cs b/Runtime/AvatarManager.cs
index caf632f..3928abf 100644
--- a/Runtime/AvatarManager.cs
+++ b/Runtime/AvatarManager.cs
@@ -43,6 +43,36 @@ public AvatarManager(BodyType bodyType, OutfitGender gender, AvatarConfig avatar
inCreatorAvatarLoader = new InCreatorAvatarLoader();
avatarAPIRequests = new AvatarAPIRequests(ctxSource.Token);
}
+
+ ///
+ /// Create a new avatar from a provided template.
+ ///
+ /// Properties which describes avatar
+ /// Avatar gameObject
+ public async Task CreateFromTemplateAvatar(AvatarProperties avatarProperties)
+ {
+ try
+ {
+ avatarProperties = await avatarAPIRequests.CreateFromTemplateAvatar(
+ avatarProperties.Id,
+ avatarProperties.Partner,
+ bodyType
+ );
+ avatarId = avatarProperties.Id;
+ }
+ catch (Exception e)
+ {
+ OnError?.Invoke(e.Message);
+ return avatarProperties;
+ }
+
+ if (ctxSource.IsCancellationRequested)
+ {
+ return avatarProperties;
+ }
+
+ return avatarProperties;
+ }
///
/// Create a new avatar.
diff --git a/Runtime/WebRequests/AvatarAPIRequests.cs b/Runtime/WebRequests/AvatarAPIRequests.cs
index 6465f33..a088c63 100644
--- a/Runtime/WebRequests/AvatarAPIRequests.cs
+++ b/Runtime/WebRequests/AvatarAPIRequests.cs
@@ -1,11 +1,13 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using ReadyPlayerMe.AvatarLoader;
using ReadyPlayerMe.Core;
+using UnityEngine;
+using UnityEngine.Networking;
namespace ReadyPlayerMe.AvatarCreator
{
@@ -16,6 +18,15 @@ public class AvatarAPIRequests
private const string COLOR_PARAMETERS = "colors?type=skin,beard,hair,eyebrow";
private const string FETCH_AVATAR_PARAMETERS = "?select=id,partner&userId=";
private const string DRAFT_PARAMETER = "draft";
+ private const string TEMPLATE = "templates";
+ private const string FULL_BODY = "fullbody";
+ private const string HALF_BODY = "halfbody";
+ private const string MALE = "male";
+ private const string FEMALE = "female";
+ private const string IMAGE_URL = "imageUrl";
+ private const string PARTNER = "partner";
+ private const string DATA = "data";
+ private const string ID = "id";
private readonly AuthorizedRequest authorizedRequest;
private readonly CancellationToken ctx;
@@ -26,7 +37,7 @@ public AvatarAPIRequests(CancellationToken ctx = default)
authorizedRequest = new AuthorizedRequest();
}
- public async Task> FetchUserAvatars(string userId)
+ public async Task> GetUserAvatars(string userId)
{
var response = await authorizedRequest.SendRequest(
new RequestData
@@ -39,8 +50,62 @@ public async Task> FetchUserAvatars(string userId)
response.ThrowIfError();
var json = JObject.Parse(response.Text);
- var data = json["data"]!;
- return data.ToDictionary(element => element["id"]!.ToString(), element => element["partner"]!.ToString());
+ var data = json[DATA]!;
+ return data.ToDictionary(element => element[ID]!.ToString(), element => element[PARTNER]!.ToString());
+ }
+
+ public async Task> GetTemplates(OutfitGender gender)
+ {
+ var response = await authorizedRequest.SendRequest(
+ new RequestData
+ {
+ Url = $"{Endpoints.AVATAR_API_V2}/{TEMPLATE}?gender=" + (gender == OutfitGender.Masculine ? MALE : FEMALE),
+ Method = HttpMethod.GET,
+ },
+ ctx: ctx
+ );
+ response.ThrowIfError();
+
+ var json = JObject.Parse(response.Text);
+ var data = json[DATA]!;
+ return data.ToDictionary(element => element[ID]!.ToString(), element => element[IMAGE_URL]!.ToString());
+ }
+
+ public async Task GetTemplateAvatarImage(string url)
+ {
+ var downloadHandler = new DownloadHandlerTexture();
+ var webRequestDispatcher = new WebRequestDispatcher();
+ var response = await webRequestDispatcher.SendRequest(url, HttpMethod.GET, downloadHandler: downloadHandler, ctx: ctx);
+
+ response.ThrowIfError();
+ return response.Texture;
+ }
+
+ public async Task CreateFromTemplateAvatar(string avatarId, string partner, BodyType bodyType)
+ {
+ var payloadData = new Dictionary
+ {
+ { nameof(partner), partner },
+ { nameof(bodyType), bodyType == BodyType.FullBody ? FULL_BODY : HALF_BODY },
+ };
+
+ var payload = AuthDataConverter.CreatePayload(payloadData);
+
+ var response = await authorizedRequest.SendRequest(
+ new RequestData
+ {
+ Url = $"{Endpoints.AVATAR_API_V2}/templates/{avatarId}",
+ Method = HttpMethod.POST,
+ Payload = payload
+ },
+ ctx: ctx
+ );
+
+ response.ThrowIfError();
+
+ var json = JObject.Parse(response.Text);
+ var data = json[DATA]!.ToString();
+ return JsonConvert.DeserializeObject(data);
}
public async Task GetAllAvatarColors(string avatarId)
@@ -72,7 +137,7 @@ public async Task GetAvatarMetadata(string avatarId)
response.ThrowIfError();
var json = JObject.Parse(response.Text);
- var data = json["data"]!.ToString();
+ var data = json[DATA]!.ToString();
return JsonConvert.DeserializeObject(data);
}
@@ -90,7 +155,7 @@ public async Task CreateNewAvatar(AvatarProperties avatarPrope
response.ThrowIfError();
var metadata = JObject.Parse(response.Text);
- var data = metadata["data"]!.ToString();
+ var data = metadata[DATA]!.ToString();
return JsonConvert.DeserializeObject(data);
}
diff --git a/Samples~/Prefabs/DefaultAvatarButton.prefab b/Samples~/Prefabs/DefaultAvatarButton.prefab
index de79ef8..d18ac6d 100644
--- a/Samples~/Prefabs/DefaultAvatarButton.prefab
+++ b/Samples~/Prefabs/DefaultAvatarButton.prefab
@@ -35,8 +35,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
- m_AnchoredPosition: {x: 0, y: -50}
- m_SizeDelta: {x: 400, y: 400}
+ m_AnchoredPosition: {x: 0, y: 0}
+ m_SizeDelta: {x: 200, y: 200}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &3862883088835331388
CanvasRenderer:
@@ -108,7 +108,7 @@ RectTransform:
- {fileID: 546109656371019473}
- {fileID: 8294350063510174813}
m_Father: {fileID: 0}
- m_RootOrder: 0
+ m_RootOrder: -1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
diff --git a/Samples~/Scripts/Data/AvatarCreatorData.cs b/Samples~/Scripts/Data/AvatarCreatorData.cs
index c5bc2b9..801b4a1 100644
--- a/Samples~/Scripts/Data/AvatarCreatorData.cs
+++ b/Samples~/Scripts/Data/AvatarCreatorData.cs
@@ -7,6 +7,7 @@ namespace ReadyPlayerMe
public class AvatarCreatorData : ScriptableObject
{
public AvatarProperties AvatarProperties;
+ public bool IsExistingAvatar;
public void Awake()
{
diff --git a/Samples~/Scripts/UI/AssetTypeUICreator.cs b/Samples~/Scripts/UI/AssetTypeUICreator.cs
index bac903e..ea91029 100644
--- a/Samples~/Scripts/UI/AssetTypeUICreator.cs
+++ b/Samples~/Scripts/UI/AssetTypeUICreator.cs
@@ -47,7 +47,6 @@ private void Awake()
cameraZoom = FindObjectOfType();
}
-
public void CreateUI(BodyType bodyType, IEnumerable assetTypes)
{
this.bodyType = bodyType;
@@ -93,6 +92,11 @@ public void ResetUI()
PanelSwitcher.Clear();
DefaultZoom();
+ if (assetTypeButtonsMap == null)
+ {
+ return;
+ }
+
foreach (var assetTypeButton in assetTypeButtonsMap)
{
Destroy(assetTypeButton.Value.gameObject);
diff --git a/Samples~/Scripts/UI/PanelSwitcher.cs b/Samples~/Scripts/UI/PanelSwitcher.cs
index 9e13e50..dd5ee5d 100644
--- a/Samples~/Scripts/UI/PanelSwitcher.cs
+++ b/Samples~/Scripts/UI/PanelSwitcher.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Linq;
using ReadyPlayerMe.AvatarCreator;
using UnityEngine;
@@ -16,14 +15,15 @@ public static class PanelSwitcher
public static void AddPanel(AssetType assetType, GameObject widget)
{
AssetTypePanelMap ??= new Dictionary();
- if(!AssetTypePanelMap.ContainsKey(assetType))
- {
- AssetTypePanelMap.Add(assetType, widget);
- }
+ AssetTypePanelMap.TryAdd(assetType, widget);
}
public static void Clear()
{
+ if (AssetTypePanelMap == null)
+ {
+ return;
+ }
foreach (var assetTypePanels in AssetTypePanelMap)
{
Object.Destroy(assetTypePanels.Value);
@@ -85,11 +85,12 @@ private static void DisableColorPanels()
SetActivePanel(AssetType.HairColor, false);
SetActivePanel(AssetType.EyebrowColor, false);
}
+
private static void SetActivePanel(AssetType assetType, bool enable)
{
if (AssetTypePanelMap.ContainsKey(assetType))
{
- AssetTypePanelMap[assetType].SetActive(enable);
+ AssetTypePanelMap[assetType].SetActive(enable);
}
}
}
diff --git a/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs b/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs
index 7d60e60..612dd7d 100644
--- a/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs
+++ b/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs
@@ -109,7 +109,11 @@ private async Task LoadAssets()
partnerAssetManager.OnError += OnErrorCallback;
var assetIconDownloadTasks = await partnerAssetManager.GetAllAssets();
-
+ if (assetIconDownloadTasks == null)
+ {
+ return;
+ }
+
CreateUI(AvatarCreatorData.AvatarProperties.BodyType, assetIconDownloadTasks);
partnerAssetManager.DownloadAssetsIcon(assetButtonCreator.SetAssetIcons);
DebugPanel.AddLogWithDuration("Got all partner assets", Time.time - startTime);
@@ -130,7 +134,15 @@ private async Task LoadAvatar()
}
else
{
- avatar = await avatarManager.GetAvatar(AvatarCreatorData.AvatarProperties.Id);
+ if (!AvatarCreatorData.IsExistingAvatar)
+ {
+ AvatarCreatorData.AvatarProperties = await avatarManager.CreateFromTemplateAvatar(AvatarCreatorData.AvatarProperties);
+ avatar = await avatarManager.GetPreviewAvatar(AvatarCreatorData.AvatarProperties.Id);
+ }
+ else
+ {
+ avatar = await avatarManager.GetAvatar(AvatarCreatorData.AvatarProperties.Id);
+ }
}
if (avatar == null)
diff --git a/Samples~/Scripts/UI/SelectionScreens/AvatarSelection.cs b/Samples~/Scripts/UI/SelectionScreens/AvatarSelection.cs
index 32f56c9..7374b72 100644
--- a/Samples~/Scripts/UI/SelectionScreens/AvatarSelection.cs
+++ b/Samples~/Scripts/UI/SelectionScreens/AvatarSelection.cs
@@ -47,7 +47,7 @@ private async void CreateAvatarButtons()
LoadingManager.EnableLoading();
avatarAPIRequests = new AvatarAPIRequests();
- avatarPartnerMap = await avatarAPIRequests.FetchUserAvatars(AuthManager.UserSession.Id);
+ avatarPartnerMap = await avatarAPIRequests.GetUserAvatars(AuthManager.UserSession.Id);
avatarButtonsMap = new Dictionary();
foreach (var avatar in avatarPartnerMap)
@@ -103,6 +103,7 @@ private async void OnCustomize(string avatarId)
{
AvatarCreatorData.AvatarProperties.Id = avatarId;
AvatarCreatorData.AvatarProperties = await avatarAPIRequests.GetAvatarMetadata(avatarId);
+ AvatarCreatorData.IsExistingAvatar = true;
StateMachine.SetState(StateType.Editor);
}
diff --git a/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs b/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs
index ef9a3f9..0f3232c 100644
--- a/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs
+++ b/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs
@@ -75,7 +75,8 @@ private void OnCameraButton()
AvatarCreatorData.AvatarProperties.Id = string.Empty;
AvatarCreatorData.AvatarProperties.Base64Image = Convert.ToBase64String(bytes);
-
+ AvatarCreatorData.IsExistingAvatar = false;
+
StateMachine.SetState(StateType.Editor);
}
}
diff --git a/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs b/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs
index 9a97573..122ff13 100644
--- a/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs
+++ b/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs
@@ -13,39 +13,7 @@ namespace ReadyPlayerMe
public class DefaultAvatarSelection : State
{
private const string LOADING_MESSAGE = "Fetching default avatars";
-
- private readonly string[] maleAvatarIds =
- {
- "64229ee84a25835c6ae8a5a4",
- "6422a00651c9394af01e94ed",
- "6422a0362ec187fafe20d48e",
- "6422a08b2ec187fafe20d4ad",
- "6422a0be7fc17f5f678cd69b",
- "6422a0eb8933ad00c8629d57",
- "6422a1242ec187fafe20d4e1",
- "6422a14e4a25835c6ae8a682",
- "6422a17cc9e8aa39b5d0e6a6",
- "6422a1b34e26f24ed304729e",
- "6422a1f17fc17f5f678cd722",
- "6422a23d8933ad00c8629dfa"
- };
- private readonly string[] femaleAvatarIds =
- {
- "64229ebbc9e8aa39b5d0e598",
- "64229f0e4a25835c6ae8a5ba",
- "6422a01fc9e8aa39b5d0e620",
- "6422a053a9cf14ab7e44d413",
- "6422a0a62ec187fafe20d4b3",
- "6422a0d47fc17f5f678cd6a2",
- "6422a10a51c9394af01e9543",
- "6422a13a8933ad00c8629d8c",
- "6422a1684e26f24ed3047276",
- "6422a1962ec187fafe20d51d",
- "6422a1d87fc17f5f678cd716",
- "6422a2254a25835c6ae8a6db",
- };
-
private readonly Dictionary avatarRenderMap = new Dictionary();
[SerializeField] private Transform parent;
@@ -53,26 +21,34 @@ public class DefaultAvatarSelection : State
public override StateType StateType => StateType.DefaultAvatarSelection;
public override StateType NextState => StateType.Editor;
-
+
+ private AvatarAPIRequests avatarAPIRequests;
private CancellationTokenSource ctxSource;
public override async void ActivateState()
{
LoadingManager.EnableLoading(LOADING_MESSAGE);
+ if (!AuthManager.IsSignedIn)
+ {
+ await AuthManager.LoginAsAnonymous();
+ }
+
+ avatarAPIRequests = new AvatarAPIRequests();
+ var templateAvatars = await avatarAPIRequests.GetTemplates(AvatarCreatorData.AvatarProperties.Gender);
ctxSource = new CancellationTokenSource();
- var avatarIds = AvatarCreatorData.AvatarProperties.Gender == OutfitGender.Feminine ? femaleAvatarIds : maleAvatarIds;
+
var downloadRenderTasks = new List();
- foreach (var avatarId in avatarIds)
+ foreach (var template in templateAvatars)
{
- if (!avatarRenderMap.ContainsKey(avatarId))
+ if (!avatarRenderMap.ContainsKey(template.Key))
{
- downloadRenderTasks.Add(CreateAvatarRender(avatarId));
+ downloadRenderTasks.Add(CreateAvatarRender(template.Key, template.Value));
}
else
{
- avatarRenderMap[avatarId].SetActive(true);
+ avatarRenderMap[template.Key].SetActive(true);
}
}
@@ -92,33 +68,33 @@ public override void DeactivateState()
}
}
- private async Task CreateAvatarRender(string avatarId)
+ private async Task CreateAvatarRender(string id, string url)
{
- Texture2D renderImage;
+ Texture renderImage;
try
{
- renderImage = await AvatarRenderHelper.GetPortrait(avatarId);
+ renderImage = await avatarAPIRequests.GetTemplateAvatarImage(url);
}
catch (Exception e)
{
Debug.Log(e);
return;
}
-
+
var button = Instantiate(buttonPrefab, parent);
var rawImage = button.GetComponentInChildren();
- button.GetComponent