Wie generiert man schockierend gute 2D-Blitz-Effekte in Unity? (JavaScript)
() translation by (you can also view the original English article)
Es gibt viele Einsatzmöglichkeiten für Blitzeffekte in Spielen, von der Hintergrundatmosphäre während eines Sturms bis hin zu den verheerenden Blitzangriffen eines Zauberers. In diesem Tutorial erkläre ich, wie Sie programmgesteuert fantastische 2D-Blitzeffekte erzeugen: Schrauben, Zweige und sogar Text.
Dieses Tutorial wurde speziell für Unity geschrieben, mit allen Code-Snippets in JavaScript. Das gleiche Tutorial ist auch mit C#-Code verfügbar. Wenn Sie Unity nicht verwenden, sehen Sie sich diese plattformunabhängige Version desselben Tutorials an. Es wurde für XNA geschrieben, aber Sie sollten in der Lage sein, dieselben Techniken und Konzepte in jeder Gamedev-Engine und -Plattform zu verwenden.
Demo
Sehen Sie sich die Demo unten an:
Klicken Sie auf das Unity-Objekt und verwenden Sie dann die Zifferntasten, um zwischen den Demos zu wechseln. Bei einigen Demos müssen Sie auf eine oder zwei Stellen klicken, um sie zu aktivieren.
Grundeinstellung
Um zu beginnen, müssen Sie ein neues 2D-Projekt in Unity erstellen. Benennen Sie es, wie Sie möchten. Erstellen Sie in Unity vier Ordner: Materials
, Prefabs
, Scripts
und Sprites
.



Klicken Sie als Nächstes auf die Hauptkamera und stellen Sie sicher, dass ihre Projektion auf Orthographic
eingestellt ist. Stellen Sie die Größe der Kamera auf 10
ein.
Klicken Sie mit der rechten Maustaste auf den Ordner Materialien und wählen Sie Erstellen > Material. Benennen Sie es in Additive
um. Wählen Sie dieses Material aus und ändern Sie seinen Shader in Partikel > Additiv. Dies wird Ihrem Blitz helfen, später zu "knallen".
Schritt 1: Zeichnen Sie eine leuchtende Linie
Der Grundbaustein, aus dem wir Blitze machen müssen, ist ein Liniensegment. Öffnen Sie zunächst Ihre bevorzugte Bildbearbeitungssoftware und zeichnen Sie eine gerade Blitzlinie mit einem Leuchteffekt. So sieht meiner aus:

Wir möchten Linien unterschiedlicher Länge zeichnen, also schneiden wir das Liniensegment wie unten gezeigt in drei Teile (schneiden Sie Ihr Bild nach Bedarf zu). Dies ermöglicht es uns, das mittlere Segment auf jede gewünschte Länge zu dehnen. Da wir das mittlere Segment strecken werden, können wir es als nur ein einzelnes Pixel dick speichern. Da das linke und das rechte Stück Spiegelbilder voneinander sind, müssen wir nur eines davon speichern; Wir können es im Code umdrehen.

Ziehen Sie Ihre Bilddateien in den Sprites-Ordner im Projekt-Fenster. Dadurch werden die Bilddateien in das Unity-Projekt importiert. Klicken Sie auf die Sprites, um sie im Inspektorfenster anzuzeigen. Stellen Sie sicher, dass der Texturtyp auf Sprite(2D \ uGUI)
eingestellt ist, und stellen Sie das Pack-Tag auf Line
ein.
Das Packing Tag hilft Unity, beim Zeichnen unseres Lightnings bei Draw-Aufrufen zu sparen, also stellen Sie sicher, dass Sie beiden Sprites das gleiche Packing Tag geben, sonst wird die Leistung nicht verbessert.



