diff --git a/Explorer/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/Explorer/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 680abb81674..2aebaa37c4c 100644 --- a/Explorer/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/Explorer/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,7 @@ MonoBehaviour: m_DefaultGroup: 47f803e1e9c5079449bd106df98a0b7d m_currentHash: serializedVersion: 2 - Hash: 8173ea4fb741d0d3cbbc7d6ca2d76db7 + Hash: 69405828b03b7e3bfc18552c607113d2 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_CatalogRequestsTimeout: 0 diff --git a/Explorer/Assets/AddressableAssetsData/AssetGroups/Essentials.asset b/Explorer/Assets/AddressableAssetsData/AssetGroups/Essentials.asset index c7cc20f73c6..5360bc2d9c1 100644 --- a/Explorer/Assets/AddressableAssetsData/AssetGroups/Essentials.asset +++ b/Explorer/Assets/AddressableAssetsData/AssetGroups/Essentials.asset @@ -170,6 +170,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 69832a4fb177e7645af56a1f27d0dce2 + m_Address: Assets/DCL/PluginSystem/Global/AvatarFaceAnimationSettings.asset + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: 6a252383173104e17acac54eb364afc4 m_Address: Assets/DCL/AvatarRendering/AvatarShape/Assets/PointAtMarker.prefab m_ReadOnly: 0 @@ -355,6 +360,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: f13fd0befee6d48479806b957b3ca94f + m_Address: Assets/DCL/PluginSystem/Global/AvatarFaceExpressionConfig.asset + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: f3a58c428a71b443e883f8fac00cfbbd m_Address: Assets/DCL/AvatarRendering/AvatarShape/Assets/EmoteMaskCatalog.asset m_ReadOnly: 0 diff --git a/Explorer/Assets/AddressableAssetsData/AssetGroups/UI.asset b/Explorer/Assets/AddressableAssetsData/AssetGroups/UI.asset index 499752308d7..cac598bff8a 100644 --- a/Explorer/Assets/AddressableAssetsData/AssetGroups/UI.asset +++ b/Explorer/Assets/AddressableAssetsData/AssetGroups/UI.asset @@ -365,6 +365,11 @@ MonoBehaviour: m_ReadOnly: 0 m_SerializedLabels: [] FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: 5c07800f394bd8945936cf8e55cdd020 + m_Address: FacialExpressionsWheelHUD.prefab + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 - m_GUID: 5deacee45f1ebfe4394a97755a51fcca m_Address: Assets/Textures/ExplorePanel/BackpackHoverUnhoverSpriteAtlas.spriteatlasv2 m_ReadOnly: 0 diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs new file mode 100644 index 00000000000..e7dc3825b82 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape.Assets +{ + [CreateAssetMenu(fileName = "AvatarFaceAnimationSettings", menuName = "DCL/Avatar/Face Animation Settings")] + public class AvatarFaceAnimationSettings : ScriptableObject + { + [field: Header("Blink")] + [field: SerializeField] public float MinBlinkInterval { get; private set; } = 2.0f; + [field: SerializeField] public float MaxBlinkInterval { get; private set; } = 8.0f; + [field: SerializeField] public float BlinkFrameDuration { get; private set; } = 0.05f; + + [field: Header("Mouth Pose")] + [field: SerializeField] public float MouthPoseDuration { get; private set; } = 0.08f; + [field: SerializeField] public float VowelMouthPoseDuration { get; private set; } = 0.12f; + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs.meta new file mode 100644 index 00000000000..7545cfa5555 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Assets/AvatarFaceAnimationSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3fa44f695c71c9d44a49c7ddfbf82738 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs new file mode 100644 index 00000000000..6590cfbce93 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// ScriptableObject holding all named face expressions available to avatars. + /// Configure via Assets > Create > DCL > Avatar > Face Expression Config. + /// Expressions are the base layer of the face, underneath blink and mouth-pose animations + /// which temporarily override eyes and mouth respectively. + /// + [CreateAssetMenu(fileName = "AvatarFaceExpressionConfig", menuName = "DCL/Avatar/Face Expression Config")] + public class AvatarFaceExpressionConfig : ScriptableObject + { + public AvatarFaceExpressionDefinition[] Expressions = Array.Empty(); + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs.meta new file mode 100644 index 00000000000..3ea032e9960 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionConfig.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3fc70577efb0ebd4fb2f7ed9436f8213 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs new file mode 100644 index 00000000000..e6f979f3754 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs @@ -0,0 +1,28 @@ +using System; +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// A named face pose combining specific atlas slice indices for eyebrows, eyes, and mouth. + /// Eyebrows atlas: 0 Idle, 1 Up, 2 Down, 3 Angry, 4 Sad, 5 Surprised, 6-15 Unused. + /// Eyes atlas: 0 Idle, 1 HalfClosed, 2 Closed, 3 WideOpen, 4-7 Look directions, 8-15 Unused. + /// Mouth atlas: 0-11 Mouth poses, 12 Sad, 13 Happy, 14 Smile, 15 Worried. + /// + [Serializable] + public struct AvatarFaceExpressionDefinition + { + public string Name; + + public Sprite Icon; + + [Range(0, 15)] + public int EyebrowsIndex; + + [Range(0, 15)] + public int EyesIndex; + + [Range(0, 15)] + public int MouthIndex; + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs.meta new file mode 100644 index 00000000000..62445162a80 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceExpressionDefinition.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 22d16ab00265faa4c860c5aa05735669 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs new file mode 100644 index 00000000000..b7cd1eb3acf --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs @@ -0,0 +1,99 @@ +using DCL.AvatarRendering.AvatarShape.Components; +using DCL.AvatarRendering.AvatarShape.Rendering.TextureArray; +using DCL.AvatarRendering.Loading.Assets; +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Material/MaterialPropertyBlock side-effects for face animation. The avatar facial + /// expression system delegates all expression-index binding here so it stays focused + /// on state transitions. + /// + public static class AvatarFaceMaterialUtils + { + public static void ApplyEyebrowsFrame(ref AvatarFaceComponent face, int eyebrowsIndex) + { + if (face.EyebrowsRenderer == null) return; + if (face.CurrentEyebrowsIndex == eyebrowsIndex) return; + + face.CurrentEyebrowsIndex = eyebrowsIndex; + SetExpressionIndex(face.EyebrowsRenderer, eyebrowsIndex); + } + + public static void ApplyEyeFrame(ref AvatarFaceComponent face, int eyeIndex) + { + if (face.EyeRenderer == null) return; + if (face.CurrentEyeIndex == eyeIndex) return; + + face.CurrentEyeIndex = eyeIndex; + SetExpressionIndex(face.EyeRenderer, eyeIndex); + } + + public static void ApplyMouthPose(ref AvatarFaceComponent face, int mouthPoseIndex) + { + if (face.MouthRenderer == null) return; + if (face.CurrentMouthPoseIndex == mouthPoseIndex) return; + + face.CurrentMouthPoseIndex = mouthPoseIndex; + SetExpressionIndex(face.MouthRenderer, mouthPoseIndex); + } + + public static void StartBlink(ref AvatarFaceComponent face) + { + face.IsBlinking = true; + face.BlinkFrameIndex = 0; + face.BlinkFrameTimer = 0f; + ApplyEyeFrame(ref face, AvatarFacialExpressionConstants.BLINK_SEQUENCE[0]); + } + + public static void EndBlink(ref AvatarFaceComponent face, float minBlinkInterval, float maxBlinkInterval) + { + face.IsBlinking = false; + face.TimeSinceLastBlink = 0f; + face.NextBlinkTime = Random.Range(minBlinkInterval, maxBlinkInterval); + ApplyEyeFrame(ref face, face.EyesExpressionIndex); + } + + public static void StopMouthAnimation(ref AvatarFaceComponent face) + { + face.AnimatingText = null; + ApplyMouthPose(ref face, face.MouthExpressionIndex); + } + + /// + /// Searches the avatar's instantiated wearables for the first renderer whose name ends + /// with . Returns null when none of the wearables expose one + /// (e.g. wearable lacks a Mask_* mesh). + /// + public static Renderer? FindRendererWithSuffix(in AvatarShapeComponent avatarShape, string suffix) + { + for (var i = 0; i < avatarShape.InstantiatedWearables.Count; i++) + { + CachedAttachment wearable = avatarShape.InstantiatedWearables[i]; + + for (var j = 0; j < wearable.Renderers.Count; j++) + { + Renderer renderer = wearable.Renderers[j]; + + if (renderer.name.EndsWith(suffix)) + return renderer; + } + } + + return null; + } + + // Sentinel index (<0) disables atlas slicing in the shader so non-atlas wearables sample + // their full base map; >=0 picks one cell of the 4x4 atlas grid. _ExpressionIndex lives + // in UnityPerMaterial CBuffer for SRP batcher, so MPB can't override it; each face + // renderer already has its own pooled material instance, so a direct write is safe. + private static void SetExpressionIndex(Renderer renderer, int index) + { + Material material = renderer.sharedMaterial; + + if (material != null) + material.SetInteger(TextureArrayConstants.EXPRESSION_INDEX_SHADER, index); + } + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs.meta new file mode 100644 index 00000000000..dfa343d23d8 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFaceMaterialUtils.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8d9334e9f4aad364ebab5fde98cacc37 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs new file mode 100644 index 00000000000..68fce9e8e05 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs @@ -0,0 +1,80 @@ +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Atlas slice indices, mouth-pose mapping, and pause-character classification used by + /// the avatar facial expression pipeline. + /// + /// + /// Atlases are 1024×1024, 4×4 grid of 256px cells (top-to-bottom, left-to-right). + /// + /// Eyebrows: 0 Idle, 1 Up, 2 Down, 3 Angry, 4 Sad, 5 Surprised, 6-15 Unused. + /// Eyes: 0 Idle, 1 HalfClosed, 2 Closed, 3 WideOpen, 4-7 Look*, 8-15 Unused. + /// Mouth: 0 Idle, 1 a/e/i, 2 b/m/p, 3 f/v, 4 d/th, 5 u, 6 c/g/h/k/n/s/t/x/y/z, + /// 7 o, 8 l, 9 r, 10 ch/j/sh, 11 w/q, 12 Sad, 13 Happy, 14 Smile, 15 Worried. + /// + public static class AvatarFacialExpressionConstants + { + public const int NO_EYEBROWS_OVERRIDE = -1; + public const int NO_EYE_OVERRIDE = -1; + public const int NO_MOUTH_POSE = -1; + + public const int EYE_HALF_CLOSED = 1; + public const int EYE_CLOSED = 2; + + public const float UPPERCASE_DURATION_MULTIPLIER = 2f; + + /// Mouth-pose-rich text looped while an avatar is actively speaking in voice chat. + public const string VOICE_CHAT_LOOP_TEXT = "el murcielago hindu comia feliz cardillo y kiwi"; + + /// One blink: HalfClosed → Closed → HalfClosed → restore expression. + public static readonly int[] BLINK_SEQUENCE = { EYE_HALF_CLOSED, EYE_CLOSED, EYE_HALF_CLOSED }; + + public static bool IsVowel(char c) + { + c = char.ToLowerInvariant(c); + return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; + } + + /// + /// Pause characters hold the expression mouth (not idle) instead of an articulated pose. + /// + public static bool IsPauseChar(char c) => + c == ',' || c == '.' || c == ';' || c == ':' || c == '!' || c == '?' || c == ' ' || c == '\n'; + + /// + /// Maps a character at to a mouth pose slice. Digraphs + /// (th, ch, sh) detected by peeking. Pause chars and any unmapped char return + /// so the caller falls back to the expression mouth. + /// + public static int MapCharToMouthPose(string text, int index) + { + char c = char.ToLowerInvariant(text[index]); + + if (IsPauseChar(c)) + return NO_MOUTH_POSE; + + char next = index + 1 < text.Length ? char.ToLowerInvariant(text[index + 1]) : '\0'; + + switch (c) + { + case 'a': case 'e': case 'i': return 1; + case 'b': case 'm': case 'p': return 2; + case 'f': case 'v': return 3; + case 't': return next == 'h' ? 4 : 6; + case 'u': return 5; + case 'd': return 4; + case 'g': case 'h': + case 'k': case 'n': case 'x': + case 'y': case 'z': return 6; + case 'c': return next == 'h' ? 10 : 6; + case 's': return next == 'h' ? 10 : 6; + case 'o': return 7; + case 'l': return 8; + case 'r': return 9; + case 'j': return 10; + case 'w': case 'q': return 11; + default: return NO_MOUTH_POSE; + } + } + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs.meta new file mode 100644 index 00000000000..e4a9918d375 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/AvatarFacialExpressionConstants.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 897324802def3d34b85dc05e3019c32c \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs new file mode 100644 index 00000000000..a94220ffbf2 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs @@ -0,0 +1,60 @@ +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape.Components +{ + /// + /// Per-avatar facial animation state. One component holds all three layers because they are + /// always added/removed together and the pipeline reads them together each frame: + /// base expression → blink overrides eyes → chat/voice overrides mouth → pause restores + /// mouth to (not idle). + /// + public struct AvatarFaceComponent + { + // Renderers — null while setup is pending or after avatar re-instantiation. + public Renderer EyebrowsRenderer; + public Renderer EyeRenderer; + public Renderer MouthRenderer; + + // Cached at the time we last wrote _ExpressionIndex. + // The skinning material pool can swap the material under us during a wearable swap while + // returning the SAME renderer instance from the wearable cache — the new material starts at + // the shader-default sentinel, so a renderer-ref-only diff would happily skip the write and + // the channel would render the full atlas. AvatarFacialExpressionSystem diffs these against + // renderer.sharedMaterial each frame to detect that case. + public Material EyebrowsMaterial; + public Material EyeMaterial; + public Material MouthMaterial; + + // Per-channel atlas capability of the currently worn wearable. Stable across animation; + // only changes when a wearable swap rebinds the face renderers. + public bool EyebrowsHasExpressionAtlas; + public bool EyesHasExpressionAtlas; + public bool MouthHasExpressionAtlas; + + // Resting atlas cell per channel (0..N when capability bool is true, -1 otherwise). + // Eyes/mouth are restored to these when blink / mouth animation ends. + public int EyebrowsExpressionIndex; + public int EyesExpressionIndex; + public int MouthExpressionIndex; + + // Currently applied MaterialPropertyBlock slice indices. -1 means no override (material default). + public int CurrentEyebrowsIndex; + public int CurrentEyeIndex; + public int CurrentMouthPoseIndex; + + // Blink state. + public bool IsBlinking; + public float TimeSinceLastBlink; + public float NextBlinkTime; + public int BlinkFrameIndex; + public float BlinkFrameTimer; + + // Mouth-pose animation state. AnimatingText is null when idle. + public string? AnimatingText; + public int CharacterIndex; + public float CharacterTimer; + + /// True when expression indices changed and need to be pushed to renderers this frame. + public bool IsDirty; + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs.meta new file mode 100644 index 00000000000..2ae4fd305f4 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarFaceComponent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 136cbc65d4cd923489a70626f8a022b3 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs new file mode 100644 index 00000000000..afe1450decb --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs @@ -0,0 +1,21 @@ +namespace DCL.AvatarRendering.AvatarShape.Components +{ + /// + /// Bridge component written by external services (chat + voice chat) to drive mouth animation. + /// AvatarFacialExpressionSystem reads and clears it each frame. + /// + /// Two independent writers update different fields, so writers must use a partial-update + /// pattern (Has + Get or Add) instead of AddOrSet to avoid clobbering each other. + /// + /// + /// / — set by chat service when a message arrives. + /// — set by voice handler when speaking state changes. + /// + /// + public struct AvatarMouthInputComponent + { + public string? PendingMessage; + public bool MessageIsDirty; + public bool IsVoiceChatSpeaking; + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs.meta new file mode 100644 index 00000000000..b61498b43be --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarMouthInputComponent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 68754f419783b9a4a8c72cda3bc21f51 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarShapeComponent.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarShapeComponent.cs index 7c435ad2dd2..571a09d40a0 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarShapeComponent.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/AvatarShapeComponent.cs @@ -28,6 +28,11 @@ public struct AvatarShapeComponent public bool ShowOnlyWearables; + // Per-channel atlas capability stamped by AvatarInstantiatorSystem from the worn facial-feature wearables. + public bool EyebrowsHasExpressionAtlas; + public bool EyesHasExpressionAtlas; + public bool MouthHasExpressionAtlas; + public AvatarShapeComponent(string name, string id, BodyShape bodyShape, WearablePromise wearablePromise, Color skinColor, Color hairColor, Color eyesColor, bool showOnlyWearables = false) { @@ -45,6 +50,9 @@ public AvatarShapeComponent(string name, string id, BodyShape bodyShape, Wearabl HiddenByModifierArea = false; IsPreview = false; ShowOnlyWearables = showOnlyWearables; + EyebrowsHasExpressionAtlas = false; + EyesHasExpressionAtlas = false; + MouthHasExpressionAtlas = false; } public void CreateOutlineCompatibilityList() @@ -72,6 +80,9 @@ public AvatarShapeComponent(string name, string id) : this() InstantiatedWearables = new List(); OutlineCompatibleRenderers = new List(); IsVisible = true; + EyebrowsHasExpressionAtlas = false; + EyesHasExpressionAtlas = false; + MouthHasExpressionAtlas = false; } } } diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs new file mode 100644 index 00000000000..7da6e6133e6 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs @@ -0,0 +1,15 @@ +namespace DCL.AvatarRendering.AvatarShape.Components +{ + /// + /// One-frame intent placed on the local player to apply a face pose. The shape mirrors the + /// network payload (3 atlas indices) so the wheel/shortcut writers don't need the + /// AvatarFaceExpressionDefinition table — they translate slot → indices upstream. + /// Consumed by . + /// + public struct TriggerFacialExpressionIntent + { + public byte EyebrowsIndex; + public byte EyesIndex; + public byte MouthIndex; + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs.meta new file mode 100644 index 00000000000..442b200cf9d --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Components/TriggerFacialExpressionIntent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 036fb31d787c86d42b9e3e98a73dfde2 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression.meta new file mode 100644 index 00000000000..0d448ba3b42 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3f53c2a9e5ca54f4a8390ec6b0150b20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref new file mode 100644 index 00000000000..b24e4ef9e3d --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref @@ -0,0 +1,3 @@ +{ + "reference": "GUID:fc4fd35fb877e904d8cedee73b2256f6" +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref.meta new file mode 100644 index 00000000000..7bc92274cea --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/DCL.AvatarShape.FacialExpression.asmref.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 215d2bc374736a04797c1e8b34c90852 +AssemblyDefinitionReferenceImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs new file mode 100644 index 00000000000..f69f9de109a --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs @@ -0,0 +1,75 @@ +using System; +using UnityEngine; +using Utility; +using InputAction = UnityEngine.InputSystem.InputAction; + +namespace DCL.AvatarRendering.AvatarShape.FacialExpression +{ + public enum FacialExpressionTriggerSource + { + /// Click on a wheel slot, press [0-9], or use a cycler while the wheel is open. + WHEEL_SLOT, + + /// Press Y+[0-9] while the wheel is closed. + SHORTCUT, + } + + /// + /// Owns the Y key (facial expressions shortcut) release handling. Receives expression-played + /// notifications via ; when Y is released, applies + /// ignore-next-release and time-based lock, then publishes + /// . + /// + public class FacialExpressionsWheelShortcutHandler : IDisposable + { + private const float QUICK_APPLY_LOCK_TIME = 0.5f; + + private readonly IEventBus eventBus; + private readonly DCLInput dclInput; + + private bool ignoreNextRelease; + private float lockUntilTime; + + public FacialExpressionsWheelShortcutHandler(IEventBus eventBus) + { + this.eventBus = eventBus; + dclInput = DCLInput.Instance; + dclInput.Shortcuts.FaceExpression.canceled += OnShortcutReleased; + } + + public void Dispose() => + dclInput.Shortcuts.FaceExpression.canceled -= OnShortcutReleased; + + public void NotifyExpressionPlayed(FacialExpressionTriggerSource source) + { + switch (source) + { + case FacialExpressionTriggerSource.SHORTCUT: + ignoreNextRelease = true; + break; + case FacialExpressionTriggerSource.WHEEL_SLOT: + lockUntilTime = UnityEngine.Time.time + QUICK_APPLY_LOCK_TIME; + break; + default: + throw new ArgumentOutOfRangeException(nameof(source), source, null); + } + } + + private void OnShortcutReleased(InputAction.CallbackContext _) + { + if (ignoreNextRelease) + { + ignoreNextRelease = false; + return; + } + + if (UnityEngine.Time.time < lockUntilTime) + { + lockUntilTime = 0f; + return; + } + + eventBus.Publish(new RequestToggleFacialExpressionsWheelEvent()); + } + } +} diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs.meta new file mode 100644 index 00000000000..f9e949486cf --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpression/FacialExpressionsWheelShortcutHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd675c62e648e134986bf9cec8fb0068 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs new file mode 100644 index 00000000000..b5084d0b172 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs @@ -0,0 +1,18 @@ +using Arch.Core; +using DCL.AvatarRendering.AvatarShape.Components; + +namespace DCL.AvatarRendering.AvatarShape.FacialExpression +{ + public static class FacialExpressionApplier + { + public static void Apply(World world, Entity playerEntity, byte eyebrowsIndex, byte eyesIndex, byte mouthIndex) + { + world.AddOrGet(playerEntity, new TriggerFacialExpressionIntent + { + EyebrowsIndex = eyebrowsIndex, + EyesIndex = eyesIndex, + MouthIndex = mouthIndex, + }); + } + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs.meta new file mode 100644 index 00000000000..11a8186c5e3 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/FacialExpressionApplier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 98b4b0b30cd532f49a1941398015bbe6 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Helpers/AvatarInstantiationPolymorphicBehaviour.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Helpers/AvatarInstantiationPolymorphicBehaviour.cs index a8bb4719083..70ccb6e0a29 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Helpers/AvatarInstantiationPolymorphicBehaviour.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Helpers/AvatarInstantiationPolymorphicBehaviour.cs @@ -37,7 +37,8 @@ public static class AvatarInstantiationPolymorphicBehaviour case WearableType.FacialFeature: var texturesSet = facialFeaturesTextures.Value[category]; - texturesSet[TextureArrayConstants.MAINTEX_ORIGINAL_TEXTURE] = ((AttachmentTextureAsset)mainAsset).Texture; + var mainTex = ((AttachmentTextureAsset)mainAsset).Texture; + texturesSet[TextureArrayConstants.MAINTEX_ORIGINAL_TEXTURE] = mainTex; // Mask is optional var maskAssetRes = originalAssets[WearablePolymorphicBehaviour.MASK_ASSET_INDEX]; @@ -45,6 +46,10 @@ public static class AvatarInstantiationPolymorphicBehaviour if (maskAssetRes is { Asset: not null }) texturesSet[TextureArrayConstants.MASK_ORIGINAL_TEXTURE_ID] = ((AttachmentTextureAsset)maskAssetRes.Value.Asset!).Texture; + // WearablePromise.Result may be gone by the time the face system runs; stamp atlas capability here. + if (resultWearable.HasFacialExpressionsTexture) + facialFeaturesTextures.MarkHasExpressions(category); + return null; default: { diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Rendering/TextureArray/TextureArrayConstants.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Rendering/TextureArray/TextureArrayConstants.cs index fc16cc19c1d..67911ce8bac 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Rendering/TextureArray/TextureArrayConstants.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Rendering/TextureArray/TextureArrayConstants.cs @@ -40,6 +40,7 @@ public static class TextureArrayConstants public static readonly int MAINTEX_ORIGINAL_TEXTURE = WearableTextureConstants.MAINTEX_ORIGINAL_TEXTURE; public static readonly int MAINTEX_ARR_SHADER_INDEX = Shader.PropertyToID("_MainTexArr_ID"); public static readonly int MAINTEX_ARR_TEX_SHADER = Shader.PropertyToID("_MainTexArr"); + public static readonly int EXPRESSION_INDEX_SHADER = Shader.PropertyToID("_ExpressionIndex"); public static readonly int BASE_MAP_ORIGINAL_TEXTURE = Shader.PropertyToID("_BaseMap"); public static readonly int BASE_MAP_TEX_ARR_INDEX = Shader.PropertyToID("_BaseMapArr_ID"); diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs new file mode 100644 index 00000000000..308d0abff0c --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs @@ -0,0 +1,9 @@ +namespace DCL.AvatarRendering.AvatarShape.FacialExpression +{ + /// + /// Published on the generic event bus when requesting to toggle the facial expressions wheel. + /// + public struct RequestToggleFacialExpressionsWheelEvent + { + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs.meta new file mode 100644 index 00000000000..5df6b498062 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/RequestToggleFacialExpressionsWheelEvent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d227284a5d4b06b45bf3ad25a32752f7 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/AvatarInstantiatorSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/AvatarInstantiatorSystem.cs index ed165f9b23e..1514dfe23d9 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/AvatarInstantiatorSystem.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/AvatarInstantiatorSystem.cs @@ -19,6 +19,7 @@ using ECS.Abstract; using ECS.LifeCycle.Components; using ECS.StreamableLoading.Common; +using Runtime.Wearables; using System.Collections.Generic; using UnityEngine; using UnityEngine.Pool; @@ -253,6 +254,11 @@ private AvatarCustomSkinningComponent InstantiateAvatar(ref AvatarShapeComponent bodyShape = instance; } + // Freeze per-channel atlas capability for AvatarFacialExpressionSystem. + avatarShapeComponent.EyebrowsHasExpressionAtlas = facialFeatureTextures.HasExpressions(WearableCategories.Categories.EYEBROWS); + avatarShapeComponent.EyesHasExpressionAtlas = facialFeatureTextures.HasExpressions(WearableCategories.Categories.EYES); + avatarShapeComponent.MouthHasExpressionAtlas = facialFeatureTextures.HasExpressions(WearableCategories.Categories.MOUTH); + WearableComponentsUtils.HideBodyShape(bodyShape, wearablesToHide, usedCategories); HashSetPool.Release(usedCategories); diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression.meta new file mode 100644 index 00000000000..d86abbf56c3 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 91abfaa82595cb24bb956be994dd19a7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs new file mode 100644 index 00000000000..d08aaf8b59c --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs @@ -0,0 +1,67 @@ +using Arch.Core; +using Arch.System; +using Arch.SystemGroups; +using DCL.AvatarRendering.AvatarShape.Components; +using DCL.Character.Components; +using DCL.Diagnostics; +using DCL.Input; +using DCL.Multiplayer.FacialExpression; +using ECS.Abstract; +using ECS.LifeCycle.Components; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Consumes on the local player and applies the + /// three atlas indices to (drives rendering) and + /// (drives the network send). The system + /// never sees the expression table — callers (wheel controller, keyboard adapter) translate + /// slot → indices upstream so the data shape mirrors the rfc4 payload. + /// + [UpdateInGroup(typeof(InputGroup))] + [LogCategory(ReportCategory.AVATAR)] + public partial class ApplyFacialExpressionIntentSystem : BaseUnityLoopSystem + { + internal ApplyFacialExpressionIntentSystem(World world) : base(world) { } + + protected override void Update(float t) + { + SetupLocalPlayerFacialExpressionQuery(World); + ApplyIntentQuery(World); + } + + /// + /// Adds to the player entity as soon as + /// is available. Runs at most once per player thanks to + /// the [None] filter. + /// + [Query] + [All(typeof(PlayerComponent), typeof(AvatarFaceComponent))] + [None(typeof(LocalPlayerFacialExpressionComponent), typeof(DeleteEntityIntention))] + private void SetupLocalPlayerFacialExpression(Entity entity) + { + World.Add(entity, new LocalPlayerFacialExpressionComponent()); + } + + [Query] + [All(typeof(PlayerComponent), typeof(AvatarFaceComponent))] + [None(typeof(DeleteEntityIntention))] + private void ApplyIntent( + Entity entity, + ref AvatarFaceComponent face, + ref LocalPlayerFacialExpressionComponent local, + ref TriggerFacialExpressionIntent intent) + { + face.EyebrowsExpressionIndex = intent.EyebrowsIndex; + face.EyesExpressionIndex = intent.EyesIndex; + face.MouthExpressionIndex = intent.MouthIndex; + face.IsDirty = true; + + local.EyebrowsIndex = intent.EyebrowsIndex; + local.EyesIndex = intent.EyesIndex; + local.MouthIndex = intent.MouthIndex; + + World.Remove(entity); + } + } +} \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs.meta new file mode 100644 index 00000000000..eb424ac0a55 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/ApplyFacialExpressionIntentSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12507785487b2ac468a722ff3ccf78e4 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs new file mode 100644 index 00000000000..0f4265d6ced --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs @@ -0,0 +1,272 @@ +using Arch.Core; +using Arch.System; +using Arch.SystemGroups; +using DCL.AvatarRendering.AvatarShape.Assets; +using DCL.AvatarRendering.AvatarShape.Components; +using ECS.Abstract; +using ECS.LifeCycle.Components; +using UnityEngine; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Drives 2D facial animation for instantiated avatars: eyebrow expression (base layer), + /// blink (overrides eyes temporarily), and mouth pose (overrides mouth temporarily). + /// Pause characters in animated text restore the expression mouth instead of an articulated pose. + /// Material binding is delegated to and + /// all atlas / pose constants live in . + /// + [UpdateInGroup(typeof(AvatarGroup))] + [UpdateAfter(typeof(AvatarInstantiatorSystem))] + public partial class AvatarFacialExpressionSystem : BaseUnityLoopSystem + { + private readonly AvatarFaceAnimationSettings settings; + + internal AvatarFacialExpressionSystem( + World world, + AvatarFaceAnimationSettings settings) : base(world) + { + this.settings = settings; + } + + protected override void Update(float t) + { + SetupFaceComponentsQuery(World); + UpdateFaceQuery(World, t); + } + + // ─── Setup ──────────────────────────────────────────────────────────── + + /// + /// Adds + together + /// so external services (chat / voice) can write inputs without checking the face + /// component is present, and so the per-frame query can require both. + /// + [Query] + [All(typeof(AvatarCustomSkinningComponent))] + [None(typeof(AvatarFaceComponent), typeof(DeleteEntityIntention))] + private void SetupFaceComponents(in Entity entity, ref AvatarShapeComponent avatarShape) + { + var face = new AvatarFaceComponent + { + EyebrowsRenderer = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Eyebrows"), + EyeRenderer = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Eyes"), + MouthRenderer = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Mouth"), + EyebrowsHasExpressionAtlas = avatarShape.EyebrowsHasExpressionAtlas, + EyesHasExpressionAtlas = avatarShape.EyesHasExpressionAtlas, + MouthHasExpressionAtlas = avatarShape.MouthHasExpressionAtlas, + EyebrowsExpressionIndex = avatarShape.EyebrowsHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_EYEBROWS_OVERRIDE, + EyesExpressionIndex = avatarShape.EyesHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_EYE_OVERRIDE, + MouthExpressionIndex = avatarShape.MouthHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_MOUTH_POSE, + CurrentEyebrowsIndex = AvatarFacialExpressionConstants.NO_EYEBROWS_OVERRIDE, + CurrentEyeIndex = AvatarFacialExpressionConstants.NO_EYE_OVERRIDE, + CurrentMouthPoseIndex = AvatarFacialExpressionConstants.NO_MOUTH_POSE, + NextBlinkTime = Random.Range(settings.MinBlinkInterval, settings.MaxBlinkInterval), + IsDirty = true, + }; + + World.Add(entity, face, new AvatarMouthInputComponent()); + } + + // ─── Per-frame update ───────────────────────────────────────────────── + + [Query] + [None(typeof(DeleteEntityIntention))] + private void UpdateFace( + [Data] float t, + ref AvatarFaceComponent face, + ref AvatarMouthInputComponent mouthInput, + ref AvatarShapeComponent avatarShape) + { + ReInitRenderersIfNeeded(ref face, in avatarShape); + + if (face.IsDirty) + ApplyExpressionLayer(ref face); + + bool visible = avatarShape.IsVisible; + + // Blink and mouth animation drive atlas slice overrides via MaterialPropertyBlock. + // Skip whole-channel when the wearable cannot atlas-slice, leaving the static face. + if (face.EyesHasExpressionAtlas && face.EyeRenderer != null) + StepBlink(t, ref face, visible); + + if (face.MouthHasExpressionAtlas && face.MouthRenderer != null) + StepMouthAnimation(t, ref face, ref mouthInput, visible); + } + + // A wearable swap can return the same renderer instance from the cache but with a + // freshly-pooled skinning material whose _ExpressionIndex sits at the shader-default + // sentinel. Diff both the renderer ref AND its sharedMaterial against the cached pair so + // we don't miss that case; on any mismatch (or a capability bool change) reset Current* + // and the resting index, then flag IsDirty so ApplyExpressionLayer pushes the correct value. + private void ReInitRenderersIfNeeded(ref AvatarFaceComponent face, in AvatarShapeComponent avatarShape) + { + Renderer? eyebrows = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Eyebrows"); + Renderer? eyes = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Eyes"); + Renderer? mouth = AvatarFaceMaterialUtils.FindRendererWithSuffix(in avatarShape, "Mask_Mouth"); + + Material? eyebrowsMat = eyebrows != null ? eyebrows.sharedMaterial : null; + Material? eyesMat = eyes != null ? eyes.sharedMaterial : null; + Material? mouthMat = mouth != null ? mouth.sharedMaterial : null; + + bool rebuilt = false; + + if (eyebrows != null && (face.EyebrowsRenderer != eyebrows || face.EyebrowsMaterial != eyebrowsMat || face.EyebrowsHasExpressionAtlas != avatarShape.EyebrowsHasExpressionAtlas)) + { + face.EyebrowsRenderer = eyebrows; + face.EyebrowsMaterial = eyebrowsMat; + face.EyebrowsHasExpressionAtlas = avatarShape.EyebrowsHasExpressionAtlas; + face.EyebrowsExpressionIndex = avatarShape.EyebrowsHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_EYEBROWS_OVERRIDE; + face.CurrentEyebrowsIndex = AvatarFacialExpressionConstants.NO_EYEBROWS_OVERRIDE; + rebuilt = true; + } + + if (eyes != null && (face.EyeRenderer != eyes || face.EyeMaterial != eyesMat || face.EyesHasExpressionAtlas != avatarShape.EyesHasExpressionAtlas)) + { + face.EyeRenderer = eyes; + face.EyeMaterial = eyesMat; + face.EyesHasExpressionAtlas = avatarShape.EyesHasExpressionAtlas; + face.EyesExpressionIndex = avatarShape.EyesHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_EYE_OVERRIDE; + face.CurrentEyeIndex = AvatarFacialExpressionConstants.NO_EYE_OVERRIDE; + face.IsBlinking = false; + face.BlinkFrameIndex = 0; + face.BlinkFrameTimer = 0f; + face.TimeSinceLastBlink = 0f; + face.NextBlinkTime = Random.Range(settings.MinBlinkInterval, settings.MaxBlinkInterval); + rebuilt = true; + } + + if (mouth != null && (face.MouthRenderer != mouth || face.MouthMaterial != mouthMat || face.MouthHasExpressionAtlas != avatarShape.MouthHasExpressionAtlas)) + { + face.MouthRenderer = mouth; + face.MouthMaterial = mouthMat; + face.MouthHasExpressionAtlas = avatarShape.MouthHasExpressionAtlas; + face.MouthExpressionIndex = avatarShape.MouthHasExpressionAtlas ? 0 : AvatarFacialExpressionConstants.NO_MOUTH_POSE; + face.CurrentMouthPoseIndex = AvatarFacialExpressionConstants.NO_MOUTH_POSE; + face.AnimatingText = null; + face.CharacterIndex = 0; + face.CharacterTimer = 0f; + rebuilt = true; + } + + if (rebuilt) + face.IsDirty = true; + } + + /// + /// Pushes the expression layer to renderers. Eyebrows always apply; eyes/mouth apply + /// only when no override layer is currently animating — the override layer will + /// restore to the new resting indices when it ends. + /// + private void ApplyExpressionLayer(ref AvatarFaceComponent face) + { + face.IsDirty = false; + + // Channels whose wearable lacks an `*_expressions.png` atlas keep the wearable's static + // texture: skip the MaterialPropertyBlock override so we don't paint global atlas slices + // onto a single-frame face texture. + if (face.EyebrowsHasExpressionAtlas) + AvatarFaceMaterialUtils.ApplyEyebrowsFrame(ref face, face.EyebrowsExpressionIndex); + + if (face.EyesHasExpressionAtlas && !face.IsBlinking) + AvatarFaceMaterialUtils.ApplyEyeFrame(ref face, face.EyesExpressionIndex); + + if (face.MouthHasExpressionAtlas && face.AnimatingText == null) + AvatarFaceMaterialUtils.ApplyMouthPose(ref face, face.MouthExpressionIndex); + } + + // ─── Blink layer ────────────────────────────────────────────────────── + + private void StepBlink(float t, ref AvatarFaceComponent face, bool isVisible) + { + if (!isVisible || !face.EyeRenderer.enabled) + { + if (face.IsBlinking) + AvatarFaceMaterialUtils.EndBlink(ref face, settings.MinBlinkInterval, settings.MaxBlinkInterval); + + return; + } + + if (face.IsBlinking) + { + face.BlinkFrameTimer += t; + + if (face.BlinkFrameTimer < settings.BlinkFrameDuration) return; + + face.BlinkFrameTimer = 0f; + face.BlinkFrameIndex++; + + if (face.BlinkFrameIndex >= AvatarFacialExpressionConstants.BLINK_SEQUENCE.Length) + AvatarFaceMaterialUtils.EndBlink(ref face, settings.MinBlinkInterval, settings.MaxBlinkInterval); + else + AvatarFaceMaterialUtils.ApplyEyeFrame(ref face, AvatarFacialExpressionConstants.BLINK_SEQUENCE[face.BlinkFrameIndex]); + } + else + { + face.TimeSinceLastBlink += t; + + if (face.TimeSinceLastBlink >= face.NextBlinkTime) + AvatarFaceMaterialUtils.StartBlink(ref face); + } + } + + // ─── Mouth animation layer ──────────────────────────────────────────── + + private void StepMouthAnimation(float t, ref AvatarFaceComponent face, ref AvatarMouthInputComponent input, bool isVisible) + { + if (!isVisible || !face.MouthRenderer.enabled) + { + AvatarFaceMaterialUtils.StopMouthAnimation(ref face); + return; + } + + // Chat message overrides the voice-chat loop; consume it. + if (input.MessageIsDirty) + { + face.AnimatingText = input.PendingMessage; + face.CharacterIndex = 0; + face.CharacterTimer = 0f; + input.MessageIsDirty = false; + } + + bool isSpeaking = input.IsVoiceChatSpeaking; + + if (face.AnimatingText != null && face.CharacterIndex < face.AnimatingText.Length) + { + // Stop the voice loop if speaking ended mid-animation. + if (!isSpeaking && face.AnimatingText == AvatarFacialExpressionConstants.VOICE_CHAT_LOOP_TEXT) + { + AvatarFaceMaterialUtils.StopMouthAnimation(ref face); + return; + } + + int mouthPose = AvatarFacialExpressionConstants.MapCharToMouthPose(face.AnimatingText, face.CharacterIndex); + AvatarFaceMaterialUtils.ApplyMouthPose( + ref face, + mouthPose == AvatarFacialExpressionConstants.NO_MOUTH_POSE ? face.MouthExpressionIndex : mouthPose); + + face.CharacterTimer += t; + + char currentChar = face.AnimatingText[face.CharacterIndex]; + float baseDuration = AvatarFacialExpressionConstants.IsVowel(currentChar) ? settings.VowelMouthPoseDuration : settings.MouthPoseDuration; + float duration = char.IsUpper(currentChar) ? baseDuration * AvatarFacialExpressionConstants.UPPERCASE_DURATION_MULTIPLIER : baseDuration; + + if (face.CharacterTimer >= duration) + { + face.CharacterTimer = 0f; + face.CharacterIndex++; + } + } + else if (isSpeaking) + { + face.AnimatingText = AvatarFacialExpressionConstants.VOICE_CHAT_LOOP_TEXT; + face.CharacterIndex = 0; + face.CharacterTimer = 0f; + } + else + { + AvatarFaceMaterialUtils.StopMouthAnimation(ref face); + } + } + } +} diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs.meta new file mode 100644 index 00000000000..0eac688e196 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/AvatarFacialExpressionSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6950c2e6be708d47a0ab6069f08f507 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs new file mode 100644 index 00000000000..97c689e4298 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs @@ -0,0 +1,220 @@ +using Arch.Core; +using Arch.System; +using Arch.SystemGroups; +using DCL.AvatarRendering.AvatarShape.Components; +using DCL.Character.Components; +using DCL.Chat.History; +using DCL.Diagnostics; +using DCL.Input; +using DCL.LiveKit.Public; +using DCL.Utilities; +using DCL.VoiceChat; +using ECS.Abstract; +using ECS.LifeCycle.Components; +using LiveKit.Rooms; +using LiveKit.Rooms.Participants; +using System; +using System.Collections.Generic; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Single source of mouth-animation input. Listens to chat (text) and voice-chat (LiveKit + /// active speakers + call status) and writes on the + /// matching avatar entity. Per ADR-317 lip-sync is local-only — events are interpreted by + /// each client and never propagated. + /// + /// Per-participant coalescing: many events for the same wallet within one frame collapse + /// to a single dict entry; remote application happens through a single query that drains + /// the dict by . Chat takes priority over voice — + /// handled downstream by AvatarFacialExpressionSystem.StepMouthAnimation which + /// consumes first; the voice loop + /// only resumes when no chat text is animating. + /// + /// + [UpdateInGroup(typeof(InputGroup))] + [LogCategory(ReportCategory.AVATAR)] + public partial class MouthAnimationSystem : BaseUnityLoopSystem + { + public struct PendingInput + { + public string? Message; // null when no chat update happened this frame + public bool? IsSpeaking; // null when no speaking change happened this frame + } + + private readonly IChatHistory chatHistory; + private readonly IRoom voiceChatRoom; + private readonly IDisposable statusSubscription; + + private PendingInput pendingSelf; + private readonly Dictionary pendingByWallet = new (); + + private readonly HashSet activeSpeakers = new (); + private readonly HashSet nextActiveSpeakers = new (); + + internal MouthAnimationSystem( + World world, + IChatHistory chatHistory, + IRoom voiceChatRoom, + IVoiceChatOrchestratorState voiceChatOrchestratorState) : base(world) + { + this.chatHistory = chatHistory; + this.voiceChatRoom = voiceChatRoom; + + chatHistory.MessageAdded += OnChatMessageAdded; + voiceChatRoom.ActiveSpeakers.Updated += OnActiveSpeakersUpdated; + voiceChatRoom.Participants.UpdatesFromParticipant += OnParticipantUpdated; + statusSubscription = voiceChatOrchestratorState.CurrentCallStatus.Subscribe(OnCallStatusChanged); + } + + protected override void OnDispose() + { + chatHistory.MessageAdded -= OnChatMessageAdded; + voiceChatRoom.ActiveSpeakers.Updated -= OnActiveSpeakersUpdated; + voiceChatRoom.Participants.UpdatesFromParticipant -= OnParticipantUpdated; + statusSubscription?.Dispose(); + } + + protected override void Update(float t) + { + if (pendingSelf.Message != null || pendingSelf.IsSpeaking.HasValue) + { + ApplyToSelfQuery(World, pendingSelf); + pendingSelf = default; + } + + if (pendingByWallet.Count > 0) + ApplyToRemoteQuery(World); + } + + [Query] + [All(typeof(PlayerComponent))] + private void ApplyToSelf([Data] PendingInput pending, ref AvatarMouthInputComponent input) + { + ApplyPending(pending, ref input); + } + + /// + /// Iterates remote avatar entities and consumes their pending input from + /// by . Entries with + /// no matching avatar entity stay in the dict and are retried next frame (covers the + /// race where chat/voice events arrive before the avatar is instantiated). + /// + [Query] + [All(typeof(AvatarMouthInputComponent), typeof(AvatarShapeComponent))] + [None(typeof(PlayerComponent), typeof(DeleteEntityIntention))] + private void ApplyToRemote(in AvatarShapeComponent avatarShape, ref AvatarMouthInputComponent input) + { + if (string.IsNullOrEmpty(avatarShape.ID)) return; + + if (!pendingByWallet.Remove(avatarShape.ID, out PendingInput pending)) + return; + + ApplyPending(pending, ref input); + } + + private static void ApplyPending(PendingInput pending, ref AvatarMouthInputComponent input) + { + if (pending.Message != null) + { + input.PendingMessage = pending.Message; + input.MessageIsDirty = true; + } + + if (pending.IsSpeaking.HasValue) + input.IsVoiceChatSpeaking = pending.IsSpeaking.Value; + } + + private void OnChatMessageAdded(ChatChannel _, ChatMessage msg, int __) + { + if (msg.IsSystemMessage) return; + + if (msg.IsSentByOwnUser) + { + pendingSelf.Message = msg.Message; + return; + } + + if (pendingByWallet.TryGetValue(msg.SenderWalletAddress, out PendingInput existing)) + { + existing.Message = msg.Message; + pendingByWallet[msg.SenderWalletAddress] = existing; + } + else + pendingByWallet[msg.SenderWalletAddress] = new PendingInput { Message = msg.Message }; + } + + private void StashSpeaking(string walletId, bool isSpeaking, bool isSelf) + { + if (isSelf) + { + pendingSelf.IsSpeaking = isSpeaking; + return; + } + + if (pendingByWallet.TryGetValue(walletId, out PendingInput existing)) + { + existing.IsSpeaking = isSpeaking; + pendingByWallet[walletId] = existing; + } + else + pendingByWallet[walletId] = new PendingInput { IsSpeaking = isSpeaking }; + } + + private void OnActiveSpeakersUpdated() + { + nextActiveSpeakers.Clear(); + + foreach (string speakerId in voiceChatRoom.ActiveSpeakers) + { + nextActiveSpeakers.Add(speakerId); + + if (!activeSpeakers.Contains(speakerId)) + EnqueueSpeaking(speakerId, true); + } + + foreach (string oldSpeakerId in activeSpeakers) + if (!nextActiveSpeakers.Contains(oldSpeakerId)) + EnqueueSpeaking(oldSpeakerId, false); + + activeSpeakers.Clear(); + activeSpeakers.UnionWith(nextActiveSpeakers); + + void EnqueueSpeaking(string participantId, bool isSpeaking) + { + bool isSelf = voiceChatRoom.Participants.LocalParticipant()?.Identity == participantId; + StashSpeaking(participantId, isSpeaking, isSelf); + } + } + + private void OnParticipantUpdated(LKParticipant participant, UpdateFromParticipant update) + { + if (update != UpdateFromParticipant.Disconnected) return; + + StashSpeaking(participant.Identity, false, isSelf: false); + activeSpeakers.Remove(participant.Identity); + } + + private void OnCallStatusChanged(VoiceChatStatus status) + { + switch (status) + { + case VoiceChatStatus.VOICE_CHAT_IN_CALL: + pendingSelf.IsSpeaking = false; + OnActiveSpeakersUpdated(); + break; + + case VoiceChatStatus.VOICE_CHAT_ENDING_CALL: + case VoiceChatStatus.DISCONNECTED: + case VoiceChatStatus.VOICE_CHAT_GENERIC_ERROR: + pendingSelf.IsSpeaking = false; + activeSpeakers.Clear(); + + foreach ((string participantId, _) in voiceChatRoom.Participants.RemoteParticipantIdentities()) + StashSpeaking(participantId, false, isSelf: false); + + break; + } + } + } +} diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs.meta new file mode 100644 index 00000000000..dd7ca7f4ea0 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/MouthAnimationSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8f3a4abcb44e2e4aac6a4cbae534626 \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs new file mode 100644 index 00000000000..3963df8f234 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs @@ -0,0 +1,74 @@ +using Arch.Core; +using Arch.SystemGroups; +using Arch.SystemGroups.DefaultSystemGroups; +using DCL.AvatarRendering.AvatarShape.Components; +using DCL.Diagnostics; +using DCL.Multiplayer.FacialExpression; +using DCL.Multiplayer.Movement.Systems; +using DCL.Multiplayer.Profiles.Tables; +using ECS.Abstract; +using System.Collections.Generic; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Drains intentions and applies them to the matching + /// remote avatar's . When the participant entity isn't + /// known yet (avatar still loading), the intention is requeued for the next frame. + /// Per ADR-317 face state is last-write-wins; no timestamp/order check. + /// + /// + /// TODO/WARNING — pending architect review. This system bypasses ECS and resolves the target + /// entity by walletId via instead of running a + /// query. Same pattern as RemoteEmotesSystem; performant but at odds with the + /// "systems are the sole entity manipulation entry point via queries" principle. Validate the + /// approach (or rework as a query-driven system) before merging. + /// + [UpdateInGroup(typeof(PresentationSystemGroup))] + [UpdateAfter(typeof(RemotePlayersMovementSystem))] + [LogCategory(ReportCategory.MULTIPLAYER_MOVEMENT)] + public partial class RemoteFacialExpressionSystem : BaseUnityLoopSystem + { + private readonly IReadOnlyEntityParticipantTable entityParticipantTable; + private readonly IFacialExpressionMessageBus bus; + private readonly List drained = new (); + + internal RemoteFacialExpressionSystem(World world, IReadOnlyEntityParticipantTable entityParticipantTable, IFacialExpressionMessageBus bus) : base(world) + { + this.entityParticipantTable = entityParticipantTable; + this.bus = bus; + } + + protected override void Update(float t) + { + bus.Drain(drained); + + if (drained.Count == 0) + return; + + foreach (RemoteFacialExpressionIntention intention in drained) + { + if (!entityParticipantTable.TryGet(intention.WalletId, out IReadOnlyEntityParticipantTable.Entry entry)) + { + bus.SaveForRetry(intention); + continue; + } + + ref AvatarFaceComponent face = ref World.TryGetRef(entry.Entity, out bool exists); + + if (!exists) + { + bus.SaveForRetry(intention); + continue; + } + + face.EyebrowsExpressionIndex = intention.EyebrowsIndex; + face.EyesExpressionIndex = intention.EyesIndex; + face.MouthExpressionIndex = intention.MouthIndex; + face.IsDirty = true; + } + + drained.Clear(); + } + } +} diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs.meta new file mode 100644 index 00000000000..f92b84cff8b --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/RemoteFacialExpressionSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 19962c707cae4184ba57b5bf7e05baef \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs new file mode 100644 index 00000000000..f0829091004 --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs @@ -0,0 +1,111 @@ +using Arch.Core; +using Arch.System; +using Arch.SystemGroups; +using DCL.AvatarRendering.AvatarShape.Components; +using DCL.AvatarRendering.AvatarShape.FacialExpression; +using DCL.Character.Components; +using DCL.Diagnostics; +using DCL.FacialExpressionsWheel; +using DCL.Input; +using ECS.Abstract; +using ECS.LifeCycle.Components; +using System; +using System.Collections.Generic; +using UnityEngine.InputSystem; +using InputAction = UnityEngine.InputSystem.InputAction; + +namespace DCL.AvatarRendering.AvatarShape +{ + /// + /// Listens to the FaceExpressions input map (Y+[0-9]) and writes a + /// on the local player. Notifies + /// so the upcoming Y release + /// doesn't toggle the wheel. + /// + [LogCategory(ReportCategory.AVATAR)] + [UpdateInGroup(typeof(InputGroup))] + public partial class UpdateFacialExpressionInputSystem : BaseUnityLoopSystem + { + private const int SLOT_COUNT = 10; + + private readonly AvatarFaceExpressionConfig config; + private readonly FacialExpressionsWheelShortcutHandler shortcutHandler; + private readonly DCLInput.FaceExpressionsActions actions; + private readonly Dictionary slotByActionName = new (); + + private int triggeredSlot = -1; + + internal UpdateFacialExpressionInputSystem( + World world, + AvatarFaceExpressionConfig config, + FacialExpressionsWheelShortcutHandler shortcutHandler) : base(world) + { + this.config = config; + this.shortcutHandler = shortcutHandler; + actions = DCLInput.Instance.FaceExpressions; + ListenToSlotsInput(actions.Get()); + } + + protected override void OnDispose() + { + UnregisterSlotsInput(actions.Get()); + } + + protected override void Update(float t) + { + if (triggeredSlot < 0) return; + + if (triggeredSlot < config.Expressions.Length) + { + ApplyFaceQuery(World, triggeredSlot); + shortcutHandler.NotifyExpressionPlayed(FacialExpressionTriggerSource.SHORTCUT); + } + + triggeredSlot = -1; + } + + [Query] + [All(typeof(PlayerComponent))] + [None(typeof(DeleteEntityIntention))] + private void ApplyFace([Data] int slot, in Entity entity) + { + ref AvatarFaceExpressionDefinition def = ref config.Expressions[slot]; + + World.AddOrGet(entity, new TriggerFacialExpressionIntent + { + EyebrowsIndex = (byte)def.EyebrowsIndex, + EyesIndex = (byte)def.EyesIndex, + MouthIndex = (byte)def.MouthIndex, + }); + } + + private void OnSlotPerformed(InputAction.CallbackContext ctx) => + triggeredSlot = slotByActionName[ctx.action.name]; + + private void ListenToSlotsInput(InputActionMap map) + { + for (var i = 0; i < SLOT_COUNT; i++) + { + string name = FacialExpressionWheelUtils.GetSlotActionName(i); + + try + { + InputAction action = map.FindAction(name); + action.started += OnSlotPerformed; + slotByActionName[name] = FacialExpressionWheelUtils.SlotIndexFromActionName(name); + } + catch (Exception e) { ReportHub.LogException(e, GetReportData()); } + } + } + + private void UnregisterSlotsInput(InputActionMap map) + { + for (var i = 0; i < SLOT_COUNT; i++) + { + string name = FacialExpressionWheelUtils.GetSlotActionName(i); + InputAction action = map.FindAction(name); + action.started -= OnSlotPerformed; + } + } + } +} diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs.meta b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs.meta new file mode 100644 index 00000000000..b537556dcdf --- /dev/null +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Systems/FacialExpression/UpdateFacialExpressionInputSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 398f540048a2ffa4b8ccdb75c2e9fb2a \ No newline at end of file diff --git a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/FakeWearable.cs b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/FakeWearable.cs index f1aca52db00..5c7d11a4478 100644 --- a/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/FakeWearable.cs +++ b/Explorer/Assets/DCL/AvatarRendering/AvatarShape/Tests/EditMode/FakeWearable.cs @@ -40,6 +40,8 @@ public void SetAmount(int amount) public WearableType Type { get; } + public bool HasFacialExpressionsTexture { get; set; } + public WearableAssets[] WearableAssetResults { get; } public FakeWearable( diff --git a/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/IWearable.cs b/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/IWearable.cs index 6124c62fa29..e94b1eeebd4 100644 --- a/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/IWearable.cs +++ b/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/IWearable.cs @@ -17,6 +17,12 @@ public interface IWearable : IAvatarAttachment, ITrimmedWearable { WearableType Type { get; } + /// + /// True when a facial-feature wearable ships an `*_expressions.png` atlas. Driven by + /// during promise creation. + /// + bool HasFacialExpressionsTexture { get; set; } + /// /// Per [MALE, FEMALE] /// diff --git a/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/Wearable.cs b/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/Wearable.cs index 2d1b6128712..974a8521809 100644 --- a/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/Wearable.cs +++ b/Explorer/Assets/DCL/AvatarRendering/Wearables/Components/Wearable.cs @@ -53,6 +53,8 @@ StreamableLoadingResult IAvatarAttachment.Model public WearableType Type { get; private set; } + public bool HasFacialExpressionsTexture { get; set; } + public bool IsLoading { get; private set; } public void UpdateLoadingStatus(bool isLoading) diff --git a/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/FacialFeaturesTextures.cs b/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/FacialFeaturesTextures.cs index eeb1cf3c6cb..c9f824ba768 100644 --- a/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/FacialFeaturesTextures.cs +++ b/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/FacialFeaturesTextures.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using UnityEngine; namespace DCL.AvatarRendering.Wearables.Helpers @@ -6,15 +6,27 @@ namespace DCL.AvatarRendering.Wearables.Helpers public readonly struct FacialFeaturesTextures { private readonly Dictionary> texturesByCategory; + private readonly HashSet categoriesWithExpressions; public IReadOnlyDictionary> Value => texturesByCategory; public FacialFeaturesTextures(Dictionary> value) { texturesByCategory = value; + categoriesWithExpressions = new HashSet(); } public Texture this[string category, int originalTextureId] => Value[category][originalTextureId]; + // Categories whose wearable carries an `*_expressions.png` atlas. Stamped during AppendToAvatar. + public void MarkHasExpressions(string category) => + categoriesWithExpressions.Add(category); + + public bool HasExpressions(string category) => + categoriesWithExpressions.Contains(category); + + public void ClearExpressionFlags() => + categoriesWithExpressions.Clear(); + public void CopyInto(ref FacialFeaturesTextures other) { var texturesByCategory = other.texturesByCategory; @@ -30,6 +42,10 @@ public void CopyInto(ref FacialFeaturesTextures other) foreach ((int textureId, Texture? texture) in existingTextures) newTextures[textureId] = texture; } + + other.categoriesWithExpressions.Clear(); + foreach (string c in categoriesWithExpressions) + other.categoriesWithExpressions.Add(c); } } } diff --git a/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/WearablePolymorphicBehaviour.cs b/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/WearablePolymorphicBehaviour.cs index 3652fe02df0..9a41ac2d391 100644 --- a/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/WearablePolymorphicBehaviour.cs +++ b/Explorer/Assets/DCL/AvatarRendering/Wearables/Helpers/WearablePolymorphicBehaviour.cs @@ -34,6 +34,13 @@ public static class WearablePolymorphicBehaviour public const int MAIN_ASSET_INDEX = 0; public const int MASK_ASSET_INDEX = 1; + // Wearables that ship a facial-expression atlas use these suffixes so the loader picks + // the matching main + mask pair instead of the legacy single-frame face texture. + // Example pair: `eyes_12_expressions.png` + `eyes_12_expressions_mask.png`. + private const string EXPRESSIONS_MAIN_FILE_SUFFIX = "_expressions.png"; + private const string EXPRESSIONS_MASK_FILE_SUFFIX = "_expressions_mask.png"; + private const string LEGACY_MASK_FILE_SUFFIX = "_mask.png"; + /// /// Create a certain number of Asset Promises based on the type of the wearable, /// if promises are already created does nothing and returns false @@ -86,7 +93,11 @@ public static bool TryCreateSingleGameObjectPromise( } /// - /// Facial feature can consist of the main texture and the mask + /// Facial feature can consist of the main texture and the mask. When the wearable + /// ships an `*_expressions.png` atlas (4×4 expression grid), the loader substitutes it + /// for the legacy single-frame texture and pairs the `*_expressions_mask.png` as mask. + /// The DTO's mainFile pointer is unchanged; the substitution is loader-side only and + /// flags so renderers can atlas-slice. /// private static bool TryCreateFacialFeaturePromises( in GetWearablesByPointersIntention intention, @@ -101,10 +112,77 @@ private static bool TryCreateFacialFeaturePromises( // 0 stands for the main texture // 1 stands for the mask - return TryCreateMainFilePromise(typeof(Texture), intention, customStreamingSubdirectory, wearable, partitionComponent, ref wearableAssets, bodyShape, world, reportData) + return TryCreateFacialFeatureMainPromise(in intention, customStreamingSubdirectory, wearable, partitionComponent, ref wearableAssets, bodyShape, world, reportData) | TryCreateMaskPromise(intention, customStreamingSubdirectory, wearable, partitionComponent, ref wearableAssets, bodyShape, world); } + private static bool TryCreateFacialFeatureMainPromise( + in GetWearablesByPointersIntention intention, + URLSubdirectory customStreamingSubdirectory, + IWearable wearable, + IPartitionComponent partitionComponent, + ref WearableAssets wearableAssets, + BodyShape bodyShape, + World world, + ReportData reportData) + { + if (wearableAssets.Results[MAIN_ASSET_INDEX] != null) + return false; + + bool hasExpressionsTexture = wearable.TryGetFileHashConditional(bodyShape, + static content => content.EndsWith(EXPRESSIONS_MAIN_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase), + out string? mainHash); + + // Fallback path: no `*_expressions.png` content entry, OR the wearable's declared + // mainFile itself is one (TryGetFileHashConditional skips the mainFile). + if (!hasExpressionsTexture) + { + if (!wearable.TryGetMainFileHash(bodyShape, out mainHash)) + { + wearableAssets.Results[MAIN_ASSET_INDEX] = + new StreamableLoadingResult(reportData, new Exception("Main file hash not found")); + return false; + } + + hasExpressionsTexture = TryGetMainFileKey(wearable, bodyShape, out string? mainFileKey) + && mainFileKey != null + && mainFileKey.EndsWith(EXPRESSIONS_MAIN_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase); + } + + wearable.HasFacialExpressionsTexture = hasExpressionsTexture; + + CreatePromise(typeof(Texture), intention, customStreamingSubdirectory, mainHash!, + wearable, MAIN_ASSET_INDEX, partitionComponent, world); + return true; + } + + private static bool TryGetMainFileKey(IAvatarAttachment attachment, BodyShape bodyShape, out string? key) + { + AvatarAttachmentDTO? dto = attachment.DTO; + + if (dto?.Metadata.AbstractData.representations != null) + { + AvatarAttachmentDTO.Representation[] representations = dto.Metadata.AbstractData.representations; + + for (var i = 0; i < representations.Length; i++) + { + AvatarAttachmentDTO.Representation representation = representations[i]; + string[] shapes = representation.bodyShapes; + + for (var j = 0; j < shapes.Length; j++) + { + if (shapes[j] != bodyShape) continue; + + key = representation.mainFile; + return true; + } + } + } + + key = null; + return false; + } + private static ref WearableAssets InitializeResultsArray(IWearable wearable, BodyShape bodyShape, int size) { if (wearable.IsUnisex() && wearable.HasSameModelsForAllGenders()) @@ -131,9 +209,15 @@ private static bool TryCreateMaskPromise( if (wearableAssets.Results[MASK_ASSET_INDEX] != null) return false; - if (!wearable.TryGetFileHashConditional(bodyShape, - static content => content.EndsWith("_mask.png", StringComparison.OrdinalIgnoreCase), - out string mainFileHash)) + // Expression-capable wearables pair their atlas with `*_expressions_mask.png`. Other + // wearables fall back to the legacy `*_mask.png` (excluding the expressions variant to + // avoid grabbing the wrong file when both pairs ship side-by-side). + Func contentMatch = wearable.HasFacialExpressionsTexture + ? static content => content.EndsWith(EXPRESSIONS_MASK_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase) + : static content => content.EndsWith(LEGACY_MASK_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase) + && !content.EndsWith(EXPRESSIONS_MASK_FILE_SUFFIX, StringComparison.OrdinalIgnoreCase); + + if (!wearable.TryGetFileHashConditional(bodyShape, contentMatch, out string mainFileHash)) { // If there is no mask, we don't need to create a promise for it, and it's not an exception wearableAssets.Results[MASK_ASSET_INDEX] = new StreamableLoadingResult((AttachmentTextureAsset)null); diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Backpack.asset b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Backpack.asset index e3ff1cbf08a..3f76a305577 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Backpack.asset +++ b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Backpack.asset @@ -39,7 +39,7 @@ MonoBehaviour: k__BackingField: 10 k__BackingField: {x: 27, y: 8} k__BackingField: 6 - k__BackingField: 0.2 + k__BackingField: 0.6 k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset new file mode 100644 index 00000000000..f47690af2f4 --- /dev/null +++ b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset @@ -0,0 +1,38 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d834b5e6adc04c359c4d6b299db0f5ae, type: 3} + m_Name: CharacterPreviewCameraSettings_FacialExpressionsWheel + m_EditorClassIdentifier: + k__BackingField: + k__BackingField: + - k__BackingField: {x: 0, y: 0.7, z: 0} + k__BackingField: 5 + k__BackingField: 4 + k__BackingField: 0 + k__BackingField: 0.3 + k__BackingField: -0.84 + k__BackingField: 0.5 + k__BackingField: 0 + k__BackingField: 1 + k__BackingField: 32 + k__BackingField: 10 + k__BackingField: {x: 40, y: 16} + k__BackingField: 6 + k__BackingField: 0 + k__BackingField: 1 + k__BackingField: 1 + k__BackingField: 1 + k__BackingField: + - inputAction: 1 + cursorSprite: {fileID: 21300000, guid: d34aedda08e64344091b34deada8211e, type: 3} + - inputAction: 0 + cursorSprite: {fileID: 21300000, guid: a0dd824ee2daadd46a4437a4e3145c9e, type: 3} diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset.meta b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset.meta new file mode 100644 index 00000000000..99d5dca3087 --- /dev/null +++ b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_FacialExpressionsWheel.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0d82ea8f478e4ca4ebef878b5167a684 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_LoadingScreen.asset b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_LoadingScreen.asset index 8a8f85bbf32..3138a40dec7 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_LoadingScreen.asset +++ b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_LoadingScreen.asset @@ -27,7 +27,7 @@ MonoBehaviour: k__BackingField: 10 k__BackingField: {x: 40, y: 16} k__BackingField: 6 - k__BackingField: 0.2 + k__BackingField: 0.6 k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Passport.asset b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Passport.asset index d3556c0c4f8..3f4dbf55924 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Passport.asset +++ b/Explorer/Assets/DCL/Character/CharacterPreview/Assets/CharacterPreviewCameraSettings_Passport.asset @@ -14,7 +14,7 @@ MonoBehaviour: m_EditorClassIdentifier: k__BackingField: k__BackingField: - - k__BackingField: {x: 0, y: -0.2, z: 0} + - k__BackingField: {x: 0, y: 0.17, z: 0} k__BackingField: 24 k__BackingField: 4 k__BackingField: 0 @@ -27,7 +27,7 @@ MonoBehaviour: k__BackingField: 10 k__BackingField: {x: 40, y: 16} k__BackingField: 6 - k__BackingField: 0.2 + k__BackingField: 0.6 k__BackingField: 1 k__BackingField: 1 k__BackingField: 1 diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs index 5db03cc24f6..c0929408866 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs +++ b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewAvatarContainer.cs @@ -18,11 +18,12 @@ public class CharacterPreviewAvatarContainer : MonoBehaviour, IDisposable private const float ANGULAR_VELOCITY_LOWER_THRES = 0.01f; private const float FOW_LOWER_THRES = 0.01f; private const float FOV_SPEED_COEFF = 3.5f; - private const float FOV_SMOOTH_TIME = 0.6f; + private const float FOV_SMOOTH_TIME_DEFAULT = 0.6f; private float fovTransitionStartTime; private float fovTransitionStartValue; private bool isFOVTransitioning; + private float fovSmoothDuration = FOV_SMOOTH_TIME_DEFAULT; [field: SerializeField] internal Vector3 previewPositionInScene { get; private set; } [field: SerializeField] internal Transform avatarParent { get; private set; } @@ -84,11 +85,23 @@ public void SetCameraPosition(CharacterPreviewCameraPreset preset) public void StartFOVTransition(float targetFOV) { TargetFOV = targetFOV; + + if (fovSmoothDuration <= 0f) + { + fovTransitionStartValue = targetFOV; + freeLookCamera.m_Lens.FieldOfView = targetFOV; + isFOVTransitioning = false; + return; + } + fovTransitionStartTime = Time.time; fovTransitionStartValue = freeLookCamera.m_Lens.FieldOfView; isFOVTransitioning = true; } + public void SetFOVSmoothDuration(float duration) => + fovSmoothDuration = duration > 0f ? duration : 0f; + public void SetPreviewPlatformActive(bool isActive) => previewPlatform.SetActive(isActive); @@ -155,7 +168,7 @@ private void UpdateFOV() { // Smooth transition for category changes float elapsedTime = Time.time - fovTransitionStartTime; - float normalizedTime = Mathf.Clamp01(elapsedTime / FOV_SMOOTH_TIME); + float normalizedTime = Mathf.Clamp01(elapsedTime / fovSmoothDuration); float easedTime = Mathf.SmoothStep(0f, 1f, normalizedTime); newFOV = Mathf.Lerp(fovTransitionStartValue, TargetFOV, easedTime); diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewCameraController.cs b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewCameraController.cs index 4019c03ee9c..e911ccbd3cb 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewCameraController.cs +++ b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewCameraController.cs @@ -26,6 +26,8 @@ public CharacterPreviewCameraController(CharacterPreviewInputEventBus characterP characterPreviewInputEventBus.OnScrollEvent += OnScroll; characterPreviewInputEventBus.OnChangePreviewFocusEvent += OnChangePreviewCategory; + characterPreviewAvatarContainer.SetFOVSmoothDuration(cameraSettings.fieldOfViewDuration); + OnChangePreviewCategory(AvatarWearableCategoryEnum.Body); } diff --git a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewController.cs b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewController.cs index 85ffefc06e1..3626d172d51 100644 --- a/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewController.cs +++ b/Explorer/Assets/DCL/Character/CharacterPreview/CharacterPreviewController.cs @@ -34,6 +34,8 @@ namespace DCL.CharacterPreview private readonly CharacterPreviewAvatarContainer characterPreviewAvatarContainer; private readonly IComponentPool characterPreviewContainerPool; private readonly Entity characterPreviewEntity; + + public Entity PreviewEntity => characterPreviewEntity; private readonly World globalWorld; private readonly bool builderEmotesPreview; @@ -163,6 +165,24 @@ public void ResetEmote() emoteComponent.Reset(); } + /// + /// Writes the resting facial expression layer on the preview avatar. No-ops until + /// has been attached by the rendering pipeline + /// (after wearable instantiation), so safe to call before the avatar has loaded. + /// + public void TrySetFace(int eyebrowsIndex, int eyesIndex, int mouthIndex) + { + if (!globalWorld.IsAlive(characterPreviewEntity)) return; + + ref AvatarFaceComponent face = ref globalWorld.TryGetRef(characterPreviewEntity, out bool exists); + if (!exists) return; + + face.EyebrowsExpressionIndex = eyebrowsIndex; + face.EyesExpressionIndex = eyesIndex; + face.MouthExpressionIndex = mouthIndex; + face.IsDirty = true; + } + public bool IsPlayingEmote() => globalWorld.TryGet(characterPreviewEntity, out CharacterEmoteComponent emoteComponent) && emoteComponent.IsPlayingEmote; diff --git a/Explorer/Assets/DCL/EmotesWheel/Assets/EmoteSlot.prefab b/Explorer/Assets/DCL/EmotesWheel/Assets/EmoteSlot.prefab index 6f9b54b6b68..ae32381e514 100644 --- a/Explorer/Assets/DCL/EmotesWheel/Assets/EmoteSlot.prefab +++ b/Explorer/Assets/DCL/EmotesWheel/Assets/EmoteSlot.prefab @@ -34,8 +34,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: 7} - m_SizeDelta: {x: 32, y: 32} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 174, y: 160} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &69245956534039968 CanvasRenderer: @@ -58,14 +58,89 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.8117647, g: 0.8039216, b: 0.83137256, a: 1} + m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 0 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: 0dcdab27d9530440f8dd0973134f8717, type: 3} + m_Sprite: {fileID: 21300000, guid: 3a3dee0180f4b47859cca03776a00e7c, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1923248890120471536 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3331880382409646134} + - component: {fileID: 2249465867215692124} + - component: {fileID: 8829078566679261275} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3331880382409646134 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1923248890120471536} + m_LocalRotation: {x: 0, y: 0, z: 0.10452846, w: 0.9945219} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 8974564133226285288} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 12} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 4.2, y: -18.2} + m_SizeDelta: {x: 18, y: 18} + m_Pivot: {x: 0, y: 1} +--- !u!222 &2249465867215692124 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1923248890120471536} + m_CullTransparentMesh: 1 +--- !u!114 &8829078566679261275 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1923248890120471536} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 9349f1fea510d448cbac392ca619d66e, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -110,7 +185,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 7} - m_SizeDelta: {x: 50, y: 50} + m_SizeDelta: {x: 90, y: 90} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &8209566919986367981 CanvasRenderer: @@ -184,8 +259,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0} m_AnchorMax: {x: 0.5, y: 0} - m_AnchoredPosition: {x: 0, y: 6.836853} - m_SizeDelta: {x: 15.6108, y: 15.6108} + m_AnchoredPosition: {x: 0, y: 8} + m_SizeDelta: {x: 20, y: 20} m_Pivot: {x: 0.5, y: 0} --- !u!222 &3925924027178008141 CanvasRenderer: @@ -242,8 +317,8 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 14 - m_fontSizeBase: 14 + m_fontSize: 18 + m_fontSizeBase: 18 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 10 @@ -253,6 +328,7 @@ MonoBehaviour: m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 + m_characterHorizontalScale: 1 m_wordSpacing: 0 m_lineSpacing: 0 m_lineSpacingMax: 0 @@ -320,8 +396,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.0707, y: -1.0678} - m_SizeDelta: {x: 112, y: 105} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 180, y: 168} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6732855126494316192 CanvasRenderer: @@ -351,7 +427,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 21300000, guid: d6787e389abeb437ead1ebad0a81a461, type: 3} + m_Sprite: {fileID: 21300000, guid: 2092305e118884e81bb75ffbb7109788, type: 3} m_Type: 0 m_PreserveAspect: 0 m_FillCenter: 1 @@ -396,8 +472,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: -1.0469} - m_SizeDelta: {x: 111, y: 103} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 174, y: 160} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6807344510761186898 CanvasRenderer: @@ -513,6 +589,7 @@ RectTransform: - {fileID: 5343966063347327097} - {fileID: 677941972702109446} - {fileID: 5294823707738111947} + - {fileID: 3331880382409646134} - {fileID: 4063225283202921380} - {fileID: 1176605725860819126} m_Father: {fileID: 1895304305689352378} @@ -520,7 +597,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 100, y: 100} + m_SizeDelta: {x: 174, y: 160} m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &8978849373333944373 GameObject: @@ -650,11 +727,11 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 1166888513132788191, guid: d565b61885fb1ef41b1582a285e748e9, type: 3} propertyPath: m_SizeDelta.x - value: 40 + value: 50 objectReference: {fileID: 0} - target: {fileID: 1166888513132788191, guid: d565b61885fb1ef41b1582a285e748e9, type: 3} propertyPath: m_SizeDelta.y - value: 40 + value: 50 objectReference: {fileID: 0} - target: {fileID: 1166888513132788191, guid: d565b61885fb1ef41b1582a285e748e9, type: 3} propertyPath: m_LocalPosition.x diff --git a/Explorer/Assets/DCL/EmotesWheel/Assets/EmotesWheelHUD.prefab b/Explorer/Assets/DCL/EmotesWheel/Assets/EmotesWheelHUD.prefab index b2fd7a5feca..7f52aba4de2 100644 --- a/Explorer/Assets/DCL/EmotesWheel/Assets/EmotesWheelHUD.prefab +++ b/Explorer/Assets/DCL/EmotesWheel/Assets/EmotesWheelHUD.prefab @@ -34,8 +34,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: -43.872906} - m_SizeDelta: {x: 156, y: 26.373} + m_AnchoredPosition: {x: 0, y: -50} + m_SizeDelta: {x: 190, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &3738596849537230179 CanvasRenderer: @@ -66,7 +66,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: [] m_text: Hold <#fcfcfc>[b+num] to run an emote while the wheel is - closed + closed. m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 35aa85d68d15435418848a03a2db81ec, type: 2} m_sharedMaterial: {fileID: 1701868249614554837, guid: 35aa85d68d15435418848a03a2db81ec, type: 2} @@ -75,8 +75,8 @@ MonoBehaviour: m_fontMaterials: [] m_fontColor32: serializedVersion: 2 - rgba: 4293782508 - m_fontColor: {r: 0.9254902, g: 0.92156863, b: 0.92941177, a: 1} + rgba: 4292136399 + m_fontColor: {r: 0.8117647, g: 0.8039216, b: 0.83137256, a: 1} m_enableVertexGradient: 0 m_colorMode: 3 m_fontColorGradient: @@ -93,8 +93,8 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 10 - m_fontSizeBase: 10 + m_fontSize: 12 + m_fontSizeBase: 12 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 10 @@ -104,6 +104,7 @@ MonoBehaviour: m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 + m_characterHorizontalScale: 1 m_wordSpacing: 0 m_lineSpacing: 0 m_lineSpacingMax: 0 @@ -167,13 +168,15 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 - m_Children: [] + m_Children: + - {fileID: 186355610268972269} + - {fileID: 7706739768243273297} m_Father: {fileID: 3368308072246989936} 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: 1.0523955} - m_SizeDelta: {x: 120, y: 124.4} + m_AnchoredPosition: {x: 0, y: -6} + m_SizeDelta: {x: 80, y: 32} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &374315793872558327 CanvasRenderer: @@ -196,14 +199,14 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 0} + m_Color: {r: 1, g: 1, b: 1, a: 0.29803923} m_RaycastTarget: 1 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_Sprite: {fileID: 0} + m_Sprite: {fileID: 21300000, guid: 1dcae7b3183f24eb39310ae8e4c092a5, type: 3} m_Type: 1 m_PreserveAspect: 0 m_FillCenter: 1 @@ -212,7 +215,7 @@ MonoBehaviour: m_FillClockwise: 1 m_FillOrigin: 0 m_UseSpriteMesh: 0 - m_PixelsPerUnitMultiplier: 1 + m_PixelsPerUnitMultiplier: 2.5 --- !u!114 &1341166584425063779 MonoBehaviour: m_ObjectHideFlags: 0 @@ -234,13 +237,13 @@ MonoBehaviour: m_SelectOnRight: {fileID: 0} m_Transition: 1 m_Colors: - m_NormalColor: {r: 1, g: 1, b: 1, a: 1} - m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} - m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_NormalColor: {r: 1, g: 1, b: 1, a: 0.019607844} + m_HighlightedColor: {r: 1, g: 1, b: 1, a: 0.050980393} + m_PressedColor: {r: 1, g: 1, b: 1, a: 0.019607844} + m_SelectedColor: {r: 1, g: 1, b: 1, a: 0.019607844} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 0.019607844} m_ColorMultiplier: 1 - m_FadeDuration: 0.1 + m_FadeDuration: 0 m_SpriteState: m_HighlightedSprite: {fileID: 0} m_PressedSprite: {fileID: 0} @@ -253,146 +256,10 @@ MonoBehaviour: m_SelectedTrigger: Selected m_DisabledTrigger: Disabled m_Interactable: 1 - m_TargetGraphic: {fileID: 50054618743786208} + m_TargetGraphic: {fileID: 3021313394658728330} m_OnClick: m_PersistentCalls: m_Calls: [] ---- !u!1 &1910573828672085254 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 7155408817035108562} - - component: {fileID: 9030177804243959839} - - component: {fileID: 8296175027738707069} - m_Layer: 5 - m_Name: Cutomise - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &7155408817035108562 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1910573828672085254} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 3368308072246989936} - 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: -8.47291} - m_SizeDelta: {x: 150, y: 16} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &9030177804243959839 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1910573828672085254} - m_CullTransparentMesh: 0 ---- !u!114 &8296175027738707069 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1910573828672085254} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_text: Customise <#fcfcfc>[E] - m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 35aa85d68d15435418848a03a2db81ec, type: 2} - m_sharedMaterial: {fileID: 1701868249614554837, guid: 35aa85d68d15435418848a03a2db81ec, type: 2} - m_fontSharedMaterials: [] - m_fontMaterial: {fileID: 0} - m_fontMaterials: [] - m_fontColor32: - serializedVersion: 2 - rgba: 4293782508 - m_fontColor: {r: 0.9254902, g: 0.92156863, b: 0.92941177, a: 1} - m_enableVertexGradient: 0 - m_colorMode: 3 - m_fontColorGradient: - topLeft: {r: 1, g: 1, b: 1, a: 1} - topRight: {r: 1, g: 1, b: 1, a: 1} - bottomLeft: {r: 1, g: 1, b: 1, a: 1} - bottomRight: {r: 1, g: 1, b: 1, a: 1} - m_fontColorGradientPreset: {fileID: 0} - m_spriteAsset: {fileID: 0} - m_tintAllSprites: 0 - m_StyleSheet: {fileID: 0} - m_TextStyleHashCode: -1183493901 - m_overrideHtmlColors: 0 - m_faceColor: - serializedVersion: 2 - rgba: 4294967295 - m_fontSize: 10 - m_fontSizeBase: 10 - m_fontWeight: 400 - m_enableAutoSizing: 0 - m_fontSizeMin: 10 - m_fontSizeMax: 12 - m_fontStyle: 0 - m_HorizontalAlignment: 2 - m_VerticalAlignment: 512 - m_textAlignment: 65535 - m_characterSpacing: 0 - m_wordSpacing: 0 - m_lineSpacing: 0 - m_lineSpacingMax: 0 - m_paragraphSpacing: 0 - m_charWidthMaxAdj: 0 - m_TextWrappingMode: 1 - m_wordWrappingRatios: 0.4 - m_overflowMode: 0 - m_linkedTextComponent: {fileID: 0} - parentLinkedComponent: {fileID: 0} - m_enableKerning: 1 - m_ActiveFontFeatures: 6e72656b - m_enableExtraPadding: 0 - checkPaddingRequired: 0 - m_isRichText: 1 - m_EmojiFallbackSupport: 1 - m_parseCtrlCharacters: 1 - m_isOrthographic: 1 - m_isCullingEnabled: 0 - m_horizontalMapping: 0 - m_verticalMapping: 0 - m_uvLineOffset: 0 - m_geometrySortingOrder: 0 - m_IsTextObjectScaleStatic: 0 - m_VertexBufferAutoSizeReduction: 1 - m_useMaxVisibleDescender: 1 - m_pageToDisplay: 1 - m_margin: {x: 0, y: 0, z: 0, w: 0} - m_isUsingLegacyAnimationComponent: 0 - m_isVolumetricText: 0 - m_hasFontAssetChanged: 0 - m_baseMaterial: {fileID: 0} - m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &2730020739912452495 GameObject: m_ObjectHideFlags: 0 @@ -427,8 +294,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: 48.1} - m_SizeDelta: {x: 118, y: 40} + m_AnchoredPosition: {x: 0, y: 60} + m_SizeDelta: {x: 240, y: 70} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &5735411371286715404 CanvasRenderer: @@ -485,17 +352,18 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 14 + m_fontSize: 18 m_fontSizeBase: 18 m_fontWeight: 400 m_enableAutoSizing: 1 m_fontSizeMin: 10 - m_fontSizeMax: 14 + m_fontSizeMax: 18 m_fontStyle: 0 m_HorizontalAlignment: 2 - m_VerticalAlignment: 512 + m_VerticalAlignment: 1024 m_textAlignment: 65535 m_characterSpacing: 0 + m_characterHorizontalScale: 1 m_wordSpacing: 0 m_lineSpacing: 0 m_lineSpacingMax: 0 @@ -590,6 +458,7 @@ MonoBehaviour: - {fileID: 5066361607108311281} - {fileID: 8782135904965761850} k__BackingField: {fileID: 1341166584425063779} + k__BackingField: {fileID: 2299642542487610113} k__BackingField: - {fileID: 4437412575776298108} - {fileID: 6804220589846975022} @@ -623,6 +492,7 @@ Canvas: m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 m_VertexColorAlwaysGammaSpace: 0 + m_UseReflectionProbes: 0 m_AdditionalShaderChannelsFlag: 25 m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 @@ -668,7 +538,7 @@ MonoBehaviour: m_UiScaleMode: 1 m_ReferencePixelsPerUnit: 100 m_ScaleFactor: 1 - m_ReferenceResolution: {x: 1536, y: 864} + m_ReferenceResolution: {x: 1920, y: 1080} m_ScreenMatchMode: 0 m_MatchWidthOrHeight: 0 m_PhysicalUnit: 3 @@ -745,7 +615,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 400, y: 400} + m_SizeDelta: {x: 680, y: 680} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &5950188987537430702 CanvasRenderer: @@ -785,7 +655,7 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 1 ---- !u!1 &3967867082645134450 +--- !u!1 &3932073865174807102 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -793,125 +663,50 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 6387641994105437130} - - component: {fileID: 6428647394411284262} - - component: {fileID: 6467756172435730145} + - component: {fileID: 4794220686679903806} + - component: {fileID: 8505923806040835807} + - component: {fileID: 2369410462101712670} m_Layer: 5 - m_Name: Image + m_Name: Text m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &6387641994105437130 +--- !u!224 &4794220686679903806 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3967867082645134450} + m_GameObject: {fileID: 3932073865174807102} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 2445375703582184909} + m_Father: {fileID: 623529305124580425} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: -18, y: -18} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &6428647394411284262 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3967867082645134450} - m_CullTransparentMesh: 0 ---- !u!114 &6467756172435730145 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3967867082645134450} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 0 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Sprite: {fileID: 21300000, guid: a4df2165befde144db1abdaf69cece34, type: 3} - m_Type: 0 - m_PreserveAspect: 0 - m_FillCenter: 1 - m_FillMethod: 4 - m_FillAmount: 1 - m_FillClockwise: 1 - m_FillOrigin: 0 - m_UseSpriteMesh: 0 - m_PixelsPerUnitMultiplier: 1 ---- !u!1 &4979140711266332923 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 7706739768243273297} - - component: {fileID: 4081166362944202792} - - component: {fileID: 5782514664531182739} - m_Layer: 5 - m_Name: Title - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &7706739768243273297 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4979140711266332923} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 1 - m_Children: [] - m_Father: {fileID: 3368308072246989936} - 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: 6.9270954} - m_SizeDelta: {x: 164, y: 17.4034} + m_AnchoredPosition: {x: -0.0001449585, y: -0.00005531311} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &4081166362944202792 +--- !u!222 &8505923806040835807 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4979140711266332923} + m_GameObject: {fileID: 3932073865174807102} m_CullTransparentMesh: 0 ---- !u!114 &5782514664531182739 +--- !u!114 &2369410462101712670 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4979140711266332923} + m_GameObject: {fileID: 3932073865174807102} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -925,7 +720,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: Emotes + m_text: 'Facial Expressions [Y] ' m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} m_sharedMaterial: {fileID: 735423033564544980, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} @@ -934,8 +729,8 @@ MonoBehaviour: m_fontMaterials: [] m_fontColor32: serializedVersion: 2 - rgba: 4294769916 - m_fontColor: {r: 0.9882353, g: 0.9882353, b: 0.9882353, a: 1} + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} m_enableVertexGradient: 0 m_colorMode: 3 m_fontColorGradient: @@ -952,17 +747,18 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 12 - m_fontSizeBase: 12 + m_fontSize: 18 + m_fontSizeBase: 18 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 10 - m_fontSizeMax: 14 - m_fontStyle: 16 + m_fontSizeMax: 12 + m_fontStyle: 0 m_HorizontalAlignment: 2 m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 + m_characterHorizontalScale: 1 m_wordSpacing: 0 m_lineSpacing: 0 m_lineSpacingMax: 0 @@ -996,7 +792,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &6941404510284847514 +--- !u!1 &3967867082645134450 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -1004,54 +800,897 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 2445375703582184909} - - component: {fileID: 5505085621591519339} - - component: {fileID: 8746213363341550890} - - component: {fileID: 8782135904965761850} - - component: {fileID: 6630055616766752555} - - component: {fileID: 1479204841276357964} + - component: {fileID: 6387641994105437130} + - component: {fileID: 6428647394411284262} + - component: {fileID: 6467756172435730145} m_Layer: 5 - m_Name: CloseButton + m_Name: Image m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &2445375703582184909 +--- !u!224 &6387641994105437130 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 6941404510284847514} + m_GameObject: {fileID: 3967867082645134450} m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 1 - m_Children: - - {fileID: 6387641994105437130} - m_Father: {fileID: 3368308072246989936} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2445375703582184909} 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: 166.7, y: 147} - m_SizeDelta: {x: 28, y: 28} - m_Pivot: {x: 0.5, y: 0.5} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -24, y: -24} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6428647394411284262 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3967867082645134450} + m_CullTransparentMesh: 0 +--- !u!114 &6467756172435730145 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3967867082645134450} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: f49bfce399a8c41568bf017a951a357a, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &4979140711266332923 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7706739768243273297} + - component: {fileID: 4081166362944202792} + - component: {fileID: 5782514664531182739} + m_Layer: 5 + m_Name: Title + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7706739768243273297 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4979140711266332923} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: [] + m_Father: {fileID: 6092354033751835054} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4081166362944202792 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4979140711266332923} + m_CullTransparentMesh: 0 +--- !u!114 &5782514664531182739 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4979140711266332923} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: Edit <#716B7C>[E] + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} + m_sharedMaterial: {fileID: 735423033564544980, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294769916 + m_fontColor: {r: 0.9882353, g: 0.9882353, b: 0.9882353, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 12 + m_fontSizeBase: 12 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 10 + m_fontSizeMax: 14 + m_fontStyle: 16 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 1 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &5739133538321435758 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6023903367326146379} + - component: {fileID: 3926131445273152610} + - component: {fileID: 7088067592052326316} + - component: {fileID: 7106644198525987485} + m_Layer: 5 + m_Name: EmotesButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6023903367326146379 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5739133538321435758} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 3425933121361192302} + m_Father: {fileID: 844836543111741865} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 6, y: 0} + m_SizeDelta: {x: 120, y: 36} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &3926131445273152610 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5739133538321435758} + m_CullTransparentMesh: 1 +--- !u!114 &7088067592052326316 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5739133538321435758} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0.45490196, b: 0.22352941, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 12dd1efc4e826764f9b02be515a9a033, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 2 +--- !u!114 &7106644198525987485 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5739133538321435758} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.8867924, g: 0.8867924, b: 0.8867924, a: 1} + m_PressedColor: {r: 1, g: 1, b: 1, a: 1} + m_SelectedColor: {r: 1, g: 1, b: 1, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 7088067592052326316} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &5873845419881500616 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 844836543111741865} + - component: {fileID: 7835879697316485249} + - component: {fileID: 5040959464859691122} + m_Layer: 5 + m_Name: Buttons + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &844836543111741865 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5873845419881500616} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6023903367326146379} + - {fileID: 623529305124580425} + - {fileID: 5444716907913731492} + m_Father: {fileID: 3368308072246989936} + 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: 370} + m_SizeDelta: {x: 397, y: 48} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7835879697316485249 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5873845419881500616} + m_CullTransparentMesh: 1 +--- !u!114 &5040959464859691122 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5873845419881500616} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 0.9490196} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 12dd1efc4e826764f9b02be515a9a033, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1.5 +--- !u!1 &6662805588403719236 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3425933121361192302} + - component: {fileID: 2754684901851011965} + - component: {fileID: 3992472208725122564} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3425933121361192302 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6662805588403719236} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6023903367326146379} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -0.00016784668, y: -0.00005531311} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2754684901851011965 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6662805588403719236} + m_CullTransparentMesh: 0 +--- !u!114 &3992472208725122564 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6662805588403719236} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_text: 'Emotes [B] ' + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} + m_sharedMaterial: {fileID: 735423033564544980, guid: 96ae0a2159a39234f858ea23bdcc74ad, type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 18 + m_fontSizeBase: 18 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 10 + m_fontSizeMax: 12 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_characterHorizontalScale: 1 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_TextWrappingMode: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 0 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_ActiveFontFeatures: 6e72656b + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_EmojiFallbackSupport: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 1 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!1 &6941404510284847514 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2445375703582184909} + - component: {fileID: 5505085621591519339} + - component: {fileID: 8746213363341550890} + - component: {fileID: 8782135904965761850} + - component: {fileID: 6630055616766752555} + - component: {fileID: 1479204841276357964} + m_Layer: 5 + m_Name: CloseButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &2445375703582184909 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6941404510284847514} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 1 + m_Children: + - {fileID: 6387641994105437130} + m_Father: {fileID: 3368308072246989936} + 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: 300, y: 226} + m_SizeDelta: {x: 36, y: 36} + m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &5505085621591519339 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 6941404510284847514} - m_CullTransparentMesh: 0 ---- !u!114 &8746213363341550890 + m_GameObject: {fileID: 6941404510284847514} + m_CullTransparentMesh: 0 +--- !u!114 &8746213363341550890 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6941404510284847514} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 12dd1efc4e826764f9b02be515a9a033, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 2.5 +--- !u!114 &8782135904965761850 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6941404510284847514} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 0.08627451, g: 0.08235294, b: 0.09411765, a: 1} + m_HighlightedColor: {r: 0.2627451, g: 0.2509804, b: 0.2901961, a: 1} + m_PressedColor: {r: 0, g: 0, b: 0, a: 1} + m_SelectedColor: {r: 0.08627451, g: 0.08235294, b: 0.09411765, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 8746213363341550890} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!95 &6630055616766752555 +Animator: + serializedVersion: 7 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6941404510284847514} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 9100000, guid: c22edc5eaba53f342bc161d6cbd411de, type: 2} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_AnimatePhysics: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &1479204841276357964 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6941404510284847514} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6a66e979ca644f3d91fd0980c5cd5f5f, type: 3} + m_Name: + m_EditorClassIdentifier: +