Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bf735c77ff4b1804598c47717e221c94
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1 @@
# ClientSim Overview

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e8e14c10c546a5343b67b9effaabe261
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,50 @@
using UnityEditor;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim.Editor
{
/// <summary>
/// This class is used to link any editor specific method to a runtime event hook.
/// Currently only used for the Pause Menu to be able to open the Settings Window
/// and to check for proper project settings.
/// </summary>
public static class ClientSimEditorRuntimeLinker
{
private static void ModeStateChanged(PlayModeStateChange state)
{
if(state == PlayModeStateChange.EnteredPlayMode)
{
ClientSimMenu.openSettingsHook += ClientSimSettingsWindow.Init;
ClientSimMenu.checkValidSettingsHook += ClientSimProjectSettingsSetup.IsUsingCorrectSettings;
}
// On exiting playmode, remove the Editor method hooks.
if (state == PlayModeStateChange.ExitingPlayMode)
{
ClientSimMenu.openSettingsHook -= ClientSimSettingsWindow.Init;
ClientSimMenu.checkValidSettingsHook -= ClientSimProjectSettingsSetup.IsUsingCorrectSettings;
}
if (state == PlayModeStateChange.ExitingEditMode)
{
InitializeScene();
}
}
// When entering playmode, set Editor method hooks.
// Using this runtime initialized method due to timing issues with with Domain Reloading on and off.
[InitializeOnLoadMethod]
private static void OnProjectLoadedInEditor()
{
EditorApplication.playModeStateChanged += ModeStateChanged;
}
private static void InitializeScene()
{
ClientSimNetworkingUtilities.DoSceneSetup();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2a0f4918cb6411b912ba5d1dd9e43fd
timeCreated: 1638825412

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 99e3ecdd7a73ba74a86916756ce43205
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,73 @@
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements;
namespace VRC.SDK3.ClientSim.Editor
{
#if VRC_ENABLE_PLAYER_PERSISTENCE
[UnityEditor.CustomEditor(typeof(ClientSimNetworkIdHolder))]
public class ClientSimNetworkIdHolderEditor : UnityEditor.Editor
{
private VisualElement _List;
private IClientSimEventDispatcher _eventDispatcher;
public void OnEnable()
{
if (!ClientSimMain.TryGetInstance(out var instance)) return;
_eventDispatcher = instance.GetEventDispatcher();
_eventDispatcher.Subscribe<ClientSimOnPlayerObjectUpdatedEvent>(OnPlayerObjectUpdated);
}
public override VisualElement CreateInspectorGUI()
{
VisualElement root = new VisualElement();
ClientSimNetworkIdHolder networkIdHolder = (ClientSimNetworkIdHolder) target;
if (networkIdHolder._components != null)
{
root.Add(new Label("Network Id: " + networkIdHolder._networkId.GetNetworkId()));
root.Add(new Label("Network Components"));
_List = new VisualElement();
for (int i = 0; i < networkIdHolder._components.Count; i++)
{
if (ClientSimNetworkHolderInstanceElement.encodeDecoders.TryGetValue(networkIdHolder._components[i].GetType().FullName, out var encodeDecoder))
{
MonoBehaviour component = networkIdHolder._components[i];
if(networkIdHolder._data.Count > i)
_List.Add(encodeDecoder.GenerateFields(component,networkIdHolder._data[i].DataDictionary));
}
}
root.Add(_List);
}
return root;
}
private void OnPlayerObjectUpdated(ClientSimOnPlayerObjectUpdatedEvent e)
{
ClientSimNetworkIdHolder networkIdHolder = (ClientSimNetworkIdHolder) target;
if ((ClientSimNetworkIdHolder)e.Data != networkIdHolder) return;
if (networkIdHolder._components != null)
{
for (int i = 0; i < networkIdHolder._components.Count; i++)
{
if (ClientSimNetworkHolderInstanceElement.encodeDecoders.TryGetValue(networkIdHolder._components[i].GetType().FullName, out var encodeDecoder))
{
MonoBehaviour component = networkIdHolder._components[i];
if(networkIdHolder._data.Count > i)
encodeDecoder.UpdateFields(component,_List[i],networkIdHolder._data[i].DataDictionary);
}
}
}
}
public void OnDisable()
{
_eventDispatcher?.Unsubscribe<ClientSimOnPlayerObjectUpdatedEvent>(OnPlayerObjectUpdated);
}
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ce7f02c154a942e6b63d5e78ae579eac
timeCreated: 1716481811

View File

@ -0,0 +1,15 @@
using UnityEditor;
namespace VRC.SDK3.ClientSim.Editor
{
[CustomEditor(typeof(ClientSimObjectPoolHelper))]
public class ClientSimObjectPoolHelperEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
ClientSimSyncableEditorHelper.DisplaySyncOptions(target as ClientSimObjectPoolHelper);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 656208559eae27245bd3efa317c971ec
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,15 @@
using UnityEditor;
namespace VRC.SDK3.ClientSim.Editor
{
[CustomEditor(typeof(ClientSimObjectSyncHelper))]
public class ClientSimObjectSyncHelperEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
ClientSimSyncableEditorHelper.DisplaySyncOptions(target as ClientSimObjectSyncHelper);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6279c2de63b5b040bc9d4c9c52558cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
using UnityEditor;
namespace VRC.SDK3.ClientSim.Editor
{
[CustomEditor(typeof(ClientSimSpatialAudioHelper))]
public class ClientSimSpatialAudioHelperEditor : UnityEditor.Editor
{
public override void OnInspectorGUI()
{
// Show nothing
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5c1ea3d6c56287f4f96ffe530c5180bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim.Editor
{
public static class ClientSimSyncableEditorHelper
{
public static void DisplaySyncOptions(IClientSimSyncable syncable)
{
int currentOwner = 0;
List<VRCPlayerApi> players = VRCPlayerApi.AllPlayers;
string[] playerNames = new string[players.Count];
for (int i = 0; i < players.Count; ++i)
{
if (players[i].playerId == syncable.GetOwner())
{
currentOwner = i;
}
playerNames[i] = players[i].displayName;
}
int owner = EditorGUILayout.Popup("Set Owner", currentOwner, playerNames);
if (owner != currentOwner)
{
Networking.SetOwner(players[owner], (syncable as MonoBehaviour).gameObject);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dac4b19c793b4b04989e17f3979de4eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,88 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using VRC.Udon;
using VRC.Udon.Editor.ProgramSources;
namespace VRC.SDK3.ClientSim.Editor
{
[CustomEditor(typeof(ClientSimUdonHelper))]
public class ClientSimUdonHelperEditor : UnityEditor.Editor
{
private static readonly MethodInfo _drawPropertyMethod;
private bool _expandVariableEditor = false;
private bool _expandEventSelector = false;
static ClientSimUdonHelperEditor()
{
_drawPropertyMethod = typeof(UdonProgramAsset).GetMethod("DrawPublicVariableField", BindingFlags.NonPublic | BindingFlags.Instance);
}
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
ClientSimUdonHelper udonHelper = target as ClientSimUdonHelper;
ClientSimSyncableEditorHelper.DisplaySyncOptions(udonHelper);
UdonBehaviour udonBehaviour = udonHelper.GetUdonBehaviour();
ShowVariableEditor(udonBehaviour);
ShowExportedEvents(udonBehaviour);
}
private void ShowVariableEditor(UdonBehaviour udonBehaviour)
{
_expandVariableEditor = EditorGUILayout.Foldout(_expandVariableEditor, "Edit Public Variables", true);
if (!_expandVariableEditor)
{
return;
}
var program = udonBehaviour.programSource;
if (!(program is UdonProgramAsset programAsset))
{
return;
}
var publicVariables = udonBehaviour.publicVariables;
foreach (var varName in publicVariables.VariableSymbols)
{
publicVariables.TryGetVariableType(varName, out Type varType);
object value = udonBehaviour.GetProgramVariable(varName);
object[] parameters = {varName, value, varType, false, true};
var res = _drawPropertyMethod.Invoke(programAsset, parameters);
if ((bool)parameters[3])
{
udonBehaviour.SetProgramVariable(varName, res);
}
}
}
private void ShowExportedEvents(UdonBehaviour udonBehaviour)
{
_expandEventSelector = EditorGUILayout.Foldout(_expandEventSelector, "Run Custom Event", true);
if (!_expandEventSelector)
{
return;
}
foreach (string eventName in udonBehaviour.GetPrograms())
{
if (GUILayout.Button(eventName))
{
udonBehaviour.SendCustomEvent(eventName);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c921b1cc91fd76140b3200b9f7ff0ac8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bfc554a2126c4021ad13d6bbbf07d42c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Serialization;
namespace VRC.SDK3.ClientSim
{
public class ClientSimInputAxesSettings : ScriptableObject, IEquatable<ClientSimInputAxesSettings>
{
// Name copying Unity's Input settings
public List<ClientSimInputAxis> m_Axes = new List<ClientSimInputAxis>();
public void LoadFromSerializedObject(SerializedObject serializedObject)
{
m_Axes.Clear();
SerializedProperty axesProp = serializedObject.FindProperty(nameof(m_Axes));
for (int curAxis = 0; curAxis < axesProp.arraySize; ++curAxis)
{
SerializedProperty inputAxisProperty = axesProp.GetArrayElementAtIndex(curAxis);
m_Axes.Add(new ClientSimInputAxis(inputAxisProperty));
}
}
public bool Equals(ClientSimInputAxesSettings other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
if (m_Axes.Count != other.m_Axes.Count)
{
return false;
}
for (int cur = 0; cur < m_Axes.Count; ++cur)
{
if (!m_Axes[cur].Equals(other.m_Axes[cur]))
{
return false;
}
}
return true;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ClientSimInputAxesSettings) obj);
}
public override int GetHashCode()
{
return (m_Axes != null ? m_Axes.GetHashCode() : 0);
}
[Serializable]
public class ClientSimInputAxis : IEquatable<ClientSimInputAxis>
{
[FormerlySerializedAs("name")]
public string m_Name;
public string descriptiveName;
public string descriptiveNegativeName;
public string negativeButton;
public string positiveButton;
public string altNegativeButton;
public string altPositiveButton;
public float gravity;
public float dead;
public float sensitivity;
public bool snap;
public bool invert;
public int type;
public int axis;
public int joyNum;
public ClientSimInputAxis() { }
public ClientSimInputAxis(SerializedProperty inputAxisProperty)
{
SerializedProperty nameProp = inputAxisProperty.FindPropertyRelative(nameof(m_Name));
SerializedProperty descriptiveNameProp = inputAxisProperty.FindPropertyRelative(nameof(descriptiveName));
SerializedProperty descriptiveNegativeNameProp = inputAxisProperty.FindPropertyRelative(nameof(descriptiveNegativeName));
SerializedProperty negativeButtonProp = inputAxisProperty.FindPropertyRelative(nameof(negativeButton));
SerializedProperty positiveButtonProp = inputAxisProperty.FindPropertyRelative(nameof(positiveButton));
SerializedProperty altNegativeButtonProp = inputAxisProperty.FindPropertyRelative(nameof(altNegativeButton));
SerializedProperty altPositiveButtonProp = inputAxisProperty.FindPropertyRelative(nameof(altPositiveButton));
SerializedProperty gravityProp = inputAxisProperty.FindPropertyRelative(nameof(gravity));
SerializedProperty deadProp = inputAxisProperty.FindPropertyRelative(nameof(dead));
SerializedProperty sensitivityProp = inputAxisProperty.FindPropertyRelative(nameof(sensitivity));
SerializedProperty snapProp = inputAxisProperty.FindPropertyRelative(nameof(snap));
SerializedProperty invertProp = inputAxisProperty.FindPropertyRelative(nameof(invert));
SerializedProperty typeProp = inputAxisProperty.FindPropertyRelative(nameof(type));
SerializedProperty axisProp = inputAxisProperty.FindPropertyRelative(nameof(axis));
SerializedProperty joyNumProp = inputAxisProperty.FindPropertyRelative(nameof(joyNum));
m_Name = nameProp.stringValue;
descriptiveName = descriptiveNameProp.stringValue;
descriptiveNegativeName = descriptiveNegativeNameProp.stringValue;
negativeButton = negativeButtonProp.stringValue;
positiveButton = positiveButtonProp.stringValue;
altNegativeButton = altNegativeButtonProp.stringValue;
altPositiveButton = altPositiveButtonProp.stringValue;
gravity = gravityProp.floatValue;
dead = deadProp.floatValue;
sensitivity = sensitivityProp.floatValue;
snap = snapProp.boolValue;
invert = invertProp.boolValue;
type = typeProp.intValue;
axis = axisProp.intValue;
joyNum = joyNumProp.intValue;
}
public bool Equals(ClientSimInputAxis other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return m_Name == other.m_Name
&& descriptiveName == other.descriptiveName
&& descriptiveNegativeName == other.descriptiveNegativeName
&& negativeButton == other.negativeButton
&& positiveButton == other.positiveButton
&& altNegativeButton == other.altNegativeButton
&& altPositiveButton == other.altPositiveButton
&& Mathf.Approximately(gravity, other.gravity)
&& Mathf.Approximately(dead, other.dead)
&& Mathf.Approximately(sensitivity, other.sensitivity)
&& snap == other.snap
&& invert == other.invert
&& type == other.type
&& axis == other.axis
&& joyNum == other.joyNum;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((ClientSimInputAxis) obj);
}
public override int GetHashCode()
{
unchecked
{
var hashCode = (m_Name != null ? m_Name.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (descriptiveName != null ? descriptiveName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (descriptiveNegativeName != null ? descriptiveNegativeName.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (negativeButton != null ? negativeButton.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (positiveButton != null ? positiveButton.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (altNegativeButton != null ? altNegativeButton.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (altPositiveButton != null ? altPositiveButton.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ gravity.GetHashCode();
hashCode = (hashCode * 397) ^ dead.GetHashCode();
hashCode = (hashCode * 397) ^ sensitivity.GetHashCode();
hashCode = (hashCode * 397) ^ snap.GetHashCode();
hashCode = (hashCode * 397) ^ invert.GetHashCode();
hashCode = (hashCode * 397) ^ type;
hashCode = (hashCode * 397) ^ axis;
hashCode = (hashCode * 397) ^ joyNum;
return hashCode;
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b749a2c3c49f074cbaac7ef4353ac9f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,133 @@
using System.IO;
using UnityEditor;
using UnityEngine;
namespace VRC.SDK3.ClientSim.Editor
{
/// <summary>
/// Class used to verify current project settings and to apply required ClientSim Settings.
/// Currently only has the ability to set Audio Spatializer, Input Axes, and Input Type project settings.
/// </summary>
public static class ClientSimProjectSettingsSetup
{
public static bool IsUsingCorrectSettings()
{
return
IsUsingCorrectInputAxesSettings()
&& IsUsingCorrectInputTypeSettings()
&& IsUsingCorrectAudioSettings()
&& (UpdateLayers.AreLayersSetup() && UpdateLayers.IsCollisionLayerMatrixSetup());
}
#region InputAxes
private const string INPUT_AXES_NAME = "ClientSimInputManager";
private const string INPUT_AXES_RAW_NAME = ".ClientSimInputManagerRaw.asset";
private static readonly string _projectInputManagerPath = Path.Combine("ProjectSettings", "InputManager.asset");
private static readonly string _inputAxesRawPath = Path.Combine("Editor", "Resources", INPUT_AXES_RAW_NAME);
private static ClientSimInputAxesSettings _inputAxes;
public static bool IsUsingCorrectInputAxesSettings()
{
ClientSimInputAxesSettings clientSimAxes = ReadClientSimInputAxes();
ClientSimInputAxesSettings projectAxes = ReadProjectInputAxes();
// TODO verify axes on an individual level rather than verifying the entire axis list. Users may want other
// axes for personal reasons but this prevents that.
return clientSimAxes.Equals(projectAxes);
}
public static void ApplyClientSimInputAxes()
{
string packagePath = ClientSimResourceLoader.GetResolvePath();
string clientSimAxesPath = Path.Combine(packagePath, _inputAxesRawPath);
// User may have installed ClientSim in a non package way. The file is expected to be in the same directory
// as the input settings file. Use that to determine the proper location.
if (string.IsNullOrEmpty(packagePath))
{
ReadClientSimInputAxes();
string directory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(_inputAxes));
clientSimAxesPath = Path.Combine(directory, INPUT_AXES_RAW_NAME);
}
string projectAxesPath = Path.Combine(Application.dataPath, "..", _projectInputManagerPath);
File.Copy(clientSimAxesPath, projectAxesPath, true);
AssetDatabase.Refresh();
}
private static ClientSimInputAxesSettings ReadClientSimInputAxes()
{
if (_inputAxes == null)
{
_inputAxes = Resources.Load<ClientSimInputAxesSettings>(INPUT_AXES_NAME);
}
return _inputAxes;
}
private static ClientSimInputAxesSettings ReadProjectInputAxes()
{
Object inputManager = AssetDatabase.LoadAssetAtPath<Object>(_projectInputManagerPath);
ClientSimInputAxesSettings inputObject = ScriptableObject.CreateInstance<ClientSimInputAxesSettings>();
inputObject.LoadFromSerializedObject(new SerializedObject(inputManager));
return inputObject;
}
#endregion
#region Input Manager
// Verify input is set to use both old and new input types.
public static bool IsUsingCorrectInputTypeSettings()
{
SerializedObject serializedObject = new SerializedObject(AssetDatabase.LoadAssetAtPath<Object>("ProjectSettings/ProjectSettings.asset"));
SerializedProperty activeInputHandler = serializedObject.FindProperty("activeInputHandler");
return activeInputHandler.intValue != 1;
}
public static void SetInputTypeSettings()
{
SerializedObject serializedObject = new SerializedObject(AssetDatabase.LoadAssetAtPath<Object>("ProjectSettings/ProjectSettings.asset"));
SerializedProperty activeInputHandler = serializedObject.FindProperty("activeInputHandler");
activeInputHandler.intValue = 1;
serializedObject.ApplyModifiedProperties();
}
#endregion
#region Audio Spatializer
public static bool IsUsingCorrectAudioSettings()
{
SerializedObject serializedObject = new SerializedObject(AssetDatabase.LoadAssetAtPath<Object>("ProjectSettings/AudioManager.asset"));
SerializedProperty spatializerPluginProp = serializedObject.FindProperty("m_SpatializerPlugin");
SerializedProperty ambisonicDecoderPluginProp = serializedObject.FindProperty("m_AmbisonicDecoderPlugin");
return
spatializerPluginProp.stringValue == "OculusSpatializer"
&& ambisonicDecoderPluginProp.stringValue == "OculusSpatializer";
}
public static void SetAudioSettings()
{
SerializedObject serializedObject = new SerializedObject(AssetDatabase.LoadAssetAtPath<Object>("ProjectSettings/AudioManager.asset"));
SerializedProperty spatializerPluginProp = serializedObject.FindProperty("m_SpatializerPlugin");
SerializedProperty ambisonicDecoderPluginProp = serializedObject.FindProperty("m_AmbisonicDecoderPlugin");
spatializerPluginProp.stringValue = "OculusSpatializer";
ambisonicDecoderPluginProp.stringValue = "OculusSpatializer";
serializedObject.ApplyModifiedProperties();
}
#endregion
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bac8caddcef14fa9a80bf167d612dbaf
timeCreated: 1638973572

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f22daada0216a8c4eb4c1e603407310a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3c6d75275331c9e4fb6acb2ec61ef6ab
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,304 @@
.content {
margin: 5px;
}
.menu-buttons {
align-items: flex-start;
justify-content: flex-start;
flex-direction: row;
flex-wrap: nowrap;
align-self: auto;
position: relative;
top: auto;
left: auto;
width: 150px;
height: 40px;
padding-left: 0;
padding-top: 0;
margin-bottom: 2.5px;
margin-left: 0;
}
.menu-button {
height: 34px;
width: 50px;
margin: 0;
}
.menu-button-open {
background-image: resource('Icons/FolderOpen');
-unity-background-scale-mode: scale-to-fit;
-unity-background-image-tint-color: rgb(255, 255, 255);
background-color: rgba(0, 0, 0, 0);
scale: 0.75 0.75;
width: auto;
height: 30px;
}
.menu-button-refresh {
background-image: resource('Icons/Refresh');
-unity-background-scale-mode: scale-to-fit;
-unity-background-image-tint-color: rgb(255, 255, 255);
background-color: rgba(0, 0, 0, 0);
scale: 0.75 0.75;
width: auto;
height: 30px;
align-items: auto;
flex-direction: column;
display: flex;
}
.menu-button-clear {
background-image: resource('Icons/Trash');
padding: 0;
-unity-background-image-tint-color: rgb(255, 255, 255);
scale: 0.5 0.5;
background-color: rgba(0, 0, 0, 0);
height: 30px;
-unity-background-scale-mode: scale-to-fit;
}
.menu-button-left {
border-radius: 7px 0 0 7px;
}
.menu-button-right {
border-radius: 0 7px 7px 0;
}
.menu-button-center {
border-radius: 0 0 0 0;
}
.dropdown {
flex-direction: column;
flex-wrap: nowrap;
align-items: auto;
justify-content: flex-start;
}
.dropdown-player {
align-items: auto;
align-self: auto;
justify-content: flex-start;
width: 150px;
flex-direction: column;
margin-right: 0;
min-height: auto;
min-width: auto;
display: flex;
visibility: visible;
overflow: visible;
opacity: 1;
flex-wrap: nowrap;
margin-left: 0;
position: relative;
top: auto;
left: auto;
background-color: rgba(255, 255, 255, 0);
margin-top: 0;
height: 20px;
}
.dropdown-sort {
align-items: flex-start;
align-self: auto;
justify-content: flex-start;
width: 150px;
flex-direction: row;
margin-right: 0;
min-height: auto;
min-width: auto;
display: none;
visibility: visible;
overflow: visible;
opacity: 1;
flex-wrap: nowrap;
margin-left: 0;
position: relative;
top: auto;
left: auto;
background-color: rgba(255, 255, 255, 0);
margin-top: 0;
height: 30px;
padding-top: 10px;
}
.data-header {
flex-direction: row;
margin-right: 0;
}
.randomize-button {
margin: 0;
padding: 0;
width: 150px;
height: 30px;
display: none;
}
.info-text {
align-items: auto;
display: flex;
background-color: rgba(0, 0, 0, 0);
-unity-background-image-tint-color: rgb(0, 0, 0);
-unity-background-scale-mode: stretch-to-fill;
margin-left: 0;
padding-left: 0;
color: rgb(173, 173, 173);
font-size: 12px;
margin-top: 0;
justify-content: flex-start;
align-self: auto;
flex-direction: column;
left: -8px;
top: 1px;
}
.info-icon {
flex-grow: 1;
width: 50px;
flex-direction: column;
flex-wrap: nowrap;
align-items: auto;
justify-content: flex-start;
align-self: auto;
max-width: 50px;
-unity-background-scale-mode: scale-to-fit;
background-image: resource('Icons/Log');
scale: 0.8 0.8;
}
.info {
flex-grow: 1;
flex-direction: row;
height: 50px;
-unity-background-image-tint-color: rgb(255, 255, 255);
flex-wrap: nowrap;
align-items: stretch;
justify-content: flex-start;
align-self: auto;
width: 320px;
border-left-color: rgb(0, 0, 0);
border-right-color: rgb(0, 0, 0);
border-top-color: rgb(0, 0, 0);
border-bottom-color: rgb(0, 0, 0);
border-left-width: 1px;
border-right-width: 1px;
border-top-width: 1px;
border-bottom-width: 1px;
border-radius: 5px;
padding-top: 0;
margin-top: 5px;
background-color: rgb(64, 64, 64);
-unity-background-scale-mode: scale-to-fit;
}
.unity-label {
padding-left: 100px;
margin-left: -100px;
}
.unity-base-field {
margin-top: 0;
margin-bottom: 0;
padding-bottom: 4px;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
margin-left: 0;
padding-top: 0;
height: auto;
}
.unity-base-field .unity-base-field {
padding-bottom: 0;
}
.unity-composite-field {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: auto;
justify-content: flex-start;
align-self: stretch;
}
.unity-composite-field__field-spacer {
display: none;
}
.scroll-view {
height: auto;
justify-content: flex-start;
min-height: auto;
margin: 0;
padding: 10px;
}
.scroll-view .unity-label {
-unity-background-scale-mode: stretch-to-fill;
left: auto;
position: relative;
width: 250px;
}
.paging {
flex-grow: 1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: flex-end;
align-self: auto;
border-right-width: 0;
border-left-width: 0;
border-top-width: 0;
border-bottom-width: 0;
margin-right: 0;
}
.paging .unity-label {
width: auto;
height: auto;
align-items: auto;
justify-content: flex-start;
align-self: auto;
flex-direction: column;
flex-wrap: nowrap;
margin: 0;
padding: 0;
}
.paging-next {
background-image: resource('Icons/Arrow');
background-color: rgb(88, 88, 88);
rotate: -90deg;
-unity-background-scale-mode: scale-to-fit;
-unity-background-image-tint-color: rgb(255, 255, 255);
width: 40px;
height: 30px;
scale: 0.5 0.75;
}
.paging-previous {
background-color: rgb(88, 88, 88);
background-image: resource('Icons/Arrow');
-unity-background-scale-mode: scale-to-fit;
rotate: 90deg;
width: 40px;
height: 30px;
scale: 0.5 0.75;
padding: 0;
margin: 0;
}
.unity-composite-field__field .unity-label {
width: 20px;
min-width: 20px;
max-width: 20px;
-unity-text-align: middle-center;
margin: 0;
padding: 0;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd10e9a97cf7d424bb40e1f5898c19c6
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@ -0,0 +1,31 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="False">
<Style src="project://database/Packages/com.vrchat.worlds/Integrations/ClientSim/Editor/Resources/ClientSimPlayerDataWindow.uss?fileID=7433441132597879392&amp;guid=dd10e9a97cf7d424bb40e1f5898c19c6&amp;type=3#ClientSimPlayerDataWindow" />
<ui:VisualElement name="Content" class="content">
<ui:VisualElement name="MenuButtons" class="menu-buttons">
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="OpenDataFolderButton" tooltip="Open folder containing the ClientSim PlayerData" class="menu-button menu-button-left">
<ui:VisualElement name="OpenFolderIcon" class="menu-button-open" />
</ui:Button>
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="RefreshDataButton" tooltip="Refresh data - Use this after you&apos;ve edited a PlayerData JSON file" class="menu-button menu-button-center">
<ui:VisualElement name="RefreshIcon" class="menu-button-refresh" />
</ui:Button>
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="ClearDataButton" tooltip="Delete all ClientSim PlayerData" usage-hints="None" class="menu-button menu-button-right">
<ui:VisualElement name="ClearIcon" class="menu-button-clear" />
</ui:Button>
</ui:VisualElement>
<ui:DropdownField index="0" choices="[1] Local Player" name="PlayerDropdown" tooltip="In Edit Mode, the local player is the only option. In Play Mode, entries for any remote players that have been spawned will appear here." class="dropdown-player dropdown" />
<ui:VisualElement name="NoDataInfo" class="info">
<ui:VisualElement name="InfoIcon" class="info-icon" />
<ui:Label tabindex="-1" text="PlayerData for the selected player is empty." parse-escape-sequences="true" display-tooltip-when-elided="true" name="InfoText" class="info-text" />
</ui:VisualElement>
<ui:VisualElement name="DataHeader" class="data-header">
<ui:DropdownField index="0" choices="Alphabetically,Last Updated,First Added" name="SortDropdown" label="Sort by" tooltip="Sort ClientSim PlayerData" class="dropdown-sort" />
<ui:VisualElement name="Paging" class="paging">
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="PreviousPageButton" class="paging-previous" />
<ui:Label tabindex="-1" text="1 / 1" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PageLabel" />
<ui:Button parse-escape-sequences="true" display-tooltip-when-elided="true" name="NextPageButton" class="paging-next" />
</ui:VisualElement>
</ui:VisualElement>
<ui:Button text="Randomize Data" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RandomizeButton" tooltip="Generate random PlayerData for the selected remote player based on the local player&apos;s PlayerData schema" class="randomize-button" />
<ui:ScrollView name="PlayerDataList" mode="Vertical" class="scroll-view" />
</ui:VisualElement>
</ui:UXML>

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4608c965813f48448be4a84ca4c801ce
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}

View File

@ -0,0 +1,84 @@
.header {
position: relative;
visibility: visible;
display: flex;
overflow: visible;
padding-top: 0;
padding-bottom: 0;
margin-bottom: 5px;
margin-top: 10px;
margin-left: 5px;
flex-shrink: 0;
}
.searchAndSort {
width: 100%;
flex-direction: row;
}
.search {
flex-grow: 1;
min-width: 100px;
}
.sort {
flex-grow: 1;
min-width: auto;
}
.scroll-view {
margin: 0;
padding: 10px;
flex-grow: 1;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
}
.helptext {
align-items: stretch;
-unity-text-align: middle-center;
font-size: 15px;
flex-grow: 1;
flex-wrap: wrap;
white-space: normal;
}
.helpTextbox {
height: auto;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
border-left-color: rgb(0, 0, 0);
border-right-color: rgb(0, 0, 0);
border-top-color: rgb(0, 0, 0);
border-bottom-color: rgb(0, 0, 0);
flex-grow: 0;
margin-top: 5px;
margin-right: 5px;
margin-bottom: 5px;
margin-left: 5px;
background-color: rgb(81, 81, 81);
flex-wrap: wrap;
min-height: 40px;
}
.paging {
justify-content: center;
flex-shrink: 0;
padding-top: 5px;
padding-bottom: 5px;
}
ClientSimNetworkHolderInstanceElement Button {
padding-left: 6px;
padding-bottom: 2px;
width: 16px;
height: 16px;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f2b1e23097f00540a7705261726f8e9
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0

View File

@ -0,0 +1,19 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" editor-extension-mode="True">
<Style src="project://database/Packages/com.vrchat.worlds/Integrations/ClientSim/Editor/Resources/ClientSimPlayerObjectWindow.uss?fileID=7433441132597879392&amp;guid=7f2b1e23097f00540a7705261726f8e9&amp;type=3#ClientSimPlayerObjectWindow" />
<ui:VisualElement name="Header" class="header">
<ui:VisualElement name="SearchAndSort" class="searchAndSort">
<ui:TextField picking-mode="Ignore" name="Search" keyboard-type="Search" class="search" />
<ui:DropdownField label="Sort by" index="-1" choices="System.Collections.Generic.List`1[System.String]" name="Sort" class="sort" />
</ui:VisualElement>
<ui:DropdownField name="PlayerDropdown" class="dropdown" />
</ui:VisualElement>
<ui:ScrollView name="PlayerObjectList" mode="Vertical" class="scroll-view" style="overflow: hidden; display: flex; visibility: visible;" />
<ui:VisualElement name="HelpTextBox" class="helpTextbox">
<ui:Label tabindex="-1" text="Enter Play Mode to view PlayerObjects" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HelpTextBoxLabel" class="helptext" />
</ui:VisualElement>
<ui:VisualElement name="Paging" class="paging" style="flex-direction: row;">
<ui:Button text="&lt;" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Left" />
<ui:Label tabindex="-1" text="0/5" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PagingLabel" />
<ui:Button text="&gt;" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Right" />
</ui:VisualElement>
</ui:UXML>

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b2d01571760c4a2986a424f626d5bfef
timeCreated: 1715610035

View File

@ -0,0 +1,14 @@
fileFormatVersion: 2
<<<<<<<< HEAD:SdkStaging-VPM/Packages/com.vrchat.worlds/Integrations/ClientSim/Editor/Resources/Icons.meta
guid: d4fe049ca08e18642b9c4e99d726517e
|||||||| 59c708fea2b:Assets/Features/BifrostUI/Icons.meta
guid: ac4616caad22f914b90dae1156f28e29
========
guid: 0db01d374bad60844ae3ac004610a0a4
>>>>>>>> ca19f46b5b6d897fe23e0f1c411a64c4a706eb7f:Assets/Editor/ScriptMigration.meta
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: c8fd5f3786124da488b0afb915024290
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 1
swizzle: 50462976
cookieLightType: 2
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 0
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: e64f6f7f406d52944a7c771560f32776
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 16
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 B

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 5760aa0404414ba46a3ab17f0cf9a119
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 12
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 6209135682e85d548aa7d8880d78f277
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 16
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

View File

@ -0,0 +1,140 @@
fileFormatVersion: 2
guid: 73d1b9d7ead861c44ad39986ed8447af
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 16
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,20 @@
{
"name": "VRC.ClientSim.Editor",
"references": [
"VRC.ClientSim",
"VRC.Udon",
"VRC.Udon.Editor",
"Unity.InputSystem"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 96e35a33b2567c04bae44e25e4994158
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1a79ea3eada4487186d1fd69e1ead853
timeCreated: 1715614704

View File

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements.EncodeDecodeEditors;
using VRC.SDK3.ClientSim.Interfaces;
using VRC.SDK3.Components;
using VRC.SDK3.Data;
using VRC.Udon;
namespace VRC.SDK3.ClientSim.Editor.VisualElements
{
#if VRC_ENABLE_PLAYER_PERSISTENCE
public class ClientSimNetworkHolderInstanceElement : VisualElement
{
private GameObject _gameObject;
private string _componentName;
private string _componentType;
private MonoBehaviour _component;
private Foldout foldout;
private IClientSimNetworkSerializer networkView;
internal static Dictionary<string,IClientSimEncodeDecoderEditor> encodeDecoders = new Dictionary<string,IClientSimEncodeDecoderEditor>
{
{ typeof(VRCObjectSync).FullName, new ClientSimObjectSyncEncodeDecodeEditor() },
{ typeof(UdonBehaviour).FullName, new ClientSimUdonEncodeDecodeEditor() },
{ typeof(VRCObjectPool).FullName, new ClientSimObjectPoolEncodeDecodeEditor()}
};
private VisualElement _dataElements;
public ClientSimNetworkHolderInstanceElement()
{
foldout = new Foldout();
Button selectButton = new Button(() =>
{
Selection.activeObject = _gameObject;
});
selectButton.text = ">";
foldout.Q<Toggle>().Add(selectButton);
this.Add(foldout);
}
private void GetDataFromComponent(int index)
{
_component = networkView.GetNetworkComponents()[index];
_gameObject = _component.gameObject;
_componentType = _component.GetType().FullName;
if (_component is UdonBehaviour)
{
_componentName = ((UdonBehaviour) _component).programSource.name;
}
else
{
_componentName = _component.GetType().Name;
}
}
private void GenerateDataElements(int index)
{
if (encodeDecoders.TryGetValue(_componentType, out var encodeDecoder))
{
_dataElements = encodeDecoder.GenerateFields(_component, networkView.GetData()[index].DataDictionary);
}
}
public void UpdateData(ClientSimPlayerObjectWindow.PlayerObjectData data)
{
if (data.NetworkView != networkView)
{
foldout.Clear();
networkView = data.NetworkView;
GetDataFromComponent(data.index);
this.name = $"{_componentName}({_gameObject.name})";
foldout.text = $"{_componentName}({_gameObject.name})";
GenerateDataElements(data.index);
foldout.Add(_dataElements);
}
if (encodeDecoders.TryGetValue(_componentType, out var encodeDecoder))
{
if (_dataElements == null)
{
GenerateDataElements(data.index);
foldout.Clear();
foldout.Add(_dataElements);
}
else
{
encodeDecoder.UpdateFields(_component, _dataElements, networkView.GetData()[data.index].DataDictionary);
}
}
style.display = networkView.GetData().Count > 0 ? DisplayStyle.Flex : DisplayStyle.None;
}
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f3e1ce223c6460aa9a05edbecf50b31
timeCreated: 1715614752

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5715f6d9033043f6a25678681d632679
timeCreated: 1716478398

View File

@ -0,0 +1,33 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements.Fields;
using VRC.SDK3.Data;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.EncodeDecodeEditors
{
public class ClientSimObjectPoolEncodeDecodeEditor : IClientSimEncodeDecoderEditor
{
public VisualElement GenerateFields(MonoBehaviour component, DataDictionary data)
{
VisualElement container = new VisualElement();
DataList values = (DataList) data["Values"];
for(int i = 0; i < (int)data["Length"].Double; i++)
{
container.Add(FieldFactory.GenerateField("Object " + i , values[i].Boolean));
}
return container;
}
public void UpdateFields(MonoBehaviour component, VisualElement dataElement, DataDictionary data)
{
DataList values = (DataList) data["Values"];
for(int i = 0; i < (int)data["Length"].Double; i++)
{
FieldFactory.UpdateField(dataElement[i], values[i].Boolean);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9861710bbbed4136b87f8ecfb5500488
timeCreated: 1716478455

View File

@ -0,0 +1,140 @@
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements.Fields;
using VRC.SDK3.Data;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.EncodeDecodeEditors
{
public class ClientSimObjectSyncEncodeDecodeEditor : IClientSimEncodeDecoderEditor
{
private const string Discontinuitycounter = "DiscontinuityCounter";
private const string Discontinuity = "Discontinuity";
private const string Heldinhand = "HeldInHand";
private const string Time = "Time";
private const string Wassleeping = "WasSleeping";
private const string Usegravity = "UseGravity";
private const string Iskinematic = "IsKinematic";
private const string Velocity = "Velocity";
private const string Rotation = "Rotation";
private const string FieldName = "Position";
private const string NotAvailable = "not available";
public VisualElement GenerateFields(MonoBehaviour component, DataDictionary data)
{
VisualElement dataElement = new VisualElement();
if (data.TryGetValue(FieldName, out DataToken positionToken))
{
dataElement.Add(FieldFactory.GenerateField(FieldName, positionToken.GetVector3()));
}
if (data.TryGetValue(Rotation, out DataToken rotationToken))
{
dataElement.Add(FieldFactory.GenerateField("Rotation: " , rotationToken.GetQuaternion()));
}
if (data.TryGetValue(Velocity, out DataToken velocityToken))
{
if(velocityToken.TokenType != TokenType.DataDictionary)
dataElement.Add(FieldFactory.GenerateField("Velocity: " , NotAvailable));
else
dataElement.Add(FieldFactory.GenerateField("Velocity: " , velocityToken.GetVector3()));
}
if (data.TryGetValue(Iskinematic, out DataToken isKinematicToken))
{
dataElement.Add(FieldFactory.GenerateField("Is Kinematic: " , isKinematicToken.Boolean));
}
if (data.TryGetValue(Usegravity, out DataToken useGravityToken))
{
dataElement.Add(FieldFactory.GenerateField("Use Gravity: " , useGravityToken.Boolean));
}
if (data.TryGetValue(Wassleeping, out DataToken wasSleepingToken))
{
dataElement.Add(FieldFactory.GenerateField("Was Sleeping: " , wasSleepingToken.Boolean));
}
if (data.TryGetValue(Time, out DataToken timeToken))
{
dataElement.Add(FieldFactory.GenerateField("Time: " , (float)timeToken.Double));
}
if (data.TryGetValue(Heldinhand, out DataToken heldInHandToken))
{
dataElement.Add(FieldFactory.GenerateField("Held In Hand: " , heldInHandToken.String));
}
if (data.TryGetValue(Discontinuity, out DataToken discontinuityToken))
{
dataElement.Add(FieldFactory.GenerateField("Discontinuity: " , discontinuityToken.String));
}
if (data.TryGetValue(Discontinuitycounter, out DataToken discontinuityCounterToken))
{
dataElement.Add(FieldFactory.GenerateField("Discontinuity Counter: " , discontinuityCounterToken.String));
}
return dataElement;
}
public void UpdateFields(MonoBehaviour component, VisualElement dataElement, DataDictionary data)
{
if (data.TryGetValue(FieldName, out DataToken positionToken))
{
FieldFactory.UpdateField(dataElement[0], positionToken.GetVector3());
}
if (data.TryGetValue(Rotation, out DataToken rotationToken))
{
FieldFactory.UpdateField(dataElement[1], rotationToken.GetQuaternion());
}
if (data.TryGetValue(Velocity, out DataToken velocityToken))
{
if(velocityToken.TokenType != TokenType.DataDictionary)
FieldFactory.UpdateField(dataElement[2], NotAvailable);
else
FieldFactory.UpdateField(dataElement[2], velocityToken.GetVector3());
}
if (data.TryGetValue(Iskinematic, out DataToken isKinematicToken))
{
FieldFactory.UpdateField(dataElement[3], isKinematicToken.Boolean);
}
if (data.TryGetValue(Usegravity, out DataToken useGravityToken))
{
FieldFactory.UpdateField(dataElement[4], useGravityToken.Boolean);
}
if (data.TryGetValue(Wassleeping, out DataToken wasSleepingToken))
{
FieldFactory.UpdateField(dataElement[5], wasSleepingToken.Boolean);
}
if (data.TryGetValue(Time, out DataToken timeToken))
{
FieldFactory.UpdateField(dataElement[6], (float)timeToken.Double);
}
if (data.TryGetValue(Heldinhand, out DataToken heldInHandToken))
{
FieldFactory.UpdateField(dataElement[7], heldInHandToken.String);
}
if (data.TryGetValue(Discontinuity, out DataToken discontinuityToken))
{
FieldFactory.UpdateField(dataElement[8] , discontinuityToken.String);
}
if (data.TryGetValue(Discontinuitycounter, out DataToken discontinuityCounterToken))
{
FieldFactory.UpdateField(dataElement[9], discontinuityCounterToken.String);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 77215cffe9bd4a6fa08514c2722c218a
timeCreated: 1716478863

View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements.Fields;
using VRC.SDK3.Data;
using VRC.SDKBase;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
using VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI;
using VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.EncodeDecodeEditors
{
public class ClientSimUdonEncodeDecodeEditor : IClientSimEncodeDecoderEditor
{
public VisualElement GenerateFields(MonoBehaviour component,DataDictionary data)
{
UdonBehaviour udonBehaviour = component as UdonBehaviour;
IEnumerable<IUdonSyncMetadata> SyncMetadatas = udonBehaviour.SyncMetadataTable.GetAllSyncMetadata();
VisualElement dataElement = new VisualElement();
foreach (IUdonSyncMetadata syncMetadata in SyncMetadatas)
{
if (data.ContainsKey(syncMetadata.Name))
{
VisualElement field = FieldFactory.GenerateField(syncMetadata.Name, GetCorrectTypeFromDataToken(data[syncMetadata.Name]));
field.name = syncMetadata.Name;
dataElement.Add(field);
}
}
return dataElement;
}
public void UpdateFields(MonoBehaviour component, VisualElement dataElement, DataDictionary data)
{
UdonBehaviour udonBehaviour = component as UdonBehaviour;
IEnumerable<IUdonSyncMetadata> SyncMetadatas = udonBehaviour.SyncMetadataTable.GetAllSyncMetadata();
int index = 0;
foreach (IUdonSyncMetadata syncMetadata in SyncMetadatas)
{
if (data.ContainsKey(syncMetadata.Name))
{
FieldFactory.UpdateField(dataElement[index], GetCorrectTypeFromDataToken(data[syncMetadata.Name]));
index++;
}
}
}
private object GetCorrectTypeFromDataToken(DataToken token)
{
switch (token.TokenType)
{
case TokenType.Boolean:
return token.Boolean;
case TokenType.SByte:
return token.SByte;
case TokenType.Byte:
return token.Byte;
case TokenType.Short:
return token.Short;
case TokenType.UShort:
return token.UShort;
case TokenType.Int:
return token.Int;
case TokenType.UInt:
return token.UInt;
case TokenType.Long:
return token.Long;
case TokenType.ULong:
return token.ULong;
case TokenType.Float:
return token.Float;
case TokenType.Double:
return token.Double;
case TokenType.String:
return token.String;
case TokenType.Reference:
return token.Reference;
case TokenType.DataList:
DataList dataList = token.DataList;
object[] list = new object[dataList.Count];
for(int i =0; i < dataList.Count; i++)
{
list[i] = GetCorrectTypeFromDataToken(dataList[i]);
}
return list;
case TokenType.DataDictionary:
DataDictionary dataDictionary = token.DataDictionary;
if(!dataDictionary.ContainsKey("T")) return null;
string type = dataDictionary["T"].String;
switch (type)
{
case "V2":
return token.GetVector2();
case "V3":
return token.GetVector3();
case "V4":
return token.GetVector4();
case "Q":
return token.GetQuaternion();
case "C":
return token.GetColor();
case "C32":
return token.GetColor32();
default:
return null;
}
default:
return null;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d6751e9563f4c939b96aaf7bbc7cb0f
timeCreated: 1716481521

View File

@ -0,0 +1,13 @@
using Newtonsoft.Json.Linq;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.Data;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.EncodeDecodeEditors
{
public interface IClientSimEncodeDecoderEditor
{
public VisualElement GenerateFields(MonoBehaviour component, DataDictionary data);
public void UpdateFields(MonoBehaviour component, VisualElement dataElement, DataDictionary data);
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ccf6036bf1854d7f800ce01e60ab4d1c
timeCreated: 1716478565

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f0ace7b221a14ff8af6b1c0262de9da0
timeCreated: 1716411089

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class ByteField : TextValueField<byte>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-byte-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = ByteField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = ByteField.ussClassName + "__input";
private ByteField.ByteInput byteInput => (ByteField.ByteInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(byte v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override byte StringToValue(string str)
{
byte num;
return ClientSimUINumericFieldsUtils.TryConvertStringToByte(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public ByteField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public ByteField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public ByteField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<byte>.TextValueInput) new ByteField.ByteInput())
{
this.AddToClassList(ByteField.ussClassName);
this.labelElement.AddToClassList(ByteField.labelUssClassName);
this.AddLabelDragger<byte>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, byte startValue) => this.byteInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<ByteField, ByteField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class ByteInput : TextValueField<byte>.TextValueInput
{
private ByteField parentByteField => (ByteField) this.parent;
internal ByteInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, byte startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentByteField.isDelayed)
this.text = this.ValueToString((byte)Mathf.Clamp(num,0,255));
else
this.parentByteField.value = (byte)Mathf.Clamp(num,0,255);
}
protected override string ValueToString(byte v) => v.ToString(this.formatString);
protected override byte StringToValue(string str)
{
byte num;
ClientSimUINumericFieldsUtils.TryConvertStringToByte(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 00039a4bded043e386c649a36454b746
timeCreated: 1716566750

View File

@ -0,0 +1,40 @@
using System;
using UnityEngine;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class ClientSimNumericFieldDraggerUtility
{
private static bool s_UseYSign;
private const double kDragSensitivity = 0.029999999329447746;
private const double kYSignThreshold = 0.10000000149011612;
internal static float Acceleration(bool shiftPressed, bool altPressed) => (float) ((shiftPressed ? 4.0 : 1.0) * (altPressed ? 0.25 : 1.0));
internal static float NiceDelta(Vector2 deviceDelta, float acceleration)
{
deviceDelta.y = -deviceDelta.y;
if ((double) Mathf.Abs(Mathf.Abs(deviceDelta.x) - Mathf.Abs(deviceDelta.y)) / (double) Mathf.Max(Mathf.Abs(deviceDelta.x), Mathf.Abs(deviceDelta.y)) > kYSignThreshold)
ClientSimNumericFieldDraggerUtility.s_UseYSign = (double) Mathf.Abs(deviceDelta.x) <= (double) Mathf.Abs(deviceDelta.y);
return ClientSimNumericFieldDraggerUtility.s_UseYSign ? Mathf.Sign(deviceDelta.y) * deviceDelta.magnitude * acceleration : Mathf.Sign(deviceDelta.x) * deviceDelta.magnitude * acceleration;
}
internal static double CalculateFloatDragSensitivity(double value) => double.IsInfinity(value) || double.IsNaN(value) ? 0.0 : Math.Max(1.0, Math.Pow(Math.Abs(value), 0.5)) * kDragSensitivity;
internal static double CalculateFloatDragSensitivity(
double value,
double minValue,
double maxValue)
{
return double.IsInfinity(value) || double.IsNaN(value) ? 0.0 : Math.Abs(maxValue - minValue) / 100.0 * kDragSensitivity;
}
internal static long CalculateIntDragSensitivity(long value) => (long) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((double) value);
internal static ulong CalculateIntDragSensitivity(ulong value) => (ulong) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((double) value);
private static double CalculateIntDragSensitivity(double value) => Math.Max(1.0, Math.Pow(Math.Abs(value), 0.5) * kDragSensitivity);
internal static long CalculateIntDragSensitivity(long value, long minValue, long maxValue) => Math.Max(1L, (long) (kDragSensitivity * (double) Math.Abs(maxValue - minValue) / 100.0));
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 47c8de80824e4de98b823afd757d760c
timeCreated: 1716567169

View File

@ -0,0 +1,110 @@
using UnityEngine;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class ClientSimUINumericFieldsUtils
{
public static readonly string k_AllowedCharactersForFloat = "inftynaeINFTYNAE0123456789.,-*/+%^()cosqrludxvRL=pP#";
public static readonly string k_AllowedCharactersForByte = "0123456789-*/+%^()cosintaqrtelfundxvRL,=pPI#";
public static readonly string k_DoubleFieldFormatString = "R";
public static readonly string k_FloatFieldFormatString = "g7";
public static readonly string k_ByteFieldFormatString = "###0";
public static bool TryConvertStringToLong(
string str,
out long value)
{
return ExpressionEvaluator.Evaluate<long>(str, out value);
}
public static bool TryConvertStringToULong(
string str,
out ulong value)
{
return ExpressionEvaluator.Evaluate<ulong>(str, out value);
}
public static bool TryConvertStringToLong(
string str,
string initialValueAsString,
out long value)
{
bool flag = TryConvertStringToLong(str, out value);
long num;
if (!flag && !string.IsNullOrEmpty(initialValueAsString) && TryConvertStringToLong(initialValueAsString, out num))
{
value = num;
}
return flag;
}
public static bool TryConvertStringToByte(
string str,
string initialValueAsString,
out byte value)
{
long num;
bool flag = TryConvertStringToLong(str, initialValueAsString, out num);
value = (byte)Mathf.Clamp((int)num, (int)byte.MinValue, (int)byte.MaxValue);
return flag;
}
public static bool TryConvertStringToSbyte(
string str,
string initialValueAsString,
out sbyte value)
{
long num;
bool flag = TryConvertStringToLong(str, initialValueAsString, out num);
value = (sbyte)Mathf.Clamp((int)num, (int)sbyte.MinValue, (int)sbyte.MaxValue);
return flag;
}
public static bool TryConvertStringToUInt(
string str,
string initialValueAsString,
out uint value)
{
long num;
bool flag = TryConvertStringToLong(str, initialValueAsString, out num);
value = num < uint.MinValue ? uint.MinValue: num > uint.MaxValue ? uint.MaxValue : (uint)num;
return flag;
}
public static bool TryConvertStringToULong(
string str,
string initialValueAsString,
out ulong value)
{
bool flag = TryConvertStringToULong(str, out value);
if (!flag && !string.IsNullOrEmpty(initialValueAsString) && TryConvertStringToULong(initialValueAsString, out ulong num))
{
value = num;
}
return flag;
}
public static bool TryConvertStringToShort(
string str,
string initialValueAsString,
out short value)
{
long num;
bool flag = TryConvertStringToLong(str, initialValueAsString, out num);
value = num < short.MinValue ? short.MinValue: num > short.MaxValue ? short.MaxValue : (short)num;
return flag;
}
public static bool TryConvertStringToUShort(
string str,
string initialValueAsString,
out ushort value)
{
long num;
bool flag = TryConvertStringToLong(str, initialValueAsString, out num);
value = num < ushort.MinValue ? ushort.MinValue: num > ushort.MaxValue ? ushort.MaxValue : (ushort)num;
return flag;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 19c8c6acc9b1415c9b2efaadd21e3916
timeCreated: 1716567113

View File

@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class FieldFactory
{
private static Dictionary<Type, (Func<string, VisualElement>, Action<VisualElement,object>)> _types =
new Dictionary<Type, (Func<string, VisualElement>, Action<VisualElement,object>)>()
{
{ typeof(byte), ((name) => new ByteField(name),
(element, o) => ((ByteField)element).SetValueWithoutNotify((byte)o)) },
{ typeof(sbyte), ((name) => new SbyteField(name),
(element, o) => ((SbyteField)element).SetValueWithoutNotify((sbyte)o)) },
{ typeof(double), ((name) => new DoubleField(name),
(element, o) => ((DoubleField)element).SetValueWithoutNotify((double)o)) },
{ typeof(float), ((name) => new FloatField(name),
(element, o) => ((FloatField)element).SetValueWithoutNotify((float)o)) },
{ typeof(int), ((name) => new IntegerField(name),
(element, o) => ((IntegerField)element).SetValueWithoutNotify((int)o)) },
{ typeof(uint), ((name) => new UIntField(name),
(element, o) => ((UIntField)element).SetValueWithoutNotify((uint)o)) },
{ typeof(long), ((name => new LongField(name),
(element, o) => ((LongField)element).SetValueWithoutNotify((long)o))) },
{ typeof(ulong), ((name) => new ULongField(name),
(element, o) => ((ULongField)element).SetValueWithoutNotify((ulong)o)) },
{ typeof(short), ((name) => new ShortField(name),
(element, o) => ((ShortField)element).SetValueWithoutNotify((short)o)) },
{ typeof(ushort), ((name) => new UShortField(name),
(element, o) => ((UShortField)element).SetValueWithoutNotify((ushort)o)) },
{ typeof(string), ((name) => new TextField(name),
(element, o) => ((TextField)element).SetValueWithoutNotify((string)o)) },
{ typeof(char), ((name) => new TextField(name) { maxLength = 1 },
(element, o) => ((TextField)element).SetValueWithoutNotify(o.ToString())) },
{ typeof(bool), ((name) => new Toggle(name),
(element, o) => ((Toggle)element).SetValueWithoutNotify((bool)o)) },
{ typeof(Vector2), ((name) => new Vector2Field(name),
(element, o) => ((Vector2Field)element).SetValueWithoutNotify((Vector2)o))},
{ typeof(Vector3), ((name) => new Vector3Field(name),
(element, o) => ((Vector3Field)element).SetValueWithoutNotify((Vector3)o))},
{ typeof(Vector4), ((name) => new Vector4Field(name),
(element, o) => ((Vector4Field)element).SetValueWithoutNotify((Vector4)o))},
{ typeof(Quaternion), ((name) => new Vector3Field(name),
(element, o) => ((Vector3Field)element).SetValueWithoutNotify(((Quaternion)o).eulerAngles))},
{ typeof(Color), ((name) => new ColorField(name),
(element, o) => ((ColorField)element).SetValueWithoutNotify((Color)o))},
{ typeof(Color32), ((name) => new ColorField(name),
(element, o) => ((ColorField)element).SetValueWithoutNotify((Color32)o))},
};
private static Dictionary<Type,Type> _FieldType = new Dictionary<Type, Type>()
{
{ typeof(byte), typeof(ByteField) },
{ typeof(sbyte), typeof(SbyteField) },
{ typeof(double), typeof(DoubleField) },
{ typeof(float), typeof(FloatField) },
{ typeof(int), typeof(IntegerField) },
{ typeof(uint), typeof(UIntField) },
{ typeof(long), typeof(LongField) },
{ typeof(ulong), typeof(ULongField) },
{ typeof(short), typeof(ShortField) },
{ typeof(ushort), typeof(UShortField) },
{ typeof(string), typeof(TextField) },
{ typeof(char), typeof(TextField) },
{ typeof(bool), typeof(Toggle) },
{ typeof(Vector2), typeof(Vector2Field) },
{ typeof(Vector3), typeof(Vector3Field) },
{ typeof(Vector4), typeof(Vector4Field) },
{ typeof(Quaternion), typeof(Vector3Field) },
{ typeof(Color), typeof(ColorField) },
{ typeof(Color32), typeof(ColorField) },
};
public static VisualElement GenerateField<T>(string fieldName, T data)
{
Type type = data.GetType();
if (type.IsArray)
{
Foldout Array = new Foldout();
Array.text = fieldName;
for (int i = 0; i < ((Array)(object)data).Length; i++)
{
VisualElement field = GenerateField(i.ToString(), ((Array)(object)data).GetValue(i));
field.name = i.ToString();
Array.Add(field);
}
return Array;
}
if (_types.ContainsKey(type))
{
VisualElement field = _types[type].Item1(fieldName);
field.name = fieldName;
return field;
}
else
{
Label label = new Label(fieldName +" ("+type.Name+ "): " + data.ToString());
label.name = fieldName;
return label;
}
}
public static bool UpdateField<T>(VisualElement field, T data)
{
Type type = data.GetType();
bool visible = true;
if (type.IsArray)
{
visible = false;
int length = Math.Min(((Array)(object)data).Length,field.childCount);
for (int i = 0; i < length; i++)
{
visible |= UpdateField(field[i], ((Array)(object)data).GetValue(i));
}
if(length < field.childCount)
{
for (int i = length; i < field.childCount; i++)
{
field[i].style.display = DisplayStyle.None;
}
}
else if(length > field.childCount)
{
for (int i = field.childCount; i < length; i++)
{
VisualElement newField = GenerateField(i.ToString(), ((Array)(object)data).GetValue(i));
newField.name = i.ToString();
field.Add(newField);
}
}
return visible;
}
if (!_types.ContainsKey(type))
{
((Label)field).text = field.name + "("+type.Name+ "): " + data.ToString();
return visible;
}
// If the field is not the correct type, replace it with the correct type
// Can happen because json deserialization deserializes int, float, double, etc as double
if(_FieldType.ContainsKey(type))
{
if (field.GetType() != _FieldType[type])
{
VisualElement newField = GenerateField(field.name, data);
field.parent.Add(newField);
newField.PlaceInFront(field);
field.RemoveFromHierarchy();
return true;
}
}
else
{
Label label = new Label(field.name +" ("+type.Name+ "): " + data.ToString());
label.name = field.name;
field.parent.Add(label);
field.RemoveFromHierarchy();
return true;
}
_types[type].Item2(field, data);
return visible;
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b4807fe039954aa0b1c03bf75d5c6bdb
timeCreated: 1716411106

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class SbyteField : TextValueField<sbyte>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-sbyte-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = SbyteField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = SbyteField.ussClassName + "__input";
private SbyteField.SbyteInput sbyteInput => (SbyteField.SbyteInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(sbyte v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override sbyte StringToValue(string str)
{
sbyte num;
return ClientSimUINumericFieldsUtils.TryConvertStringToSbyte(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public SbyteField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public SbyteField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public SbyteField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<sbyte>.TextValueInput) new SbyteField.SbyteInput())
{
this.AddToClassList(SbyteField.ussClassName);
this.labelElement.AddToClassList(SbyteField.labelUssClassName);
this.AddLabelDragger<sbyte>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, sbyte startValue) => this.sbyteInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<SbyteField, SbyteField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class SbyteInput : TextValueField<sbyte>.TextValueInput
{
private SbyteField parentSByteField => (SbyteField) this.parent;
internal SbyteInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, sbyte startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentSByteField.isDelayed)
this.text = this.ValueToString((sbyte)Mathf.Clamp(num,sbyte.MinValue,sbyte.MaxValue));
else
this.parentSByteField.value = (sbyte)Mathf.Clamp(num,sbyte.MinValue,sbyte.MaxValue);
}
protected override string ValueToString(sbyte v) => v.ToString(this.formatString);
protected override sbyte StringToValue(string str)
{
sbyte num;
ClientSimUINumericFieldsUtils.TryConvertStringToSbyte(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 344e9f5df1e2381419eed10bb8eb4c1b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class ShortField : TextValueField<short>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-short-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = ShortField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = ShortField.ussClassName + "__input";
private ShortField.ShortInput shortInput => (ShortField.ShortInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(short v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override short StringToValue(string str)
{
short num;
return ClientSimUINumericFieldsUtils.TryConvertStringToShort(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public ShortField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public ShortField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public ShortField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<short>.TextValueInput) new ShortField.ShortInput())
{
this.AddToClassList(ShortField.ussClassName);
this.labelElement.AddToClassList(ShortField.labelUssClassName);
this.AddLabelDragger<short>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, short startValue) => this.shortInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<ShortField, ShortField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class ShortInput : TextValueField<short>.TextValueInput
{
private ShortField parentByteField => (ShortField) this.parent;
internal ShortInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, short startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentByteField.isDelayed)
this.text = this.ValueToString((short)Mathf.Clamp(num,0,255));
else
this.parentByteField.value = (short)Mathf.Clamp(num,0,255);
}
protected override string ValueToString(short v) => v.ToString(this.formatString);
protected override short StringToValue(string str)
{
short num;
ClientSimUINumericFieldsUtils.TryConvertStringToShort(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ef408243da1c4500b4542b7f7cc933ce
timeCreated: 1717421533

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class UIntField : TextValueField<uint>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-UInt-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = UIntField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = UIntField.ussClassName + "__input";
private UIntField.UIntInput sbyteInput => (UIntField.UIntInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(uint v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override uint StringToValue(string str)
{
uint num;
return ClientSimUINumericFieldsUtils.TryConvertStringToUInt(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public UIntField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public UIntField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public UIntField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<uint>.TextValueInput) new UIntField.UIntInput())
{
this.AddToClassList(UIntField.ussClassName);
this.labelElement.AddToClassList(UIntField.labelUssClassName);
this.AddLabelDragger<uint>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, uint startValue) => this.sbyteInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<UIntField, UIntField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class UIntInput : TextValueField<uint>.TextValueInput
{
private UIntField parentUIntField => (UIntField) this.parent;
internal UIntInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, uint startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentUIntField.isDelayed)
this.text = this.ValueToString((uint)Mathf.Clamp(num,uint.MinValue,uint.MaxValue));
else
this.parentUIntField.value = (uint)Mathf.Clamp(num,uint.MinValue,uint.MaxValue);
}
protected override string ValueToString(uint v) => v.ToString(this.formatString);
protected override uint StringToValue(string str)
{
uint num;
ClientSimUINumericFieldsUtils.TryConvertStringToUInt(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d60bebe1b61e4fec9775a266a41b8ea9
timeCreated: 1716966716

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class ULongField : TextValueField<ulong>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-ulong-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = ULongField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = ULongField.ussClassName + "__input";
private ULongField.ULongInput sbyteInput => (ULongField.ULongInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(ulong v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override ulong StringToValue(string str)
{
ulong num;
return ClientSimUINumericFieldsUtils.TryConvertStringToULong(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public ULongField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public ULongField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public ULongField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<ulong>.TextValueInput) new ULongField.ULongInput())
{
this.AddToClassList(UIntField.ussClassName);
this.labelElement.AddToClassList(UIntField.labelUssClassName);
this.AddLabelDragger<ulong>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, ulong startValue) => this.sbyteInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<ULongField, ULongField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class ULongInput : TextValueField<ulong>.TextValueInput
{
private ULongField parentULongField => (ULongField) this.parent;
internal ULongInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, ulong startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentULongField.isDelayed)
this.text = this.ValueToString((uint)Mathf.Clamp(num,uint.MinValue,uint.MaxValue));
else
this.parentULongField.value = (uint)Mathf.Clamp(num,uint.MinValue,uint.MaxValue);
}
protected override string ValueToString(ulong v) => v.ToString(this.formatString);
protected override ulong StringToValue(string str)
{
ulong num;
ClientSimUINumericFieldsUtils.TryConvertStringToULong(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e4558ecaaa6441868c8be3649d1222b4
timeCreated: 1716968548

View File

@ -0,0 +1,150 @@
using System;
using System.Globalization;
using UnityEngine;
using UnityEngine.UIElements;
namespace VRC.SDK3.ClientSim.Editor.VisualElements.Fields
{
public class UShortField : TextValueField<ushort>
{
/// <summary>
/// <para>
/// USS class name of elements of this type.
/// </para>
/// </summary>
public new static readonly string ussClassName = "vrc-ushort-field";
/// <summary>
/// <para>
/// USS class name of labels in elements of this type.
/// </para>
/// </summary>
public new static readonly string labelUssClassName = UShortField.ussClassName + "__label";
/// <summary>
/// <para>
/// USS class name of input elements in elements of this type.
/// </para>
/// </summary>
public new static readonly string inputUssClassName = UShortField.ussClassName + "__input";
private UShortField.UShortInput ushortInput => (UShortField.UShortInput) this.textInputBase;
/// <summary>
/// <para>
/// Converts the given integer to a string.
/// </para>
/// </summary>
/// <param name="v">The integer to be converted to string.</param>
/// <returns>
/// <para>The integer as string.</para>
/// </returns>
protected override string ValueToString(ushort v) => v.ToString(this.formatString, (IFormatProvider) CultureInfo.InvariantCulture.NumberFormat);
/// <summary>
/// <para>
/// Converts a string to an integer.
/// </para>
/// </summary>
/// <param name="str">The string to convert.</param>
/// <returns>
/// <para>The integer parsed from the string.</para>
/// </returns>
protected override ushort StringToValue(string str)
{
ushort num;
return ClientSimUINumericFieldsUtils.TryConvertStringToUShort(str, this.textInputBase.text, out num) ? num : this.rawValue;
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
public UShortField()
: this((string) null)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
public UShortField(int maxLength)
: this((string) null, maxLength)
{
}
/// <summary>
/// <para>
/// Constructor.
/// </para>
/// </summary>
/// <param name="maxLength">Maximum number of characters the field can take.</param>
/// <param name="label"></param>
public UShortField(string label, int maxLength = -1)
: base(label, maxLength, (TextValueField<ushort>.TextValueInput) new UShortField.UShortInput())
{
this.AddToClassList(UShortField.ussClassName);
this.labelElement.AddToClassList(UShortField.labelUssClassName);
this.AddLabelDragger<ushort>();
}
/// <summary>
/// <para>
/// Applies the values of a 3D delta and a speed from an input device.
/// </para>
/// </summary>
/// <param name="delta">A vector used to compute the value change.</param>
/// <param name="speed">A multiplier for the value change.</param>
/// <param name="startValue">The start value.</param>
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, ushort startValue) => this.ushortInput.ApplyInputDeviceDelta(delta, speed, startValue);
/// <summary>
/// <para>
/// Instantiates an IntegerField using the data read from a UXML file.
/// </para>
/// </summary>
public new class UxmlFactory : UnityEngine.UIElements.UxmlFactory<UShortField, UShortField.UxmlTraits>
{
}
/// <summary>
/// <para>
/// Defines UxmlTraits for the IntegerField.
/// </para>
/// </summary>
public new class UxmlTraits : TextValueFieldTraits<int, UxmlIntAttributeDescription>
{
}
private class UShortInput : TextValueField<ushort>.TextValueInput
{
private UShortField parentByteField => (UShortField) this.parent;
internal UShortInput() => this.formatString = ClientSimUINumericFieldsUtils.k_ByteFieldFormatString;
protected override string allowedCharacters => ClientSimUINumericFieldsUtils.k_AllowedCharactersForByte;
public override void ApplyInputDeviceDelta(Vector3 delta, DeltaSpeed speed, ushort startValue)
{
double intDragSensitivity = (double) ClientSimNumericFieldDraggerUtility.CalculateIntDragSensitivity((long) startValue);
float acceleration = ClientSimNumericFieldDraggerUtility.Acceleration(speed == DeltaSpeed.Fast, speed == DeltaSpeed.Slow);
long num = (long) this.StringToValue(this.text) + (long) Math.Round((double) ClientSimNumericFieldDraggerUtility.NiceDelta((Vector2) delta, acceleration) * intDragSensitivity);
if (this.parentByteField.isDelayed)
this.text = this.ValueToString((ushort)Mathf.Clamp(num,0,255));
else
this.parentByteField.value = (ushort)Mathf.Clamp(num,0,255);
}
protected override string ValueToString(ushort v) => v.ToString(this.formatString);
protected override ushort StringToValue(string str)
{
ushort num;
ClientSimUINumericFieldsUtils.TryConvertStringToUShort(str, this.text, out num);
return num;
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 2134bc1ca6ec4053bcfecd5650b4efdf
timeCreated: 1717422039

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3f206055f222d444fb2cdfdd03156916
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,968 @@
#if VRC_ENABLE_PLAYER_PERSISTENCE
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Persistence;
using VRC.SDK3.Persistence;
using VRC.SDKBase;
using Random = UnityEngine.Random;
namespace VRC.SDK3.ClientSim.Editor
{
public class ClientSimPlayerDataWindow : EditorWindow
{
// max number of characters to display PlayerData values with (value strings longer than this are truncated)
private const int MAX_LENGTH_SINGLE_LINE = 50;
private const int MAX_LENGTH_MULTI_LINE = 10000;
// number of PlayerData elements to show per page
private const int PAGE_SIZE = 12;
// key used to store selected sort mode in player prefs
private const string SORT_MODE_KEY = "PlayerDataSortMode";
private bool hasLocalPlayerData;
private bool isLocalPlayerSelected = true;
private string localPlayerName;
private int page;
private int numPages;
private Dictionary<string, ClientSimPlayerDataPair> localPlayerData;
private IClientSimEventDispatcher eventDispatcher;
private VisualElement labelContainer;
private VisualElement noDataInfoContainer;
private VisualElement pagingContainer;
private Label pageLabel;
private readonly List<VisualElement> elementsToDisableInPlayMode = new();
private DropdownField playerDropdown;
private DropdownField sortDropdown;
private Button addRemotePlayerTestDataButton;
private Button nextPageButton;
private Button prevPageButton;
private Button clearDataButton;
private static string LocalPlayerDataRoot
{
get
{
string root = Path.GetDirectoryName(Application.dataPath);
string path = Path.Combine(root, ClientSimPlayerDataStorage.PlayerDataFolder);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
}
}
private static string LocalPlayerDataPath => LocalPlayerDataRoot + $"/PlayerData_1_{SceneManager.GetActiveScene().name}.json";
private static string LocalDataPathPattern => $"PlayerData_*_{SceneManager.GetActiveScene().name}.json";
[MenuItem("VRChat SDK/ClientSim PlayerData", false, 1500)]
public static void Init()
{
var window = GetWindow<ClientSimPlayerDataWindow>(false, "ClientSim PlayerData");
window.minSize = new Vector2(400, 400);
window.Show();
}
private static void SetData(ClientSimPlayerDataPair data, VRCPlayerApi player)
{
var playerData = player.GetClientSimPlayer().PlayerDataObject;
switch (data.Value.Type)
{
case ClientSimPlayerDataType.Vector2:
playerData.SetVector2(data.Key, data.Value.AsVector2());
break;
case ClientSimPlayerDataType.Vector3:
playerData.SetVector3(data.Key, data.Value.AsVector3());
break;
case ClientSimPlayerDataType.Vector4:
playerData.SetVector4(data.Key, data.Value.AsVector4());
break;
case ClientSimPlayerDataType.Quaternion:
playerData.SetQuaternion(data.Key, data.Value.AsQuaternion());
break;
case ClientSimPlayerDataType.Color:
playerData.SetColor(data.Key, data.Value.AsColor());
break;
case ClientSimPlayerDataType.Color32:
playerData.SetColor32(data.Key, data.Value.AsColor32());
break;
case ClientSimPlayerDataType.WrappedString:
playerData.SetString(data.Key, data.Value.AsWrappedString());
break;
case ClientSimPlayerDataType.WrappedShort:
playerData.SetShort(data.Key, data.Value.AsWrappedShort());
break;
case ClientSimPlayerDataType.WrappedInt:
playerData.SetInt(data.Key, data.Value.AsWrappedInt());
break;
case ClientSimPlayerDataType.WrappedFloat:
playerData.SetFloat(data.Key, data.Value.AsWrappedFloat());
break;
case ClientSimPlayerDataType.WrappedBool:
playerData.SetBool(data.Key, data.Value.AsWrappedBool());
break;
case ClientSimPlayerDataType.WrappedByte:
playerData.SetSByte(data.Key, data.Value.AsWrappedSByte());
break;
case ClientSimPlayerDataType.WrappedUByte:
playerData.SetByte(data.Key, data.Value.AsWrappedUByte());
break;
case ClientSimPlayerDataType.WrappedBytes:
playerData.SetBytes(data.Key, data.Value.AsWrappedBytes());
break;
case ClientSimPlayerDataType.WrappedUShort:
playerData.SetUShort(data.Key, data.Value.AsWrappedUShort());
break;
case ClientSimPlayerDataType.WrappedUInt:
playerData.SetUInt(data.Key, data.Value.AsWrappedUInt());
break;
case ClientSimPlayerDataType.WrappedULong:
playerData.SetULong(data.Key, data.Value.AsWrappedULong());
break;
case ClientSimPlayerDataType.WrappedDouble:
playerData.SetDouble(data.Key, data.Value.AsWrappedDouble());
break;
case ClientSimPlayerDataType.WrappedLong:
playerData.SetLong(data.Key, data.Value.AsWrappedLong());
break;
}
}
public void OnEnable()
{
VisualElement root = rootVisualElement;
VisualTreeAsset visualTree = Resources.Load<VisualTreeAsset>(nameof(ClientSimPlayerDataWindow));
root.Add(visualTree.CloneTree());
Button openDataFolderButton = root.Q<Button>("OpenDataFolderButton");
Button refreshDataButton = root.Q<Button>("RefreshDataButton");
clearDataButton = root.Q<Button>("ClearDataButton");
nextPageButton = root.Q<Button>("NextPageButton");
prevPageButton = root.Q<Button>("PreviousPageButton");
addRemotePlayerTestDataButton = root.Q<Button>("RandomizeButton");
playerDropdown = root.Q<DropdownField>("PlayerDropdown");
sortDropdown = root.Q<DropdownField>("SortDropdown");
noDataInfoContainer = root.Q<VisualElement>("NoDataInfo");
pagingContainer = root.Q<VisualElement>("Paging");
pageLabel = root.Q<Label>("PageLabel");
ScrollView playerDataList = root.Q<ScrollView>("PlayerDataList");
labelContainer = new VisualElement();
playerDataList.Add(labelContainer);
openDataFolderButton.clicked += OpenLocalDataDirectory;
refreshDataButton.clicked += RefreshPlayerData;
clearDataButton.clicked += ClearPlayerData;
nextPageButton.clicked += NextPage;
prevPageButton.clicked += PreviousPage;
addRemotePlayerTestDataButton.clicked += AddRemotePlayerTestData;
// dropdown always includes local player so that users can view PlayerData outside of play mode
CheckLocalPlayerName();
playerDropdown.choices = new List<string> { localPlayerName };
playerDropdown.index = 0;
playerDropdown.RegisterValueChangedCallback(OnPlayerSelected);
// load sort mode from player prefs - it will be applied in LoadPlayerData
sortDropdown.SetValueWithoutNotify(PlayerPrefs.GetString(SORT_MODE_KEY, "Alphabetically"));
sortDropdown.RegisterValueChangedCallback(OnSortSelected);
LoadPlayerData();
UpdatePage(false);
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
if (EditorApplication.isPlaying)
{
OnPlayModeStateChanged(PlayModeStateChange.EnteredPlayMode);
}
// reload data on scene switch
EditorSceneManager.sceneOpened += (_, _) => LoadPlayerData();
}
private void AddRemotePlayerTestData()
{
object RandomizePlayerDataValue(ClientSimPlayerDataTypeUnion dataPoint)
{
return dataPoint.Type switch
{
ClientSimPlayerDataType.Vector2 => new Vector2(Random.Range(0,100), Random.Range(0,100)),
ClientSimPlayerDataType.Vector3 => new Vector3(Random.Range(0,100), Random.Range(0,100), Random.Range(0,100)),
ClientSimPlayerDataType.Vector4 => new Vector4(Random.Range(0,100), Random.Range(0,100), Random.Range(0,100), Random.Range(0,100)),
ClientSimPlayerDataType.Quaternion => new Quaternion(Random.Range(0,100), Random.Range(0,100), Random.Range(0,100), Random.Range(0,100)),
ClientSimPlayerDataType.Color => new Color(Random.Range(0,1f), Random.Range(0,1f), Random.Range(0,1f), Random.Range(0,1f)),
ClientSimPlayerDataType.Color32 => (Color32)new Color(Random.Range(0,1f), Random.Range(0,1f), Random.Range(0,1f), Random.Range(0,1f)),
ClientSimPlayerDataType.WrappedString => MakeRandomString(),
ClientSimPlayerDataType.WrappedShort => (short)Random.Range(-10, 10),
ClientSimPlayerDataType.WrappedInt => Random.Range(-100, 100),
ClientSimPlayerDataType.WrappedFloat => Random.Range(0, 1f),
ClientSimPlayerDataType.WrappedBool => Random.Range(0, 2) != 0,
ClientSimPlayerDataType.WrappedByte => (sbyte)Random.Range(sbyte.MinValue, sbyte.MaxValue),
ClientSimPlayerDataType.WrappedUByte => (byte)Random.Range(byte.MinValue, byte.MaxValue),
ClientSimPlayerDataType.WrappedBytes => MakeRandomByteArray(),
ClientSimPlayerDataType.WrappedUShort => (ushort)Random.Range(0, 10),
ClientSimPlayerDataType.WrappedUInt => (uint)Random.Range(0, 100),
ClientSimPlayerDataType.WrappedULong => (ulong)Random.Range(0, 1000),
ClientSimPlayerDataType.WrappedDouble => (double)Random.Range(0, 0.1f),
ClientSimPlayerDataType.WrappedLong => (long)Random.Range(-1000, 1000),
_ => default
};
}
string MakeRandomString()
{
const string characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
string result = "";
for (int i = 0; i < 10; i++)
{
result += characters[Random.Range(0, characters.Length)];
}
return result;
}
byte[] MakeRandomByteArray()
{
byte[] randomBytes = new byte[2];
for (int i = 0; i < randomBytes.Length; i++)
{
randomBytes[i] = (byte)Random.Range(byte.MinValue, byte.MaxValue + 1);
}
return randomBytes;
}
string playerName = playerDropdown.value;
VRCPlayerApi player = VRCPlayerApi.AllPlayers.Find(p => p.displayName.Equals(playerName));
if (player.isLocal) return;
foreach (var kvp in localPlayerData)
{
var randomizedValue = RandomizePlayerDataValue(kvp.Value.Value);
var typeUnion = new ClientSimPlayerDataTypeUnion
{
Type = kvp.Value.Value.Type,
Value = randomizedValue
};
var dataPair = new ClientSimPlayerDataPair
{
Key = kvp.Key,
Value = typeUnion
};
SetData(dataPair, player);
}
}
private void OnPlayerSelected(ChangeEvent<string> playerSelectedEvent)
{
string selectedPlayerName = playerSelectedEvent.newValue;
bool isLocalPlayer = string.Equals(selectedPlayerName, localPlayerName);
addRemotePlayerTestDataButton.style.display = isLocalPlayer || !hasLocalPlayerData
? DisplayStyle.None
: DisplayStyle.Flex;
isLocalPlayerSelected = isLocalPlayer;
if (isLocalPlayer)
{
LoadPlayerData(Networking.LocalPlayer);
return;
}
foreach (VRCPlayerApi player in VRCPlayerApi.AllPlayers)
{
if (player.displayName.Equals(selectedPlayerName))
{
LoadPlayerData(player);
break;
}
}
}
private void OnSortSelected(ChangeEvent<string> evt)
{
PlayerPrefs.SetString(SORT_MODE_KEY, evt.newValue);
LoadPlayerDataForSelectedPlayer();
}
private void LoadPlayerDataForSelectedPlayer()
{
if (!EditorApplication.isPlaying)
{
LoadPlayerData();
return;
}
string selectedPlayerName = playerDropdown.value;
foreach (VRCPlayerApi player in VRCPlayerApi.AllPlayers)
{
if (player.displayName.Equals(selectedPlayerName))
{
LoadPlayerData(player);
break;
}
}
}
private void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
private void LoadPlayerData(VRCPlayerApi player = null, bool broadcastPlayerDataUpdated = false)
{
// outside of play mode, we don't have a player game object
string path = player == null ? LocalPlayerDataPath : ClientSimPlayerDataStorage.PlayerDataFilePath(player);
if (!File.Exists(path))
{
File.WriteAllText(path, "{}");
CheckDataEmpty();
}
else
{
string json = File.ReadAllText(path);
var data = JsonConvert.DeserializeObject<Dictionary<string, ClientSimPlayerDataPair>>(json);
UpdatePlayerDataList(data, player);
// when adding test data for a remote player, let the rest of the system know
if (EditorApplication.isPlaying && broadcastPlayerDataUpdated)
{
var infos = data
.Select(kvp => new PlayerData.Info(kvp.Key, PlayerData.State.Added))
.ToArray();
player.GetClientSimPlayer().PlayerDataObject.QueuePlayerDataUpdate(infos);
}
}
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (!ClientSimMain.TryGetInstance(out var instance)) return;
if (state == PlayModeStateChange.EnteredPlayMode)
{
eventDispatcher = instance.GetEventDispatcher();
eventDispatcher.Subscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
eventDispatcher.Subscribe<ClientSimOnPlayerDataUpdatedEvent>(OnPlayerDataUpdated);
eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
eventDispatcher.Subscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
CheckLocalPlayerName();
playerDropdown.choices[0] = localPlayerName;
playerDropdown.value = localPlayerName;
elementsToDisableInPlayMode.ForEach(e => e.SetEnabled(false));
}
else if (state == PlayModeStateChange.ExitingPlayMode)
{
playerDropdown.choices.RemoveRange(1, playerDropdown.choices.Count - 1);
playerDropdown.index = 0;
eventDispatcher?.Unsubscribe<ClientSimOnPlayerRestoredEvent>(OnPlayerRestored);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerDataUpdatedEvent>(OnPlayerDataUpdated);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
eventDispatcher = null;
elementsToDisableInPlayMode.ForEach(e => e.SetEnabled(true));
}
}
private void OnPlayerRestored(ClientSimOnPlayerRestoredEvent payload)
{
LoadPlayerData(payload.player);
}
private void OnPlayerDataUpdated(ClientSimOnPlayerDataUpdatedEvent payload)
{
UpdatePlayerDataList(payload.playerData, payload.player);
}
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
{
// local player is initialized via ClientSim init flow
if (payload.player.isLocal) return;
if (!playerDropdown.choices.Contains(payload.player.displayName))
{
playerDropdown.choices.Add(payload.player.displayName);
}
}
private void OnPlayerLeft(ClientSimOnPlayerLeftEvent payload)
{
if (payload.player.isLocal) return;
if (playerDropdown.choices.Contains(payload.player.displayName))
{
playerDropdown.choices.Remove(payload.player.displayName);
}
if (playerDropdown.value.Equals(payload.player.displayName))
{
playerDropdown.index = 0;
}
}
private void UpdatePlayerDataList(Dictionary<string, ClientSimPlayerDataPair> playerData, VRCPlayerApi player = null, bool redraw = true)
{
numPages = playerData.Count % PAGE_SIZE == 0
? playerData.Count / PAGE_SIZE
: playerData.Count / PAGE_SIZE + 1;
UpdatePage(false);
if (player != null)
{
// show button for adding test data if local player has data and a remote player is selected
if (player.isLocal)
{
localPlayerData = playerData;
if (!hasLocalPlayerData && localPlayerData.Count > 0)
{
hasLocalPlayerData = true;
if (!isLocalPlayerSelected)
{
addRemotePlayerTestDataButton.style.display = DisplayStyle.Flex;
}
}
}
// only update display if data is updated for currently selected player
if (!string.Equals(player.displayName, playerDropdown.value)) return;
}
if (!redraw || CheckDataEmpty(playerData.Count))
{
return;
}
var sorted = sortDropdown.value switch
{
// sorts by key
"Alphabetically" => playerData.OrderBy(x => x.Key).ToList(),
// sorts by what keys were most recently set
"Last Updated" => playerData
.OrderBy(x => x.Value.LastUpdated)
.ThenBy(x => x.Key)
.Reverse()
.ToList(),
// sorts data as shown in the JSON
_ => playerData.ToList()
};
// color fields don't have an isDelayed property, so we disable them in play mode
elementsToDisableInPlayMode.Clear();
int pageIndex = 0;
int labelIndex = 0;
foreach (var kvp in sorted)
{
// paging
if (labelIndex >= PAGE_SIZE)
{
labelIndex = 0;
pageIndex++;
}
labelIndex++;
if (pageIndex != page) continue;
VisualElement valueField;
switch (kvp.Value.Value.Type)
{
case ClientSimPlayerDataType.Vector2:
Vector2Field vector2Field = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsVector2()
};
SetIsDelayedForCompositeField(vector2Field);
vector2Field.RegisterValueChangedCallback(ValueFieldCallback);
valueField = vector2Field;
break;
case ClientSimPlayerDataType.Vector3:
Vector3Field vector3Field = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsVector3()
};
SetIsDelayedForCompositeField(vector3Field);
vector3Field.RegisterValueChangedCallback(ValueFieldCallback);
valueField = vector3Field;
break;
case ClientSimPlayerDataType.Vector4:
Vector4Field vector4Field = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsVector4()
};
SetIsDelayedForCompositeField(vector4Field);
vector4Field.RegisterValueChangedCallback(ValueFieldCallback);
valueField = vector4Field;
break;
// show quaternions as euler angles, so they're more intuitive to edit
case ClientSimPlayerDataType.Quaternion:
Vector3Field quaternionField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsQuaternion().eulerAngles
};
SetIsDelayedForCompositeField(quaternionField);
quaternionField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = quaternionField;
break;
case ClientSimPlayerDataType.Color:
ColorField colorField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsColor()
};
colorField.RegisterValueChangedCallback(ValueFieldCallback);
colorField.SetEnabled(!EditorApplication.isPlaying);
elementsToDisableInPlayMode.Add(colorField);
valueField = colorField;
break;
// color32 is shown as a regular color field
case ClientSimPlayerDataType.Color32:
ColorField color32Field = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsColor32()
};
color32Field.RegisterValueChangedCallback(ValueFieldCallback);
color32Field.SetEnabled(!EditorApplication.isPlaying);
elementsToDisableInPlayMode.Add(color32Field);
valueField = color32Field;
break;
// string fields are multiline
case ClientSimPlayerDataType.WrappedString:
TextField textField = new()
{
label = kvp.Key,
value = Truncate(true, kvp.Value.Value.AsWrappedString()),
isDelayed = true,
multiline = true,
maxLength = MAX_LENGTH_MULTI_LINE
};
textField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = textField;
break;
case ClientSimPlayerDataType.WrappedShort:
IntegerField shortField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedShort(),
isDelayed = true
};
shortField.RegisterValueChangedCallback(e =>
{
if (e.newValue is >= short.MinValue and <= short.MaxValue)
{
ValueFieldCallback(e);
}
else
{
shortField.SetValueWithoutNotify(e.previousValue);
}
});
valueField = shortField;
break;
case ClientSimPlayerDataType.WrappedInt:
IntegerField intField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedInt(),
isDelayed = true
};
intField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = intField;
break;
case ClientSimPlayerDataType.WrappedFloat:
FloatField floatField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedFloat(),
isDelayed = true
};
floatField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = floatField;
break;
case ClientSimPlayerDataType.WrappedBool:
Toggle toggle = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedBool()
};
toggle.RegisterValueChangedCallback(ValueFieldCallback);
valueField = toggle;
break;
case ClientSimPlayerDataType.WrappedByte:
IntegerField sbyteField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedSByte(),
isDelayed = true
};
sbyteField.RegisterValueChangedCallback(e =>
{
if (e.newValue is >= sbyte.MinValue and <= sbyte.MaxValue)
{
ValueFieldCallback(e);
}
else
{
sbyteField.SetValueWithoutNotify(e.previousValue);
}
});
valueField = sbyteField;
break;
// byte arrays are readonly to avoid confusion about encoding and format
// users can still manually set byte array values in the JSON if needed
case ClientSimPlayerDataType.WrappedBytes:
var byteArray = kvp.Value.Value.AsWrappedBytes();
string readableValue = byteArray.Aggregate("[ ", (current, b) => current + $"{(int)b} ") + "]";
TextField bytesField = new()
{
label = kvp.Key,
value = Truncate(false, readableValue),
isDelayed = true,
maxLength = MAX_LENGTH_SINGLE_LINE
};
bytesField.SetEnabled(false);
valueField = bytesField;
break;
case ClientSimPlayerDataType.WrappedUShort:
UnsignedIntegerField ushortField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedUShort(),
isDelayed = true,
};
ushortField.RegisterValueChangedCallback(e =>
{
if (e.newValue <= ushort.MaxValue)
{
ValueFieldCallback(e);
}
else
{
ushortField.SetValueWithoutNotify(e.previousValue);
}
});
valueField = ushortField;
break;
case ClientSimPlayerDataType.WrappedUByte:
UnsignedIntegerField byteField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedUByte(),
isDelayed = true,
};
byteField.RegisterValueChangedCallback(e =>
{
if (e.newValue <= byte.MaxValue)
{
ValueFieldCallback(e);
}
else
{
byteField.SetValueWithoutNotify(e.previousValue);
}
});
valueField = byteField;
break;
case ClientSimPlayerDataType.WrappedUInt:
UnsignedIntegerField uintField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedUInt(),
isDelayed = true,
};
uintField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = uintField;
break;
case ClientSimPlayerDataType.WrappedULong:
UnsignedLongField ulongField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedULong(),
isDelayed = true,
};
ulongField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = ulongField;
break;
case ClientSimPlayerDataType.WrappedDouble:
DoubleField doubleField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedDouble(),
isDelayed = true,
};
doubleField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = doubleField;
break;
case ClientSimPlayerDataType.WrappedLong:
LongField longField = new()
{
label = kvp.Key,
value = kvp.Value.Value.AsWrappedLong(),
isDelayed = true,
};
longField.RegisterValueChangedCallback(ValueFieldCallback);
valueField = longField;
break;
default:
TextField field = new()
{
label = kvp.Key,
value = Truncate(false, kvp.Value.Value.Value.ToString()),
isDelayed = true,
maxLength = MAX_LENGTH_SINGLE_LINE
};
field.RegisterValueChangedCallback(ValueFieldCallback);
valueField = field;
break;
}
labelContainer.Add(valueField);
continue;
// truncate a value
string Truncate(bool isMultiline, string value)
{
const string end = "... (truncated)";
int maxLength = isMultiline ? MAX_LENGTH_MULTI_LINE : MAX_LENGTH_SINGLE_LINE;
return value.Length > maxLength
? $"{value.Truncate(maxLength - end.Length)}{end}"
: value;
}
// in play mode, set value through PlayerData interface
// in edit mode, update the JSON directly
void ValueFieldCallback<T>(ChangeEvent<T> evt)
{
ClientSimPlayerDataTypeUnion typeUnion = new ClientSimPlayerDataTypeUnion
{
Type = kvp.Value.Value.Type,
Value = evt.newValue
};
// quaternions are displayed as Vector3 eulers
if (kvp.Value.Value.Type == ClientSimPlayerDataType.Quaternion && evt.newValue is Vector3 eulers)
{
typeUnion.Value = Quaternion.Euler(eulers);
}
// color32s are displayed as colors
if (kvp.Value.Value.Type == ClientSimPlayerDataType.Color32 && evt.newValue is Color color)
{
typeUnion.Value = (Color32)color;
}
// bytes are displayed ints
if (kvp.Value.Value.Type == ClientSimPlayerDataType.WrappedUByte)
{
typeUnion.Value = Convert.ToByte(evt.newValue);
}
if (kvp.Value.Value.Type == ClientSimPlayerDataType.WrappedByte)
{
typeUnion.Value = Convert.ToSByte(evt.newValue);
}
ClientSimPlayerDataPair dataPair = new() { Key = kvp.Key, Value = typeUnion };
if (EditorApplication.isPlaying)
{
SetData(dataPair, player);
}
else
{
string json = File.ReadAllText(LocalPlayerDataPath);
var data = JsonConvert.DeserializeObject<Dictionary<string, ClientSimPlayerDataPair>>(json);
data[kvp.Key] = dataPair;
json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(LocalPlayerDataPath, json);
// After serializing the new value, reload it into ClientSim PlayerData and refresh the UI.
// Note: Color fields lack an isDelayed property and serialize every frame during editing.
// Rebuilding the UI while a User edits a color will break the field's update loop.
// To avoid this, we update ClientSim PlayerData directly and skip the UI rebuild.
// If the user has sorted fields by "Last Updated", the color field will not sort to the top from this.
if (kvp.Value.Value.Type is ClientSimPlayerDataType.Color or ClientSimPlayerDataType.Color32)
{
UpdatePlayerDataList(data, player, false);
}
else
{
UpdatePlayerDataList(data, player);
}
}
}
// is there a better way to find all FloatField children and their immediate parent?
void SetIsDelayedForCompositeField(VisualElement compositeField)
{
VisualElement container = compositeField.contentContainer.Children().ToList()[1];
container.style.flexShrink = 1;
foreach (var child in container.Children())
{
if (child is FloatField f)
{
f.isDelayed = true;
}
}
}
}
}
private void RefreshPlayerData()
{
if (!EditorApplication.isPlaying)
{
LoadPlayerData();
}
else
{
foreach (var player in VRCPlayerApi.AllPlayers)
{
player.GetClientSimPlayer().PlayerDataObject.Decode(false);
}
}
}
private static void OpenLocalDataDirectory()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start("explorer.exe", LocalPlayerDataRoot.Replace("/", "\\"));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", LocalPlayerDataRoot);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", LocalPlayerDataRoot);
}
}
private void ClearPlayerData()
{
StringBuilder clearedFiles = new StringBuilder("Cleared PlayerData files:");
string[] playerDataFiles = Directory.GetFiles(LocalPlayerDataRoot, LocalDataPathPattern);
foreach (var file in playerDataFiles)
{
File.Delete(file);
clearedFiles.Append($"\n\t{file}");
// in play mode, let other systems know PlayerData was cleared
if (EditorApplication.isPlaying)
{
string playerId = file.Split("_")[1].Split(".")[0];
int id = int.Parse(playerId);
var player = VRCPlayerApi.GetPlayerById(id);
{
eventDispatcher.SendEvent(new ClientSimOnPlayerDataClearedEvent { player = player });
}
}
hasLocalPlayerData = false;
if (!isLocalPlayerSelected)
{
addRemotePlayerTestDataButton.style.display = DisplayStyle.None;
}
CheckDataEmpty();
}
page = 0;
numPages = 0;
UpdatePage();
if (playerDataFiles.Length > 0)
{
this.Log(clearedFiles.ToString());
}
}
private void NextPage()
{
page++;
UpdatePage();
}
private void PreviousPage()
{
page--;
UpdatePage();
}
private void UpdatePage(bool reload = true)
{
bool isFirstPage = page == 0;
bool isLastPage = page == numPages - 1;
prevPageButton.SetEnabled(!isFirstPage);
nextPageButton.SetEnabled(!isLastPage);
pageLabel.text = $"{page+1} / {numPages}";
if (reload)
{
LoadPlayerDataForSelectedPlayer();
}
}
private bool CheckDataEmpty(int numPlayerData = 0)
{
bool isEmpty = numPlayerData == 0;
clearDataButton.SetEnabled(!isEmpty);
noDataInfoContainer.style.display = isEmpty ? DisplayStyle.Flex : DisplayStyle.None;
sortDropdown.style.display = isEmpty ? DisplayStyle.None : DisplayStyle.Flex;
pagingContainer.style.display = numPlayerData > PAGE_SIZE ? DisplayStyle.Flex : DisplayStyle.None;
labelContainer.Clear();
return isEmpty;
}
private void CheckLocalPlayerName()
{
localPlayerName = string.IsNullOrEmpty(ClientSimSettings.Instance.customLocalPlayerName)
? "[1] Local Player"
: ClientSimSettings.Instance.customLocalPlayerName;
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4b21b5684b10448b9b0a445cfc16fcfd
timeCreated: 1709067526

View File

@ -0,0 +1,296 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using VRC.SDK3.ClientSim.Editor.VisualElements;
using VRC.SDK3.ClientSim.Interfaces;
using VRC.SDKBase;
namespace VRC.SDK3.ClientSim.Editor
{
#if VRC_ENABLE_PLAYER_PERSISTENCE
public class ClientSimPlayerObjectWindow : EditorWindow
{
private Dictionary<int, List<PlayerObjectData>> playerObjects = new Dictionary<int, List<PlayerObjectData>>();
private List<ClientSimNetworkHolderInstanceElement> playerObjectVisualElements = new List<ClientSimNetworkHolderInstanceElement>();
private List<VRCPlayerApi> players = new List<VRCPlayerApi>();
private IClientSimEventDispatcher eventDispatcher;
private DropdownField playerDropdown;
private DropdownField Sort;
private Label pageLabel;
private Button nextPage;
private Button prevPage;
private TextField searchField;
private ScrollView playerDataList;
private VisualElement HelpBox;
private Label HelpBoxLabel;
private int CurrentIndex = 0;
private int CurrentSelectedPlayerID = -1;
private static int CountPerPage = 10;
private static string HelpTextEnterPlayMode = "Enter Play Mode to view PlayerObjects";
[MenuItem("VRChat SDK/ClientSim PlayerObjects", false, 1500)]
public static void Init()
{
var window = GetWindow<ClientSimPlayerObjectWindow>(false, "ClientSim PlayerObjects");
window.minSize = new Vector2(400, 400);
window.Show();
}
public void OnEnable()
{
VisualElement root = rootVisualElement;
VisualTreeAsset visualTree = Resources.Load<VisualTreeAsset>(nameof(ClientSimPlayerObjectWindow));
visualTree.CloneTree(root);
playerDropdown = root.Q<DropdownField>("PlayerDropdown");
playerDropdown.choices = new List<string>();
playerDropdown.RegisterValueChangedCallback((evt) =>
{
CurrentSelectedPlayerID = players[playerDropdown.index].playerId;
CurrentIndex = 0;
pageLabel.text = "Page 0";
UpdateCurrentPage(null);
});
Sort = root.Q<DropdownField>("Sort");
Sort.choices = new List<string>()
{
"Alphabetical",
"Last modified"
};
Sort.SetValueWithoutNotify("Alphabetical");
searchField = root.Q<TextField>("Search");
searchField.RegisterValueChangedCallback((evt) =>
{
UpdateCurrentPage(null);
});
playerDataList = root.Q<ScrollView>("PlayerObjectList");
nextPage = root.Q<Button>("Right");
nextPage.clicked += NextPage;
nextPage.style.display = DisplayStyle.None;
prevPage = root.Q<Button>("Left");
prevPage.clicked += PreviousPage;
prevPage.style.display = DisplayStyle.None;
pageLabel = root.Q<Label>("PagingLabel");
pageLabel.text = "Page 1";
HelpBox = root.Q<VisualElement>("HelpTextBox");
HelpBoxLabel = HelpBox.Q<Label>("HelpTextBoxLabel");
HelpBoxLabel.text = HelpTextEnterPlayMode;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
if (EditorApplication.isPlaying)
{
OnPlayModeStateChanged(PlayModeStateChange.EnteredPlayMode);
}
VRCPlayerApi[] newplayers = new VRCPlayerApi[VRCPlayerApi.GetPlayerCount()];
newplayers = VRCPlayerApi.GetPlayers(newplayers);
foreach (VRCPlayerApi player in newplayers)
{
ClientSimOnPlayerJoinedEvent playerJoinedEvent = new ClientSimOnPlayerJoinedEvent();
playerJoinedEvent.player = player;
OnPlayerJoined(playerJoinedEvent);
}
for(int i = 0; i < CountPerPage; i++)
{
ClientSimNetworkHolderInstanceElement element = new ClientSimNetworkHolderInstanceElement();
playerObjectVisualElements.Add(element);
playerDataList.Add(element);
element.style.display = DisplayStyle.None;
}
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (!ClientSimMain.TryGetInstance(out var instance)) return;
if (state == PlayModeStateChange.EnteredPlayMode)
{
eventDispatcher = instance.GetEventDispatcher();
eventDispatcher.Subscribe<ClientSimOnPlayerObjectUpdatedEvent>(OnPlayerObjectUpdated);
eventDispatcher.Subscribe<ClientSimOnPlayerObjectUpdateEndedEvent>(UpdateCurrentPage);
eventDispatcher.Subscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
eventDispatcher.Subscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
nextPage.style.display = DisplayStyle.Flex;
HelpBox.style.display = DisplayStyle.None;
}
else if (state == PlayModeStateChange.ExitingPlayMode)
{
playerDropdown.choices.RemoveRange(1, playerDropdown.choices.Count - 1);
playerDropdown.index = 0;
playerDataList.Clear();
CurrentIndex = 0;
CurrentSelectedPlayerID = -1;
nextPage.style.display = DisplayStyle.None;
prevPage.style.display = DisplayStyle.None;
HelpBox.style.display = DisplayStyle.Flex;
eventDispatcher?.Unsubscribe<ClientSimOnPlayerObjectUpdatedEvent>(OnPlayerObjectUpdated);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerObjectUpdateEndedEvent>(UpdateCurrentPage);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerJoinedEvent>(OnPlayerJoined);
eventDispatcher?.Unsubscribe<ClientSimOnPlayerLeftEvent>(OnPlayerLeft);
eventDispatcher = null;
}
}
private void NextPage()
{
CurrentIndex += CountPerPage;
pageLabel.text = "Page " + (CurrentIndex / CountPerPage + 1);
if(CurrentIndex+CountPerPage > playerObjects[CurrentSelectedPlayerID].Count)
nextPage.style.display = DisplayStyle.None;
else
{
nextPage.style.display = DisplayStyle.Flex;
prevPage.style.display = DisplayStyle.Flex;
}
}
private void PreviousPage()
{
CurrentIndex -= CountPerPage;
pageLabel.text = "Page " + (CurrentIndex / CountPerPage + 1);
if(CurrentIndex <= 0)
prevPage.style.display = DisplayStyle.None;
else
{
prevPage.style.display = DisplayStyle.Flex;
nextPage.style.display = DisplayStyle.Flex;
}
}
private void OnPlayerObjectUpdated(ClientSimOnPlayerObjectUpdatedEvent payload)
{
int playerId = payload.Data.GetPlayerId();
if(playerObjects.TryGetValue(playerId, out var instanceElements))
{
int index = instanceElements.FindIndex((x) => x.NetworkView == payload.Data);
if (index == -1)
{
AddElementToList(payload);
}
} else {
playerObjects[playerId] = new List<PlayerObjectData>();
AddElementToList(payload);
}
}
private void AddElementToList(ClientSimOnPlayerObjectUpdatedEvent payload)
{
int componentCount = payload.Data.GetNetworkComponentCount();
int playerId = payload.Data.GetPlayerId();
for (int i = 0; i < componentCount; i++)
{
MonoBehaviour component = payload.Data.GetNetworkComponents()[i];
PlayerObjectData element =
new PlayerObjectData
{
NetworkView = payload.Data,
lastModified = DateTime.Now,
Name = $"{component.GetType().Name} ({component.gameObject.name})",
};
playerObjects[playerId].Add(element);
}
}
private void UpdateCurrentPage(ClientSimOnPlayerObjectUpdateEndedEvent e)
{
if(playerObjects.Count == 0 || CurrentSelectedPlayerID == -1)
return;
int index = CurrentIndex;
int count = playerObjects[CurrentSelectedPlayerID].Count;
int localIndex = 0;
for (; index < count;)
{
PlayerObjectData element = playerObjects[CurrentSelectedPlayerID][index];
if(searchField.value != "" && !element.Name.Contains(searchField.value))
{
index++;
continue;
}
playerObjectVisualElements[localIndex].UpdateData(element);
localIndex++;
if(localIndex >= CountPerPage)
break;
index++;
}
for (int i = localIndex; i < playerObjectVisualElements.Count; i++)
{
playerObjectVisualElements[i].style.display = DisplayStyle.None;
}
prevPage.style.display = CurrentIndex - CountPerPage < 0 ? DisplayStyle.None : DisplayStyle.Flex;
nextPage.style.display = CurrentIndex + CountPerPage >= count ? DisplayStyle.None : DisplayStyle.Flex;
pageLabel.style.display = prevPage.style.display == DisplayStyle.None && nextPage.style.display == DisplayStyle.None ? DisplayStyle.Flex : DisplayStyle.None;
}
private void OnPlayerJoined(ClientSimOnPlayerJoinedEvent payload)
{
if (players.Contains(payload.player)) return;
if(playerObjects.ContainsKey(payload.player.playerId) == false)
playerObjects.Add(payload.player.playerId, new List<PlayerObjectData>());
playerDropdown.choices.Add(payload.player.displayName);
players.Add(payload.player);
if (CurrentSelectedPlayerID == -1)
{
playerDropdown.index = 0;
CurrentSelectedPlayerID = payload.player.playerId;
}
}
private void OnPlayerLeft(ClientSimOnPlayerLeftEvent payload)
{
if (!playerObjects.ContainsKey(payload.player.playerId)) return;
playerObjects.Remove(payload.player.playerId);
players.Remove(payload.player);
playerDropdown.choices.Remove(playerDropdown.choices.Find(x => x == payload.player.displayName));
if (CurrentSelectedPlayerID == payload.player.playerId)
{
playerDropdown.index = 0;
CurrentSelectedPlayerID = players[0].playerId;
}
}
public struct PlayerObjectData
{
public IClientSimNetworkSerializer NetworkView;
public DateTime lastModified;
public string Name;
public int index;
}
}
#endif
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 780b242d9ac449059aaf28150bd5a6ef
timeCreated: 1715609826

View File

@ -0,0 +1,511 @@

using UnityEngine;
using UnityEditor;
using VRC.SDKBase;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine.InputSystem;
namespace VRC.SDK3.ClientSim.Editor
{
public class ClientSimSettingsWindow : EditorWindow
{
// General content
private readonly GUIContent _generalFoldoutGuiContent = new GUIContent("General Settings", "");
private readonly GUIContent _enableToggleGuiContent = new GUIContent("Enable ClientSim", "If enabled, all triggers will function similarly to VRChat. Note that behavior may be different than the actual game!");
private readonly GUIContent _displayLogsToggleGuiContent = new GUIContent("Enable Console Logging", "Enabling logging will print messages to the console when certain events happen. Examples include trigger execution, pickup grabbed, station entered, etc.");
private readonly GUIContent _deleteEditorOnlyToggleGuiContent = new GUIContent("Remove \"EditorOnly\"", "Enabling this setting will ensure that all objects with the tag \"EditorOnly\" are deleted when in playmode. This can be helpful in finding objects that will not be uploaded with your world. Enable console logging to see which objects are deleted.");
private readonly GUIContent _startupDelayGuiContent = new GUIContent("Startup Delay", "The duration that the Client Sim will wait to simulate the VRChat client loading before spawning the player and initializing Udon. This is useful to test when Unity components behave differently at startup compared to VRChat.");
private readonly GUIContent _stopOnScriptChangesToggleGuiContent = new GUIContent("Stop On Script Changes", "If enabled, the editor will stop if script changes are detected while in play mode. This will override the Unity Editor setting 'Preferences > General > Script Changes While Playing'.");
private readonly GUIContent _hideMenuOnLaunchToggleGuiContent = new GUIContent("Hide Menu On Launch", "Enabling this setting will prevent the ClientSim menu from being displayed when initially entering play mode.");
private readonly GUIContent _setTargetFrameRateGuiContent = new GUIContent("Set Target FrameRate", "Should ClientSim set the target framerate on startup? This will automatically set the physics delta time to match expected framerate. Disabling this setting is useful when profiling.");
private readonly GUIContent _targetFrameRateGuiContent = new GUIContent("Target FrameRate", "The target framerate unity should aim for. Default is 90 fps.");
// Player Controller content
private readonly GUIContent _playerControllerFoldoutGuiContent = new GUIContent("Player Controller Settings", "");
private readonly GUIContent _playerControllerToggleGuiContent = new GUIContent("Spawn Player Controller", "If enabled, a player controller will spawn and allow you to move around your world as if in desktop mode. Supports interacts and pickups.");
private readonly GUIContent _showDesktopReticleGuiContent = new GUIContent("Show Desktop Reticle", "Show or hide the center Desktop reticle image.");
private readonly GUIContent _showTooltipsGuiContent = new GUIContent("Show Tooltips", "If enabled, hovering over an interactable object or pickup will display a tooltip above the object.");
private readonly GUIContent _invertMouseLookGuiContent = new GUIContent("Invert Mouse Look", "If enabled, moving the mouse up or down will invert the direction the player will look up and down.");
private int selectedLanguageIndex;
// Player settings
private readonly GUIContent _playerButtonsFoldoutGuiContent = new GUIContent("Player Settings", "");
private readonly GUIContent _localPlayerCustomNameGuiContent = new GUIContent("Local Player Name", "Set a custom name for the local player. Useful for testing udon script name detection");
private readonly GUIContent _playerHeightGuiContent = new GUIContent("Initial Player Height", "How tall should the player be in meters at app start. Default height is 1.9. Note that the player's collision capsule is 1.6 and never changes.");
private readonly GUIContent _currentLanguageGuiContent = new GUIContent("Current Language", "The language the player is currently using.");
private readonly GUIContent _isMasterGuiContent = new GUIContent("Local Player Is Master", "Set whether the local player starts off as the master of the instance. Setting this to false and starting Client Sim will spawn a remote player before the local player.");
private readonly GUIContent _isInstanceOwnerGuiContent = new GUIContent("Is Instance Owner", "Set whether the local player is considered the instance owner");
private readonly GUIContent _isVRCPlusGuiContent = new GUIContent("Is VRC+", "Set whether the local player has an active VRC+ subscription");
private readonly GUIContent _remotePlayerCustomNameGuiContent = new GUIContent("Remote Player Name", "Set a custom name for the next spawned remote player. Useful for testing udon script name detection");
private const int WARNING_ICON_SIZE = 60;
private static Texture2D _warningIcon;
private static ClientSimSettings _settings;
private static IClientSimPlayerHeightManager _heightManager;
private Vector2 _scrollPosition;
private GUIStyle _boxStyle;
private GUIStyle _multilineLabel;
private bool _showGeneralSettings = true;
private bool _showPlayerControllerSettings = true;
private bool _showPlayerButtons = true;
private string _version;
private string _remotePlayerCustomName = "";
private bool _needsInputSetup = false;
private bool _needsInputManagerSetup = false;
private bool _needsAudioSetup = false;
private bool _needsLayerSetup = false;
[MenuItem("VRChat SDK/ClientSim", false, 1500)]
public static void Init()
{
ClientSimSettingsWindow window = GetWindow<ClientSimSettingsWindow>(false, "ClientSim Settings");
window.Show();
}
private void OnEnable()
{
if (_settings == null)
{
_settings = ClientSimSettings.Instance;
}
if (_warningIcon == null)
{
// Reuse VRChat's warning icon.
_warningIcon = Resources.Load<Texture2D>("2FAIcons/SDK_Warning_Triangle_icon");
}
_version = ClientSimResourceLoader.GetVersion();
}
private void OnFocus()
{
// Verify settings to know if we need to display "Do It" buttons
_needsInputSetup = !ClientSimProjectSettingsSetup.IsUsingCorrectInputAxesSettings();
_needsInputManagerSetup = !ClientSimProjectSettingsSetup.IsUsingCorrectInputTypeSettings();
_needsAudioSetup = !ClientSimProjectSettingsSetup.IsUsingCorrectAudioSettings();
// VRChat layer setup
_needsLayerSetup = !UpdateLayers.AreLayersSetup() || !UpdateLayers.IsCollisionLayerMatrixSetup();
}
void OnGUI()
{
float tempLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 175;
_boxStyle = new GUIStyle(EditorStyles.helpBox);
_multilineLabel = new GUIStyle(EditorStyles.label);
_multilineLabel.wordWrap = true;
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
DrawHeader();
DrawWindow();
DrawFooter();
EditorGUILayout.EndScrollView();
EditorGUIUtility.labelWidth = tempLabelWidth;
}
private void DrawWindow()
{
if (_needsAudioSetup || _needsInputSetup || _needsInputManagerSetup || _needsLayerSetup)
{
DrawDoItButtons();
return;
}
EditorGUI.BeginChangeCheck();
// Disables UI if ClientSim is disabled
DrawGeneralSettings();
DrawPlayerControllerSettings();
DrawPlayerButtons();
// Disable group from General settings
EditorGUI.EndDisabledGroup();
if (EditorGUI.EndChangeCheck())
{
ClientSimSettings.SaveSettings(_settings);
}
}
private void DrawHeader()
{
EditorGUILayout.Space();
}
private void DrawFooter()
{
EditorGUILayout.Space();
DrawVersion();
}
private void DrawVersion()
{
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Version: " + _version);
EditorGUILayout.EndHorizontal();
}
private void DrawDoItButtons()
{
EditorGUILayout.BeginVertical(_boxStyle);
// Display a warning icon informing them of project setting issues.
GUIStyle labelStyle = new GUIStyle(EditorStyles.label)
{ alignment = TextAnchor.MiddleCenter, wordWrap = true };
EditorGUILayout.BeginHorizontal();
string content = "You must address the following issues before you can test this content using ClientSim!";
if (Application.isPlaying)
{
content += "\nPlease exit playmode before applying these settings.";
}
GUILayout.Label(new GUIContent(_warningIcon), GUILayout.Width(WARNING_ICON_SIZE), GUILayout.Height(WARNING_ICON_SIZE));
EditorGUILayout.LabelField(content, labelStyle, GUILayout.Height(WARNING_ICON_SIZE));
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
GUILayout.Space(5);
EditorGUI.BeginDisabledGroup(Application.isPlaying);
DrawAudioSettingsDoIt();
DrawInputAxesSettingsDoIt();
DrawInputManagerSettingsDoIt();
DrawLayerSettingsSection();
EditorGUI.EndDisabledGroup();
}
private void DrawAudioSettingsDoIt()
{
if (!_needsAudioSetup)
{
return;
}
BeginWarningArea();
GUILayout.Label("Audio Spatializer Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
GUILayout.Label("VRChat uses an audio spatializer that is different from the default Unity spatializer. Clicking this button will modify the project's audio settings to use this audio spatializer.", _multilineLabel);
EditorGUILayout.Space();
if (GUILayout.Button("Set Audio Spatializer"))
{
bool doIt = EditorUtility.DisplayDialog("Set Audio Spatializer for ClientSim",
"This will modify the project's audio settings to use the audio spatializer. Are you sure you want to continue?",
"Do it!", "Don't do it");
if (doIt)
{
ClientSimProjectSettingsSetup.SetAudioSettings();
_needsAudioSetup = false;
}
}
EndWarningArea();
}
private void DrawInputAxesSettingsDoIt()
{
if (!_needsInputSetup)
{
return;
}
BeginWarningArea();
GUILayout.Label("Input Axes Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
GUILayout.Label("VRChat uses a custom list of Input Axes. This will allow you to test these Input Axes in Udon. Clicking this button will replace this project's input axes with VRChat's and remove any custom axes added by the user.", _multilineLabel);
EditorGUILayout.Space();
if (GUILayout.Button("Apply VRChat Input Axes"))
{
bool doIt = EditorUtility.DisplayDialog("Set Input Axes for ClientSim",
"This will replace this project's input axes with VRChat's. Any custom input axes will be removed. Are you sure you want to continue?",
"Do it!", "Don't do it");
if (doIt)
{
ClientSimProjectSettingsSetup.ApplyClientSimInputAxes();
_needsInputSetup = false;
}
}
EndWarningArea();
}
private void DrawInputManagerSettingsDoIt()
{
if (!_needsInputManagerSetup)
{
return;
}
BeginWarningArea();
GUILayout.Label("Input Manager Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
GUILayout.Label("VRChat and ClientSim use both the legacy Input Manager and the new Input System package. Without this setting, input will not work in playmode for ClientSim or Udon. Clicking this button will update the project settings to use both input systems and then *RESTART* Unity to apply the changes.", _multilineLabel);
EditorGUILayout.Space();
if (GUILayout.Button("Set Input Manager"))
{
bool doIt = EditorUtility.DisplayDialog("Set Input Manager for ClientSim",
"This will update the project settings to use both the legacy Input Manager and the new Input System package. Clicking \"Do it!\" will also *RESTART* Unity to apply the changes. Are you sure you want to continue?",
"Do it!", "Don't do it");
if (doIt)
{
ClientSimProjectSettingsSetup.SetInputTypeSettings();
_needsInputManagerSetup = false;
// After importing the new input system, a dialog is displayed to enable it and disable the old.
// This method is then called after to restart unity and recompile code.
// Since the class is internal, Reflection is needed to call the method.
var inputAssembly = typeof(InputSystem).Assembly;
var editorHelpersType = inputAssembly.GetType("UnityEngine.InputSystem.Editor.EditorHelpers");
var restartEditorMethod = editorHelpersType.GetMethod("RestartEditorAndRecompileScripts",
BindingFlags.Public | BindingFlags.Static);
restartEditorMethod.Invoke(null, new object[] { false });
}
}
EndWarningArea();
}
private void DrawLayerSettingsSection()
{
if (!_needsLayerSetup)
{
return;
}
BeginWarningArea();
GUILayout.Label("Layer Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
GUILayout.Label("VRChat scenes must have the same Unity layer configuration as VRChat. Please see the VRChat Build Control Panel to setup the project's layers and collision matrix.", _multilineLabel);
// TODO create button to open build control panel.
EndWarningArea();
}
private void DrawGeneralSettings()
{
EditorGUILayout.BeginVertical(_boxStyle);
_showGeneralSettings = EditorGUILayout.Foldout(_showGeneralSettings, _generalFoldoutGuiContent, true);
if (_showGeneralSettings)
{
AddIndent();
if (_settings.enableClientSim && FindFirstObjectByType<VRC_SceneDescriptor>() == null)
{
EditorGUILayout.HelpBox("No VRC_SceneDescriptor in scene. Please add one to enable ClientSim.", MessageType.Warning);
}
if (_settings.enableClientSim && Application.isPlaying && !ClientSimMain.HasInstance())
{
EditorGUILayout.HelpBox("Please exit and re-enter playmode to enable ClientSim!", MessageType.Warning);
}
_settings.enableClientSim = EditorGUILayout.Toggle(_enableToggleGuiContent, _settings.enableClientSim);
EditorGUI.BeginDisabledGroup(!_settings.enableClientSim);
_settings.displayLogs = EditorGUILayout.Toggle(_displayLogsToggleGuiContent, _settings.displayLogs);
_settings.stopOnScriptChanges = EditorGUILayout.Toggle(_stopOnScriptChangesToggleGuiContent, _settings.stopOnScriptChanges);
// Settings that cannot be changed at runtime
EditorGUI.BeginDisabledGroup(Application.isPlaying);
_settings.deleteEditorOnly = EditorGUILayout.Toggle(_deleteEditorOnlyToggleGuiContent, _settings.deleteEditorOnly);
_settings.hideMenuOnLaunch = EditorGUILayout.Toggle(_hideMenuOnLaunchToggleGuiContent, _settings.hideMenuOnLaunch);
_settings.setTargetFrameRate = EditorGUILayout.Toggle(_setTargetFrameRateGuiContent, _settings.setTargetFrameRate);
EditorGUI.BeginDisabledGroup(!_settings.setTargetFrameRate);
_settings.targetFrameRate = EditorGUILayout.IntField(_targetFrameRateGuiContent, _settings.targetFrameRate);
_settings.targetFrameRate = Mathf.Max(1, _settings.targetFrameRate);
EditorGUI.EndDisabledGroup();
_settings.initializationDelay = EditorGUILayout.FloatField(_startupDelayGuiContent, _settings.initializationDelay);
_settings.initializationDelay = Mathf.Max(0, _settings.initializationDelay);
EditorGUI.EndDisabledGroup();
RemoveIndent();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void DrawPlayerControllerSettings()
{
EditorGUILayout.BeginVertical(_boxStyle);
_showPlayerControllerSettings = EditorGUILayout.Foldout(_showPlayerControllerSettings, _playerControllerFoldoutGuiContent, true);
if (_showPlayerControllerSettings)
{
AddIndent();
_settings.spawnPlayer = EditorGUILayout.Toggle(_playerControllerToggleGuiContent, _settings.spawnPlayer);
EditorGUI.BeginDisabledGroup(!_settings.spawnPlayer);
_settings.showDesktopReticle = EditorGUILayout.Toggle(_showDesktopReticleGuiContent, _settings.showDesktopReticle);
_settings.showTooltips = EditorGUILayout.Toggle(_showTooltipsGuiContent, _settings.showTooltips);
_settings.invertMouseLook = EditorGUILayout.Toggle(_invertMouseLookGuiContent, _settings.invertMouseLook);
EditorGUI.EndDisabledGroup();
RemoveIndent();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void DrawPlayerButtons()
{
EditorGUILayout.BeginVertical(_boxStyle);
_showPlayerButtons = EditorGUILayout.Foldout(_showPlayerButtons, _playerButtonsFoldoutGuiContent, true);
if (_showPlayerButtons)
{
AddIndent();
bool hasInstance = ClientSimMain.HasInstance();
// Values cannot change once ClientSim has started.
EditorGUI.BeginDisabledGroup(hasInstance || Application.isPlaying);
_settings.customLocalPlayerName = EditorGUILayout.TextField(_localPlayerCustomNameGuiContent, _settings.customLocalPlayerName);
_settings.playerStartHeight = EditorGUILayout.FloatField(_playerHeightGuiContent, _settings.playerStartHeight);
selectedLanguageIndex = EditorGUILayout.Popup(_currentLanguageGuiContent, selectedLanguageIndex, _settings.GetAvailableDisplayLanguages());
_settings.currentLanguage = _settings.GetLanguage(selectedLanguageIndex);
_settings.localPlayerIsMaster = EditorGUILayout.Toggle(_isMasterGuiContent, _settings.localPlayerIsMaster);
_settings.isInstanceOwner = EditorGUILayout.Toggle(_isInstanceOwnerGuiContent, _settings.isInstanceOwner);
_settings.isVRCPlus = EditorGUILayout.Toggle(_isVRCPlusGuiContent, _settings.isVRCPlus);
// TODO display desktop/vr option here
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!hasInstance || !Application.isPlaying);
_remotePlayerCustomName = EditorGUILayout.TextField(_remotePlayerCustomNameGuiContent, _remotePlayerCustomName);
if (GUILayout.Button("Spawn Remote Player"))
{
ClientSimMain.SpawnRemotePlayer(_remotePlayerCustomName);
}
List<VRCPlayerApi> playersToRemove = new List<VRCPlayerApi>();
if (Application.isPlaying)
{
foreach (var player in VRCPlayerApi.AllPlayers)
{
GUILayout.BeginHorizontal();
GUILayout.Label(player.displayName);
GUILayout.Space(5);
EditorGUI.BeginDisabledGroup(VRCPlayerApi.AllPlayers.Count == 1 || player.isLocal);
if (GUILayout.Button("Remove Player"))
{
playersToRemove.Add(player);
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(VRCPlayerApi.AllPlayers.Count == 1);
if (GUILayout.Button("Simulate VRC+ Gift"))
{
player.GetClientSimPlayer().SimulateVRCPlusGift();
}
EditorGUI.EndDisabledGroup();
GUILayout.EndHorizontal();
}
for (int i = playersToRemove.Count - 1; i >= 0; --i)
{
ClientSimMain.RemovePlayer(playersToRemove[i]);
}
playersToRemove.Clear();
}
EditorGUI.EndDisabledGroup();
RemoveIndent();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void AddIndent()
{
++EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
GUILayout.Space(EditorGUI.indentLevel * 7 + 4);
EditorGUILayout.BeginVertical();
}
private void RemoveIndent()
{
--EditorGUI.indentLevel;
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
private void BeginWarningArea()
{
EditorGUILayout.BeginVertical(_boxStyle);
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical();
}
private void EndWarningArea()
{
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.EndVertical();
GUILayout.Space(5);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9447dfb6ba20ab1479485c89c34cf4a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
# VRCHAT INC.
### VRCHAT DISTRO LICENSE FILE
Version: February 24, 2022
**SUMMARY OF TERMS:** Any materials subject to this Distro Asset License may be distributed by you, with or without modifications, on a non-commercial basis (i.e., at no charge), in accordance with the full terms of the Materials License Agreement.
This Distro License File is a "License File" as defined in the VRChat Materials License Agreement, found at https://hello.vrchat.com/legal/sdk (or any successor link designated by VRChat) (as may be revised from time to time, the "Materials License Agreement").
This Distro License File applies to all the files in the Folder containing this Distro License File and those in all Child Folders within that Folder (except with respect to files in any Child Folder that contains a different License File) (such files, other than this Distro License File, the "Covered Files"). All capitalized terms used but not otherwise defined in this Distro License File have the meanings provided in the Materials License Agreement.
This Distro License File only provides a summary of the terms applicable to the Covered Files. To understand your rights and obligations and the full set of terms that apply to use of the Covered Files, please see the relevant sections of the Materials License Agreement, including terms applicable to Distro Materials.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 267e20b7d3e9da34eaddc193c847e05e
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@
# ClientSim
## A VRChat Client Simulator for building Worlds
This tool enables you to test your VRChat SDK3 Worlds directly in Unity! You can look at the state of all objects to verify things directly.
## Features
- Debug everything in Unity.
- Inspect Udon variables in Play Mode.
- Desktop player controller.
- Grab Pickups, use Interacts, UI and Stations.
- Delete EditorOnly objects on Play.
### New Features in ClientSim
- Pickup manipulation through I, J, K, L, U, O, mouse scrolling and gamepad.
- Input based on keyboard layout instead of specific keys.
- Local and Remote player now have humanoid avatars, avatar bones supported (but not full Avatar systems).
- View and set player data directly - Locomotion values, voice and avatar audio settings, combat health.
- Unlock mouse by holding tab - you can highlight off-center objects this way.
- Pointer displays when a UI element can be interacted with.
- New runtime options: start as non-master, invert mouse, show tooltips, change player scale, set target framerate, delay start to simulate loading.
- New playmode menu with updated style, player info and settings buttons.
- New buttons in the Settings Window to update Project Settings, no longer happens without user consent.
- Mesh highlighting that matches Android.
- Tooltip location updated to match client.
- Better gamepad support.
- Support for Disabled Domain Reload to enter playmode without delay - note that there is a Unity bug that causes UI Events to fail if they're not set to "Editor and Runtime".
- Automated Testing - you need to specifically enable this so it won't affect your project. Documentation forthcoming.
### Known Issues
- Manually changing Unity Project settings to enable the new input system may not properly allow input. Users should use the buttons in the ClientSim Settings Window.
- Physics.RaycastNonAlloc sometimes does not return colliders that have moved and do not have rigidbodies.
- On exiting playmode may throw exceptions occasionally due to order in which objects are destroyed.
- Highlight shader does not work on Mac (Metal).
## Setup
### Requirements
- Unity 2019.4.29-31
- Package versions of [VRChat Base](https://github.com/vrchat/packages/tree/main/packages/com.vrchat.base) and [Worlds](https://github.com/vrchat/packages/tree/main/packages/com.vrchat.worlds) SDKs
### Installing
- Use the [VRChat Creator Companion](https://vcc.docs.vrchat.com) to add the package to your project from the 'Curated Packages' section
### Getting started
- Open your VRChat world scene
- Press play in Unity
- Test your world
## Copyright
Copyright (c) 2023 VRChat
See License.md for full usage information
## Credits
Based on [CyanEmu](https://github.com/CyanLaser/CyanEmu) by [CyanLaser](https://github.com/CyanLaser), who also made this version.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3a343146949d3e342ba5abcfe6c4c808
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f022ebc28ef54284b97a301de9096d74
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("VRC.ClientSim.Editor")]
[assembly: InternalsVisibleTo("VRC.ClientSim.Editor.Tests")]
[assembly: InternalsVisibleTo("VRC.ClientSim.Tests")]

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfb84e0933b14ae380634bcd70b18a70
timeCreated: 1641884637

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7fd647d22852c4f4db99e7161481e563
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 70f6d4c20b8065e408b6b1fe3223d9e7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,19 @@
%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: 8474ec07ef88d52458e79ca0f5240d96, type: 3}
m_Name: Cam_InternalUI
m_EditorClassIdentifier:
cameraName: Cam_InternalUI
renderLayer:
serializedVersion: 2
m_Bits: 524288
useOcclusionCulling: 0

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d6ef66aee2eba604da699a98a28bba17
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
using UnityEngine;
namespace VRC.SDK3.ClientSim
{
public class ClientSimStackedCamera : ScriptableObject
{
[SerializeField] private string cameraName = "Generic Stacked Camera";
[SerializeField] private LayerMask renderLayer;
[SerializeField] private bool useOcclusionCulling = true;
public string CameraName => cameraName;
public LayerMask RenderLayer => renderLayer;
public bool UseOcclusionCulling => useOcclusionCulling;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8474ec07ef88d52458e79ca0f5240d96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,120 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.XR;
namespace VRC.SDK3.ClientSim
{
public class ClientSimStackedVRCameraSystem : MonoBehaviour
{
[SerializeField]
private ClientSimStackedCamera[] cameraStack;
private bool _isCameraStackingEnabled;
private Camera _mainSceneCamera;
private bool _isInitialized;
private bool _isReady;
private List<Camera> _cameras;
private ClientSimMenu _clientSimMenu;
private IClientSimEventDispatcher _eventDispatcher;
public IReadOnlyList<Camera> Cameras => _cameras;
// all layers handled by this system - note that this does not change whether the system is active or not,
// meaning it is valid to call this at any time, which `OnCameraSettingsChanged` may need to do
public LayerMask RequestedStackLayers => cameraStack.Aggregate(0, (mask, cam) => mask | cam.RenderLayer);
public void Initialize(Camera playerCamera, ClientSimMenu menu, IClientSimEventDispatcher eventDispatcher)
{
_mainSceneCamera = playerCamera;
_clientSimMenu = menu;
_eventDispatcher = eventDispatcher;
}
public void Ready()
{
_isReady = true;
}
void Update()
{
if(!_isReady) return;
if (!_isInitialized) { InitializeStackedSystem(); }
}
void OnDisable()
{
if (_mainSceneCamera != null)
{
if (_isCameraStackingEnabled)
DestroyCameraStack();
}
}
private void InitializeStackedSystem()
{
_cameras = new List<Camera>();
if (_mainSceneCamera != null)
{
CreateCameraStack();
_isInitialized = true;
_eventDispatcher.SendEvent(new ClientSimStackedCameraReadyEvent());
}
}
private void CreateCameraStack()
{
for (int i = 0; i < cameraStack.Length; i++)
{
AddCamera(i);
}
_isCameraStackingEnabled = true;
}
private void DestroyCameraStack()
{
for (int i = 0; i < _cameras.Count; i++)
{
DestroyCamera(i);
}
_isCameraStackingEnabled = false;
}
private void AddCamera(int index)
{
var cameraObj = new GameObject();
cameraObj.transform.parent = _mainSceneCamera.transform;
Camera cam = cameraObj.AddComponent<Camera>();
XRDevice.DisableAutoXRCameraTracking(cam, true);
cam.CopyFrom(_mainSceneCamera); // Start by copying all the settings from the main camera
#if VRC_VR_STEAM // We only want this on SteamVR.
cameraObj.AddComponent<SteamVRCantedProjectionCullingFix>();
#endif
_cameras.Add(cam);
cameraObj.tag = "Untagged";
cameraObj.name = $"StackedCamera : {cameraStack[index].CameraName}";
cam.clearFlags = CameraClearFlags.Depth;
cam.depth = 100 - index;
cam.cullingMask = cameraStack[index].RenderLayer;
cam.useOcclusionCulling = cameraStack[index].UseOcclusionCulling;
//Remove this cameras layers from the base camera
_mainSceneCamera.cullingMask = _mainSceneCamera.cullingMask ^ cameraStack[index].RenderLayer;
// Set the ClientSim UI canvas to use this camera
_clientSimMenu.SetCanvasCamera(cam);
}
private void DestroyCamera(int index)
{
Camera cam = _cameras[index];
_cameras.RemoveAt(index);
//Restore Layers from this camera to the main camera
_mainSceneCamera.cullingMask = _mainSceneCamera.cullingMask | cameraStack[index].RenderLayer;
Destroy(cam.gameObject);
}
}
}

Some files were not shown because too many files have changed in this diff Show More