Č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:
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 podmienkamiStiahnuté 690x (563.59 kB)