diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 115f783..c7f66b3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,19 +12,6 @@ -## Changes - -#### Added - -- List your additions and new features here. - -#### Updated - -- List your updates and changes here. - -#### Removed - -- List what is removed here. diff --git a/Documentation~/CustomizationGuide.md b/Documentation~/CustomizationGuide.md new file mode 100644 index 0000000..c1c5e7e --- /dev/null +++ b/Documentation~/CustomizationGuide.md @@ -0,0 +1,47 @@ +# Customization Guide + +This guide will help you to customize the Ready Player Me avatar creator to fit your needs. The UI was designed in a way that separates the logic from the UI elements + +## Requirements +You are allowed to change the entire UI, the only thing that you are required to have in your custom implementation is the Ready Player Me sign-in button and Ready Player Me account-creation UI. This is a legal requirement from Ready Player Me. + +![image](https://github.com/readyplayerme/rpm-unity-sdk-avatar-creator/assets/1121080/c80e9bfa-a635-4ed4-878a-be9506d7d7c1) + +![image](https://github.com/readyplayerme/rpm-unity-sdk-avatar-creator/assets/1121080/c2d8135b-f8e5-4c9d-9ea6-ca00f4af3514) + + +## Creating a Custom UI From Scratch + +All required APIs are provided in the package. They can be used to create a new custom UI. The package also contains a sample UI that can be used as a reference. The following are the most important APIs that you will required. +- AuthManager - Requests for handling authentication. +- AvatarManager - Requests for creating, updating and deleting avatars. +- PartnerAssetManager - Requests for loading partner assets. + +## Customizing the Sample + + The sample comes with a default UI similar to the Web avatar creator. The UI is built using uGUI. All major UI components such as screen, asset buttons, asset type panels are prefabs, and can be edited easily. For detailed description of structure of the sample please see the [Sample Structure.](SampleStructure.md) + +### Changing background color +- Select different screens under UI > AvatarCreatorCanvas > Creator > Screens and set color in image component or change the sprite. +- Select camera and change the background color. +- Select header under UI > AvatarCreatorCanvas > Creator and change the color. +- Select LoadingManager and change color for either type of loading screen. + +Following demonstrate on how to change the background color of different screens. + +https://github.com/readyplayerme/rpm-unity-sdk-avatar-creator/assets/1121080/ae412932-1bd9-4d00-b6b5-6525adecf9c7 + +### Adapting UI according to portrait mode +- Select a panel prefab from the "Prefabs/Panels" folder that you want to modify. +- Adjust the size and position of the panel to suit your desired location. +- To switch from a vertical to a horizontal layout, make the following changes: + - Locate the Grid Layout Group component and adjust the constraint from "Fixed Column Count" to "Fixed Row Count". + - Find the Scroll Rect component of the panel and select the "Horizontal" option while deselecting the "Vertical" option. + +Following demonstrate on how to change avatar creation selection panels according to portrait mode. This is done in runtime but can be replicated by changing the prefabs as mentioned above. + +https://github.com/readyplayerme/rpm-unity-sdk-avatar-creator/assets/1121080/f706e33d-8fb8-4226-8d3c-3e5b6bb17026 + + + + diff --git a/Documentation~/CustomizationGuide.md.meta b/Documentation~/CustomizationGuide.md.meta new file mode 100644 index 0000000..74794a2 --- /dev/null +++ b/Documentation~/CustomizationGuide.md.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 055527c06b5045aba1fff99e92a613c9 +timeCreated: 1687273144 \ No newline at end of file diff --git a/README.md b/README.md index 7c2af98..136d242 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,11 @@ The plugin is currently in **alpha** stage. We recommend not to use it in the pr - The package contains APIs required for creating, customizing and loading the avatar. - It also contains a sample which demonstrate the usage of the APIs and replicates RPM web avatar creator. - The documentation of provided sample can be found [here.](Documentation~/SampleStructure.md) + +## Customization + +The documentation for customization can be found [here.](Documentation~/CustomizationGuide.md) + ## Note - Camera support is currently only available for PC using Unity’s webcam native API. 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/Data/PartnerAssets.cs b/Runtime/Data/PartnerAssets.cs index 89ba726..49eb6a1 100644 --- a/Runtime/Data/PartnerAssets.cs +++ b/Runtime/Data/PartnerAssets.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; using ReadyPlayerMe.AvatarLoader; namespace ReadyPlayerMe.AvatarCreator @@ -12,5 +13,12 @@ public struct PartnerAsset public OutfitGender Gender; public string Icon; public string Mask; + public LockedCategories[] LockedCategories; + } + + public struct LockedCategories + { + public string Name; + public KeyValuePair[] CustomizationCategories; } } diff --git a/Runtime/Data/TemplateData.cs b/Runtime/Data/TemplateData.cs new file mode 100644 index 0000000..f279613 --- /dev/null +++ b/Runtime/Data/TemplateData.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; +using ReadyPlayerMe.AvatarLoader; + +namespace ReadyPlayerMe.AvatarCreator +{ + public struct TemplateData + { + public string ImageUrl; + [JsonConverter(typeof(GenderConverter))] + public OutfitGender Gender; + public string Id; + } +} diff --git a/Runtime/Data/TemplateData.cs.meta b/Runtime/Data/TemplateData.cs.meta new file mode 100644 index 0000000..0830417 --- /dev/null +++ b/Runtime/Data/TemplateData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f2222085428945828153b8003ded6b64 +timeCreated: 1687870275 \ No newline at end of file diff --git a/Runtime/Data/UserSession.cs b/Runtime/Data/UserSession.cs index 1202713..d0fa3a9 100644 --- a/Runtime/Data/UserSession.cs +++ b/Runtime/Data/UserSession.cs @@ -1,9 +1,7 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace ReadyPlayerMe.AvatarCreator { - [Serializable] public struct UserSession { [JsonProperty("_id")] diff --git a/Runtime/JsonHelpers/PartnerAssetsDictionaryConverter.cs b/Runtime/JsonHelpers/PartnerAssetsDictionaryConverter.cs index 9a7b846..c06fee7 100644 --- a/Runtime/JsonHelpers/PartnerAssetsDictionaryConverter.cs +++ b/Runtime/JsonHelpers/PartnerAssetsDictionaryConverter.cs @@ -35,6 +35,10 @@ public override Dictionary ReadJson(JsonReader reader, Type o } var pascalCaseKey = char.ToUpperInvariant(element.Key[0]) + element.Key.Substring(1); + if(!Enum.IsDefined(typeof(AssetType), pascalCaseKey)) + { + continue; + } var assetType = (AssetType) Enum.Parse(typeof(AssetType), pascalCaseKey); assets.Add(assetType, element.Value); } diff --git a/Runtime/PartnerAssetsManager.cs b/Runtime/PartnerAssetsManager.cs index b68e4fb..41fb40e 100644 --- a/Runtime/PartnerAssetsManager.cs +++ b/Runtime/PartnerAssetsManager.cs @@ -77,6 +77,12 @@ public async void DownloadAssetsIcon(Action> onDownl } } + public bool IsLockedAssetCategories(string id) + { + var asset = assets.FirstOrDefault(x => x.Id == id); + return asset.LockedCategories != null && asset.LockedCategories.Length > 0; + } + private async Task> DownloadIcons(List chunk) { var assetIconMap = new Dictionary>(); diff --git a/Runtime/WebRequests/AvatarAPIRequests.cs b/Runtime/WebRequests/AvatarAPIRequests.cs index 6465f33..82a2abb 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,12 @@ 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 PARTNER = "partner"; + private const string DATA = "data"; + private const string ID = "id"; private readonly AuthorizedRequest authorizedRequest; private readonly CancellationToken ctx; @@ -26,7 +34,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 +47,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() + { + var response = await authorizedRequest.SendRequest( + new RequestData + { + Url = $"{Endpoints.AVATAR_API_V2}/{TEMPLATE}", + Method = HttpMethod.GET, + }, + ctx: ctx + ); + response.ThrowIfError(); + + var json = JObject.Parse(response.Text); + var data = json[DATA]!; + return JsonConvert.DeserializeObject(data.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 +134,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 +152,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~.meta b/Samples~.meta deleted file mode 100644 index e88be33..0000000 --- a/Samples~.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f371ab3fb09d7c446a65704b548287fb -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: 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..1508ece 100644 --- a/Samples~/Scripts/UI/AssetTypeUICreator.cs +++ b/Samples~/Scripts/UI/AssetTypeUICreator.cs @@ -36,7 +36,7 @@ private class AssetTypeUI [SerializeField] private GameObject leftSidePanelPrefab; [SerializeField] private List assetTypeIcons; - private Dictionary assetTypeButtonsMap; + public Dictionary assetTypeButtonsMap; private AssetTypeButton selectedAssetTypeButton; private CameraZoom cameraZoom; @@ -47,7 +47,6 @@ private void Awake() cameraZoom = FindObjectOfType(); } - public void CreateUI(BodyType bodyType, IEnumerable assetTypes) { this.bodyType = bodyType; @@ -65,14 +64,12 @@ public void CreateUI(BodyType bodyType, IEnumerable assetTypes) else if (assetType.IsFaceAsset()) { CreateAssetTypePanel(assetType, faceAssetPanelPrefab, assetTypeUI.panelParent); - CreateAssetTypeButton(assetType, faceAssetTypePanel.GetComponent().content.transform, () => - PanelSwitcher.Switch(assetType)); + CreateAssetTypeButton(assetType, faceAssetTypePanel.GetComponent().content.transform); } else { CreateAssetTypePanel(assetType, assetTypeUI.panelPrefab, assetTypeUI.panelParent); - CreateAssetTypeButton(assetType, assetTypeUI.buttonParent, () => - PanelSwitcher.Switch(assetType)); + CreateAssetTypeButton(assetType, assetTypeUI.buttonParent); } } @@ -88,11 +85,38 @@ public void CreateUI(BodyType bodyType, IEnumerable assetTypes) }); } + public void SetDefaultSelection(AssetType assetType) + { + SwitchZoomByAssetType(assetType); + assetTypeButtonsMap[assetType].SetSelect(true); + selectedAssetTypeButton.SetSelect(false); + faceAssetTypeButton.SetSelect(assetType.IsFaceAsset()); + selectedAssetTypeButton = assetTypeButtonsMap[assetType]; + PanelSwitcher.Switch(assetType); + } + + public void SetActiveAssetTypeButtons(bool enable) + { + faceAssetTypeButton.SetInteractable(enable); + foreach (var assetTypeButton in assetTypeButtonsMap) + { + if (assetTypeButton.Key != AssetType.Outfit) + { + assetTypeButton.Value.SetInteractable(enable); + } + } + } + public void ResetUI() { PanelSwitcher.Clear(); DefaultZoom(); + if (assetTypeButtonsMap == null) + { + return; + } + foreach (var assetTypeButton in assetTypeButtonsMap) { Destroy(assetTypeButton.Value.gameObject); @@ -111,7 +135,7 @@ private void CreateAssetTypePanel(AssetType assetType, GameObject panelPrefab, T PanelSwitcher.AddPanel(assetType, assetTypePanel); } - private void CreateAssetTypeButton(AssetType assetType, Transform parent, Action onClick) + private void CreateAssetTypeButton(AssetType assetType, Transform parent) { var assetTypeButtonGameObject = Instantiate(assetTypeUI.buttonPrefab, parent); var assetTypeButton = assetTypeButtonGameObject.GetComponent(); @@ -124,12 +148,7 @@ private void CreateAssetTypeButton(AssetType assetType, Transform parent, Action assetTypeButton.AddListener(() => { - SwitchZoomByAssetType(assetType); - assetTypeButton.SetSelect(true); - selectedAssetTypeButton.SetSelect(false); - faceAssetTypeButton.SetSelect(assetType.IsFaceAsset()); - selectedAssetTypeButton = assetTypeButton; - onClick?.Invoke(); + SetDefaultSelection(assetType); }); assetTypeButtonsMap.Add(assetType, assetTypeButton); } diff --git a/Samples~/Scripts/UI/Buttons/AssetTypeButton.cs b/Samples~/Scripts/UI/Buttons/AssetTypeButton.cs index 5af6a56..39fdb12 100644 --- a/Samples~/Scripts/UI/Buttons/AssetTypeButton.cs +++ b/Samples~/Scripts/UI/Buttons/AssetTypeButton.cs @@ -38,5 +38,10 @@ public void SetSelect(bool isSelected) { icon.color = isSelected ? selectedColor : defaultColor; } + + public void SetInteractable(bool isInteractable) + { + button.interactable = isInteractable; + } } } diff --git a/Samples~/Scripts/UI/PanelSwitcher.cs b/Samples~/Scripts/UI/PanelSwitcher.cs index 9e13e50..99d2d84 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.Add(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..453162c 100644 --- a/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs +++ b/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs @@ -71,9 +71,30 @@ private async void Setup() await LoadAvatarColors(); assetButtonCreator.SetSelectedAssets(AvatarCreatorData.AvatarProperties.Assets); + ToggleAssetTypeButtons(); + LoadingManager.DisableLoading(); } + private void ToggleAssetTypeButtons() + { + var assets = AvatarCreatorData.AvatarProperties.Assets; + if (!assets.TryGetValue(AssetType.Outfit, out var outfitId)) + { + return; + } + + if (partnerAssetManager.IsLockedAssetCategories(outfitId.ToString())) + { + assetTypeUICreator.SetActiveAssetTypeButtons(false); + assetTypeUICreator.SetDefaultSelection(AssetType.Outfit); + } + else + { + assetTypeUICreator.SetActiveAssetTypeButtons(true); + } + } + private void Cleanup() { if (currentAvatar != null) @@ -109,7 +130,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 +155,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) @@ -169,11 +202,17 @@ private async Task LoadAvatarColors() private void CreateUI(BodyType bodyType, Dictionary assets) { assetTypeUICreator.CreateUI(bodyType, AssetTypeHelper.GetAssetTypeList(bodyType)); - assetButtonCreator.CreateAssetButtons(assets, UpdateAvatar); + assetButtonCreator.CreateAssetButtons(assets, OnAssetButtonClicked); assetButtonCreator.CreateClearButton(UpdateAvatar); saveButton.gameObject.SetActive(true); } + private void OnAssetButtonClicked(string id, AssetType assetType) + { + assetTypeUICreator.SetActiveAssetTypeButtons(!partnerAssetManager.IsLockedAssetCategories(id)); + UpdateAvatar(id, assetType); + } + private void OnSaveButton() { if (AuthManager.IsSignedIn) 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..f8d03d4 100644 --- a/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs +++ b/Samples~/Scripts/UI/SelectionScreens/DefaultAvatarSelection.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using ReadyPlayerMe.AvatarCreator; -using ReadyPlayerMe.AvatarLoader; using UnityEngine; using UnityEngine.UI; @@ -13,74 +12,42 @@ 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; [SerializeField] private GameObject buttonPrefab; public override StateType StateType => StateType.DefaultAvatarSelection; public override StateType NextState => StateType.Editor; - + + private Dictionary avatarRenderMap; + private AvatarAPIRequests avatarAPIRequests; private CancellationTokenSource ctxSource; - public override async void ActivateState() + private void Awake() { - LoadingManager.EnableLoading(LOADING_MESSAGE); - - ctxSource = new CancellationTokenSource(); - var avatarIds = AvatarCreatorData.AvatarProperties.Gender == OutfitGender.Feminine ? femaleAvatarIds : maleAvatarIds; - var downloadRenderTasks = new List(); + avatarRenderMap = new Dictionary(); + } - foreach (var avatarId in avatarIds) + public override async void ActivateState() + { + if (avatarRenderMap.Count == 0) { - if (!avatarRenderMap.ContainsKey(avatarId)) - { - downloadRenderTasks.Add(CreateAvatarRender(avatarId)); - } - else + LoadingManager.EnableLoading(LOADING_MESSAGE); + + if (!AuthManager.IsSignedIn) { - avatarRenderMap[avatarId].SetActive(true); + await AuthManager.LoginAsAnonymous(); } + + await FetchTemplates(); + + LoadingManager.DisableLoading(); } - while (!downloadRenderTasks.All(x => x.IsCompleted) && !ctxSource.IsCancellationRequested) + foreach (var template in avatarRenderMap) { - await Task.Yield(); + avatarRenderMap[template.Key].SetActive(template.Key.Gender == AvatarCreatorData.AvatarProperties.Gender); } - LoadingManager.DisableLoading(); } public override void DeactivateState() @@ -92,33 +59,54 @@ public override void DeactivateState() } } - private async Task CreateAvatarRender(string avatarId) + private async Task FetchTemplates() + { + var downloadRenderTasks = new List(); + ctxSource = new CancellationTokenSource(); + + avatarAPIRequests = new AvatarAPIRequests(); + var templateAvatars = await avatarAPIRequests.GetTemplates(); + foreach (var template in templateAvatars) + { + if (!avatarRenderMap.ContainsKey(template)) + { + downloadRenderTasks.Add(CreateAvatarRender(template)); + } + } + + while (!downloadRenderTasks.All(x => x.IsCompleted) && !ctxSource.IsCancellationRequested) + { + await Task.Yield(); + } + } + + private async Task CreateAvatarRender(TemplateData templateData) { - Texture2D renderImage; + Texture renderImage; try { - renderImage = await AvatarRenderHelper.GetPortrait(avatarId); + renderImage = await avatarAPIRequests.GetTemplateAvatarImage(templateData.ImageUrl); } catch (Exception e) { Debug.Log(e); return; } - + var button = Instantiate(buttonPrefab, parent); var rawImage = button.GetComponentInChildren(); - button.GetComponent