Lassen Sie uns nun eine neue Klasse für das Zeichnen von Liniensegmenten deklarieren:
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 und B sind die Endpunkte der Linie. Durch Skalieren und Drehen der Teile der Linie können wir eine Linie beliebiger Dicke, Länge und Ausrichtung zeichnen.
Fügen Sie die folgende Draw()
-Methode am Ende der LineJS
-Klasse hinzu:
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 |
}
|
Die Art und Weise, wie wir das mittlere Segment und die Kappen positionieren, werden sie beim Zeichnen nahtlos verbinden. Die Startkappe wird an Punkt A positioniert, das mittlere Segment wird auf die gewünschte Breite gestreckt und die Endkappe wird um 180° gedreht und an Punkt B gezeichnet.
Jetzt müssen wir ein Prefab erstellen, mit dem unsere LineJS-Klasse arbeiten kann. Wählen Sie in Unity im Menü GameObject > Leer erstellen. Das Objekt wird in Ihrem Hierarchie-Bereich angezeigt. Benennen Sie es in LineJS
um und ziehen Sie Ihr LineJS-Skript darauf. Es sollte in etwa wie das Bild unten aussehen.



Wir verwenden dieses Objekt als Container für die Teile unseres Liniensegments.
Jetzt müssen wir Objekte für die Teile unseres Liniensegments erstellen. Erstellen Sie drei Sprites, indem Sie GameObject > Create Other > Sprite aus dem Menü auswählen. Benennen Sie sie in StartCap
, MiddleSegment
und EndCap
um. Ziehen Sie sie auf unser LineJS-Objekt, sodass sie zu seinen Kindern werden – dies sollte ungefähr wie in der Abbildung unten aussehen.



Gehen Sie jedes Kind durch und stellen Sie sein Material im Sprite-Renderer auf das zuvor erstellte Additiv-Material ein. Weisen Sie jedem Kind das entsprechende Sprite zu. (Die beiden Caps sollten das Cap-Sprite erhalten und das mittlere Segment sollte das Line-Sprite erhalten.)
Klicken Sie auf das LineJS-Objekt, damit Sie das Skript im Inspektor-Fenster sehen können. Weisen Sie die Kinder den entsprechenden Slots zu und ziehen Sie dann das LineJS-Objekt in den Prefabs-Ordner, um ein Prefab dafür zu erstellen. Sie können das LineJS-Objekt jetzt aus dem Hierarchiebereich löschen.
Schritt 2: Erstellen Sie gezackte Linien
Blitze neigen dazu, gezackte Linien zu bilden, daher benötigen wir einen Algorithmus, um diese zu generieren. Wir werden dies tun, indem wir zufällig Punkte entlang einer Linie auswählen und sie um einen zufälligen Abstand von der Linie verschieben.
Die Verwendung einer völlig zufälligen Verschiebung führt dazu, dass die Linie zu gezackt wird. Daher glätten wir die Ergebnisse, indem wir begrenzen, wie weit benachbarte Punkte voneinander verschoben werden können – siehe den Unterschied zwischen der zweiten und dritten Linie in der Abbildung unten.

Wir glätten die Linie, indem wir Punkte mit einem ähnlichen Versatz zum vorherigen Punkt platzieren; Dadurch kann die Linie als Ganzes auf und ab wandern, während verhindert wird, dass irgendein Teil davon zu gezackt wird.
Lassen Sie uns eine LightningBoltJS
-Klasse erstellen, um das Erstellen unserer gezackten Linien zu verarbeiten.
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 |
}
|
Der Code sieht vielleicht etwas einschüchternd aus, ist aber nicht so schlimm, wenn Sie die Logik verstanden haben. Bevor wir fortfahren, sollten Sie verstehen, dass wir uns entschieden haben, unsere Liniensegmente in den Bolzen zu bündeln (da das ständige Instanziieren und Zerstören von Objekten in Unity kostspielig sein kann).
- Die Funktion
Initialize()
wird für jeden Blitz einmal aufgerufen und bestimmt, wie viele Liniensegmente jeder Blitz verwenden darf. - Die Funktion
activateLine()
aktiviert ein Liniensegment mit den gegebenen Positionsdaten. - Die Funktion
DeactivateSegments()
deaktiviert alle aktiven Liniensegmente in unserer Schraube. - Die Funktion
ActivateBolt()
übernimmt die Erstellung unserer gezackten Linien und ruft die FunktionactivateLine()
auf, um unsere Liniensegmente an den entsprechenden Positionen zu aktivieren.
Um unsere gezackten Linien zu erstellen, berechnen wir zunächst die Steigung zwischen unseren beiden Punkten sowie den Normalenvektor zu dieser Steigung. Wir wählen dann eine Reihe von zufälligen Positionen entlang der Linie und speichern sie in unserer Positionsliste. Wir skalieren diese Positionen zwischen 0
und 1
, sodass 0
den Anfang der Linie und 1
den Endpunkt darstellt. Dann sortieren wir diese Positionen, damit wir einfach Liniensegmente dazwischen einfügen können.
Die Schleife durchläuft die zufällig ausgewählten Punkte und verschiebt sie um einen zufälligen Betrag entlang der Normalen. Der scale
-Faktor dient dazu, zu scharfe Winkel zu vermeiden, und die envelope
stellt sicher, dass der Blitz tatsächlich zum Zielpunkt geht, indem sie die Verschiebung begrenzt, wenn wir uns dem Ende nähern. Die spread
soll dabei helfen, zu kontrollieren, wie weit die Segmente von der Steigung unserer Linie abweichen; ein spread
von 0
ergibt im Wesentlichen eine gerade Linie.



