Bei der Entwicklung von Multiplayer-Spielen mit PUN2 kann es vorkommen, dass benutzerdefinierte Typen wie benutzerdefinierte Strukturen oder Klassen über RPC und Ereignisse usw. gesendet werden. Das direkte Senden dieser benutzerdefinierten Typen an das Netzwerk ist in PUN2 jedoch nicht zulässig. Stattdessen bietet PUN2 eine benutzerdefinierte Typregistrierungsmethode als Lösung.
Um die benutzerdefinierte Typregistrierungsmethode von PUN2 verwenden zu können, muss ein benutzerdefinierter Typ in ein Byte-Array serialisiert werden. In vielen Fällen wird die Byte-Serialisierung mithilfe des Binärformatierers durchgeführt. Allerdings erzeugt der Binärformatierer eine beträchtliche Menge zusätzlicher Bytes, was den Datenverkehr bei netzwerkintensiven Spielen beeinträchtigt und für mobile Benutzer, die keine kostenlose WLAN-Verbindung nutzen können, ein schlechtes Erlebnis bietet. Darüber hinaus kann Binary Formatter Unitys Vector2, Vector3, Quaternion usw. nicht serialisieren.
Dieses Archiv bietet eine benutzerdefinierte Typregistrierungsmethode mit Skript und vergleicht den Unterschied mit anderen Serialisierungsmethoden.
Die gegebene Struktur wird mit Binary Formatter serialisiert und die Größe wird von Marshal gemessen. Beachten Sie, dass Unity-Objekte wie Vector3, Quaternion usw. nicht enthalten sind, da diese vom Binary Formatter nicht serialisiert werden können.
public struct TestStruct
{
public int int_1 ;
public int int_2 ;
public string string_1 ;
public float float_1 ;
public float float_2 ;
}
TestStruct testStruct = new TestStruct
{
int_1 = 30 ,
int_2 = 71 ,
string_1 = " ABC가나다 " ,
float_1 = 0.162f ,
float_2 = 62f ,
} ;
Die Serialisierung der Struktur und die Messung der Größe erfolgen wie folgt:
public void BinaryFormatterSerialize ( TestStruct testStruct )
{
byte [ ] bytes ;
MemoryStream memoryStream = new MemoryStream ( ) ;
BinaryFormatter binaryFormatter = new BinaryFormatter ( ) ;
binaryFormatter . Serialize ( memoryStream , testStruct ) ;
memoryStream . Close ( ) ;
bytes = memoryStream . ToArray ( ) ;
Debug . Log ( string . Format ( " Bianary Formatter Serialized Size : {0} bytes " , bytes . Length ) ) ;
}
public void CheckSize ( TestStruct testStruct )
{
Debug . Log ( string . Format ( " Original Size : {0} bytes " , Marshal . SizeOf ( testStruct ) ) ) ;
Debug . Log ( JsonUtility . ToJson ( testStruct , true ) ) ;
}
Das Ergebnis ist wie folgt:
Größe (Byte) | |
---|---|
Original | 24 Byte |
Binärer Formatierer | 199 Byte |
Die theoretische Größe der gegebenen Struktur beträgt 24 Bytes. Wenn die gegebene Struktur mit Binary Formatter serialisiert wird, beträgt die Größe 199 Bytes, was achtmal größer ist als die theoretische Größe. Dies kann beim Serialisieren und Senden an das Netzwerk zu einem Datenverkehrs-Overhead führen.
Die obige Struktur wird mit JsonUtility serialisiert und in Bytes umgewandelt. Die Serialisierung der Struktur erfolgt wie folgt:
public void JsonSerialize ( TestStruct testStruct )
{
byte [ ] bytes = Encoding . UTF8 . GetBytes ( JsonUtility . ToJson ( testStruct ) ) ;
Debug . Log ( string . Format ( " JsonUtility Serialized Size : {0} bytes " , bytes . Length ) ) ;
}
Das Ergebnis ist wie folgt:
Größe (Byte) | |
---|---|
Original | 24 Byte |
JsonUtility und Bytekonvertierung | 94 Byte |
Wenn die gegebene Struktur mit JsonUtility serialisiert und in Bytes konvertiert wird, beträgt die Größe 94 Bytes, was etwa viermal größer ist als die theoretische Größe. Diese Größe kann reduziert werden, indem die Namen der Variablen gekürzt werden. Wenn beispielsweise die Namen von Variablen wie unten gezeigt geändert werden, ist das Ergebnis wie folgt.
public struct TestStruct
{
public int a ;
public int b ;
public string c ;
public float d ;
public float e ;
}
TestStruct testStruct = new TestStruct
{
a = 30 ,
b = 71 ,
c = " ABC가나다 " ,
d = 0.162f ,
e = 62f ,
} ;
Größe (Byte) | |
---|---|
Original | 24 Byte |
JsonUtility und Bytekonvertierung | 67 Byte |
Die Bytegröße wurde von 94 Byte auf 67 Byte reduziert. Es ist jedoch immer noch größer als die theoretische Größe von 24 Bytes.
Dieses Archiv führt einen benutzerdefinierten Serialisierer ein, der benutzerdefinierte Typen wie benutzerdefinierte Strukturen oder Klassen serialisieren kann. Dieser Serialisierer kann eine Größe bereitstellen, die nahe an der theoretischen Größe liegt. Serialisierbare Typen und Größen sind wie folgt:
Typ | Größe (Byte) |
---|---|
Byte | 1 Byte |
Byte[] | 4 + (1 * Länge) Bytes |
bool | 1 Byte |
bool[] | 4 + (1 * Länge) Bytes |
int | 4 Bytes |
int[] | 4 + (4 * Länge) Bytes |
schweben | 4 Bytes |
schweben[] | 4 + (4 * Länge) Bytes |
Vektor2 | 8 Byte |
Vektor2[] | 4 + (8 * Länge) Bytes |
Vektor3 | 12 Byte |
Vektor3[] | 4 + (12 * Länge) Bytes |
Quaternion | 16 Byte |
Quaternion[] | 4 + (16 * Länge) Bytes |
Zeichenfolge | 4 + α (UTF8-Kodierung) Bytes |
string[] | 4 + ((4 + α) * Länge) Bytes |
Deklarieren Sie zunächst mit MSLIMA.Serializer oben.
using MSLIMA.Serializer;
Nehmen wir dann an, dass die Struktur wie folgt gegeben ist:
public struct TestStruct
{
public int int_1 ;
public int int_2 ;
public float float_1 ;
public bool bool_1 ;
public string string_1 ;
public Vector3 vector3_1 ;
public Vector3 vector3_2 ;
public Quaternion quaternion_1 ;
}
TestStruct testStruct = new TestStruct
{
int_1 = 30 ,
int_2 = 71 ,
float_1 = 0.162f ,
bool_1 = true ,
string_1 = " ABC가나다 " ,
vector3_1 = new Vector3 ( - 23f , 62f , 26f ) ,
vector3_2 = new Vector3 ( 1f , 7f , - 15f ) ,
quaternion_1 = Quaternion . Euler ( 35f , 0f , 15f )
} ;
Erstellen Sie zunächst statische Methoden mit den Namen „Serialize“ und „Deserialize“ innerhalb des benutzerdefinierten Typs . Die Methode „Serialisieren“ hat einen Parameter mit dem Typ des Objekts und einen Rückgabetyp mit dem Typ Byte[]. Die Methode „Deserialize“ hat einen Parameter mit dem Typ „Byte[]“ und dem Rückgabetyp „Objekt“.
Beachten Sie, dass die Namen der Methoden, Parameter und Rückgabetypen mit den beschriebenen übereinstimmen müssen. Darüber hinaus müssen diese Methoden statisch sein.
public static byte [ ] Serialize ( object customObject )
{
}
public static object Deserialize ( byte [ ] bytes )
{
}
Zweitens: Wandeln Sie „customObject“ in einen benutzerdefinierten Typ um und deklarieren Sie das Byte-Array in der „Serialize“-Methode.
public static byte [ ] Serialize ( object customObject )
{
TestStruct o = ( TestStruct ) customObject ;
byte [ ] bytes = new byte [ 0 ] ;
}
Verwenden Sie nun die Methode des Serializers, um die gewünschten Felder zu serialisieren und endlich Bytes zurückzugeben. Beachten Sie, dass das Byte-Array mit dem Schlüsselwort ref übergeben wird.
public static byte [ ] Serialize ( object customObject )
{
.. .
Serializer . Serialize ( o . int_1 , ref bytes ) ;
Serializer . Serialize ( o . int_2 , ref bytes ) ;
Serializer . Serialize ( o . float_1 , ref bytes ) ;
Serializer . Serialize ( o . bool_1 , ref bytes ) ;
Serializer . Serialize ( o . string_1 , ref bytes ) ;
Serializer . Serialize ( o . vector3_1 , ref bytes ) ;
Serializer . Serialize ( o . vector3_2 , ref bytes ) ;
Serializer . Serialize ( o . quaternion_1 , ref bytes ) ;
return bytes ;
}
Drittens erstellen Sie einen neuen benutzerdefinierten Typ in der Methode „Deserialize“, deklarieren die Offset-Variable mit dem Typ „int“ und initialisieren sie mit 0.
public static object Deserialize ( byte [ ] bytes )
{
TestStruct o = new TestStruct ( ) ;
int offset = 0 ;
}
Verwenden Sie nun die Deserialisierungsmethode des Serializers, um die oben serialisierten Felder zu deserialisieren. Der Offset wird mit dem Schlüsselwort ref übergeben und gibt den oben erstellten benutzerdefinierten Typ zurück.
Beachten Sie, dass die Reihenfolge der Deserialisierung mit der Reihenfolge der Serialisierung übereinstimmen muss.
public static object Deserialize ( byte [ ] bytes )
{
.. .
o . int_1 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . int_2 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . float_1 = Serializer . DeserializeInt ( bytes , ref offset ) ;
o . bool_1 = Serializer . DeserializeBool ( bytes , ref offset ) ;
o . string_1 = Serializer . DeserializeString ( bytes , ref offset ) ;
o . vector3_1 = Serializer . DeserializeVector3 ( bytes , ref offset ) ;
o . vector3_2 = Serializer . DeserializeVector3 ( bytes , ref offset ) ;
o . quaternion_1 = Serializer . DeserializeQuaternion ( bytes , ref offset ) ;
return o ;
}
Zuletzt sollte der benutzerdefinierte Typ bei PUN2 registriert werden. Rufen Sie die unten beschriebene Methode einmal auf, um den benutzerdefinierten Typ zu registrieren. Wenn mehrere benutzerdefinierte Typen registriert werden müssen, muss der Bytecode unterschiedlich sein. Dies wird einfach durch Ändern des Alphabets des Parameters in der Methode erreicht.
Serializer . RegisterCustomType < TestStruct > ( ( byte ) 'A' ) ;
Das Ergebnis der Serialisierung einer gegebenen Struktur ist wie folgt:
Größe (Byte) | |
---|---|
Original | 64 Byte |
Benutzerdefinierter Serialisierer | 69 Byte |
Die theoretische Größe beträgt 64 Byte, während die tatsächliche serialisierte Größe 69 Byte beträgt. Der Unterschied von 5 Byte wird durch einen String verursacht, dessen Größe je nach Länge variabel sein kann. Das Ergebnis ist akzeptabel.
Der benutzerdefinierte Serialisierer bietet eine kleinere Größe als die Serialisierung mit Binary Formatter oder JsonUtility. Allerdings gibt es Einschränkungen, da es unpraktisch sein kann, alle Serialisierungsmethoden für alle benutzerdefinierten Typen zu schreiben, die serialisiert werden sollen und keine verschachtelten Typen unterstützen. Dennoch würde dieser benutzerdefinierte Serialisierer hilfreich sein, wenn einfache benutzerdefinierte Typen mit primitiven Typen serialisiert und häufig an das Netzwerk gesendet werden.
Fügen Sie diese Registrierung mit Gültigkeitsbereich hinzu.
"scopedRegistries" : [
{
"name" : " MS-LIMA " ,
"url" : " https://package.openupm.com " ,
"scopes" : [
" com.ms-lima "
]
}
]