IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

Časticový systém v XNA

Vitajte po ... a vlastne nie, tento diel nebudeme počítať. V tomto diele, ktorý nijako nezapadá do sekvencie bludiska ani k hlsl shaderům, si vytvorím pre náš engin jednoduchý časticový systém. Zatiaľ len v 2D prevedení. Budeme potrebovať projekt s nastaveným enginom. Než začneme tvoriť samotný systém, bolo by dobré popísať si ako to bude fungovať. Manažér nám bude postupne v presne zadanom intervale vypúšťať častice. Časticou budeme rozumieť otexturovaný obdĺžnik vykreslený pomocou SpriteBatche. Rozhodol som sa použiť práve SpriteBatch, pretože nám rieši vykresľovanie a je pomerne dobre optimalizovaná. Jednoducho len hovoríme kde čo chceme vykresliť. To pre 2D systém postačuje, ale pre plnohodnotný 3D systém to je málo. Každá častica bude mať svoju polohu, rýchlosť pohybu (udanú vektorom), čas od vypustenia, čas kedy začne miznúť, čas kedy zmizne úplne a deaktivuje sa tak, mierka na začiatku a meradlo na konci, farbu na začiatku a farbu na konci, rýchlosť rotácie . Je to pomerne veľa parametrov, ktoré každá častica musí poznať, ale tiež je to na výsledku vidieť. Na všetky potom pôsobí ešte gravitácia. To sú všetky požiadavky a nie je ich zrovna málo. Ako to vyzerá, keď je všetko v chode sa môžete pozrieť na nasledujúcom videu:

Pridáme si teda do enginu nový komponent. Ja som ju nazval Particle2DManager. Triedu opäť urobíme verejnú. Najprv pridáme parameter pre textúru, ktorú budú mať všetky častice:
private string textureName;
protected Texture2D texture;

Budeme počítať s tým, že sú častice všetky rovnaké. Vložte tiež vnútorné triedu Particle:

protected class Particle{
}

Dovnútra si pridáme premennú, ktorá bude mať v sebe aktuálny čas života častice, ďalej premennú označujúci čas kedy začne častice miznúť a premennou kedy častice zmizne:

public float Time;
public float FadeStart;
public float FadeEnd;

Každá bude potrebovať aj svoju polohu a rýchlosť pohybu:

public Vector2 Position;
public Vector2 Velocity;

Ešte uložíme či je častica aktívny:

public bool Visible;

Pre začiatok bude postačovať toto. Do hlavnej triedy vložíme kolekciu so všetkými časticami a triedu pre generovanie náhodných čísel:

protected List<Particle> Particles;

Random r;

Potrebujeme ešte určiť, ako často budeme vypúšťať novú časticu ak ju budeme mať.

public TimeSpan NextParticle;

V konstruktoru všetky parametre inicializujeme:

public Particle2DManager(){
  Particles = new List<Particle>();
  NextParticle = TimeSpan.FromMilliseconds(50);
  r = new Random();
}

Ešte nám chýba jednu premennú a tou bude počítadlo času, ktorý ubehol od posledného vypustenia častice:

TimeSpan next;

Zrealizujeme si teda metódu skrze, ktorú pridáme manažéru častice, s ktorými bude pracovať. Nazval som ju AddParticles a skrze nej odovzdáme tiež nastavenie častíc. Neskôr uvidíte, že tých parametrov bude dosť. Pre začiatok si vystačíme so štartovej pozíciou a smerom, životnosťou a taky počtom častíc a menom textúry:

public void AddParticles(Vector2 startPos, Vector2 minDirection, Vector2 maxDirection, float minFadeStart, float maxFadeStart, float minFadeEnd, float maxFadeEnd, int count, string textureName)

Všetky hodnoty potrebuje v rozmedzí min-max, aby ich išlo náhodne vyberať. Pozíciu však ponecháme jeden bod, je pravda že ho pôjde ľahko nahradiť treba nejakú oblastí, štvorcom, kruhom to už bude na použitie. Najprv pre istotu premažeme zoznam častíc a nahráme novú textúru:

