Advertisement
  1. Game Development
  2. Game Art Effects

كيفية توليد تأثيرات ضوئية جيدة 2D البرق في الوحدة (JavaScript)

Scroll to top
Read Time: 40 min

Arabic (العربية/عربي) translation by Dzu Al-Faqqar (you can also view the original English article)

هناك الكثير من الاستخدامات لتأثيرات البرق في الألعاب ، من الأجواء الخلفية أثناء العاصفة إلى هجمات البرق المدمرة للساحر. في هذا البرنامج التعليمي ، سأشرح كيفية إنشاء تأثيرات البرق الباهرة 2D بشكل برمجي: البراغي ، والفروع ، وحتى النص.

هذا البرنامج التعليمي مكتوب خصيصًا لـ Unity ، مع كل مقتطفات الشفرة في JavaScript. نفس البرنامج التعليمي متاح أيضًا مع كود C. إذا كنت لا تستخدم Unity ، فقم بإلقاء نظرة على هذا الإصدار بدون تعريفات من نفس البرنامج التعليمي ؛ هو مكتوب ل XNA ، ولكن يجب أن تكون قادرا على استخدام نفس التقنيات والمفاهيم في أي محرك gamedev ومنصة.

عرض

تحقق من العرض التوضيحي أدناه:

انقر على كائن الوحدة ، ثم استخدم مفاتيح الأرقام للتبديل بين العروض التوضيحية. تتطلب بعض العروض التوضيحية النقر في موقع واحد أو موقعين لتنشيطهم.

الإعداد الأساسي

للبدء ، ستحتاج إلى إنشاء مشروع ثنائي الأبعاد جديد في Unity. سمها ما شئت. في Unity ، أنشئ أربعة مجلدات: Materials ، Prefabs ، Scripts ، و Sprites.

بعد ذلك ، انقر فوق الكاميرا الرئيسية وتأكد من تعيين Projection الخاص بها إلى Orthographic. اضبط حجم الكاميرا على 10.

انقر بالزر الأيمن على المجلد المواد وحدد إنشاء > مواد. أعد تسميته إلى مضافة. قم بتحديد هذه المواد وتغيير تظليل للجسيمات > المضافة. هذا سوف يساعد الخاص بك البرق "بوب" في وقت لاحق.

الخطوة 1: رسم خط متوهجة

كتلة الإنشاء الأساسية ونحن بحاجة إلى جعل البرق من خط مستقيم. ابدأ بفتح المفضلة صورة برامج تحرير ورسم خط مستقيم من البرق مع تأثير توهج. هنا هو ما يشبه الألغام:

أننا نريد لرسم خطوط ذات أطوال مختلفة، حيث أننا سوف يقتطع قطعة خط ثلاث قطع كما هو مبين أدناه (اقتصاص الصورة الخاصة بك حسب الضرورة). وهذا سيتيح لنا على امتداد الجزء الأوسط لأي طول نحب. نظراً لأننا نريد أن تمتد في الجزء الأوسط، نحن حفظه فقط بكسل واحدة سميكة. أيضا، كما قطع اليمين واليسار صور مرآة لبعضها البعض، نحن بحاجة فقط لإنقاذ واحد منهم؛ نحن انعكاس ذلك في التعليمات البرمجية.

اسحب ملفات الصور الخاصة بك إلى مجلد العفاريت في لوحة المشروع. هذا سوف استيراد ملفات الصور إلى مشروع الوحدة. انقر فوق العفاريت عرضها في لوحة التفتيش. تأكد من تعيين "نوع النسيج" إلى (Sprite(2D \ uGUI، وتعيين "العلامة التعبئة" للخط.

العلامة التعبئة سوف تساعد الوحدة حفظ رسم المكالمات عند رسم لنا البرق، ولذلك تأكد من يمكنك إعطاء كل العفاريت نفس "العلامة التعبئة"، وأﻻ فإنها لن تحسن الأداء.

الآن، دعنا بتعريف فئة جديدة للتعامل مع أجزاء خط الرسم:

1
#pragma strict
2
3
class LineJS extends MonoBehaviour
4
{
5
    //Start

6
    public var A: Vector2;
7
    
8
    //End

9
    public var B: Vector2;
10
    
11
    //Thickness of line

12
    public var Thickness: float;
13
    
14
    //Children that contain the pieces that make up the line

15
    public var StartCapChild : GameObject;
16
    public var LineChild : GameObject;
17
    public var EndCapChild : GameObject;
18
    
19
    //Create a new line

20
    public function Line(a : Vector2, b : Vector2, thickness : float)
21
    {
22
        A = a;
23
        B = b;
24
        Thickness = thickness;
25
    }
26
    
27
    //Used to set the color of the line

28
    public function SetColor(color : Color)
29
    {
30
        StartCapChild.GetComponent(SpriteRenderer).color = color;
31
        LineChild.GetComponent(SpriteRenderer).color = color;
32
        EndCapChild.GetComponent(SpriteRenderer).color = color;
33
    }
34
    
35
    //...

36
}

A و B هي النهاية للخط. بالقياس وتدوير قطعة الخط، ونحن رسم خط أي سمك وطول، واتجاه.

أضف الأسلوب التالي ()Draw إلى الجزء السفلي من LIneJS لينيجس:

1
//Will actually draw the line

2
public function Draw()
3
{
4
    var difference : Vector2 = B - A;
5
    var rotation : float = Mathf.Atan2(difference.y, difference.x) * Mathf.Rad2Deg;
6
    
7
    //Set the scale of the line to reflect length and thickness

8
    LineChild.transform.localScale = new Vector3(100 * (difference.magnitude / LineChild.GetComponent(SpriteRenderer).sprite.rect.width), 
9
                                                 Thickness, 
10
                                                 LineChild.transform.localScale.z);
11
    
12
    StartCapChild.transform.localScale = new Vector3(StartCapChild.transform.localScale.x, 
13
                                                     Thickness, 
14
                                                     StartCapChild.transform.localScale.z);
15
    
16
    EndCapChild.transform.localScale = new Vector3(EndCapChild.transform.localScale.x, 
17
                                                   Thickness, 
18
                                                   EndCapChild.transform.localScale.z);
19
    
20
    //Rotate the line so that it is facing the right direction

21
    LineChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation));
22
    StartCapChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation));
23
    EndCapChild.transform.rotation = Quaternion.Euler(new Vector3(0,0, rotation + 180));
24
    
25
    //Move the line to be centered on the starting point

26
    LineChild.transform.position = new Vector3 (A.x, A.y, LineChild.transform.position.z);
27
    StartCapChild.transform.position = new Vector3 (A.x, A.y, StartCapChild.transform.position.z);
28
    EndCapChild.transform.position = new Vector3 (A.x, A.y, EndCapChild.transform.position.z);
29
    
30
    //Need to convert rotation to radians at this point for Cos/Sin

31
    rotation *= Mathf.Deg2Rad;
32
    
33
    //Store these so we only have to access once

34
    var lineChildWorldAdjust : float = LineChild.transform.localScale.x * LineChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
35
    var startCapChildWorldAdjust : float = StartCapChild.transform.localScale.x * StartCapChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
36
    var endCapChildWorldAdjust : float = EndCapChild.transform.localScale.x * EndCapChild.GetComponent(SpriteRenderer).sprite.rect.width / 2f;
37
    
38
    //Adjust the middle segment to the appropriate position

39
    LineChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * lineChildWorldAdjust, 
40
                                                 .01f * Mathf.Sin(rotation) * lineChildWorldAdjust,
41
                                                 0);
42
    
43
    //Adjust the start cap to the appropriate position

44
    StartCapChild.transform.position -= new Vector3 (.01f * Mathf.Cos(rotation) * startCapChildWorldAdjust, 
45
                                                     .01f * Mathf.Sin(rotation) * startCapChildWorldAdjust,
46
                                                     0);
47
    
48
    //Adjust the end cap to the appropriate position

49
    EndCapChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * lineChildWorldAdjust * 2, 
50
                                                   .01f * Mathf.Sin(rotation) * lineChildWorldAdjust * 2,
51
                                                   0);
52
    EndCapChild.transform.position += new Vector3 (.01f * Mathf.Cos(rotation) * endCapChildWorldAdjust, 
53
                                                   .01f * Mathf.Sin(rotation) * endCapChildWorldAdjust,
54
                                                   0);
