Slik genererer du sjokkerende gode 2D Lyn effekter

Lyn har rikelig med bruksområder i spill, fra bakgrunnsstemperaturen i en storm til de ødeleggende lynangrepene av en trollmann. I denne opplæringen vil jeg forklare hvordan du programmatisk genererer fantastiske 2D lyneffekter: bolter, grener og jevn tekst.

Merk: Selv om denne opplæringen er skrevet med C # og XNA, bør du kunne bruke de samme teknikkene og konseptene i nesten hvilket som helst spillutviklingsmiljø.


Final Video Preview


Trinn 1: Tegn en glødende linje

Den grunnleggende byggesteinen vi trenger for å lage lyn er et linjesegment. Start med å åpne din favoritt bilderedigeringsprogramvare og tegne en rett linje med lyn. Her ser min ut som:

Vi ønsker å tegne linjer av forskjellige lengder, så vi skal kutte linjesegmentet i tre stykker som vist nedenfor. Dette vil tillate oss å strekke midtsegmentet til enhver lengde vi liker. Siden vi skal strekke det midtre segmentet, kan vi lagre det som bare en enkelt piksel tykk. Også som venstre og høyre brikker er speilbilder av hverandre, trenger vi bare å lagre en av dem. Vi kan vende det i koden.

La oss nå erklære en ny klasse for å håndtere tegningslinjesegmenter:

offentlig klasse Linje offentlig Vector2 A; offentlig Vector2 B; offentlig flyte tykkelse; offentlig linje ()  offentlig linje (Vector2 a, Vector2 b, float thickness = 1) A = a; B = b; Tykkelse = tykkelse; 

A og B er linjens endepunkter. Ved å skalere og rotere stykkene på linjen kan vi tegne en linje med hvilken som helst tykkelse, lengde og orientering. Legg til følgende Tegne() metode til Linje klasse:

offentlig ugyldig tegning (SpriteBatch spriteBatch, Color farge) Vector2 tangent = B - A; float rotation = (float) Math.Atan2 (tangent.Y, tangent.X); const float ImageThickness = 8; float thicknessScale = Tykkelse / ImageThickness; Vector2 capOrigin = ny Vector2 (Art.HalfCircle.Width, Art.HalfCircle.Height / 2f); Vector2 middleOrigin = ny Vector2 (0, Art.LightningSegment.Height / 2f); Vector2 middleScale = ny Vector2 (tangent.Length (), thicknessScale); SpriteBatch.Draw (Art.LightningSegment, A, null, farge, rotasjon, middleOrigin, middleScale, SpriteEffects.None, 0f); spriteBatch.Draw (Art.HalfCircle, A, null, farge, rotasjon, capOrigin, thicknessScale, SpriteEffects.None, 0f); spriteBatch.Draw (Art.HalfCircle, B, null, farge, rotasjon + MathHelper.Pi, capOrigin, thicknessScale, SpriteEffects.None, 0f); 

Her, Art.LightningSegment og Art.HalfCircle er statiske Texture2D variabler som holder bildene av stykkene av linjesegmentet. ImageThickness er satt til tykkelsen av linjen uten glød. I bildet mitt er det 8 piksler. Vi angir opprinnelsen til hetten til høyre side, og opprinnelsen til det midtre segmentet til venstre side. Dette vil få dem til å slutte seg sømløst når vi tegner dem begge ved punkt A. Midtsegmentet strekkes til ønsket bredde, og en annen lue trekkes ved punkt B, roteres 180 °.

XNA er SpriteBatch klassen lar deg passere den a SpriteSortMode i sin konstruktør, som indikerer rekkefølgen der den skal tegne sprites. Når du tegner linjen, må du passe den a SpriteBatch med dens SpriteSortMode satt til SpriteSortMode.Texture. Dette er å forbedre ytelsen.

Grafikkort er flott å tegne samme tekstur mange ganger. Men hver gang de bytter teksturer, er det overhead. Hvis vi tegner en rekke linjer uten sortering, ville vi tegne våre teksturer i denne rekkefølgen:

LightSegment, HalfCircle, HalfCircle, LightSegment, HalfCircle, HalfCircle, ...