Particles.Clear();
if (this.textureName != textureName){
  this.textureName = textureName;
  if(texture!=null)texture.Dispose();
  if(Loaded)texture=Parent.Content.Load<Texture2D>(textureName);
}

Textúru načítame iba ak je načítaná komponent ako taká. To všetko aby bolo možné zavolať metódu aj pokiaľ nie je komponenta načítané. Preto si ešte pridáme obvyklú Load metódu:

protected override void Load(){
  if(!String.IsNullOrEmpty(textureName)) texture=Parent.Content.Load<Texture2D>(textureName);
 }

Načítame textúru pochopiteľne len a len ak nejakú máme zadanú. Načítanie textúry máme hotové. Môžeme sa pustiť do častíc. Všetky parametre čo odovzdávame metódou si budeme musieť uložiť. Nedá sa svietiť, pretože budeme chcieť generovať nové parametre častice aj po jej znovuoživenie. Takže si pridáme patričné premenné:

public Vector2 StartLocation;
public Vector2 StartMinVelocity;
public Vector2 StartMaxVelocity;
public float MinFadeStart;
public float MaxFadeStart;
public float MinFadeEnd;
public float MaxFadeEnd;

A priradíme im hodnoty:

StartLocation = startPos;
StartMinVelocity = minDirection;
StartMaxVelocity = maxDirection;

MinFadeStart = minFadeStart;
MaxFadeStart = maxFadeStart;
MinFadeEnd = minFadeEnd;
MaxFadeEnd = maxFadeEnd;

V cykle potom vytvoríme všetky častice. Nastavíme im viditeľnosť na false a pridáme ich do poľa:

for (int i = 0; i < count; i++){
  Particle p = new Particle();
  p.Visible = false;
  Particles.Add(p);
}

To bude zatiaľ všetko. Presunieme sa do metódy Update, kde si napíšeme vypúšťanie častíc. K uběhlému času prirátame čas od posledného zavolanie:

next += Parent.Engine.GameTime.ElapsedGameTime;

A v cykle postupne prejdeme všetky častice a ak sú viditeľné, tak im zdvihneme čas a vypočítame novú pozíciu:

foreach(Particle p in Particles){
  if (p.Visible){
    //pokad je videt tak ji soupnem
    p.Position += p.Velocity * (float)Parent.Engine.GameTime.ElapsedGameTime.TotalSeconds;
    p.Time += (float)Parent.Engine.GameTime.ElapsedGameTime.TotalSeconds;
  }
  else if(next>NextParticle){
    //dame na start a zviditelnime
  }
}

Ak však nie je častice viditeľná, skontrolujeme čas a ak je väčšia ako interval vypúšťania, časticu vypustíme. Proste ju nastavíme Visible na true, vygenerujeme jej parametre. Od toho mám metódu SetParticleParametres, kde parametre Generujem. Pridajme si ju:

protected void SetParticleParametres(Particle p){
  p.Position = StartLocation;
  p.Velocity = Vector2Mezi(StartMinVelocity, StartMaxVelocity);
  p.FadeStart = floatMezi(MinFadeStart, MaxFadeStart);
  p.FadeEnd = floatMezi(MinFadeEnd, MaxFadeEnd);
  p.Time = 0;
}

Napísal som si dve pomocné metůdky pre generovanie náhodného čísla z rozsahu a taky generovanie vektora z rozsahu:

protected float floatMezi(float min, float max){
  return (float)(min + (max - min) * r.NextDouble());
}

protected Vector2 Vector2Mezi(Vector2 min, Vector2 max){
  return new Vector2(floatMezi(min.X,max.X), floatMezi(min.Y,max.Y));
}

Vráťme sa do metódy Update a dorobíme ju:

p.Visible = true;
SetParticleParametres(p);
next = TimeSpan.Zero;

Častice môžeme teraz pridávať, updatovať ešte nám zostáva vykreslenie. Uplatňuje SpriteBatch a to len pretože všetko obstaráva prakticky za nás.