55
}

الطريقة التي يمكننا وضع الجزء الأوسط والقبعات سيجعل منهم الانضمام بسلاسة عندما نوجه لهم. وضع الغطاء ابدأ عند النقطة أ، يتم تمدد الجزء الأوسط إلى العرض الذي تريده، ونهاية سقف استدارة 180 درجة ورسمها في النقطة باء

الآن نحن بحاجة إلى خلق الجاهزة للصف لينيجس للعمل مع. في الوحدة، من القائمة، حدد جاميوبجيكت > "إنشاء فارغة". وسوف يظهر الكائن في لوحة التسلسل الهرمي الخاص بك. تسميته إلى LineJS واسحب البرنامج النصي لينيجس على ذلك. ينبغي أن ننظر شيئا مثل الصورة أدناه.

سنستخدم هذا الكائن كحاوية لقطعة من لدينا خط مستقيم.

الآن نحن بحاجة لإنشاء كائنات لقطعة من لدينا خط مستقيم. إنشاء العفاريت الثلاثة عن طريق تحديد جاميوبجيكت > إنشاء أخرى > العفريت من القائمة. أعد تسميتها إلى StartCap ، الشريحة المتوسطة ، ونهاية النهاية. اسحبها إلى كائن LineJS بحيث تصبح أطفالها - يجب أن يبدو هذا مثل الصورة أدناه.

تذهب من خلال كل طفل وموادها في "العارض العفريت" للمواد المضافة التي أنشأنا في وقت سابق. قم بتعيين كل طفل العفريت المناسبة. (قبعات اثنين يجب أن تحصل على العفريت كاب ويجب أن تحصل على الجزء الأوسط العفريت خط).

انقر فوق الكائن لينيجس حيث يمكنك أن ترى البرنامج النصي في لوحة التفتيش. تعيين الأطفال إلى فتحاتها المناسبة وثم اسحب الكائن لينيجس إلى المجلد بريفابس لإنشاء الجاهزة لذلك. يمكنك الآن حذف الكائن لينيجس من لوحة التسلسل الهرمي.

الخطوة 2: إنشاء الخطوط المتعرجة

البرق يميل إلى تشكيل الخطوط المتعرجة، حيث أننا سنحتاج خوارزمية لتوليد هذه. سوف نقوم بذلك باختيار النقاط بشكل عشوائي على طول خط، وتشريد لهم عشوائي بعيداً عن الخط.

استخدام إزاحة عشوائية تماما يميل إلى جعل الخط خشنة جداً، حيث أننا سوف سلسة النتائج عن طريق الحد من مدى أبعد من كل النقاط الأخرى المجاورة يمكن أن المشردين-انظر الفرق بين الأسطر الثاني والثالث في الشكل أدناه.

ونحن على نحو سلس السطر بوضع النقاط عند إزاحة مشابهة للنقطة السابقة؛ يسمح هذا السطر ككل يهيم صعودا وهبوطاً، مع منع أي جزء منه من يجري خشنة جداً.

دعونا إنشاء فئة LightningBoltJS للتعامل مع إنشاء خطوطنا الخشنة.

1
#pragma strict
2
import System.Collections.Generic;
3
class LightningBoltJS extends MonoBehaviour
4
{    
5
    //List of all of our active/inactive lines

6
    public var ActiveLineObj : List.<GameObject>;
7
    public var InactiveLineObj : List.<GameObject>;
8
    
9
    //Prefab for a line

10
    public var LinePrefab : GameObject;
11
    
12
    //Transparency

13
    public var Alpha : float;
14
    
15
    //The speed at which our bolts will fade out

16
    public var FadeOutRate : float;
17
    
18
    //The color of our bolts

19
    public var Tint : Color;
20
    
21
    //The position where our bolt started

22
    public function Start()
23
    {
24
        var first : GameObject = ActiveLineObj[0];
25
        return first.GetComponent(LineJS).A;
26
    }
27
    
28
    //The position where our bolt ended

29
    public function End()
30
    {
31
        var last : GameObject = ActiveLineObj[ActiveLineObj.Count-1];
32
        return last.GetComponent(LineJS).B;
33
    }
34
    
35
    //True if the bolt has completely faded out

36
    public function IsComplete()
37
    { 
38
      return Alpha <= 0;
39
    }
40
    
41
    public function Initialize(maxSegments : int)
42
    {
43
        //Initialize lists for pooling

44
        ActiveLineObj = new List.<GameObject>();
45
        InactiveLineObj = new List.<GameObject>();
46
        
47
        for(var i : int = 0; i < maxSegments; i++)
48
        {
49
            //instantiate from our Line Prefab

50
            var line : GameObject = GameObject.Instantiate(LinePrefab);
51
            
52
            //parent it to our bolt object

53
            line.transform.parent = transform;
54
            
55
            //set it inactive

56
            line.SetActive(false);
57
            
58
            //add it to our list

59
            InactiveLineObj.Add(line);
60
        }
61
    }
62
    
63
    public function ActivateBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
64
    {
65
        //for use in loops later

66
        var i : int;
67
        
68
        //Store tint

69
        Tint = color;
70
        
71
        //Store alpha

72
        Alpha = 1.5f;
73
        
74
        //Store fade out rate

75
        FadeOutRate = 0.03f;
76
        
77
        //actually create the bolt

78
        //Prevent from getting a 0 magnitude

79
        if(Vector2.Distance(dest, source) <= 0)
80
        {
81
            var adjust : Vector2 = Random.insideUnitCircle;
82
            if(adjust.magnitude <= 0) adjust.x += .1f;
83
            dest += adjust;
84
        }
85
        
86
        //difference from source to destination

87
        var slope : Vector2 = dest - source;
88
        var normal : Vector2 = (new Vector2(slope.y, -slope.x)).normalized;
89
        
90
        //distance between source and destination

91
        var distance : float = slope.magnitude;
92
        
93
        var positions : List.<float> = new List.<float>();
94
        positions.Add(0);
95
        
96
        for (i = 0; i < distance / 4; i++) 
97
        {
98
            //Generate random positions between 0 and 1 to break up the bolt

99
            //positions.Add (Random.Range(0f, 1f));

100
            positions.Add(Random.Range(.25f, .75f));
101
        }
102
        
103
        positions.Sort();
104
        
105
        var Sway : float = 80;
106
        var Jaggedness : float = 1 / Sway;
107
        
108
        //Affects how wide the bolt is allowed to spread

109
        var spread : float = 1f;
110
        
111
        //Start at the source

112
        var prevPoint : Vector2 = source;
113
        
114
        //No previous displacement, so just 0

115
        var prevDisplacement : float = 0;
116
        
117
        for (i = 1; i < positions.Count; i++)
118
        {
119
            //don't allow more than we have in the pool

120
            var inactiveCount : int = InactiveLineObj.Count;
121
            if(inactiveCount <= 0) break;
122
            
123
            var pos : float = positions[i];
124
            var prevPos : float = positions[i - 1];
125
            //used to prevent sharp angles by ensuring very close positions also have small perpendicular variation.

126
            var scale : float = (distance * Jaggedness) * (pos - prevPos);
127
            
128
            //defines an envelope. Points near the middle of the bolt can be further from the central line.

129
            var envelope : float = pos > 0.95f ? 20 * (1 - pos) : spread;
130
            
131
            //calculate the displacement

132
            var displacement : float = Random.Range(-Sway, Sway);
133
            displacement -= (displacement - prevDisplacement) * (1 - scale);
134
            displacement *= envelope;
135
            
136
            //Calculate the end point

137
            var point : Vector2 = source + (pos * slope) + (displacement * normal);
138
            
139
            activateLine(prevPoint, point, thickness);
140
            prevPoint = point;
141
            prevDisplacement = displacement;
142
        }
143
        
144
        activateLine(prevPoint, dest, thickness);
145
    }
146
    
147
    public function DeactivateSegments()
148
    {
149
        for(var i : int = ActiveLineObj.Count - 1; i >= 0; i--)
150
        {
151
            var line : GameObject = ActiveLineObj[i];
152
            line.SetActive(false);
153
            
154
            ActiveLineObj.RemoveAt(i);
155
            InactiveLineObj.Add(line);
156
        }
157
    }
158
    
159
    function activateLine(A : Vector2, B : Vector2, thickness : float)
160
    {
161
        //get the inactive count

162
        var inactiveCount : int = InactiveLineObj.Count;
163
        
164
        //only activate if we can pull from inactive

165
        if(inactiveCount <= 0) return;
166
        
167
        //pull the GameObject

168
        var lineObj : GameObject = InactiveLineObj[InactiveLineObj.Count - 1];
169
        
170
        //set it active

171
        lineObj.SetActive(true);
172
        
173
        //get the Line component

174
        var lineComponent : LineJS = lineObj.GetComponent(LineJS);
175
        lineComponent.SetColor(Color.white);
176
        lineComponent.A = A;
177
        lineComponent.B = B;
178
        lineComponent.Thickness = thickness;
179
        ActiveLineObj.Add(lineObj);
180
        InactiveLineObj.Remove(lineObj);
181
    }
182
    
183
    public function Draw()
184
    {
185
        //if the bolt has faded out, no need to draw

186
        if (Alpha <= 0) return;
187
        
188
        for(var i : int = 0; i < ActiveLineObj.Count; i++)
189
        {
190
            var obj : GameObject = ActiveLineObj[i];
191
            var lineComponent : LineJS = obj.GetComponent(LineJS);
192
            lineComponent.SetColor(Tint * (Alpha * 0.6f));
193
            lineComponent.Draw();
194
        }
195
    }
196
    
197
    public function Update()
198
    {
199
        Alpha -= FadeOutRate;
200
    }
201
    
202
    //...

203
}

