कण प्रणाली का उपयोग करके इन-गेम संदेश प्रदर्शित करें

छवि

कार्य


जब हमारे गेम द अनलाइविंग का विकास हो रहा है, तो हमने कण प्रणाली का उपयोग करके विभिन्न संदेशों को प्रदर्शित करने का काम निर्धारित किया है, जैसे कि नुकसान, स्वास्थ्य या ऊर्जा की कमी, इनाम मूल्य, बहाल किए गए स्वास्थ्य बिंदुओं की संख्या आदि। ऐसा करने के लिए और इस तरह के संदेशों के स्वरूप और आगे के व्यवहार को अनुकूलित करने के लिए अधिक अवसर प्राप्त करने के लिए ऐसा करने का निर्णय लिया गया था, जो यूनिटी यूआई सिस्टम के मानक तत्वों का उपयोग करते समय समस्याग्रस्त है।

इसके अलावा, यह दृष्टिकोण प्रत्येक प्रकार के संदेश के लिए केवल एक कण प्रणाली उदाहरण का उपयोग करता है, जो एकता यूआई का उपयोग करके समान संदेशों के आउटपुट की तुलना में उत्पादकता में भारी वृद्धि देता है।

क्षति रिपोर्ट

छवि

संक्षिप्त पाठ संदेश

छवि

निर्णय एल्गोरिथ्म


शेडर का उपयोग करते हुए, हम सही यूवी निर्देशांक का उपयोग करके पूर्व-तैयार बनावट प्रदर्शित करते हैं। UV-निर्देशांक के साथ जानकारी दो धाराओं (क्रिया धाराओं) द्वारा ParticleSystem का उपयोग करके ParticleSystem.etCustomParticleData में एक वेक्टर 4 सूची के रूप में प्रेषित की जाती है।

हमारे कार्यान्वयन में 10 पंक्तियों और वर्णों के 10 कॉलम वाले बनावट का उपयोग शामिल है। किसी भी मोनोस्पेस फ़ॉन्ट का उपयोग फ़ॉन्ट के रूप में किया जा सकता है। यह संदेश वर्णों के बीच विभिन्न अंतर से बचने के लिए है।

PSD में बनावट स्रोत

कदम से कदम कार्यान्वयन


वर्टेक्स स्ट्रीम में स्थानांतरण के लिए वेक्टर 4 बनाना

चरित्र सेट का वर्णन करने के लिए, हम SymbolsTextureData संरचना का उपयोग करेंगे।

वर्ण सरणी को ऊपरी बाएं कोने से प्रारंभ करके सभी फ़ॉन्ट बनावट प्रतीकों को जोड़कर, मैन्युअल रूप से भरना चाहिए।

[Serializable]
public struct SymbolsTextureData
{
    //   
    public Texture texture;
    //    ,   - 
    public char[] chars;
    
    //Dictionary     -    
    private Dictionary<char, Vector2> charsDict;

    public void Initialize()
    {
        charsDict = new Dictionary<char, Vector2>();
        for (int i = 0; i < chars.Length; i++)
        {
            var c = char.ToLowerInvariant(chars[i]);
            if (charsDict.ContainsKey(c)) continue;
            //  ,    
            //    , ,     10.
            var uv = new Vector2(i % 10, 9 - i / 10);
            charsDict.Add(c, uv);
        }
    }

    public Vector2 GetTextureCoordinates(char c)
    {
        c = char.ToLowerInvariant(c);
        if (charsDict == null) Initialize();

        if (charsDict.TryGetValue(c, out Vector2 texCoord))
            return texCoord;
        return Vector2.zero;
    }
}

परिणामस्वरूप, हमें TextRendererParticleSystem वर्ग मिलता है। जब आप सार्वजनिक स्पॉनपार्टिकल विधि कहते हैं, तो कण प्रणाली का एक कण वांछित मान, रंग और आकार के साथ वांछित स्थिति में आ जाएगा।

[RequireComponent(typeof(ParticleSystem))]
public class TextRendererParticleSystem : MonoBehaviour
{
    private ParticleSystemRenderer particleSystemRenderer;
    private new ParticleSystem particleSystem;
    public void SpawnParticle(Vector3 position, string message, Color color, float? startSize = null)
    {
        //  
     }
}