public override void Draw(){
  Parent.Engine.SpriteBatch.Begin();
  foreach (Particle p in Particles){
    if(p.Visible)Parent.Engine.SpriteBatch.Draw(texture,
      new Rectangle((int)p.Position.X,(int)p.Position.Y,
(int) (texture.Width),(int)(texture.Height)), null,Color.White * p.Opacity,0,new Vector2(),SpriteEffects.None,0);
    }
  Parent.Engine.SpriteBatch.End();
}

Rozhodol som sa použiť úplnú metódu. Do nej potom už len dosadíme postupne rotáciu, zmenu mierky a ďalšie zábavkami. Zastavil by som sa ale u tohto parametra:

Color.White * p.Opacity

Práve tu je vypočítavaná priehľadnosť častice. Avšak to ešte naša častice nepozná. Priehľadnosť je číslo od nuly do jednej, kedy nula znamená plne priehľadné a 1 nepriehľadné. Do tohto rozmedzí budeme musieť transparentnosť vypočítať z času. Pridáme si teda novú položku do našej častice:

public float Opacity{
  get{
    if (Time < FadeStart) return 1;
    if (Time > FadeEnd){
      Visible = false;
      return 0;
    }
    return 1-((Time-FadeStart) / (FadeEnd - FadeStart));
  }
}

Priehľadnosť budeme meniť iba ak je čas v rozmedzí v ktorom chceme. Zároveň tu budeme časticu pomocou času deaktivovať. Výpočtom zlomku dostaneme číslo v rozmedzí 0-1, ale my chceme interval presne obrátený. Preto odčítame jedničku. Samozrejme môžeme opäť nastaviť aj náhodnú počiatočné priehľadnosť. Ale pre jednoduchosť to ponecháme takto. Základný časticový systém teda máme hotový. Ale ako ho pôjdeme použiť, bolo by dobré si povedať ako vypočítať koľko častíc budeme potrebovať. Ak chceme aby častice žila maximálne 3 sekundy a chceme ich vypúšťať po 100 milisekundách, tak ich potrebujeme maximálne 30. Viac ich v jeden okamih zobrazené nebude. Možno to ľahko vypočítať:

maxdobaživota[s]*1000/interval[ms]

Zvyčajne ich bude vidieť súčasne menej, pretože rozptyl životnosti býva pomerne široký. Používať komponent iste zvládnete sami. Dalo by sa doplniť veľa rôznych ďalších doplňujúcich parametrov, ale na tie nie je v tomto článku bohužiaľ priestor. A čo si budeme hovoriť, bolo by to celkom dosť nudné, navyše sa už teraz sa blížim k limitu. Jeden ale nemôžem opomenúť a tou je vplyv gravitácie / vetra. Gravitácia začne častice spomaľovať a vietor je vychyľuje z ich plánovanej dráhy. Pridajme si teda premennú:

public Vector2 Gravity = new Vector2(0, -50);

Vnútri máme vlastne zahrnuté oba dva vplyvy. Gravitáciu pôsobiaca dole a nulový smer vetra. Ktorý by pôsobil horizontálne. Tento vplyv stačí na častice aplikovať v Update metóde rovnako ako sme dávali rýchlosť:

p.Velocity -= Gravity*(float)Parent.Engine.GameTime.ElapsedGameTime.TotalSeconds;

A to by bolo pre tento diel všetko. Zvyšné parametre nájdete v archíve pod článkom. Budem sa tešiť na komentáre, proste ako obvykle a nabudúce dovidenia zas u enginu.


 

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 690x (563.59 kB)

 

Predchádzajúci článok
XNA tvorba v 3D - vykreslenie trojuholníka
Všetky články v sekcii
Základy 3D grafiky a tvorba enginu
Článok pre vás napísal vodacek
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Vodáček dělá že umí C#, naplno se již pět let angažuje v projektu ŽvB. Nyní studuje na FEI Upa informatiku, ikdyž si připadá spíš na ekonomice. Není mu také cizí PHP a SQL. Naopak cizí mu je Java a Python.
Aktivity