Dette betyr at vi bytter teksturer to ganger for hver linje vi tegner. SpriteSortMode.Texture forteller SpriteBatch å sortere Tegne() kaller av tekstur slik at alle LightningSegments vil bli trukket sammen og alle halvsirkler vil bli tegnet sammen. I tillegg, når vi bruker disse linjene for å lage lynbolter, vil vi gjerne bruke additiv blanding for å få lyset fra overlappende lysstykker legge sammen.

SpriteBatch.Begin (SpriteSortMode.Texture, BlendState.Additive); // tegne linjer SpriteBatch.End ();

Trinn 2: Jagged Lines

Lyn har en tendens til å danne tynne linjer, så vi trenger en algoritme for å generere disse. Vi gjør dette ved å plukke poeng tilfeldig langs en linje, og forskyve dem en tilfeldig avstand fra linjen. Å bruke en helt tilfeldig forskyvning har en tendens til å gjøre linjen for tuppet, så vi vil glatte resultatene ved å begrense hvor langt fra hverandre nabosteder kan bli fortrengt.

Linjen blir jevnet ved å plassere poeng på tilsvarende utligning til forrige punkt; Dette gjør at linjen som helhet kan vandre opp og ned, samtidig som det forhindrer at en del av det blir for tett. Her er koden:

beskyttet statisk liste CreateBolt (Vector2 kilde, Vector2 dest, float tykkelse) var results = new List(); Vector2 tangent = dest-source; Vector2 normal = Vector2.Normalize (ny Vector2 (tangent.Y, -tangent.X)); float lengde = tangent.Length (); Liste stillinger = ny liste(); positions.Add (0); for (int i = 0; i < length / 4; i++) positions.Add(Rand(0, 1)); positions.Sort(); const float Sway = 80; const float Jaggedness = 1 / Sway; Vector2 prevPoint = source; float prevDisplacement = 0; for (int i = 1; i < positions.Count; i++)  float pos = positions[i]; // used to prevent sharp angles by ensuring very close positions also have small perpendicular variation. float scale = (length * Jaggedness) * (pos - positions[i - 1]); // defines an envelope. Points near the middle of the bolt can be further from the central line. float envelope = pos > 0.95f? 20 * (1 - pos): 1; float forskyvning = Rand (-Sway, Sway); forskyvning - = (forskyvning - prevDisplacement) * (1-skala); forskyvning * = konvolutt; Vector2 punkt = kilde + pos * tangent + forskyvning * normal; results.Add (ny linje (prevPoint, punkt, tykkelse)); prevPoint = punkt; prevDisplacement = forskyvning;  results.Add (ny linje (prevPoint, dest, tykkelse)); returnere resultater; 

Koden kan se litt skremmende ut, men det er ikke så ille når du forstår logikken. Vi starter med å beregne de normale og tangentvektorer av linjen, sammen med lengden. Da velger vi tilfeldig flere posisjoner langs linjen og lagrer dem i vår stillingsliste. Stillingene er skalert mellom 0 og 1 slik at 0 representerer starten på linjen og 1 representerer sluttpunktet. Disse stillingene blir deretter sortert slik at vi enkelt kan legge til linjesegmenter mellom dem.

Sløyfen går gjennom de tilfeldig valgte punktene og forskyver dem langs normal ved en tilfeldig mengde. Skalfaktoren er der for å unngå altfor skarpe vinkler, og konvolutten sikrer at lynet faktisk går til målpunktet ved å begrense forskyvning når vi er nær slutten.


Trinn 3: Animasjon

Lyn bør blinke sterkt og deretter fade ut. For å håndtere dette, la oss lage en Lyn klasse.

klasse LightningBolt offentlig liste Segmenter = Ny liste(); offentlig flyte Alpha get; sett;  offentlig float FadeOutRate get; sett;  offentlig fargetone get; sett;  offentlig bool IsComplete get return Alpha <= 0;   public LightningBolt(Vector2 source, Vector2 dest) : this(source, dest, new Color(0.9f, 0.8f, 1f))   public LightningBolt(Vector2 source, Vector2 dest, Color color)  Segments = CreateBolt(source, dest, 2); Tint = color; Alpha = 1f; FadeOutRate = 0.03f;  public void Draw(SpriteBatch spriteBatch)  if (Alpha <= 0) return; foreach (var segment in Segments) segment.Draw(spriteBatch, Tint * (Alpha * 0.6f));  public virtual void Update()  Alpha -= FadeOutRate;  protected static List CreateBolt (Vector2 kilde, Vector2 dest, float tykkelse) // ... // ...

