2024.05.1b
Huge update once again, this time, focusing more on the user frontend experience and heavily refactoring the backend for developers to extend support for their devices/software. Finger tracking, new animator, Facial Tracking API additions, desktop gestures, better gestures, VideoPlayer API, invite requests, and so much more!
Changes
- Added Finger Tracking
- All Avatars now have their fingers tracked from the current 
IFingerCurlerinterface. - Developers, see below for more information on 
IFingerCurler 
 - All Avatars now have their fingers tracked from the current 
 - Desktop Crouching
- Hit 
Cto crouch 
 - Hit 
 - Desktop Crawling
- Hit 
Xto crawl 
 - Hit 
 - New default Animation/Animators
- This new Animator includes the following animations
- Walk
- Walk Forward
 - Walk Backward
 - Walk Left
 - Walk Right
 
 - Run
- Run Forward
 - Run Backward
 - Run Left
 - Run Right
 
 - Crouch
- Crouch Walk
 - Crouch Walk Left
 - Crouch Walk Right
 
 - Lay Down
- Crawl
 
 - Jump
 - Falling
- Falling Idle
 
 - Left Hand Finger Curling
 - Right Hand Finger Curling
 
 - Walk
 
 - This new Animator includes the following animations
 - Desktop Finger Tracking
- Hold Left Shift for the Left Hand
 - Hold Right Shift for the Right Hand
 - While holding a Shift, hit a number from 1-9 to activate a Gesture in order of its properties, as listed below:
- Fist (1), OpenHand (2), Point (3), Peace (4), OkHand (5), RockAndRoll (6), Gun (7), ThumbsUp (8)
 - The current GestureIdentifier does not change the numbers hit, but will change what number the gesture returns
 
 
 - Added Invite Requests
- If your friend is in a world and not on DoNotDisturb status, you will see a Request button on their profile. Upon selecting the Request button, an Invite Request will be sent to the user.
 - The user you sent the invite request to can decide to accept the invite request
 
 - Added Badge Implementation with 
BadgeRankHandler- See API changes for more information
 
 - Added 
UnityVideoPlayeras an integration ofIVideoPlayer- This will be expanded to add support for other video players in the future
 
 - Added 
IGestureIdentifierfor creating custom Gesture numbers- By default, these are the current implementations
HypernexGestureCGestureVGesture
 - You can change which GestureIdentifier you are using in the User tab in Settings
 - See API changes for more information
 
 - By default, these are the current implementations
 - Added 
IFingerCurlerfor creating custom Finger Curlers- By default, there are three implementations
DesktopFingerCurler.Left- For Desktop, Left Hand
 
DesktopFingerCurler.Right- For Desktop, Right Hand
 
HandGetter- For VR
 
 - See API changes for more information
 
 - By default, there are three implementations
 - Added 
ICustomFaceExpressionfor creating custom Facial Expressions fromVRCFaceTrackingdata- For example, there are three implementations
CombinedEyeLidTongueXTongueY
 - See API changes for more information
 
 - For example, there are three implementations
 - Smoothened networked weights
- This makes weight changed feel smoother, without ruining the quality of the value
 
 - Added 
SmoothFloatfor smoothening a float value- See API changes for more information
 
 - Reworked 
FingerCalibrationfor the new system of Finger Curling. - Added ability for 
AnimatorCreator.SetParameter<T>(string, T, CustomPlayableAnimator, bool, bool)to set parameters in theMainAnimator - Created 
RotationOffsetDriverto handle offset rotations forAvatarCreators.- See API changes for more information
 
 - All HandleCameras are now destroyed upon leaving or joining an instance
- This is a temporary addition until the planned Cameras menu is added
 
 - Fixed a bug where FullBody-Tracking would not work
 - Fixed a bug where FullBody Trackers would not be registered as 
IsTrackingwhen they were tracking- This fix is mostly a hack, so if you see a tracker dot at center not tracking, just wait a couple of seconds before it disappears.
 
 - Fixed bug where a NetPlayer's hips would not move or rotate when using Full-Body Tracking
 - Fixed bug where you could not create more than one HandleCamera
 