قد تبدو الشفرة مرعبة بعض الشيء ، ولكنها ليست سيئة للغاية بمجرد فهمك للمنطق. قبل أن نستمر في العمل ، نفهم أننا قد اخترنا تجميع أجزاء الخط في البراغي (حيث أن عملية إنشاء وتدمير الأشياء باستمرار يمكن أن تكون مكلفة في الوحدة).

  • سوف يطلق مرة واحدة على كل صاعقة الدالة ()Initialize وسوف تحدد كم عدد أجزاء الخط كل الترباس هو السماح باستخدام.
  • سيتم تنشيط وظيفة ()activateLine خط مستقيم باستخدام بيانات موقف معين.
  • سيتم إلغاء تنشيط وظيفة ()DeactivateSegments أي أجزاء الخط النشط في أعمالنا الترباس.
  • الدالة ()ActivateBolt سوف تتعامل مع خلق لدينا خطوط خشنة وسيتم استدعاء الدالة ()activateLine لتنشيط جهودنا أجزاء الخط في المواقف المناسبة.

لإنشاء لدينا خطوط خشنة، نبدأ بالحوسبة المنحدر بين لدينا نقطتين، فضلا عن ناقلات الأمراض العادية إلى هذا المنحدر. نحن ثم اختر عدد من مواقع عشوائية على طول الخط وتخزينها في قائمة المواقع لدينا. نحن تحجيم هذه المواقف بين 0 و 1، 0 يمثل بداية السطر ويمثل 1 بنقطة النهاية.، ثم قم بفرز هذه المواقف للسماح لنا بسهولة إضافة أجزاء الخط بينهما.

تمر الحلقة عبر النقاط المختارة عشوائياً وتزيحها بطول المعتاد بمقدار عشوائي. عامل المقياس موجود لتجنب الزوايا الحادة بشكل مفرط ، ويضمن الشريح أن البرق ينتقل فعليًا إلى نقطة الوصول عن طريق الحد من النزوح عندما نكون قريبين من النهاية. الفارق هو المساعدة في التحكم في مدى انحراف القطاعات عن ميل خطنا ؛ سيعطيك انتشار 0 بشكل أساسي خطًا مستقيمًا.

لذا ، كما فعلنا مع صف LineJS لدينا ، دعونا نجعل ذلك في مكان مسبق. من القائمة ، حدد GameObject> Create Empty. سيظهر الكائن في لوحة التسلسل الهرمي. قم بإعادة تسميته إلى BoltJS ، واسحب نسخة من البرنامج النصي LightningBoltJS عليه. وأخيرًا ، انقر فوق كائن BoltJS وقم بتعيين الخاصية الخطية لـ LineJS ، من مجلد Prefabs ، إلى الفتحة المناسبة في البرنامج النصي LightningBoltJS. بمجرد الانتهاء من ذلك ، قم ببساطة بسحب كائن BoltJS إلى مجلد Prefabs لإنشاء تهيئة مسبقة.

الخطوة 3: إضافة الرسوم المتحركة

يجب أن تضيء البرق بسرعة ثم تتلاشى. هذا هو ما لدينا وظائف التحديث() ورسم() في LightningBoltJS هي ل. استدعاء ()update سيجعل الترباس تتلاشى. استدعاء ()Draw سيتم تحديث الألوان الترباس على الشاشة. ()IsComplete وسوف أقول لكم عندما الترباس قد تﻻشى تماما.

الخطوة 4: إنشاء الترباس

والآن بعد أن حصلنا على فصل LightningBoltJS ، دعنا نستخدمه جيدًا وننشئ مشهدًا تجريبيًا سريعًا.

سنستخدم تجمع عناصر لهذا العرض التوضيحي ، لذا سنرغب في إنشاء كائن فارغ لاستيعاب مسامير التثبيت النشطة وغير النشطة الخاصة بنا (ببساطة لأغراض تنظيمية). في Unity ، من القائمة ، حدد GameObject > Create Empty. سيظهر الكائن في لوحة التسلسل الهرمي. قم بإعادة تسميته إلى LightningPoolHolder.

انقر على الحق في مجلد البرامج النصية، وحدد إنشاء > جافا سكريبت. اسم البرنامج النصي الخاص بك ديموسكريبتجس وفتحه. وإليك بعض التعليمات البرمجية سريعة للحصول على أنك بدأته:

1
#pragma strict
2
import System.Collections.Generic;
3
4
class DemoScriptJS extends MonoBehaviour
5
{
6
    //Prefabs to be assigned in Editor

7
    public var BoltPrefab : GameObject;
8
    
9
    //For pooling

10
    var activeBoltsObj : List.<GameObject>;
11
    var inactiveBoltsObj : List.<GameObject>;
12
    var maxBolts : int = 1000;
13
    
14
    //For handling mouse clicks

15
    var clicks : int = 0;
16
    var pos1 : Vector2;
17
    var pos2 : Vector2;
18
    
19
    function Start()
20
    {
21
        //Initialize lists

22
        activeBoltsObj = new List.<GameObject>();
23
        inactiveBoltsObj = new List.<GameObject>();
24
        
25
        //for use later

26
        var tempV3 : Vector3;
27
        
28
        //Grab the parent we'll be assigning to our bolt pool

29
        var p : GameObject = GameObject.Find("LightningPoolHolder");
30
        
31
        //For however many bolts we've specified

32
        for(var i : int = 0; i < maxBolts; i++)
33
        {
34
            //create from our prefab

35
            var bolt : GameObject = Instantiate(BoltPrefab);
36
            
37
            //Assign parent

38
            bolt.transform.parent = p.transform;
39
            
40
            //Initialize our lightning with a preset number of max sexments

41
            bolt.GetComponent(LightningBoltJS).Initialize(25);
42
            
43
            //Set inactive to start

44
            bolt.SetActive(false);
45
            
46
            //Store in our inactive list

47
            inactiveBoltsObj.Add(bolt);
48
        }
49
    }
50
    
51
    function Update()
52
    {
53
        //Declare variables for use later

54
        var boltObj : GameObject;
55
        var boltComponent : LightningBoltJS;
56
        var i : int;
57
        var tempV3 : Vector3;
58
        var adjust : Vector2;
59
        
60
        //store off the count for effeciency

61
        var activeLineCount : int = activeBoltsObj.Count;
62
        
63
        //loop through active lines (backwards because we'll be removing from the list)

64
        for (i = activeLineCount - 1; i >= 0; i--)
65
        {
66
        	//pull GameObject

67
        	boltObj = activeBoltsObj[i];
68
        
69
        	//get the LightningBolt component

70
        	boltComponent = boltObj.GetComponent(LightningBoltJS);
71
        
72
        	//if the bolt has faded out

73
        	if(boltComponent.IsComplete())
74
        	{
75
                //deactive the segments it contains

76
                boltComponent.DeactivateSegments();
77
                
78
                //set it inactive

79
                boltObj.SetActive(false);
80
                
81
                //move it to the inactive list

82
                activeBoltsObj.RemoveAt(i);
83
                inactiveBoltsObj.Add(boltObj);
84
        	}
85
        }
86
        
87
        //If left mouse button pressed

88
        if(Input.GetMouseButtonDown(0))
89
        {
90
            //if first click

91
            if(clicks == 0)
92
            {
93
                //store starting position

94
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
95
                pos1 = new Vector2(tempV3.x, tempV3.y);
96
            }
97
            else if(clicks == 1) //second click

98
            {
99
                //store end position

100
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
101
                pos2 = new Vector2(tempV3.x, tempV3.y);
102
                
103
                CreatePooledBolt(pos1,pos2, Color.white, 1f);
104
            }
105
            
106
            //increment our tick count

107
            clicks++;
108
            
109
            //restart the count after 2 clicks

110
            if(clicks > 1) clicks = 0;
111
        }
112
        
113
        //update and draw active bolts

114
        for(i = 0; i < activeBoltsObj.Count; i++)
115
        {
116
            boltObj = activeBoltsObj[i];
117
            boltObj.GetComponent(LightningBoltJS).Update();
118
            boltObj.GetComponent(LightningBoltJS).Draw();
119
        }
120
    }
121
    
122
    function CreatePooledBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
123
    {
124
        //if there is an inactive bolt to pull from the pool

125
        if(inactiveBoltsObj.Count > 0)
126
        {
127
            //pull the GameObject

128
            var boltObj : GameObject = inactiveBoltsObj[inactiveBoltsObj.Count - 1];
129
            
130
            //set it active

131
            boltObj.SetActive(true);
132
            
133
            //move it to the active list

134
            activeBoltsObj.Add(boltObj);
135
            inactiveBoltsObj.RemoveAt(inactiveBoltsObj.Count - 1);
136
            
137
            //get the bolt component

138
            var boltComponent : LightningBoltJS =  boltObj.GetComponent(LightningBoltJS);
139
            
140
            //activate the bolt using the given position data

141
            boltComponent.ActivateBolt(source, dest, color, thickness);
142
        }
143
    }
144
}