For å bruke dette, må du bare opprette en ny Lyn og ring Oppdater() og Tegne() hver ramme. ringe Oppdater() gjør det visne. Er ferdig vil fortelle deg når bolten er helt bleknet ut.

Du kan nå tegne boltene dine ved å bruke følgende kode i spillklassen din:

LightningBolt bolt; MouseState mouseState, lastMouseState; beskyttet overstyring ugyldig Oppdatering (GameTime gameTime) lastMouseState = mouseState; mouseState = Mouse.GetState (); var screenSize = ny Vector2 (GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height); var mousePosition = ny Vector2 (mouseState.X, mouseState.Y); hvis (MouseWasClicked ()) bolt = ny LightningBolt (screenSize / 2, mousePosition); hvis (bolt! = null) bolt.Update ();  Private bool MouseWasClicked () return mouseState.LeftButton == ButtonState.Pressed && lastMouseState.LeftButton == ButtonState.Released;  beskyttet overstyring ugyldig tegning (GameTime gameTime) GraphicsDevice.Clear (Color.Black); spriteBatch.Begin (SpriteSortMode.Texture, BlendState.Additive); hvis (bolt! = null) bolt. Dra (spriteBatch); spriteBatch.End (); 

Trinn 4: Takblits

Du kan bruke Lyn klassen som byggestein for å skape mer interessante lyneffekter. For eksempel kan du gjøre boltene grenen som vist nedenfor:

For å lage lynbrettet velger vi tilfeldige poeng langs lynbolten og legger til nye bolter som grener ut fra disse punktene. I koden nedenfor oppretter vi mellom tre og seks grener som skiller seg fra hovedbolten i 30 grader vinkler.

klasse BranchLightning List bolter = ny liste(); offentlig bool IsComplete get return bolts.Count == 0;  offentlig Vector2 End get; privat sett;  Private Vector2 retning statisk Random rand = ny tilfeldig (); offentlig BranchLightning (Vector2-start, Vector2-ende) End = end; retning = Vector2.Normalize (end-start); Lag (start, slutt);  offentlig ugyldig oppdatering () bolts = bolter.Where (x =>! x.IsComplete) .ToList (); foreach (var bolt i bolter) bolt.Update ();  Offentlig tomt tegning (SpriteBatch spriteBatch) foreach (var bolt i bolter) bolt.Traw (spriteBatch);  privat tomrom Opprett (Vector2 start, Vector2 slutten) var mainBolt = ny LightningBolt (start, slutt); bolts.Add (mainBolt); int numBranches = rand.Neste (3, 6); Vector2 diff = endstart; // velg en rekke tilfeldige poeng mellom 0 og 1 og sorter dem float [] branchPoints = Enumerable.Range (0, numBranches) .Velg (x => Rand (0, 1f)) .OrderBy (x => x). ToArray (); for (int i = 0; i < branchPoints.Length; i++)  // Bolt.GetPoint() gets the position of the lightning bolt at specified fraction (0 = start of bolt, 1 = end) Vector2 boltStart = mainBolt.GetPoint(branchPoints[i]); // rotate 30 degrees. Alternate between rotating left and right. Quaternion rot = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathHelper.ToRadians(30 * ((i & 1) == 0 ? 1 : -1))); Vector2 boltEnd = Vector2.Transform(diff * (1 - branchPoints[i]), rot) + boltStart; bolts.Add(new LightningBolt(boltStart, boltEnd));   static float Rand(float min, float max)  return (float)rand.NextDouble() * (max - min) + min;  

Trinn 5: Lyntekst

Nedenfor er en video av en annen effekt du kan få ut av lynboltene:

Først må vi få piksler i teksten vi ønsker å tegne. Vi gjør dette ved å tegne teksten til en RenderTarget2D og leser tilbake pikseldataene med RenderTarget2D.GetData(). Hvis du vil lese mer om å lage tekstpartikkeleffekter, har jeg en mer detaljert opplæring her.