API Changes
BadgeRankHandler
The new BadgeRankHandler allows publishers to edit nameplates to handle Ranks for Badges. Here is an example of a verified badge being added to users with the verified rank.
BadgeRankHandler MUST have a public, parameter-less constructor, otherwise the base class will throw an exception!
using System.Linq;
using Hypernex.UI.Templates;
using HypernexSharp.APIObjects;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class VerifiedRank : BadgeRankHandler
{
    // This is for ranks only
    public override bool IsRank => true;
    // These are the ranks this will Apply to
    public override Rank[] TargetRanks => new[]{Rank.Verified, Rank.Moderator, Rank.Admin, Rank.Owner};
    public override void ApplyToNameplate(NameplateTemplate nameplateTemplate, User targetUser)
    {
        // We store any assets in the Init.BadgeRankAssets list of Objects, then just cast it to the type it is
        Texture2D verifiedTexture = (Texture2D) Init.Instance.BadgeRankAssets.ElementAt(2);
        // Working off of the status image, because it already has positioning and sizing done
        RectTransform dup = Object.Instantiate(nameplateTemplate.Status).GetComponent<RectTransform>();
        // Reparent if it isn't
        dup.SetParent(nameplateTemplate.Status.transform.parent, false);
        // Set its name
        dup.gameObject.name = "ModeratorIcon";
        // Move it up to the right-corner of the pfp
        dup.anchoredPosition3D = new Vector3(dup.anchoredPosition3D.x, dup.anchoredPosition3D.y + 82f, 0);
        // Get the image component
        Image img = dup.gameObject.GetComponent<Image>();
        // Reset the color, since the Status icon changes colors
        img.color = Color.white;
        // Create a sprite from the texture, and set the image to that sprite
        img.sprite = Sprite.Create(verifiedTexture, new Rect(0f, 0f, verifiedTexture.width, verifiedTexture.height),
            Vector2.zero);
    }
}
Here is an example that will add an icon if you have a specific badge and are verified rank.
using System.Linq;
using Hypernex.UI.Templates;
using HypernexSharp.APIObjects;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class VerifiedRankLeafBadge : BadgeRankHandler
{
    // While this requires a rank, we want this at false since its dependent on a badge
    // You will also want your rank handler (if present) to check and see if you have this badge
    // If you do have the badge, make sure ApplyToNameplate in the rank handler does not run
    public override bool IsRank => false;
    // Name of the badge (caps sensitive)
    public override string Name => "leaf";
    private readonly Color verifiedLeafColor = new(70f/255f, 180f/255f, 48f/255f);
    public override void ApplyToNameplate(NameplateTemplate nameplateTemplate, User targetUser)
    {
        // Don't run if the user is not Verified
        if(targetUser.Rank < Rank.Verified) return;
        // Get the texture from the Init.BadgeRankAssets field
        Texture2D verifiedTexture = (Texture2D) Init.Instance.BadgeRankAssets.ElementAt(3);
        // Duplicate the status icon
        RectTransform dup = Object.Instantiate(nameplateTemplate.Status).GetComponent<RectTransform>();
        dup.SetParent(nameplateTemplate.Status.transform.parent, false);
        dup.gameObject.name = "VerifiedLeafIcon";
        // Position the icon to the top right of the pfp
        dup.anchoredPosition3D = new Vector3(dup.anchoredPosition3D.x, dup.anchoredPosition3D.y + 82f, 0);
        Image img = dup.gameObject.GetComponent<Image>();
        img.color = Color.white;
        img.sprite = Sprite.Create(verifiedTexture, new Rect(0f, 0f, verifiedTexture.width, verifiedTexture.height),
            Vector2.zero);
        // Set the color of the username text to the color defined above
        nameplateTemplate.Username.color = verifiedLeafColor;
    }
}
IVideoPlayer
A new interface used to define support for VideoPlayer backends. Currently, this is only used to implement the already supported Unity VideoPlayer, but the vision is to add support for Virtual's FFmpeg.Unity and possibly vlc-unity (if I can ever figure out how to build it 😭)
You can see an example implementation of the UnityVideoPlayer here
IGestureIdentifier
This interface was designed to be easily serializable and instantiable by developers (especially for plugins). It allows you to drive custom integer values for the GestureLeft and GestureRight avatar parameters. Here is an example implementation of Hypernex's gestures.
public class HypernexGesture : IGestureIdentifier
{
    public string Name => "Hypernex";
    public int Unknown => 0;
    public int Fist => 1;
    public int OpenHand => 2;
    public int Point => 3;
    public int Peace => 4;
    public int OkHand => 5;
    public int RockAndRoll => 6;
    public int Gun => 7;
    public int ThumbsUp => 8;
}
The extra Name property is used to identify the GestureIdentifier for Config values. Be sure to not have conflicting names!
IFingerCurler
This interface was specifically designed for developers to implement their own hardware via. plugins. It allows developers to drive the finger curl values for each hand. The hand is identified with the Hand property as either Left or Right. All of the float properties are values from 0 to 1 describing how much the finger is curled, where 0 is not curled and 1 is completely curled; however, if you need the values to describe something different, override the IsCurled method. Here is a bare-bones implementation of the interface:
public class MyRandomFingerCurler : IFingerCurler
{
    public Hand Hand { get; private set; }
    private Random random;
    public MyRandomFingerCurler(bool isRight = false)
    {
        Hand = isRight ? Hand.Right : Hand.Left;
        random = new Random();
    }
    public MyRandomFingerCurler(int seed, bool isRight = false)
    {
        Hand = isRight ? Hand.Right : Hand.Left;
        random = new Random(seed);
    }
    private float RandomFloat() => (float) random.NextDouble();
    public float ThumbCurl => RandomFloat();
    public float IndexCurl => RandomFloat();
    public float MiddleCurl => RandomFloat();
    public float RingCurl => RandomFloat();
    public float PinkyCurl => RandomFloat();
}
This class will register a random finger curler, so each time the Curl value is accessed, a random value between 0 and 1 will be returned.
ICustomFaceExpression
This interface is once again designed for developers to implement custom facial expressions with VRCFaceTracking's UnifiedTrackingData. Eventually, it will be what implements all of v2 parameters, since I can't figure out how IParameters work without OSC and because it allows greater control for parameters.
Here is an example implementation of combining eye lids:
public class CombinedEyeLid : ICustomFaceExpression
{
    public string Name => "CombinedEyeLid";
    public float GetWeight(UnifiedTrackingData data) => (data.Eye.Left.Openness + data.Eye.Right.Openness) / 2;
}
SmoothFloat
SmoothFloat is a new tool that allows float values to be smoothly moved from value A to value B. The example below demonstrates how to use a SmoothFloat for a weight.
SmoothFloats are NOT thread-safe. Do not touch them outside of the main thread.
public class MySmoothWeightHandler
{
    private SmoothFloat smoothFloat = new SmoothFloat();
    public MySmoothWeightHandler(Action<float> onSmoothWeight) => onSmoothWeight += OnWeightValue;
    // This will be smoothened
    public void GetValue() => smoothFloat.Value;
    void OnWeightValue(float value) => smoothFloat.Value = value;
}
RotationOffsetDriver
The RotationOffsetDriver is a tool that will allow transforms to be rotated regardless of the bone's tracking space. This is useful for avatars that were ported from other engines that do not follow Unity's tracking space.
You can see how they are implemented for Head tracking on avatars here