मल्टीप्लेयर डेटा संपीड़न और बिट हेरफेर

Unity में मल्टीप्लेयर गेम बनाना कोई मामूली काम नहीं है, लेकिन PUN 2 जैसे तीसरे पक्ष के समाधानों की मदद से, इसने नेटवर्किंग एकीकरण को बहुत आसान बना दिया है।

वैकल्पिक रूप से, यदि आपको गेम की नेटवर्किंग क्षमताओं पर अधिक नियंत्रण की आवश्यकता है, तो आप सॉकेट तकनीक (उदा. आधिकारिक मल्टीप्लेयर, जहां सर्वर केवल प्लेयर इनपुट प्राप्त करता है और फिर यह सुनिश्चित करने के लिए अपनी स्वयं की गणना करता है) का उपयोग करके अपना स्वयं का नेटवर्किंग समाधान लिख सकता है सभी खिलाड़ी एक जैसा व्यवहार करते हैं, जिससे हैकिंग की घटनाएं कम हो जाती हैं।

भले ही आप अपनी खुद की नेटवर्किंग लिख रहे हों या किसी मौजूदा समाधान का उपयोग कर रहे हों, आपको उस विषय से सावधान रहना चाहिए जिस पर हम इस पोस्ट में चर्चा करेंगे, जो कि डेटा संपीड़न है।

मल्टीप्लेयर मूल बातें

अधिकांश मल्टीप्लेयर गेम में, खिलाड़ियों और सर्वर के बीच डेटा के छोटे बैच (बाइट्स का एक क्रम) के रूप में संचार होता है, जिसे एक निर्दिष्ट दर पर आगे और पीछे भेजा जाता है।

