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
IFingerCurler
interface. - Developers, see below for more information on
IFingerCurler
- All Avatars now have their fingers tracked from the current
- Desktop Crouching
- Hit
C
to crouch
- Hit
- Desktop Crawling
- Hit
X
to 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
UnityVideoPlayer
as an integration ofIVideoPlayer
- This will be expanded to add support for other video players in the future
- Added
IGestureIdentifier
for creating custom Gesture numbers- By default, these are the current implementations
HypernexGesture
CGesture
VGesture
- 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
IFingerCurler
for 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
ICustomFaceExpression
for creating custom Facial Expressions fromVRCFaceTracking
data- For example, there are three implementations
CombinedEyeLid
TongueX
TongueY
- 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
SmoothFloat
for smoothening a float value- See API changes for more information
- Reworked
FingerCalibration
for the new system of Finger Curling. - Added ability for
AnimatorCreator.SetParameter<T>(string, T, CustomPlayableAnimator, bool, bool)
to set parameters in theMainAnimator
- Created
RotationOffsetDriver
to handle offset rotations forAvatarCreator
s.- 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
IsTracking
when 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