كل هذه التعليمات البرمجية تعطينا طريقة لإنشاء مسامير باستخدام تجميع الكائنات. هناك طرق أخرى يمكنك إعدادها ، لكن هذا ما سنقوم به! بمجرد إعداده ، كل ما عليك فعله هو النقر مرتين لإنشاء مسمار على الشاشة: مرة واحدة لوضع البداية ومرة واحدة لموضع النهاية.

سنحتاج إلى كائن لوضع DemoScriptJS على. من القائمة ، حدد GameObject > Create Empty. سيظهر الكائن في لوحة التسلسل الهرمي. قم بإعادة تسميته إلى DemoScript واسحب البرنامج النصي DemoScriptJS إليه. انقر على كائن DemoScript حتى نتمكن من مشاهدته في لوحة المفتش. قم بتعيين البنية الجاهزة BoltJS ، من مجلد Prefabs ، إلى الفتحة المطابقة في DemoScriptJS.

ينبغي أن يكون كافياً تحصل على الذهاب! تشغيل المشهد في الوحدة وجربة!

الخطوة 5: إنشاء فرع البرق

يمكنك استخدام فئة LightningBoltJS ككتلة بناء لخلق تأثيرات البرق أكثر إثارة للاهتمام. على سبيل المثال ، يمكنك جعل البراغي تتفرع كما هو موضح أدناه:

لجعل فرع البرق ، نختار نقاطًا عشوائية على طول صاعقة البرق وإضافة مسامير جديدة تتفرع من هذه النقاط. في الشفرة أدناه ، نقوم بإنشاء ما بين ثلاثة وستة فروع منفصلة عن البرغي الرئيسي بزاوية 30 درجة.

1
#pragma strict

2
import System.Collections.Generic;
3
4
class BranchLightningJS extends MonoBehaviour
5
{
6
    //For holding all of our bolts in our branch

7
    public var boltsObj : List.<GameObject>;
8
    
9
    //If there are no bolts, then the branch is complete (we're not pooling here, but you could if you wanted)

10
    public function IsComplete()
11
    { 
12
        return boltsObj.Count <= 0;
13
    }
14
    
15
    //Start position of branch

16
    public var Start : Vector2;
17
    
18
    //End position of branch

19
    public var End : Vector2;
20
    
21
    static var rand : Random = new Random();
22
    
23
    public function Initialize(start : Vector2, end : Vector2, boltPrefab : GameObject)
24
    {
25
        //for use lateer

26
        var i : int;
27
        
28
        //store start and end positions

29
        Start = start;
30
        End = end;
31
        
32
        //create the  main bolt from our bolt prefab

33
        var mainBoltObj : GameObject = GameObject.Instantiate(boltPrefab);
34
        
35
        //get the LightningBolt component

36
        var mainBoltComponent : LightningBoltJS = mainBoltObj.GetComponent(LightningBoltJS);
37
        
38
        //initialize our bolt with a max of 5 segments

39
        mainBoltComponent.Initialize(5);
40
        
41
        //activate the bolt with our position data

42
        mainBoltComponent.ActivateBolt(start, end, Color.white, 1f);
43
        
44
        //add it to our list

45
        boltsObj.Add(mainBoltObj);
46
        
47
        //randomly determine how many sub branches there will be (3-6)

48
        var numBranches : int = Random.Range(3,6);
49
        
50
        //calculate the difference between our start and end points

51
        var diff : Vector2 = end - start;
52
        
53
        // pick a bunch of random points between 0 and 1 and sort them

54
        var branchPoints : List.<float> = new List.<float>();
55
        for(i = 0; i < numBranches; i++) branchPoints.Add(Random.value);
56
        branchPoints.Sort();
57
        
58
        //go through those points

59
        for (i = 0; i < branchPoints.Count; i++)
60
        {
61
            // Bolt.GetPoint() gets the position of the lightning bolt based on the percentage passed in (0 = start of bolt, 1 = end)

62
            var boltStart : Vector2 = mainBoltComponent.GetPoint(branchPoints[i]);
63
            
64
            //get rotation of 30 degrees. Alternate between rotating left and right. (i & 1 will be true for all odd numbers...yay bitwise operators!)

65
            var rot : Quaternion = Quaternion.AngleAxis(30 * ((i & 1) == 0 ? 1 : -1), new Vector3(0,0,1));
66
            
67
            var point : float = branchPoints[i];
68
            
69
            //calculate how much to adjust for our end position

70
            var adjust : Vector2 = rot * (Random.Range(.5f, .75f) * diff * (1 - point));
71
            
72
            //get the end position

73
            var boltEnd : Vector2 = adjust + boltStart;
74
            
75
            //instantiate from our bolt prefab

76
            var boltObj : GameObject = GameObject.Instantiate(boltPrefab);
77
            
78
            //get the LightningBolt component

79
            var boltComponent : LightningBoltJS = boltObj.GetComponent(LightningBoltJS);
80
            
81
            //initialize our bolt with a max of 5 segments

82
            boltComponent.Initialize(5);
83
            
84
            //activate the bolt with our position data

85
            boltComponent.ActivateBolt(boltStart, boltEnd, Color.white, 1f);
86
            
87
            //add it to the list

88
            boltsObj.Add(boltObj);
89
        }
90
    }
91
    
92
    public function Update()
93
    {
94
        //go through our active bolts

95
        for (var i : int = boltsObj.Count - 1; i >= 0; i--)
96
        {
97
            //get the GameObject

98
            var boltObj : GameObject = boltsObj[i];
99
            
100
            //get the LightningBolt component

101
            var boltComp : LightningBoltJS = boltObj.GetComponent(LightningBoltJS);
102
            
103
            //update/fade out the bolt

104
            boltComp.Update();
105
            
106
            //if the bolt has faded

107
            if(boltComp.IsComplete())
108
            {
109
                //remove it from our list

110
                boltsObj.RemoveAt(i);
111
                
112
                //destroy it (would be better to pool but I'll let you figure out how to do that =P)

113
                Destroy(boltObj);
114
            }
115
        }
116
    }
117
    
118
    //Draw our active bolts on screen

119
    public function Draw()
120
    {
121
        var boltObj : GameObject;
122
        for(var i : int; i < boltsObj.Count; i++)
123
        {
124
            boltObj = boltsObj[i];
125
            boltObj.GetComponent(LightningBoltJS).Draw();
126
        }
127
    }
128
}