Also, wie wir es mit unserer LineJS
-Klasse gemacht haben, machen wir daraus ein Prefab. Wählen Sie im Menü GameObject > Leer erstellen. Das Objekt wird in Ihrem Hierarchiebereich angezeigt. Benennen Sie es in BoltJS
um und ziehen Sie eine Kopie des LightningBoltJS
-Skripts darauf. Klicken Sie abschließend auf das BoltJS-Objekt und weisen Sie das LineJS-Prefab aus dem Prefabs-Ordner dem entsprechenden Slot im LightningBoltJS-Skript zu. Wenn Sie damit fertig sind, ziehen Sie einfach das BoltJS-Objekt in den Prefabs-Ordner, um ein Prefab zu erstellen.
Schritt 3: Animation hinzufügen
Blitze sollten hell aufblitzen und dann ausblenden. Dafür sind unsere Funktionen Update()
und Draw()
in LightningBoltJS
da. Durch Aufrufen von Update()
wird die Schraube ausgeblendet. Der Aufruf von Draw()
aktualisiert die Farbe der Schraube auf dem Bildschirm. IsComplete()
teilt Ihnen mit, wenn der Bolzen vollständig ausgeblendet ist.
Schritt 4: Erstellen Sie eine Schraube
Nun, da wir unsere LightningBoltJS
-Klasse haben, wollen wir sie tatsächlich sinnvoll nutzen und eine schnelle Demo-Szene einrichten.
Wir werden für diese Demo einen Objektpool verwenden, also möchten wir ein leeres Objekt erstellen, um unsere aktiven und inaktiven Schrauben aufzunehmen (einfach aus organisatorischen Gründen). Wählen Sie in Unity im Menü GameObject > Leer erstellen. Das Objekt wird in Ihrem Hierarchie-Bereich angezeigt. Benennen Sie es in LightningPoolHolder
um.
Klicken Sie mit der rechten Maustaste auf den Ordner Scripts und wählen Sie Erstellen > Javascript. Benennen Sie Ihr Skript DemoScriptJS
und öffnen Sie es. Hier ist ein kurzer Code, um Ihnen den Einstieg zu erleichtern:
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 |
}
|
Dieser Code bietet uns lediglich die Möglichkeit, Schrauben mithilfe von Objektpooling zu erstellen. Es gibt andere Möglichkeiten, dies einzurichten, aber hiermit gehen wir vor! Sobald wir es eingerichtet haben, müssen Sie nur noch zweimal klicken, um eine Schraube auf dem Bildschirm zu erstellen: einmal für die Startposition und einmal für die Endposition.
Wir benötigen ein Objekt, um unser DemoScriptJS
anzulegen. Wählen Sie im Menü GameObject > Leer erstellen. Das Objekt wird in Ihrem Hierarchiebereich angezeigt. Benennen Sie es in DemoScript
um und ziehen Sie Ihr DemoScriptJS-Skript darauf. Klicken Sie auf das DemoScript-Objekt, damit wir es im Inspektorfenster anzeigen können. Weisen Sie das BoltJS-Prefab aus dem Prefabs-Ordner dem passenden Slot in DemoScriptJS zu.



