كيفية توليد تأثيرات ضوئية جيدة 2D البرق في الوحدة (JavaScript)
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 |
}
|
الاستنتاج
البرق هو تأثير خاص كبير لتجميع الألعاب الخاصة بك. تعتبر التأثيرات الموضحة في هذا البرنامج التعليمي نقطة بداية جيدة ، ولكنها بالتأكيد ليست كل ما يمكنك القيام به مع البرق. مع قليل من الخيال يمكنك أن تجعل جميع أنواع تأثيرات البرق المذهلة! قم بتنزيل شفرة المصدر وقم بتجربتها بنفسك.