يعمل هذا الرمز بشكل مشابه جدًا لفئة LightningBoltJS الخاصة بنا باستثناء أنه لا يستخدم تجميع الكائنات. ()Calling Initialize هو كل ما ستحتاج إلى القيام به لإنشاء أداة ربط متفرعة؛ بعد ذلك ، ستحتاج فقط إلى استدعاء ()Update ورسم (). سأوضح لك بالضبط كيفية القيام بذلك في DemoScriptJS في وقت لاحق في البرنامج التعليمي.

ربما لاحظتم أن الإشارة إلى دالة GetPoint() في فئة لايتنينجبولتجس. نحن لم تكن قد نفذت فعلا تلك الوظيفة حتى الآن، لذلك دعونا تأخذ الرعاية من ذلك الآن.

إضافة الدالة التالية في الجزء السفلي من فئة LightningBoltJS:

1
// Returns the point where the bolt is at a given fraction of the way through the bolt. Passing

2
// zero will return the start of the bolt, and passing 1 will return the end.

3
public function GetPoint(position : float)
4
{
5
    var start : Vector2 = Start();
6
    var length : float = Vector2.Distance(start, End());
7
    var dir : Vector2 = (End() - start) / length;
8
    position *= length;
9
    
10
    var line : LineJS;
11
    
12
    //find the appropriate line

13
    for(var i : int = 0; i < ActiveLineObj.Count; i++)
14
    {
15
        var x : GameObject = ActiveLineObj[i];
16
        
17
        if(Vector2.Dot(x.GetComponent(LineJS).B - start, dir) >= position)
18
        {
19
            line = x.GetComponent(LineJS);
20
            break;
21
        }
22
    }
23
    var lineStartPos : float = Vector2.Dot(line.A - start, dir);
24
    var lineEndPos : float = Vector2.Dot(line.B - start, dir);
25
    var linePos : float = (position - lineStartPos) / (lineEndPos - lineStartPos);
26
    
27
    return Vector2.Lerp(line.A, line.B, linePos);
28
}

الخطوة 6: إنشاء نص البرق

أدناه هو شريط فيديو لتأثير آخر يمكنك أن تجعل من البراغي البرق:

سوف نحتاج إلى القيام بإعداد أكبر قليلاً لهذا واحد. أولاً، من فريق المشروع، حدد إنشاء > رينديرتيكستوري. إعادة تسمية RenderText وتعيين حجمه إلى 256 × 256 بكسل. (لا يجب بالضرورة أن يكون هذا الحجم بالضبط ، ولكن كلما كان أصغر ، كلما زادت سرعة تشغيل البرنامج).

من القائمة، حدد تحرير > إعدادات المشروع > العلامات والطبقات. ثم، في لوحة المفتش، قم بتوسيع قطره الطبقات الأسفل، وإضافة نص إلى 8 طبقة المستخدم.

ثم سوف نحتاج إلى إنشاء كاميرا ثانية. من القائمة، حدد جاميوبجيكت > إنشاء أخرى > الكاميرا. تسميته إلى تيكستكاميرا، وتعيين الإسقاط العامودي وبه "إشارات واضحة" إلى "لون خالص". تعيين لون الخلفية في أن (r: 0, g: 0، ب: 0، ج: 0) وتعيين "قناعها إعدام" إلا أن النص (الطبقة أنشأنا فقط). وأخيراً، تعيين في "النسيج المستهدف" إلى رينديرتيكست (رينديرتيكستوري أنشأنا في وقت سابق). سوف تحتاج على الأرجح للعب حولها مع حجم الكاميرا في وقت لاحق، من أجل الحصول على كل شيء لاحتواء على الشاشة.

الآن سوف نحتاج إلى إنشاء النص الفعلي بأننا سوف يكون الرسم مع لدينا البرق. من القائمة، حدد جاميوبجيكت > إنشاء أخرى > نص واجهة المستخدم الرسومية. حدد الكائن النص واجهة المستخدم الرسومية من التسلسل الهرمي للفريق وتعيين النص إلى البرق والربط به إلى مركز الوسط، وانحيازها مركز. ثم عين في الطبقة على طبقة النص أنشأنا في وقت سابق. ربما عليك أن تلعب حولها مع "حجم الخط" من أجل احتواء النص على الشاشة.

الآن حدد الكاميرا الرئيسية وقم بتعيين Culling Mask ليكون كل شيء ما عدا طبقة Text الخاصة بنا. سيؤدي ذلك إلى اختفاء نص GUI الخاص بنا من الشاشة ، ولكن يجب رسمه على RenderTexture الذي أنشأناه في وقت سابق: اختر RenderText من لوحة Project ، وستتمكن من رؤية كلمة LIGHTNING في المعاينة في أسفل اللوحة .

إذا لم تستطع رؤية كلمة LIGHTNING ، فستحتاج إلى اللعب مع تحديد الموضع وحجم الخط وحجم الكاميرا (النص). لمساعدتك في تحديد موضع النص ، انقر فوق TextCamera في لوحة التسلسل الهرمي ، وقم بتعي Target Texture على None. ستتمكن الآن قادرة على مشاهدة نص واجهة المستخدم الرسومية الخاصة بك إذا أنها مركز على تيكستكاميرا. وبمجرد الانتهاء من كل شيء المتمركزة، تعيين "مادة الهدف" تيكستكاميرا العودة إلى رينديرتيكست.

الآن بالنسبة للتعليمات البرمجية! سوف نحتاج إلى الحصول على بكسل من النص الذي نحن الرسم. يمكننا أن نفعل هذا بالرسم نصنا إلى رينديرتارجيت وقراءة البيانات بكسل مرة أخرى في aTexture2D مع ()Texture2D.ReadPixels ثم ، يمكننا تخزين إحداثيات البكسل من النص كقائمة. List.Vector2

هنا هو رمز للقيام بذلك:

1
//Capture the important points of our text for later

2
function TextCapture()
3
{
4
    //must wait until end of frame so something is actually drawn or else it will error

5
    yield WaitForEndOfFrame();
6
    
7
    //get the camera that draws our text

8
    var cam : Camera = GameObject.Find("TextCamera").GetComponent(Camera);
9
    
10
    //make sure it has an assigned RenderTexture

11
    if(cam.targetTexture != null) 
12
    {
13
        //pull the active RenderTexture

14
        RenderTexture.active = cam.targetTexture;
15
        
16
        //capture the image into a Texture2D

17
        var image : Texture2D = new Texture2D(cam.targetTexture.width, cam.targetTexture.height);
18
        image.ReadPixels(new Rect(0, 0, cam.targetTexture.width, cam.targetTexture.height), 0, 0);
19
        image.Apply();
20
        
21
        //calculate how the text will be scaled when it is displayed as lightning on the screen

22
        scaleText = 1 / (cam.ViewportToWorldPoint(new Vector3(1,0,0)).x - cam.ViewportToWorldPoint(Vector3.zero).x);
23
        
24
        //calculate how the text will be positioned when it is displayed as lightning on the screen (centered)

25
        positionText.x -= image.width * scaleText * .5f;
26
        positionText.y -= image.height * scaleText * .5f;
27
        
28
        //basically determines how many pixels we skip/check

29
        var interval : int = 2;
30
        
31
        //loop through pixels

32
        for(var y : int = 0; y < image.height; y += interval)
33
        {
34
            for(var x : int = 0; x < image.width; x += interval)
35
            {
36
                //get the color of the pixel

37
                var color : Color = image.GetPixel(x,y);
38
                
39
                //if the color has an r (red) value

40
                if(color.r > 0)
41
                {
42
                    //add it to our points for drawing

43
                    textPoints.Add(new Vector2(x,y));
44
                }
45
            }
46
        }
47
    }
48
}

ملاحظة: سيكون لدينا لتشغيل هذه الوظيفة كورتني في بداية برنامجنا من أجل تعمل بشكل صحيح.

بعد ذلك ، كل إطار ، يمكننا اختيار أزواج من هذه النقاط عشوائياً وإنشاء صاعقة بينهما. نحن نريد تصميمه بحيث تكون نقطتين أقرب إلى بعضهما البعض ، كلما كانت الفرصة أكبر هو أن نوجد مسمارًا بينهما.