Das sollte reichen, um dich in Schwung zu bringen! Führen Sie die Szene in Unity aus und probieren Sie es aus!
Schritt 5: Branch Lightning erstellen
Sie können die LightningBoltJS
-Klasse als Baustein verwenden, um interessantere Blitzeffekte zu erstellen. Sie können die Schrauben beispielsweise wie unten gezeigt verzweigen:

Um den Blitz zu verzweigen, wählen wir zufällige Punkte entlang des Blitzes und fügen neue Bolzen hinzu, die von diesen Punkten abzweigen. Im folgenden Code erstellen wir zwischen drei und sechs Zweige, die sich in einem Winkel von 30° vom Hauptbolzen trennen.
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 |
}
|
Dieser Code funktioniert sehr ähnlich wie unsere LightningBoltJS
-Klasse, mit der Ausnahme, dass er kein Objektpooling verwendet. Der Aufruf von Initialize()
ist alles, was Sie tun müssen, um einen Verzweigungsbolzen zu erstellen; Danach müssen Sie nur noch Update()
und Draw()
aufrufen. Wie das genau geht, zeige ich Ihnen später in unserem DemoScriptJS
im Tutorial.
Möglicherweise haben Sie den Verweis auf eine GetPoint()
-Funktion in der LightningBoltJS
-Klasse bemerkt. Wir haben diese Funktion noch nicht wirklich implementiert, also kümmern wir uns jetzt darum.
Fügen Sie unten in der LightningBoltJS
-Klasse die folgende Funktion hinzu:
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 |
}
|
Schritt 6: Blitztext erstellen
Unten ist ein Video eines anderen Effekts, den Sie aus den Blitzen machen können:
Dafür müssen wir noch etwas mehr einrichten. Wählen Sie zunächst im Projektfenster Erstellen > RenderTexture aus. Benennen Sie es in RenderText
um und setzen Sie seine Größe auf 256x256px
. (Es muss nicht unbedingt die genaue Größe haben, aber je kleiner es ist, desto schneller wird das Programm ausgeführt.)
Wählen Sie im Menü Bearbeiten > Projekteinstellungen > Tags und Ebenen. Erweitern Sie dann im Inspektor-Bedienfeld das Dropdown-Menü Ebenen und fügen Sie Text
zur Benutzerebene 8 hinzu.



Wir müssen dann eine zweite Kamera erstellen. Wählen Sie im Menü GameObject > Andere erstellen > Kamera. Benennen Sie es in TextCamera
um und setzen Sie seine Projektion auf Orthographic
und seine Clear Flags auf Solid Color
. Stellen Sie die Hintergrundfarbe auf (R: 0, G: 0, B: 0, A: 0)
und die Culling-Maske auf nur Text
(die Ebene, die wir gerade erstellt haben). Setzen Sie schließlich die Zieltextur auf RenderText
(die zuvor erstellte RenderTexture). Wahrscheinlich müssen Sie später mit der Größe der Kamera herumspielen, damit alles auf den Bildschirm passt.



Jetzt müssen wir den eigentlichen Text erstellen, den wir mit unserem Blitz zeichnen werden. Wählen Sie im Menü GameObject > Andere erstellen > GUI-Text. Wählen Sie das GUI-Textobjekt aus dem Hierarchie-Bedienfeld aus und stellen Sie seinen Text auf LIGHTNING
, seinen Anker auf die middle center
und seine Ausrichtung auf die center
ein. Setzen Sie dann seine Ebene auf die Text
-Ebene, die wir zuvor erstellt haben. Sie müssen wahrscheinlich mit der Schriftgröße herumspielen, damit der Text auf den Bildschirm passt.