एकता में कण प्रणाली आपको 2 वेक्टर 4 धाराओं के रूप में कस्टम डेटा स्थानांतरित करने की अनुमति देता है।



हमने जानबूझकर UV2 के साथ स्ट्रीम के निर्देशांक में एक बदलाव से बचने के लिए एक अतिरिक्त धारा जोड़ी है। यदि ऐसा नहीं किया जाता है, तो C # में Custom1-वेक्टर के X और Y निर्देशांक Z और W TEXCOORD0 शेड के अनुरूप होंगे। और तदनुसार, Custom1.z = TEXCOORD1.x, Custom1.w = TEXCOORD1.y। जिससे भविष्य में काफी असुविधा होगी।


जैसा कि पहले बताया गया है, हम दो वेक्टर 4 का उपयोग संदेश की लंबाई और पात्रों के यूवी-निर्देशांक को व्यक्त करने के लिए करेंगे। चूंकि वेक्टर 4 में 4 प्रकार के फ्लोट के तत्व हैं, डिफ़ॉल्ट रूप से हम इसमें 4 * 4 = 16 बाइट्स डेटा पैक कर सकते हैं। चूंकि चूंकि हमारे संदेश में केवल संदेश की लंबाई (दो-अंकीय संख्या) और वर्णों के निर्देशांक (प्रत्येक वर्ण के लिए दो-अंकीय संख्या) शामिल होंगे, फिर प्रकार बाइट (0-255) की सीमा हमारे लिए निरर्थक है। दशमलव स्थानों का उपयोग करते समय बस ठीक होगा।

फ्लोट सटीकता 6-9 वर्ण है, जिसका अर्थ है कि हम प्रत्येक वेक्टर 4 के 6 बिट्स को सुरक्षित रूप से उपयोग कर सकते हैं और डेटा की अखंडता और सटीकता के बारे में चिंता नहीं करते हैं। दरअसल, हमने 7, 8 और 9 अक्षर पैक करने की कोशिश की, लेकिन फ्लोट की सटीकता पर्याप्त नहीं है।

यह पता चलता है कि प्रत्येक फ्लोट में, दशमलव स्थानों का उपयोग करते हुए, हम चार अंडों के साथ मानक संस्करण के विपरीत 6 अंकों के रूप में पैक करेंगे। कुल, एक वेक्टर 4 में 24 एकल-अंकीय संख्याएँ होंगी।

हम स्ट्रीम में 2 वैक्टर स्थानांतरित कर सकते हैं, इसलिए हम 23 अक्षरों तक संदेश भेजने के लिए दोनों का उपयोग करेंगे:

Custom1.xyzw - संदेश के पहले 12 वर्ण।
Custom2.xyzw - संदेश के अन्य 11 वर्ण + संदेश की लंबाई (अंतिम 2 वर्ण)।

उदाहरण के लिए, संदेश "हैलो" इस तरह दिखेगा।


वर्ण निर्देशांक संख्या और बनावट में वर्ण स्थिति रेखा के अनुरूप होता है।


कोड में, दो वेक्टर 4 में एक स्ट्रिंग लपेटना इस तरह होगा:

//   Vector2     float
public float PackFloat(Vector2[] vecs)
{
    if (vecs == null || vecs.Length == 0) return 0;            
    //      float
    var result = vecs[0].y * 10000 + vecs[0].x * 100000;
    if (vecs.Length > 1) result += vecs[1].y * 100 + vecs[1].x * 1000;
    if (vecs.Length > 2) result += vecs[2].y + vecs[2].x * 10;            
        return result;
}

//  Vector4    CustomData
private Vector4 CreateCustomData(Vector2[] texCoords, int offset = 0)
{
    var data = Vector4.zero;            
    for (int i = 0; i < 4; i++)
    {
        var vecs = new Vector2[3];                
        for (int j = 0; j < 3; j++)
        {
            var ind = i * 3 + j + offset;
            if (texCoords.Length > ind)
            {
                vecs[j] = texCoords[ind];
            }
            else
            {
                data[i] = PackFloat(vecs);
                i = 5; 
                break;
            }
        }
        if (i < 4) data[i] = PackFloat(vecs);
    }
    return data;
}