Unity (और C# विशेष रूप से) में, सबसे आम मूल्य प्रकार हैं int, फ्लोट, बूल, और string (साथ ही, आपको बार-बार बदलते मान भेजते समय स्ट्रिंग का उपयोग करने से बचना चाहिए, इस प्रकार के लिए सबसे स्वीकार्य उपयोग चैट संदेश या डेटा हैं जिनमें केवल टेक्स्ट होता है)।

  • उपरोक्त सभी प्रकार बाइट्स की एक निर्धारित संख्या में संग्रहीत हैं:

int = 4 बाइट्स
फ्लोट = 4 बाइट्स
bool = 1 बाइट
string = (प्रयुक्त बाइट्स की संख्या एन्कोडिंग प्रारूप के आधार पर, एक एकल अक्षर को एनकोड करें) x (वर्णों की संख्या)

मानों को जानने के बाद, आइए एक मानक मल्टीप्लेयर एफपीएस (फर्स्ट-पर्सन शूटर) के लिए भेजे जाने वाले बाइट्स की न्यूनतम मात्रा की गणना करें:

खिलाड़ी की स्थिति: वेक्टर3 (3 फ्लोट्स x 4) = 12 बाइट्स*एच16*प्लेयर रोटेशन: क्वाटरनियन (4 फ्लोट्स x 4) = 16 बाइट्स*एच16*प्लेयर लुक लक्ष्य: वेक्टर3 (3 फ्लोट्स x 4) = 12 बाइट्स*एच16*प्लेयर फायरिंग: बूल = 1 बाइट*एच16*खिलाड़ी हवा में: बूल = 1 बाइट*एच16*खिलाड़ी झुक रहा है: बूल = 1 बाइट*एच16*खिलाड़ी दौड़ रहा है: बूल = 1 बाइट

कुल 44 बाइट्स.

हम डेटा को बाइट्स की सरणी में पैक करने के लिए एक्सटेंशन विधियों का उपयोग करेंगे, और इसके विपरीत:

  • एक नई स्क्रिप्ट बनाएं, इसे SC_ByteMethods नाम दें और फिर नीचे दिए गए कोड को इसके अंदर पेस्ट करें:

SC_ByteMethods.cs

using System;
using System.Collections;
using System.Text;

public static class SC_ByteMethods
{
    //Convert value types to byte array
    public static byte[] toByteArray(this float value)
    {
        return BitConverter.GetBytes(value);
    }

    public static byte[] toByteArray(this int value)
    {
        return BitConverter.GetBytes(value);
    }

    public static byte toByte(this bool value)
    {
        return (byte)(value ? 1 : 0);
    }

    public static byte[] toByteArray(this string value)
    {
        return Encoding.UTF8.GetBytes(value);
    }

    //Convert byte array to value types
    public static float toFloat(this byte[] bytes, int startIndex)
    {
        return BitConverter.ToSingle(bytes, startIndex);
    }

    public static int toInt(this byte[] bytes, int startIndex)
    {
        return BitConverter.ToInt32(bytes, startIndex);
    }

    public static bool toBool(this byte[] bytes, int startIndex)
    {
        return bytes[startIndex] == 1;
    }

    public static string toString(this byte[] bytes, int startIndex, int length)
    {
        return Encoding.UTF8.GetString(bytes, startIndex, length);
    }
}

उपरोक्त विधियों का उदाहरण उपयोग:

  • एक नई स्क्रिप्ट बनाएं, इसे SC_TestPackUnpack नाम दें और फिर नीचे दिए गए कोड को इसके अंदर पेस्ट करें:

SC_TestPackUnpack.cs

using System;
using UnityEngine;

public class SC_TestPackUnpack : MonoBehaviour
{
    //Example values
    public Transform lookTarget;
    public bool isFiring = false;
    public bool inTheAir = false;
    public bool isCrouching = false;
    public bool isRunning = false;

    //Data that can be sent over network
    byte[] packedData = new byte[44]; //12 + 16 + 12 + 1 + 1 + 1 + 1

    // Update is called once per frame
    void Update()
    {
        //Part 1: Example of writing Data
        //_____________________________________________________________________________
        //Insert player position bytes
        Buffer.BlockCopy(transform.position.x.toByteArray(), 0, packedData, 0, 4); //X
        Buffer.BlockCopy(transform.position.y.toByteArray(), 0, packedData, 4, 4); //Y
        Buffer.BlockCopy(transform.position.z.toByteArray(), 0, packedData, 8, 4); //Z
        //Insert player rotation bytes
        Buffer.BlockCopy(transform.rotation.x.toByteArray(), 0, packedData, 12, 4); //X
        Buffer.BlockCopy(transform.rotation.y.toByteArray(), 0, packedData, 16, 4); //Y
        Buffer.BlockCopy(transform.rotation.z.toByteArray(), 0, packedData, 20, 4); //Z
        Buffer.BlockCopy(transform.rotation.w.toByteArray(), 0, packedData, 24, 4); //W
        //Insert look position bytes
        Buffer.BlockCopy(lookTarget.position.x.toByteArray(), 0, packedData, 28, 4); //X
        Buffer.BlockCopy(lookTarget.position.y.toByteArray(), 0, packedData, 32, 4); //Y
        Buffer.BlockCopy(lookTarget.position.z.toByteArray(), 0, packedData, 36, 4); //Z
        //Insert bools
        packedData[40] = isFiring.toByte();
        packedData[41] = inTheAir.toByte();
        packedData[42] = isCrouching.toByte();
        packedData[43] = isRunning.toByte();
        //packedData ready to be sent...

        //Part 2: Example of reading received data
        //_____________________________________________________________________________
        Vector3 receivedPosition = new Vector3(packedData.toFloat(0), packedData.toFloat(4), packedData.toFloat(8));
        print("Received Position: " + receivedPosition);
        Quaternion receivedRotation = new Quaternion(packedData.toFloat(12), packedData.toFloat(16), packedData.toFloat(20), packedData.toFloat(24));
        print("Received Rotation: " + receivedRotation);
        Vector3 receivedLookPos = new Vector3(packedData.toFloat(28), packedData.toFloat(32), packedData.toFloat(36));
        print("Received Look Position: " + receivedLookPos);
        print("Is Firing: " + packedData.toBool(40));
        print("In The Air: " + packedData.toBool(41));
        print("Is Crouching: " + packedData.toBool(42));
        print("Is Running: " + packedData.toBool(43));
    }
}

उपरोक्त स्क्रिप्ट 44 की लंबाई के साथ बाइट सरणी को प्रारंभ करती है (जो उन सभी मानों के बाइट योग से मेल खाती है जिन्हें हम भेजना चाहते हैं)।

फिर प्रत्येक मान को बाइट सरणियों में परिवर्तित किया जाता है, फिर Buffer.BlockCopy का उपयोग करके पैक्डडेटा सरणी में लागू किया जाता है।

बाद में SC_ByteMethods.cs से एक्सटेंशन विधियों का उपयोग करके पैक्डडेटा को वापस मानों में परिवर्तित किया जाता है।

डेटा संपीड़न तकनीक

वस्तुतः, 44 बाइट्स बहुत अधिक डेटा नहीं है, लेकिन यदि प्रति सेकंड 10 - 20 बार भेजने की आवश्यकता होती है, तो ट्रैफ़िक जुड़ना शुरू हो जाता है।

जब नेटवर्किंग की बात आती है, तो हर बाइट मायने रखती है।

तो डेटा की मात्रा कैसे कम करें?

उत्तर सरल है, उन मानों को न भेजकर जिनके बदलने की उम्मीद नहीं है, और सरल मान प्रकारों को एक बाइट में जमा करके।

ऐसे मान न भेजें जिनमें बदलाव की उम्मीद न हो

उपरोक्त उदाहरण में हम रोटेशन के क्वाटरनियन को जोड़ रहे हैं, जिसमें 4 फ्लोट्स शामिल हैं।

हालाँकि, एफपीएस गेम के मामले में, खिलाड़ी आमतौर पर केवल Y अक्ष के चारों ओर घूमता है, यह जानते हुए, हम केवल Y के चारों ओर रोटेशन जोड़ सकते हैं, रोटेशन डेटा को 16 बाइट्स से घटाकर केवल 4 बाइट्स कर सकते हैं।

Buffer.BlockCopy(transform.localEulerAngles.y.toByteArray(), 0, packedData, 12, 4); //Local Y Rotation

एक ही बाइट में एकाधिक बूलियन को ढेर करें

एक बाइट 8 बिट्स का एक क्रम है, प्रत्येक का संभावित मान 0 और 1 है।

संयोगवश, बूल मान केवल सही या गलत हो सकता है। तो, एक सरल कोड के साथ, हम एक बाइट में 8 बूल मानों को संपीड़ित कर सकते हैं।

SC_ByteMethods.cs खोलें, फिर अंतिम समापन ब्रेस '}' से पहले नीचे दिया गया कोड जोड़ें

    //Bit Manipulation
    public static byte ToByte(this bool[] bools)
    {
        byte[] boolsByte = new byte[1];
        if (bools.Length == 8)
        {
            BitArray a = new BitArray(bools);
            a.CopyTo(boolsByte, 0);
        }

        return boolsByte[0];
    }

    //Get value of Bit in the byte by the index
    public static bool GetBit(this byte b, int bitNumber)
    {
        //Check if specific bit of byte is 1 or 0
        return (b & (1 << bitNumber)) != 0;
    }

अद्यतन SC_TestPackUnpack कोड:

SC_TestPackUnpack.cs

using System;
using UnityEngine;

public class SC_TestPackUnpack : MonoBehaviour
{
    //Example values
    public Transform lookTarget;
    public bool isFiring = false;
    public bool inTheAir = false;
    public bool isCrouching = false;
    public bool isRunning = false;

    //Data that can be sent over network
    byte[] packedData = new byte[29]; //12 + 4 + 12 + 1

    // Update is called once per frame
    void Update()
    {
        //Part 1: Example of writing Data
        //_____________________________________________________________________________
        //Insert player position bytes
        Buffer.BlockCopy(transform.position.x.toByteArray(), 0, packedData, 0, 4); //X
        Buffer.BlockCopy(transform.position.y.toByteArray(), 0, packedData, 4, 4); //Y
        Buffer.BlockCopy(transform.position.z.toByteArray(), 0, packedData, 8, 4); //Z
        //Insert player rotation bytes
        Buffer.BlockCopy(transform.localEulerAngles.y.toByteArray(), 0, packedData, 12, 4); //Local Y Rotation
        //Insert look position bytes
        Buffer.BlockCopy(lookTarget.position.x.toByteArray(), 0, packedData, 16, 4); //X
        Buffer.BlockCopy(lookTarget.position.y.toByteArray(), 0, packedData, 20, 4); //Y
        Buffer.BlockCopy(lookTarget.position.z.toByteArray(), 0, packedData, 24, 4); //Z
        //Insert bools (Compact)
        bool[] bools = new bool[8];
        bools[0] = isFiring;
        bools[1] = inTheAir;
        bools[2] = isCrouching;
        bools[3] = isRunning;
        packedData[28] = bools.ToByte();
        //packedData ready to be sent...

        //Part 2: Example of reading received data
        //_____________________________________________________________________________
        Vector3 receivedPosition = new Vector3(packedData.toFloat(0), packedData.toFloat(4), packedData.toFloat(8));
        print("Received Position: " + receivedPosition);
        float receivedRotationY = packedData.toFloat(12);
        print("Received Rotation Y: " + receivedRotationY);
        Vector3 receivedLookPos = new Vector3(packedData.toFloat(16), packedData.toFloat(20), packedData.toFloat(24));
        print("Received Look Position: " + receivedLookPos);
        print("Is Firing: " + packedData[28].GetBit(0));
        print("In The Air: " + packedData[28].GetBit(1));
        print("Is Crouching: " + packedData[28].GetBit(2));
        print("Is Running: " + packedData[28].GetBit(3));
    }
}

उपरोक्त विधियों से, हमने पैक्डडेटा की लंबाई 44 से घटाकर 29 बाइट्स (34% कमी) कर दी है।

सुझाए गए लेख
यूनिटी में फोटॉन फ्यूजन 2 का परिचय
PUN 2 का उपयोग करके यूनिटी में एक मल्टीप्लेयर गेम बनाएं
यूनिटी में मल्टीप्लेयर नेटवर्क गेम्स का निर्माण
PUN 2 के साथ एक मल्टीप्लेयर कार गेम बनाएं
यूनिटी PUN 2 रूम में मल्टीप्लेयर चैट जोड़ रही है
PHP और MySQL के साथ यूनिटी लॉगिन सिस्टम
फोटॉन नेटवर्क (क्लासिक) शुरुआती गाइड