هناك تقنية بسيطة يمكننا استخدامها لتحقيق ذلك: سنختار النقطة الأولى بشكل عشوائي ، ثم سنختار عددًا ثابتًا من النقاط الأخرى عشوائيًا ونختار الأقرب.

إليك رمز ذلك (سنقوم بإضافته إلى DemoScriptJS لاحقًا):

1
//go through the points we capture earlier

2
for (var i1 : int = 0; i1 < textPoints.Count; i1++)
3
{
4
    var point : Vector2 = textPoints[i1];
5
    //randomly ignore certain points

6
    if(Random.Range(0,75) != 0) continue;
7
    
8
    //placeholder values

9
    var nearestParticle : Vector2 = Vector2.zero;
10
    var nearestDistSquared : float = float.MaxValue;
11
    
12
    for (i = 0; i < 50; i++)
13
    {
14
        //select a random point

15
        var other : Vector2 = textPoints[Random.Range(0, textPoints.Count)];
16
        
17
        //calculate the distance (squared for performance benefits) between the two points

18
        var distSquared : float = DistanceSquared(point, other);
19
        
20
        //If this point is the nearest point (but not too near!)

21
        if (distSquared < nearestDistSquared && distSquared > 3 * 3)
22
        {
23
            //store off the data

24
            nearestDistSquared = distSquared;
25
            nearestParticle = other;
26
        }
27
    }
28
    
29
    //if the point we found isn't too near/far

30
    if (nearestDistSquared < 25 * 25 && nearestDistSquared > 3 * 3)
31
    {
32
        //create a (pooled) bolt at the corresponding screen position

33
        CreatePooledBolt((point * scaleText) + positionText, (nearestParticle * scaleText) + positionText, new Color(Random.value,Random.value,Random.value,1f), 1f);
34
    }
35
}
36
37
/* The code above uses the following function 

38
 * It'll need to be placed appropriately

39
--------------------------------------------- 

40
//calculate distance squared (no square root = performance boost)

41
public function DistanceSquared(a : Vector2, b : Vector2)

42
{

43
    return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

44
}

45
---------------------------------------------*/

سيؤثر عدد نقاط الترشيح التي نختبرها على مظهر النص البرق ؛ إن التحقق من عدد أكبر من النقاط سيسمح لنا بالبحث عن نقاط قريبة جداً لربط البراغي ، مما سيجعل النص أنيقًا ومقروءًا ، ولكن مع عدد أقل من البراغي الطويلة بين الحروف. ستجعل الأرقام الأصغر حجم النص الباهر أكثر وضوحًا ولكن أقل وضوحًا.

الخطوة 7: حاول اختلافات أخرى

لقد ناقشنا إنشاء فرع من البرق والنص البرق ، ولكن هذه بالتأكيد ليست التأثيرات الوحيدة التي يمكنك القيام بها. دعونا ننظر إلى بعض الاختلافات الأخرى على البرق قد ترغب في استخدامها.

تتحرك البرق

قد ترغب في كثير من الأحيان في صنع مسمار صاعق. يمكنك القيام بذلك عن طريق إضافة مسمار قصير جديد لكل إطار في نقطة نهاية مسمار الإطار السابق.

1
//Will contain all of the pieces for the moving bolt

2
var movingBolt : List.<GameObject>;
3
4
//used for actually moving the moving bolt

5
var lightningEnd : Vector2 = new Vector2(100, 100);
6
var lightningVelocity : Vector2 = new Vector2(1, 0);
7
8
function Update()
9
{
10
    //loop through all of our bolts that make up the moving bolt

11
    for(i = movingBolt.Count - 1; i >= 0; i--)
12
    {
13
        boltObj = movingBolt[i];
14
        //get the bolt component

15
        boltComponent = boltObj.GetComponent(LightningBoltJS);
16
        
17
        //if the bolt has faded out

18
        if(boltComponent.IsComplete())
19
        {
20
            //destroy it

21
            Destroy(movingBolt[i]);
22
            
23
            //remove it from our list

24
            movingBolt.RemoveAt(i);
25
            
26
            //on to the next one, on on to the next one

27
            continue;
28
        }
29
        
30
        //update and draw bolt

31
        boltComponent.Update();
32
        boltComponent.Draw();
33
    }
34
    
35
    //if our moving bolt is active

36
    if(movingBolt.Count > 0)
37
    {
38
        boltObj = movingBolt[movingBolt.Count-1];
39
        //calculate where it currently ends

40
        lightningEnd = boltObj.GetComponent(LightningBoltJS).End();
41
        
42
        //if the end of the bolt is within 25 units of the camera

43
        if(Vector2.Distance(lightningEnd,Camera.main.transform.position) < 25)
44
        {
45
            //instantiate from our bolt prefab

46
            boltObj = GameObject.Instantiate(BoltPrefab);
47
            
48
            //get the bolt component

49
            boltComponent = boltObj.GetComponent(LightningBoltJS);
50
            
51
            //initialize it with a maximum of 5 segments

52
            boltComponent.Initialize(5);
53
            
54
            //activate the bolt using our position data (from the current end of our moving bolt to the current end + velocity) 

55
            boltComponent.ActivateBolt(lightningEnd,lightningEnd + lightningVelocity, Color.white, 1f);
56
            
57
            //add it to our list

58
            movingBolt.Add(boltObj);
59
            
60
            //update and draw our new bolt

61
            boltComponent.Update();
62
            boltComponent.Draw();
63
        }
64
    }
65
}

انفجار البرق

يقدم هذا الاختلاف تأثيرًا كبيرًا يطلق البرق في دائرة من نقطة المركز:

1
//get the difference between our two positions (destination - source = vector from source to destination)

2
var diff : Vector2 = pos2 - pos1;
3
4
function Update()
5
{
6
    //define how many bolts we want in our circle

7
    var boltsInBurst : int = 10;
8
    
9
    for(i = 0; i < boltsInBurst; i++)
10
    {
11
        //rotate around the z axis to the appropriate angle

12
        var rot : Quaternion = Quaternion.AngleAxis((360f/boltsInBurst) * i, new Vector3(0,0,1));
13
        
14
        adjust = rot * diff;
15
        
16
        //Calculate the end position for the bolt

17
        var boltEnd : Vector2 = adjust + pos1;
18
        
19
        //create a (pooled) bolt from pos1 to boltEnd

20
        CreatePooledBolt(pos1, boltEnd, Color.white, 1f);
21
    }
22
}

خطوة 8: وضع كل ذلك معا في ديموسكريبتجس

ستحتاج إلى أن تكون قادرًا على تجربة جميع هذه التأثيرات الرائعة التي أنشأناها حتى الآن ، لذلك دعونا نضعها جميعًا في DemoScriptJS التي قمنا بها في وقت سابق. ستتمكن من التبديل بين التأثيرات من خلال ضرب مفاتيح الأرقام على لوحة المفاتيح الخاصة بك لتحديد التأثير ، ثم النقر مرتين مثلما فعلنا مع البراغي السابقة.

هنا هو رمز كامل:

1
#pragma strict

2
#pragma strict