//    
public void SpawnParticle(Vector3 position, string message, Color color, float? startSize = null)
{
    var texCords = new Vector2[24]; //  24  - 23  +  
    var messageLenght = Mathf.Min(23, message.Length);
    texCords[texCords.Length - 1] = new Vector2(0, messageLenght);
    for (int i = 0; i < texCords.Length; i++)
    {
        if (i >= messageLenght) break;
        //  GetTextureCoordinates()  SymbolsTextureData    
        texCords[i] = textureData.GetTextureCoordinates(message[i]);
    }
		
    var custom1Data = CreateCustomData(texCords);
    var custom2Data = CreateCustomData(texCords, 12);
}

CustomData के साथ वेक्टर तैयार है। यह वांछित मापदंडों के साथ एक नया कण मैन्युअल रूप से फैलाने का समय है।

कण

स्पॉन पहली चीज जो हमें करने की ज़रूरत है वह यह सुनिश्चित करें कि CustomData धाराएँ कण के रेंडरर सेटिंग्स में सक्रिय हैं:

//   ParticleSystem
if (particleSystem == null) particleSystem = GetComponent<ParticleSystem>();

if (particleSystemRenderer == null)
{
    //   ParticleSystemRenderer,       
    particleSystemRenderer = particleSystem.GetComponent<ParticleSystemRenderer>();
    var streams = new List<ParticleSystemVertexStream>();
    particleSystemRenderer.GetActiveVertexStreams(streams);
    //   Vector2(UV2, SizeXY, etc.),        
    if (!streams.Contains(ParticleSystemVertexStream.UV2)) streams.Add(ParticleSystemVertexStream.UV2);
    if (!streams.Contains(ParticleSystemVertexStream.Custom1XYZW)) streams.Add(ParticleSystemVertexStream.Custom1XYZW);
    if (!streams.Contains(ParticleSystemVertexStream.Custom2XYZW)) streams.Add(ParticleSystemVertexStream.Custom2XYZW);
    particleSystemRenderer.SetActiveVertexStreams(streams);
}

एक कण बनाने के लिए, हम कण प्रणाली के एमिट () पद्धति का उपयोग करते हैं।

//  
//      
// startSize3D  X,       
//   
var emitParams = new ParticleSystem.EmitParams
{
    startColor = color,
    position = position,
    applyShapeToPosition = true,
    startSize3D = new Vector3(messageLenght, 1, 1)
};
//      ,    SpawnParticle 
//   startSize
if (startSize.HasValue) emitParams.startSize3D *= startSize.Value * particleSystem.main.startSizeMultiplier;
//  
particleSystem.Emit(emitParams, 1);

//     
var customData = new List<Vector4>();
//  ParticleSystemCustomData.Custom1  ParticleSystem
particleSystem.GetCustomParticleData(customData, ParticleSystemCustomData.Custom1);
//   , ..  ,     
customData[customData.Count - 1] = custom1Data;
//   ParticleSystem
particleSystem.SetCustomParticleData(customData, ParticleSystemCustomData.Custom1);

//  ParticleSystemCustomData.Custom2
particleSystem.GetCustomParticleData(customData, ParticleSystemCustomData.Custom2);            
customData[customData.Count - 1] = custom2Data;
particleSystem.SetCustomParticleData(customData, ParticleSystemCustomData.Custom2);

दोनों ब्लॉक को स्पॉनपार्टिकल () विधि में जोड़ें और C # भाग तैयार है: संदेश को वर्टेक्स स्ट्रीम में दो वेक्टर 4 के रूप में GPU द्वारा पैक और प्रसारित किया जाता है। सबसे दिलचस्प बात यह है कि इस डेटा को स्वीकार करना और इसे सही ढंग से प्रदर्शित करना है।

शेडर कोड