Vi lagrer koordinatene til pikslene i teksten som en Liste. Deretter plukker vi hver tilfeldig par av disse punktene og lager en lynbolt mellom dem. Vi ønsker å designe det slik at jo nærmere to punkter er til hverandre, jo større er sjansen for at vi lager en bolt mellom dem. Det er en enkel teknikk vi kan bruke for å oppnå dette: Vi velger det første punktet tilfeldig, og deretter velger vi et fast antall andre poeng tilfeldig og velger nærmeste.

Antall kandidatpoeng vi tester vil påvirke utseendet på lynteksten; Ved å sjekke et større antall poeng kan vi finne svært nærliggende punkter for å tegne bolter mellom, noe som vil gjøre teksten veldig pen og lesbar, men med færre lange lynbolter mellom bokstaver. Mindre tall vil gjøre lynteksten ser mer gal, men mindre lesbar.

offentlig ugyldig oppdatering () foreach (var partikkel i tekstartikler) float x = particle.X / 500f; hvis (rand.Neste (50) == 0) Vector2 nearestParticle = Vector2.Zero; float nearestDist = float.MaxValue; for (int i = 0; i < 50; i++)  var other = textParticles[rand.Next(textParticles.Count)]; var dist = Vector2.DistanceSquared(particle, other); if (dist < nearestDist && dist > 10 * 10) nærmeste = dist; nærmestePartikkel = annet;  hvis (nærmesteDist < 200 * 200 && nearestDist > 10 * 10) bolter.Add (ny LightningBolt (partikkel, nærmeste partikkel, Color.White));  for (int i = bolter.Count - 1; i> = 0; i--) bolter [i] .Update (); hvis (bolter [i] .IsComplete) bolter.RemoveAt (i); 

Trinn 6: Optimalisering

Lynteksten, som vist ovenfor, kan løpe jevnt hvis du har en topp på linjedatamaskinen, men det er sikkert veldig beskattende. Hver bolt varer over 30 rammer, og vi lager dusinvis av nye bolter hver ramme. Siden hver lynboks kan ha opptil et par hundre linjersegmenter, og hvert linjesegment har tre stykker, slutter vi å tegne mange sprites. Min demo, for eksempel, tegner over 25.000 bilder hver ramme med optimaliseringer slått av. Vi kan gjøre det bedre.

I stedet for å tegne hver bolt til den fades ut, kan vi tegne hver ny bolt til et gjengivelsesmål og fade ut det gjengitte målet hver ramme. Dette betyr at i stedet for å trekke hver bolt for 30 eller flere rammer, tegner vi det bare en gang. Det betyr også at det ikke er noen ekstra ytelseskost for å gjøre våre lynbolter falle ut sakte og vare lenger.

Først vil vi endre LightningText klasse for å bare trekke hver bolt til en ramme. I din Spill klasse, erklære to RenderTarget2D variabler: currentFrame og lastFrame. I LoadContent (), initialiser dem slik:

lastFrame = ny RenderTarget2D (GraphicsDevice, screenSize.X, screenSize.Y, false, SurfaceFormat.HdrBlendable, DepthFormat.None); currentFrame = ny RenderTarget2D (GraphicsDevice, screenSize.X, screenSize.Y, false, SurfaceFormat.HdrBlendable, DepthFormat.None);

Legg merke til at overflateformatet er satt til HdrBlendable. HDR står for High Dynamic Range, og det indikerer at vår HDR-overflate kan representere et større utvalg av farger. Dette er påkrevd fordi det gjør det mulig for gjengemål å ha farger som er lysere enn hvite. Når flere lynbolter overlapper vi trenger gjengivelsesmålet for å lagre hele summen av deres farger, noe som kan legge opp over det vanlige fargespekteret. Mens disse lysere enn hvite farger fortsatt vises som hvite på skjermen, er det viktig å lagre full lysstyrke for å få dem til å falme ut riktig.

XNA tips: Vær også oppmerksom på at for HDR-blanding å virke, må du sette XNA-prosjektprofilen til Hi-Def. Du kan gjøre det ved å høyreklikke på prosjektet i løsningsoppdageren, velge egenskaper og deretter velge hi-def-profilen under fanen XNA Game Studio.

Hver ramme trekker vi først innholdet i den siste rammen til gjeldende ramme, men litt mørkere. Vi legger til noen nyopprettede bolter i gjeldende ramme. Til slutt, gjengir vi vår nåværende ramme på skjermen, og bytter deretter de to gjengene målene slik at det for vår neste ramme, lastFrame vil referere til rammen vi nettopp gjengitt.