3
import System.Collections.Generic;
4
5
class DemoScriptJS extends MonoBehaviour
6
{
7
    //Prefabs to be assigned in Editor

8
    public var BoltPrefab : GameObject;
9
    public var BranchPrefab : GameObject;
10
    
11
    //For pooling

12
    var activeBoltsObj : List.<GameObject>;
13
    var inactiveBoltsObj : List.<GameObject>;
14
    var maxBolts : int = 1000;
15
    
16
    var scaleText : float;
17
    var positionText : Vector2;
18
    
19
    //Different modes for the demo

20
    class Mode
21
    {
22
        public static final var bolt : byte = 0;
23
        public static final var branch : byte = 1;
24
        public static final var moving : byte = 2;
25
        public static final var text : byte = 3;
26
        public static final var nodes : byte = 4;
27
        public static final var burst : byte = 5;
28
    }
29
    
30
    //The current mode the demo is in

31
    var currentMode : byte = Mode.bolt;
32
    
33
    //Will contain all of the pieces for the moving bolt

34
    var movingBolt : List.<GameObject>;
35
    
36
    //used for actually moving the moving bolt

37
    var lightningEnd : Vector2 = new Vector2(100, 100);
38
    var lightningVelocity : Vector2 = new Vector2(1, 0);
39
    
40
    //Will contain all of the pieces for the branches

41
    var branchesObj : List.<GameObject>;
42
    
43
    //For handling mouse clicks

44
    var clicks : int = 0;
45
    var pos1 : Vector2;
46
    var pos2 : Vector2;
47
    
48
    //For storing all of the pixels that need to be drawn by the bolts 

49
    var textPoints : List.<Vector2>;
50
    
51
    //true in text mode

52
    var shouldText : boolean = false;
53
    
54
    function Start()
55
    {
56
        //Initialize lists

57
        activeBoltsObj = new List.<GameObject>();
58
        inactiveBoltsObj = new List.<GameObject>();
59
        branchesObj = new List.<GameObject>();
60
        
61
        //for use later

62
        var tempV3 : Vector3;
63
        
64
        //Grab the parent we'll be assigning to our bolt pool

65
        var p : GameObject = GameObject.Find("LightningPoolHolder");
66
        
67
        //For however many bolts we've specified

68
        for(var i : int = 0; i < maxBolts; i++)
69
        {
70
            //create from our prefab

71
            var bolt : GameObject = Instantiate(BoltPrefab);
72
            
73
            //Assign parent

74
            bolt.transform.parent = p.transform;
75
            
76
            //Initialize our lightning with a preset number of max sexments

77
            bolt.GetComponent(LightningBoltJS).Initialize(25);
78
            
79
            //Set inactive to start

80
            bolt.SetActive(false);
81
            
82
            //Store in our inactive list

83
            inactiveBoltsObj.Add(bolt);
84
        }
85
        
86
        //Start up a coroutine to capture the pixels we'll be drawing from our text (need the coroutine or error)

87
        StartCoroutine(TextCapture());
88
    }
89
    
90
    function Update()
91
    {
92
        //Declare variables for use later

93
        var boltObj : GameObject;
94
        var boltComponent : LightningBoltJS;
95
        var i : int;
96
        var tempV3 : Vector3;
97
        var adjust : Vector2;
98
        var branchObj : GameObject;
99
        var branchComponent : BranchLightningJS;
100
        
101
        //store off the count for effeciency

102
        var activeLineCount : int = activeBoltsObj.Count;
103
        
104
        //loop through active lines (backwards because we'll be removing from the list)

105
        for (i = activeLineCount - 1; i >= 0; i--)
106
        {
107
            //pull GameObject

108
            boltObj = activeBoltsObj[i];
109
            
110
            //get the LightningBolt component

111
            boltComponent = boltObj.GetComponent(LightningBoltJS);
112
            
113
            //if the bolt has faded out

114
            if(boltComponent.IsComplete())
115
            {
116
                //deactive the segments it contains

117
                boltComponent.DeactivateSegments();
118
                
119
                //set it inactive

120
                boltObj.SetActive(false);
121
                
122
                //move it to the inactive list

123
                activeBoltsObj.RemoveAt(i);
124
                inactiveBoltsObj.Add(boltObj);
125
            }
126
        }
127
        
128
        //check for key press and set mode accordingly

129
        if(Input.GetKeyDown(KeyCode.Alpha1) || Input.GetKeyDown(KeyCode.Keypad1))
130
        {
131
            shouldText = false;
132
            currentMode = Mode.bolt;
133
        }
134
        else if(Input.GetKeyDown(KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2))
135
        {
136
            shouldText = false;
137
            currentMode = Mode.branch;
138
        }
139
        else if(Input.GetKeyDown(KeyCode.Alpha3) || Input.GetKeyDown(KeyCode.Keypad3))
140
        {
141
            shouldText = false;
142
            currentMode = Mode.moving;
143
        }
144
        else if(Input.GetKeyDown(KeyCode.Alpha4) || Input.GetKeyDown(KeyCode.Keypad4))
145
        {
146
            shouldText = true;
147
            currentMode = Mode.text;
148
        }
149
        else if(Input.GetKeyDown(KeyCode.Alpha5) || Input.GetKeyDown(KeyCode.Keypad5))
150
        {
151
            shouldText = false;
152
            currentMode = Mode.nodes;
153
        }
154
        else if(Input.GetKeyDown(KeyCode.Alpha6) || Input.GetKeyDown(KeyCode.Keypad6))
155
        {
156
            shouldText = false;
157
            currentMode = Mode.burst;
158
        }
159
        
160
        //If left mouse button pressed

161
        if(Input.GetMouseButtonDown(0))
162
        {
163
            //if first click

164
            if(clicks == 0)
165
            {
166
                //store starting position

167
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
168
                pos1 = new Vector2(tempV3.x, tempV3.y);
169
            }
170
            else if(clicks == 1) //second click

171
            {
172
                //store end position

173
                tempV3 = Camera.main.ScreenToWorldPoint(Input.mousePosition);
174
                pos2 = new Vector2(tempV3.x, tempV3.y);
175
                
176
                //Handle the current mode appropriately

177
                switch (currentMode)
178
                {
179
                    case Mode.bolt:
180
                        //create a (pooled) bolt from pos1 to pos2

181
                        CreatePooledBolt(pos1,pos2, Color.white, 1f);
182
                    break;
183
                    
184
                    case Mode.branch:
185
                        //instantiate from our branch prefab

186
                        branchObj = GameObject.Instantiate(BranchPrefab);
187
                        
188
                        //get the branch component

189
                        branchComponent = branchObj.GetComponent(BranchLightningJS);
190
                        
191
                        //initialize the branch component using our position data

192
                        branchComponent.Initialize(pos1, pos2, BoltPrefab);
193
                        
194
                        //add it to the list of active branches

195
                        branchesObj.Add(branchObj);
196
                    break;
197
                    
198
                    case Mode.moving:
199
                        //Prevent from getting a 0 magnitude (0 causes errors 

200
                        if(Vector2.Distance(pos1, pos2) <= 0)
201
                        {
202
                            //Try a random position

203
                            adjust = Random.insideUnitCircle;
204
                            
205
                            //failsafe

206
                            if(adjust.magnitude <= 0) adjust.x += .1f;
207
                            
208
                            //Adjust the end position

209
                            pos2 += adjust;
210
                        }
211
                        
212
                        //Clear out any old moving bolt (this is designed for one moving bolt at a time)

213
                        for(i = movingBolt.Count - 1; i >= 0; i--)
214
                        {
215
                            Destroy(movingBolt[i]);
216
                            movingBolt.RemoveAt(i);
217
                        }
218
                        
219
                        //get the "velocity" so we know what direction to send the bolt in after initial creation

220
                        lightningVelocity = (pos2 - pos1).normalized;
221
                        
222
                        //instantiate from our bolt prefab

223
                        boltObj = GameObject.Instantiate(BoltPrefab);
224
                        
225
                        //get the bolt component

226
                        boltComponent = boltObj.GetComponent(LightningBoltJS);
227
                        
228
                        //initialize it with 5 max segments

229
                        boltComponent.Initialize(5);
230
                        
231
                        //activate the bolt using our position data

232
                        boltComponent.ActivateBolt(pos1, pos2, Color.white, 1f);
233
                        
234
                        //add it to our list

235
                        movingBolt.Add(boltObj);
236
                    break;
237
                    
238
                    case Mode.burst:
239
                        //get the difference between our two positions (destination - source = vector from source to destination)

240
                        var diff : Vector2 = pos2 - pos1;
241
                        
242
                        //define how many bolts we want in our circle

243
                        var boltsInBurst : int = 10;
244
                        
245
                        for(i = 0; i < boltsInBurst; i++)
246
                        {
247
                            //rotate around the z axis to the appropriate angle

248
                            var rot : Quaternion = Quaternion.AngleAxis((360f/boltsInBurst) * i, new Vector3(0,0,1));
249
                            
250
                            adjust = rot * diff;
251
                            
252
                            //Calculate the end position for the bolt

253
                            var boltEnd : Vector2 = adjust + pos1;
254
                            
255
                            //create a (pooled) bolt from pos1 to boltEnd

256
                            CreatePooledBolt(pos1, boltEnd, Color.white, 1f);
257
                        }
258
                    
259
                    break;
260
                }
261
            }
262
            
263
            //increment our tick count

264
            clicks++;
265
            
266
            //restart the count after 2 clicks

267
            if(clicks > 1) clicks = 0;
268
        }
269
        
270
        //if in node mode

271
        if(currentMode == Mode.nodes)
272
        {
273
            //constantly create a (pooled) bolt between the two assigned positions

274
            CreatePooledBolt(pos1, pos2, Color.white, 1f);
275
        }
276
        
277
        //loop through any active branches

278
        for(i = branchesObj.Count - 1; i >= 0; i--)
279
        {
280
            branchObj = branchesObj[i];
281
            
282
            //pull the branch lightning component

283
            branchComponent = branchObj.GetComponent(BranchLightningJS);
284
            
285
            //If it's faded out already

286
            if(branchComponent.IsComplete())
287
            {
288
                //destroy it

289
                Destroy(branchesObj[i]);
290
                
291
                //take it out of our list

292
                branchesObj.RemoveAt(i);
293
                
294
                //move on to the next branch

295
                continue;
296
            }
297
            
298
            //draw and update the branch

299
            branchComponent.Update();
300
            branchComponent.Draw();
301
        }
302
        
303
        //loop through all of our bolts that make up the moving bolt

304
        for(i = movingBolt.Count - 1; i >= 0; i--)
305
        {
306
            boltObj = movingBolt[i];
307
            //get the bolt component

308
            boltComponent = boltObj.GetComponent(LightningBoltJS);
309
            
310
            //if the bolt has faded out

311
            if(boltComponent.IsComplete())
312
            {
313
                //destroy it

314
                Destroy(movingBolt[i]);
315
                
316
                //remove it from our list

317
                movingBolt.RemoveAt(i);
318
                
319
                //on to the next one, on on to the next one

320
                continue;
321
            }
322
            
323
            //update and draw bolt

324
            boltComponent.Update();
325
            boltComponent.Draw();
326
        }
327
        
328
        //if our moving bolt is active

329
        if(movingBolt.Count > 0)
330
        {
331
            boltObj = movingBolt[movingBolt.Count-1];
332
            //calculate where it currently ends

333
            lightningEnd = boltObj.GetComponent(LightningBoltJS).End();
334
            
335
            //if the end of the bolt is within 25 units of the camera

336
            if(Vector2.Distance(lightningEnd,Camera.main.transform.position) < 25)
337
            {
338
                //instantiate from our bolt prefab

339
                boltObj = GameObject.Instantiate(BoltPrefab);
340
                
341
                //get the bolt component

342
                boltComponent = boltObj.GetComponent(LightningBoltJS);
343
                
344
                //initialize it with a maximum of 5 segments

345
                boltComponent.Initialize(5);
346
                
347
                //activate the bolt using our position data (from the current end of our moving bolt to the current end + velocity) 

348
                boltComponent.ActivateBolt(lightningEnd,lightningEnd + lightningVelocity, Color.white, 1f);
349
                
350
                //add it to our list

351
                movingBolt.Add(boltObj);
352
                
353
                //update and draw our new bolt

354
                boltComponent.Update();
355
                boltComponent.Draw();
356
            }
357
        }
358
        
359
        //if in text mode

360
        if(shouldText)
361
        {
362
            //go through the points we capture earlier

363
            for (var i1 : int = 0; i1 < textPoints.Count; i1++)
364
            {
365
                var point : Vector2 = textPoints[i1];
366
                //randomly ignore certain points

367
                if(Random.Range(0,75) != 0) continue;
368
                
369
                //placeholder values

370
                var nearestParticle : Vector2 = Vector2.zero;
371
                var nearestDistSquared : float = float.MaxValue;
372
                
373
                for (i = 0; i < 50; i++)
374
                {
375
                    //select a random point

376
                    var other : Vector2 = textPoints[Random.Range(0, textPoints.Count)];
377
                    
378
                    //calculate the distance (squared for performance benefits) between the two points

379
                    var distSquared : float = DistanceSquared(point, other);
380
                    
381
                    //If this point is the nearest point (but not too near!)

382
                    if (distSquared < nearestDistSquared && distSquared > 3 * 3)
383
                    {
384
                        //store off the data

385
                        nearestDistSquared = distSquared;
386
                        nearestParticle = other;
387
                    }
388
                }
389
                
390
                //if the point we found isn't too near/far

391
                if (nearestDistSquared < 25 * 25 && nearestDistSquared > 3 * 3)
392
                {
393
                    //create a (pooled) bolt at the corresponding screen position

394
                    CreatePooledBolt((point * scaleText) + positionText, (nearestParticle * scaleText) + positionText, new Color(Random.value,Random.value,Random.value,1f), 1f);
395
                }
396
            }
397
        }
398
        
399
        //update and draw active bolts

400
        for(i = 0; i < activeBoltsObj.Count; i++)
401
        {
402
            boltObj = activeBoltsObj[i];
403
            boltObj.GetComponent(LightningBoltJS).Update();
404
            boltObj.GetComponent(LightningBoltJS).Draw();
405
        }
406
    }
407
    
408
    //calculate distance squared (no square root = performance boost)

409
    public function DistanceSquared(a : Vector2, b : Vector2)
410
    {
411
        return ((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
412
    }
413
    
414
    function CreatePooledBolt(source : Vector2, dest : Vector2, color : Color, thickness : float)
415
    {
416
        //if there is an inactive bolt to pull from the pool

417
        if(inactiveBoltsObj.Count > 0)
418
        {
419
            //pull the GameObject

420
            var boltObj : GameObject = inactiveBoltsObj[inactiveBoltsObj.Count - 1];
421
            
422
            //set it active

423
            boltObj.SetActive(true);
424
            
425
            //move it to the active list

426
            activeBoltsObj.Add(boltObj);
427
            inactiveBoltsObj.RemoveAt(inactiveBoltsObj.Count - 1);
428
            
429
            //get the bolt component

430
            var boltComponent : LightningBoltJS =  boltObj.GetComponent(LightningBoltJS);
431
            
432
            //activate the bolt using the given position data

433
            boltComponent.ActivateBolt(source, dest, color, thickness);
434
        }
435
    }
436
    
437
    //Capture the important points of our text for later

438
    function TextCapture()
439
    {
440
        //must wait until end of frame so something is actually drawn or else it will error

441
        yield WaitForEndOfFrame();
442
        
443
        //get the camera that draws our text

444
        var cam : Camera = GameObject.Find("TextCamera").GetComponent(Camera);
445
        
446
        //make sure it has an assigned RenderTexture

447
        if(cam.targetTexture != null) 
448
        {
449
            //pull the active RenderTexture

450
            RenderTexture.active = cam.targetTexture;
451
            
452
            //capture the image into a Texture2D

453
            var image : Texture2D = new Texture2D(cam.targetTexture.width, cam.targetTexture.height);
454
            image.ReadPixels(new Rect(0, 0, cam.targetTexture.width, cam.targetTexture.height), 0, 0);
455
            image.Apply();
456
            
457
            //calculate how the text will be scaled when it is displayed as lightning on the screen

458
            scaleText = 1 / (cam.ViewportToWorldPoint(new Vector3(1,0,0)).x - cam.ViewportToWorldPoint(Vector3.zero).x);
459
            
460
            //calculate how the text will be positioned when it is displayed as lightning on the screen (centered)

461
            positionText.x -= image.width * scaleText * .5f;
462
            positionText.y -= image.height * scaleText * .5f;
463
            
464
            //basically determines how many pixels we skip/check

465
            var interval : int = 2;
466
            
467
            //loop through pixels

468
            for(var y : int = 0; y < image.height; y += interval)
469
            {
470
                for(var x : int = 0; x < image.width; x += interval)
471
                {
472
                    //get the color of the pixel

473
                    var color : Color = image.GetPixel(x,y);
474
                    
475
                    //if the color has an r (red) value

476
                    if(color.r > 0)
477
                    {
478
                        //add it to our points for drawing

479
                        textPoints.Add(new Vector2(x,y));
480
                    }
481
                }
482
            }
483
        }
484
    }
485
}

الاستنتاج

البرق هو تأثير خاص كبير لتجميع الألعاب الخاصة بك. تعتبر التأثيرات الموضحة في هذا البرنامج التعليمي نقطة بداية جيدة ، ولكنها بالتأكيد ليست كل ما يمكنك القيام به مع البرق. مع قليل من الخيال يمكنك أن تجعل جميع أنواع تأثيرات البرق المذهلة! قم بتنزيل شفرة المصدر وقم بتجربتها بنفسك.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.