Shader "Custom/TextParticles"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        //         10,    
        _Cols ("Columns Count", Int) = 10
        _Rows ("Rows Count", Int) = 10
    }
    SubShader
    {            
        Tags { "RenderType"="Opaque" "PreviewType"="Plane" "Queue" = "Transparent+1"}
        LOD 100
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float4 uv : TEXCOORD0;
                //    customData
                float4 customData1 : TEXCOORD1;
                float4 customData2 : TEXCOORD2;
            };           

            struct v2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
                float4 uv : TEXCOORD0;
                float4 customData1 : TEXCOORD1;
                float4 customData2 : TEXCOORD2;
            };
            
            uniform sampler2D _MainTex;
            uniform uint _Cols;
            uniform uint _Rows;
            
            v2f vert (appdata v)
            {
                v2f o;
                //        w- ?
                //       .
                //      100.
                float textLength = ceil(fmod(v.customData2.w, 100));

                o.vertex = UnityObjectToClipPos(v.vertex);
                //  UV ,   -   
                o.uv.xy = v.uv.xy * fixed2(textLength / _Cols, 1.0 / _Rows);
                o.uv.zw = v.uv.zw;
                o.color = v.color;                
                o.customData1 = floor(v.customData1);
                o.customData2 = floor(v.customData2);
                return o;
            }
            
            fixed4 frag (v2f v) : SV_Target
            {
                fixed2 uv = v.uv.xy;
                //   
                uint ind = floor(uv.x * _Cols);

                uint x = 0;
                uint y = 0;

                //  ,   
                //0-3 - customData1
                //4-7 - customData2
                uint dataInd = ind / 3;
                //   6     float
                uint sum = dataInd < 4 ? v.customData1[dataInd] : v.customData2[dataInd - 4];

                //  float      
                for(int i = 0; i < 3; ++i)
                {
                    if (dataInd > 3 & i == 3) break;
                    //  ,   10^2 = 99  ..
                    uint val = ceil(pow(10, 5 - i * 2));
                    x = sum / val;
                    sum -= x * val;

                    val = ceil(pow(10, 4 - i * 2));
                    y = sum / val;
                    sum -= floor(y * val);

                    if (dataInd * 3 + i == ind) i = 3;
                }                

                float cols = 1.0 / _Cols;
                float rows = 1.0 / _Rows;
                // UV-,  - , , 
                //     
                uv.x += x * cols - ind * rows;
                uv.y += y * rows;
                
                return tex2D(_MainTex, uv.xy) * v.color;
            }
            ENDCG
        }
    }
}

एकता संपादक

सामग्री बनाएँ और इसे हमारे shader को असाइन करें। दृश्य पर, ParticleSystem घटक के साथ एक ऑब्जेक्ट बनाएं, बनाई गई सामग्री को असाइन करें। फिर हम कण व्यवहार को समायोजित करते हैं और Play On Awake पैरामीटर को बंद करते हैं। किसी भी वर्ग से, हम RendererParticleSystem.SpawnParticle () विधि को कहते हैं या सौदेबाजी विधि का उपयोग करते हैं।

[ContextMenu("TestText")]
public void TestText()
{
    SpawnParticle(transform.position, "Hello world!", Color.red);
}

स्रोत कोड, संसाधन और उपयोग के उदाहरण यहां देखे जा सकते हैं

संदेश प्रणाली कार्रवाई में

छवि

बस इतना ही। संदेश उत्पादन कण प्रणाली का उपयोग कर तैयार! हमें उम्मीद है कि इस समाधान से एकता खेल डेवलपर्स को फायदा होगा।

UPD: प्रस्तावित समाधान का प्रदर्शन
कई लोगों की टिप्पणियों में, इस पद्धति के प्रदर्शन के बारे में एक प्रश्न उत्पन्न हुआ। एकता के प्रोफाइलर द्वारा विशेष रूप से किए गए माप। स्थितियां समान हैं - 1000 चलती, रंग बदलने वाली वस्तुएं।

मानक UI (केवल कैनवास जिस पर केवल 1000 UI पाठ ऑब्जेक्ट हैं) का उपयोग करने का परिणाम:

कुल फ्रेम समय कम से कम 50ms है, जिसमें से 40ms कैनवास को अपडेट करने पर खर्च किए जाते हैं। एक ही समय में, ऑब्जेक्ट भी स्पॉन नहीं करते हैं, लेकिन बस चलते हैं।

हमारे समाधान का उपयोग कर 1000 कणों की एक स्पॉन का परिणाम:

सभी जादू GPU पर होते हैं, यहां तक ​​कि 1000 कणों के स्पॉन के समय में, फ्रेम की गणना 5ms से कम में की जाती है।

Source: https://habr.com/ru/post/undefined/


All Articles