void DrawLightningText () GraphicsDevice.SetRenderTarget (currentFrame); GraphicsDevice.Clear (Color.Black); // tegne den siste rammen ved 96% lysstyrke spriteBatch.Begin (0, BlendState.Opaque, SamplerState.PointClamp, null, null); spriteBatch.Draw (lastFrame, Vector2.Zero, Color.White * 0.96f); spriteBatch.End (); // tegne nye bolter med additiv blanding spriteBatch.Begin (SpriteSortMode.Texture, BlendState.Additive); lightningText.Draw (); spriteBatch.End (); // tegne det hele til backbufferen GraphicsDevice.SetRenderTarget (null); spriteBatch.Begin (0, BlendState.Opaque, SamplerState.PointClamp, null, null); spriteBatch.Draw (currentFrame, Vector2.Zero, Color.White); spriteBatch.End (); Bytt (ref currentFrame, ref lastFrame);  ugyldig bytte(ref T a, ref T b) T temp = a; a = b; b = temp; 

Trinn 7: Andre variasjoner

Vi har diskutert å lage takblits og lyntekst, men de er absolutt ikke de eneste effektene du kan gjøre. La oss se på et par andre variasjoner på lyn du kan bruke til å bruke.

Flytte Lyn

Ofte kan det være lurt å lage en bevegelig bolt av lyn. Du kan gjøre dette ved å legge til en ny kort bolt hver ramme i sluttpunktet til den forrige rammens bolt.

Vector2 lightningEnd = ny Vector2 (100, 100); Vector2 lightningVelocity = ny Vector2 (50, 0); ugyldig oppdatering (GameTime gameTime) Bolts.Add (ny LightningBolt (lightningEnd, lightningEnd + lightningVelocity)); lightningEnd + = lightningVelocity; // ...

Glatt Lyn

Du har kanskje lagt merke til at lynet lyser lysere på leddene. Dette skyldes additivblandingen. Du vil kanskje ha en jevnere, jevnere se etter lynet ditt. Dette kan oppnås ved å endre blandingstilstandsfunksjonen for å velge maksimalverdien av kilden og destinasjonsfarger, som vist nedenfor.

privat statisk readonly BlendState maxBlend = ny BlendState () AlphaBlendFunction = BlendFunction.Max, ColorBlendFunction = BlendFunction.Max, AlphaDestinationBlend = Blend.One, AlphaSourceBlend = Blend.One, ColorDestinationBlend = Blend.One, ColorSourceBlend = Blend.One;

Så, i din Tegne() funksjon, samtale SpriteBatch.Begin () med maxBlend som BlendState i stedet for BlendState.Additive. Bildene nedenfor viser forskjellen mellom additiv blanding og maksimal blanding på en lynbolt.


Selvfølgelig vil maksimal blanding ikke tillate lyset fra flere bolter eller fra bakgrunnen for å legge opp pent. Hvis du vil at bolten selv skal se jevn ut, men også å blande additivt med andre bolter, kan du først gjøre bolten til et gjengemål med maksimal blanding, og deretter tegne gjengimålet til skjermen ved hjelp av additiv blanding. Vær forsiktig så du ikke bruker for mange store gjengemål, da dette vil skade ytelsen.

Et annet alternativ, som vil fungere bedre for et stort antall bolter, er å eliminere gløden bygd inn i linjesegmentbildene og legge den til igjen ved hjelp av en etterbehandlingsglødseffekt. Detaljer om bruk av shaders og å skape lyseffekter er utenfor omfanget av denne opplæringen, men du kan bruke XNA Bloom-prøven for å komme i gang. Denne teknikken vil ikke kreve mer gjengivelse når du legger til flere bolter.


Konklusjon

Lyn er en flott spesiell effekt for sprucing opp dine spill. Effektene beskrevet i denne opplæringen er et fint utgangspunkt, men det er absolutt ikke alt du kan gjøre med lyn. Med litt fantasi kan du lage alle slags ærefrykt inspirerende lyneffekter! Last ned kildekoden og eksperimentere med din egen.

Hvis du likte denne artikkelen, ta en titt på min veiledning om 2D vann effekter også.