Hello, I am fine!
This article is an explanation of an improved version of the DarkClass introduced in the previous article.
(You do not need to read the previous article to understand this one.)
【VRChat】UdonSharpでユーザー定義クラスをNewする黒魔術【闇クラス】 - チカラの技術
It also explains how to handle generation tools that automatically generate DarkClass.
What is the DarkClass?
UdonSharp (U#) which is VRChat's world gimmick development language, it can't the creation of custom classes like regular C# due to limitations. However, it is possible to create custom classes using a technical black magic called DarkClass.
// Example of creating an instance of the DarkClass var myDarkClass = MyDarkClass.New("Jane Doe", 23);
Improvements since last time
By changing type to be used from object to DataList, it is no longer necessary to write special instructions for arrays of DarkClass, making them much easier to handle.
In addition, changing the index value of an element from an int literal value to an enum type has made editing easier.
How to generate DarkClass
① Import the generation tool from VCC
①Install the nuruwo repository list from the link below. (Do this with the VCC app closed.)
Install nuruwo's vpm repositories
② Import from VCC into your Unity project.
② Start the Generation Tool
Select Tools -> Nuruwo -> DarkClassGenerator from the Unity menu.
③ Create a file for the DarkClasss
Create a new C# script (not a U# script) in Project. Name the file “MyDarkClass”.
(If generated as a U# script, delete the simultaneously generated Udon Sharp Program Asset)
④ DarkClass generation
Enter the required information in the generation tool as shown in the figure to generate it.
Click the “Generate DarkClass” button to copy the generated code to the clipboard and paste it over the script in ③.
This completes the creation of the DarkClass🎉
The generated results are as follows
using UnityEngine; using VRC.SDK3.Data; namespace Nuruwo.Dev { // Enum for assigning index of field DataTokens enum MyDarkClassField { Name, Age, Position, Count } public class MyDarkClass : DataList { // Constructor public static MyDarkClass New(string name, int age, Vector3 position) { var data = new DataToken[(int)MyDarkClassField.Count]; data[(int)MyDarkClassField.Name] = name; data[(int)MyDarkClassField.Age] = age; data[(int)MyDarkClassField.Position] = new DataToken(position); return (MyDarkClass)new DataList(data); } } public static class MyDarkClassExt { // Get methods public static string Name(this MyDarkClass instance) => (string)instance[(int)MyDarkClassField.Name]; public static int Age(this MyDarkClass instance) => (int)instance[(int)MyDarkClassField.Age]; public static Vector3 Position(this MyDarkClass instance) => (Vector3)instance[(int)MyDarkClassField.Position].Reference; // Set methods public static void Name(this MyDarkClass instance, string arg) => instance[(int)MyDarkClassField.Name] = arg; public static void Age(this MyDarkClass instance, int arg) => instance[(int)MyDarkClassField.Age] = arg; public static void Position(this MyDarkClass instance, Vector3 arg) => instance[(int)MyDarkClassField.Position] = new DataToken(arg); } }
How to use the DarkClass
Now we will use the DarkClass from a normal U# script.
Create a U# script “DarkClassTest” and paste the following code.
using UdonSharp; using UnityEngine; namespace Nuruwo.Dev { public class DarkClassTest : UdonSharpBehaviour { void Start() { //Make instance with constructor var myDarkClass = MyDarkClass.New("Jane Doe", 23, new Vector3(0.5f, 0.4f, 0.9f)); //Get Debug.Log(myDarkClass.Name()); //"Jane Doe" Debug.Log(myDarkClass.Age()); //23 Debug.Log(myDarkClass.Position()); //(0.50, 0.40, 0.90) //Set myDarkClass.Name("Strong Power"); Debug.Log(myDarkClass.Name()); //"Strong Power" } } }
Attach it to the appropriate GameObject, play it in Unity, and if the result appears in Debug.Log, you have succeeded👍
Cautions for using the DarkClass
The DarkClass should be treated and noted differently than the general class.
① Fields of instances
Defining an instance of a DarkClass as a field (member variable) of the using class will cause an error the first time ClientSim is executed.
(However, it is not a fatal error, so the script will not halt.)
public class DarkClassTest : UdonSharpBehaviour { // Defining directly in the field in this manner will result in an error. private MyDarkClass _myDarkClass; }
As a countermeasure, you can avoid the error by wrapping the field as a property as follows.
public class DarkClassTest : UdonSharpBehaviour { // Wrap to property. Access _myDarkClass from code. private MyDarkClass _myDarkClass { get { return (MyDarkClass)d_myDarkClass; } set { d_myDarkClass = value; } } // object type field, prefixed with d_. This field should not be accessed. private object d_myDarkClass; }
② Type Recognition in the Generation Tool
The Generate tool automatically recognizes the types that exist in the project when the Generate button is pressed. In other words, if you enter a type that does not exist at that time as a field, it will not be generated correctly. When specifying a user-custom Enum or DarkClass as a field, make sure that they have already been created. For example, when generating a parent-child structure DarkClass with a DarkClass as a field, create the child DarkClass before the parent DarkClass.
Useful features of the DarkClass Generator
Script load function
Various parameters can be read from the generated DarkClass script. This is useful when you want to edit them later. Drop a script file of the DarkClass.
JSON Deserialization Mode
Generates code to turn JSON into a DarkClass. (To be precise, it converts a DataDictionary deserializing a JSON string with VRC JSON into a DarkClass) Nesting of DarkClass is also supported.
The following is the generated code. (TextureFormat is a built-in enum type)
using UnityEngine; using VRC.SDK3.Data; namespace Nuruwo.Dev { // Enum for assigning index of field DataTokens enum MyDarkClassJsonField { Name, Format, Positions, Count } public class MyDarkClassJson : DataList { // Constructor // This comments for loading this script by generator : // public static MyDarkClassJson New(string name nm, TextureFormat format, Vector3[] positions pn) public static MyDarkClassJson New(DataDictionary dic) { var name = dic["nm"].String; var format = (TextureFormat)(int)dic["format"].Number; var positionsList = dic["pn"].DataList; var positionsCount = positionsList.Count; var positions = new Vector3[positionsCount]; for (int i = 0; i < positionsCount; i++) { var positionsData = positionsList[i].DataDictionary; var positionsX = (float)positionsData["x"].Number; var positionsY = (float)positionsData["y"].Number; var positionsZ = (float)positionsData["z"].Number; positions[i] = new Vector3(positionsX, positionsY, positionsZ); } // Make DataTokens var data = new DataToken[(int)MyDarkClassJsonField.Count]; data[(int)MyDarkClassJsonField.Name] = name; data[(int)MyDarkClassJsonField.Format] = new DataToken(format); data[(int)MyDarkClassJsonField.Positions] = new DataToken(positions); return (MyDarkClassJson)new DataList(data); } } public static class MyDarkClassJsonExt { // Get methods public static string Name(this MyDarkClassJson instance) => (string)instance[(int)MyDarkClassJsonField.Name]; public static TextureFormat Format(this MyDarkClassJson instance) => (TextureFormat)instance[(int)MyDarkClassJsonField.Format].Reference; public static Vector3[] Positions(this MyDarkClassJson instance) => (Vector3[])instance[(int)MyDarkClassJsonField.Positions].Reference; // Set methods public static void Name(this MyDarkClassJson instance, string arg) => instance[(int)MyDarkClassJsonField.Name] = arg; public static void Format(this MyDarkClassJson instance, TextureFormat arg) => instance[(int)MyDarkClassJsonField.Format] = new DataToken(arg); public static void Positions(this MyDarkClassJson instance, Vector3[] arg) => instance[(int)MyDarkClassJsonField.Positions] = new DataToken(arg); } }
From U#, use the following
using UdonSharp; using UnityEngine; namespace Nuruwo.Dev { public class DarkClassTest : UdonSharpBehaviour { void Start() { // format = 4 is TextureFormat.RGBA32 var jsonString = "{\"nm\":\"jsonUser\",\"format\":4,\"pn\":[{\"x\":1.0,\"y\":2.0,\"z\":3.0},{\"x\":4.0,\"y\":5.0,\"z\":6.0}]}"; if (VRC.SDK3.Data.VRCJson.TryDeserializeFromJson(jsonString, out VRC.SDK3.Data.DataToken result)) { Debug.Log("json: " + jsonString); //Make instance with constructor var myDarkClassJson = MyDarkClassJson.New(result.DataDictionary); Debug.Log("nm: " + myDarkClassJson.Name()); Debug.Log("format: " + myDarkClassJson.Format()); for (int i = 0; i < myDarkClassJson.Positions().Length; i++) { Debug.Log("positions: " + i + " / " + myDarkClassJson.Positions()[i]); } } } } }
The name "DarkClass"
The name of DarkClass is a term I coined. It comes from the following two meanings
- It is a black magic technique in C# to get around the limitations of U#. 🧙
- All kinds of data are sucked into the DataToken like a black hole. 🕳️
Summary
You'll be able to handle complex data with DarkClass in U#.
Arrays are now handled in a straightforward manner with the improvements.
JSON is also supported!
Have fun with U# development!🚀
Acknowledgements
Dear TheHelpfulHelper
I used the following code as a reference when improving the DarkClass. Thank you very much! U# Fake Custom Classes Pattern · GitHub
Dear ureishi
I have received a wide variety of suggestions for improvements to the reference code and the generation tool. I'm always grateful for your help!
https://x.com/aivrc
Dear Chiu
Chiu-san provided me with the code for automatic type identification. It is now very easy to use, eliminating the need for user definitions on the tool. Thank you very much!
https://x.com/ChiuGameProject
Dear koyashiro
He is the developer of the previous object-type DarkClass. I'm always grateful for your help!
https://x.com/koyashiro