Wählen Sie nun die Hauptkamera aus und stellen Sie ihre Culling-Maske so ein, dass sie alles außer unserer Textebene ist. Dies führt dazu, dass unser GUI-Text scheinbar vom Bildschirm verschwindet, aber er sollte auf der zuvor erstellten RenderTexture gezeichnet werden: Wählen Sie RenderText aus dem Projekt-Fenster und Sie sollten das Wort LIGHTNING in der Vorschau am unteren Rand des Bedienfelds sehen können .
Wenn Sie das Wort LIGHTNING nicht sehen können, müssen Sie mit Ihrer Positionierung, Schriftgröße und (Text-)Kameragröße herumspielen. Um Ihnen bei der Positionierung Ihres Textes zu helfen, klicken Sie im Hierarchie-Bedienfeld auf TextCamera und setzen Sie die Zieltextur auf None
. Sie können jetzt Ihren GUI-Text sehen, wenn Sie ihn auf der TextCamera zentrieren. Sobald Sie alles positioniert haben, setzen Sie die Zieltextur der TextCamera wieder auf RenderText
.
Nun zum Code! Wir müssen die Pixel aus dem Text abrufen, den wir zeichnen. Wir können dies tun, indem wir unseren Text auf ein RenderTarget
zeichnen und die Pixeldaten mit Texture2D.ReadPixels()
in Texture2D
zurücklesen. Dann können wir die Koordinaten der Pixel aus dem Text als List.<Vector2>
speichern.
Hier ist der Code, um das zu tun:
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 |
}
|
Hinweis: Wir müssen diese Funktion beim Start unseres Programms als Coroutine ausführen, damit sie korrekt ausgeführt wird.
Danach können wir in jedem Frame zufällig Paare dieser Punkte auswählen und einen Blitz zwischen ihnen erstellen. Wir wollen es so gestalten, dass je näher zwei Punkte beieinander liegen, desto größer ist die Chance, dass wir einen Bolzen zwischen ihnen erzeugen.
Es gibt eine einfache Technik, die wir verwenden können, um dies zu erreichen: Wir wählen den ersten Punkt nach dem Zufallsprinzip aus, und dann wählen wir eine feste Anzahl anderer Punkte nach dem Zufallsprinzip und wählen den nächsten.
Hier ist der Code dafür (wir werden ihn später zu unserem DemoScriptJS
hinzufügen):
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 |
---------------------------------------------*/
|
Die Anzahl der getesteten Kandidatenpunkte beeinflusst das Aussehen des Blitztextes. Wenn wir eine größere Anzahl von Punkten überprüfen, können wir sehr nahe Punkte finden, zwischen denen Bolzen gezogen werden können, wodurch der Text sehr sauber und lesbar wird, jedoch mit weniger langen Blitzen zwischen den Buchstaben. Kleinere Zahlen lassen den Blitztext wilder aussehen, aber weniger lesbar.
Schritt 7: Probieren Sie andere Variationen aus
Wir haben die Erstellung von Branch Lightning und Lightning Text besprochen, aber das sind sicherlich nicht die einzigen Effekte, die Sie erzielen können. Schauen wir uns ein paar andere Variationen von Blitzen an, die Sie möglicherweise verwenden möchten.
Beweglicher Blitz
Vielleicht möchten Sie oft einen beweglichen Blitz machen. Sie können dies tun, indem Sie jedem Rahmen eine neue kurze Schraube am Endpunkt der Schraube des vorherigen Rahmens hinzufügen.
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 |
}
|
Blitzschlag
Diese Variante bietet einen dramatischen Effekt, der Blitze in einem Kreis aus dem Mittelpunkt schießt:
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 |
}
|
Schritt 8: Fügen Sie alles in DemoScriptJS zusammen
Sie möchten all diese ausgefallenen Effekte, die wir bisher erstellt haben, ausprobieren können, also fügen wir sie alle in das DemoScriptJS
ein, das wir zuvor erstellt haben. Sie können zwischen den Effekten wechseln, indem Sie die Zifferntasten auf Ihrer Tastatur drücken, um den Effekt auszuwählen, und dann einfach zweimal klicken, wie wir es mit unseren Schrauben zuvor getan haben.
Hier ist der vollständige Code:
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 |
}
|
Abschluss
Blitz ist ein großartiger Spezialeffekt, um Ihre Spiele aufzupeppen. Die in diesem Tutorial beschriebenen Effekte sind ein guter Ausgangspunkt, aber es ist sicherlich nicht alles, was Sie mit Blitz machen können. Mit etwas Fantasie können Sie alle Arten von beeindruckenden Blitzeffekten erstellen! Laden Sie den Quellcode herunter und experimentieren Sie selbst.