source: 2014/24/EemeliK/Zombieland/Jypeli/Effects/ParticleSystem.cs @ 5974

Revision 5974, 15.5 KB checked in by empaheik, 4 years ago (diff)
Line 
1using System;
2using System.Collections.Generic;
3using Microsoft.Xna.Framework;
4using Microsoft.Xna.Framework.Graphics;
5
6namespace Jypeli.Effects
7{
8    public enum BlendMode
9    {
10        Alpha,
11        Additive
12    }
13
14
15    /// <summary>
16    /// Järjestelmä partikkelien käsittelyyn
17    /// </summary>
18    public class ParticleSystem : GameObject
19    {
20        private Random random = new Random();
21
22        // Lista efektin partikkeleille
23        private LinkedList<Particle> particles;
24
25        // Jono efektin vapaille partikkeleille
26        private Queue<Particle> freeParticles;
27
28        /// <summary>
29        /// Yksittäisen partikkelin kuva.
30        /// </summary>
31        public Image ParticleImage { get; set; }
32
33        /// <summary>
34        /// Partikkelin toissijainen kuva. Jos <c>null</c> niin käytetään vain yhtä kuvaa.
35        /// </summary>
36        public Image OuterParticleImage { get; set; }
37
38        /// <summary>
39        /// Perivien luokkien täytyy määrittää nämä muuttujat
40        /// </summary>
41        #region Subclass variables
42
43        /// <summary>
44        /// Pienin skaalaus joka efektin partikkeleilla voi olla
45        /// </summary>
46        public double MinScale { get; set; }
47        /// <summary>
48        /// Suurin skaalaus joka efektin partikkeleilla voi olla
49        /// </summary>
50        public double MaxScale { get; set; }
51
52        /// <summary>
53        /// Määrä jonka partikkeli skaalautuu päivityksessä
54        /// </summary>
55        public double ScaleAmount { get; set; }
56
57        /// <summary>
58        /// Pienin nopeus joka efektin partikkelilla voi olla
59        /// </summary>
60        public double MinVelocity { get; set; }
61        /// <summary>
62        /// Suurin nopeus joka efektin partikkelilla voi olla
63        /// </summary>
64        public double MaxVelocity { get; set; }
65
66        /// <summary>
67        /// Efektin partikkelin lyhin mahdollinen elinaika
68        /// </summary>
69        public double MinLifetime { get; set; }
70       
71        /// <summary>
72        /// Efektin partikkelin pisin mahdollinen elinaika
73        /// </summary>
74        public double MaxLifetime { get; set; }
75
76        /// <summary>
77        /// Pienin kiihtyvyys joka efektin partikkelilla voi olla
78        /// </summary>
79        public double MinAcceleration { get; set; }
80
81        /// <summary>
82        /// Suurin kiihtyvyys joka efektin partikkelilla voi olla
83        /// </summary>
84        public double MaxAcceleration { get; set; }
85
86        /// <summary>
87        /// Pienin pyörimisnopeus joka efektin partikkelilla voi olla
88        /// </summary>
89        public double MinRotationSpeed { get; set; }
90
91        /// <summary>
92        /// Suurin pyörimisnopeus joka efektin partikkelilla voi olla
93        /// </summary>
94        public double MaxRotationSpeed { get; set; }
95
96        /// <summary>
97        /// Pienin kierre mikä efektin partikkelilla voi olla
98        /// </summary>
99        public double MinRotation { get; set; }
100
101        /// <summary>
102        /// Suurin kierre mikä efektin partikkelilla voi olla
103        /// </summary>
104        public double MaxRotation { get; set; }
105
106        /// <summary>
107        /// Efektin läpinäkyvyyskerroin (0.0-1.0)
108        /// </summary>
109        public double AlphaAmount { get; set; }
110
111        #endregion
112
113        public BlendMode BlendMode { get; set; }
114
115        /// <summary>
116        /// Vaikuttaako efektiin tuuli
117        /// </summary>
118        public Boolean IgnoreWind { get; set; }
119
120        private Boolean visible = true;
121        private Boolean fadeIn = false;
122        private Boolean fadeOut = false;
123        private double originalAlpha = 1.0;
124        private double fadeTime = 0.0;
125        private double fadeTimePassed = 0.0;
126
127        /// <summary>
128        /// Efekti tulee näkyviin tietyn sekuntimäärän aikana
129        /// </summary>
130        /// <param name="timeInSeconds">Aika joka kuluu että efekti on näkyvä</param>
131        public void FadeIn(double timeInSeconds)
132        {
133            if (!visible)
134            {
135                fadeTimePassed = 0.0;
136                fadeIn = true;
137                fadeTime = timeInSeconds;
138            }
139        }
140
141        /// <summary>
142        /// Efekti hiipuu näkyvistä tietyn sekuntimäärän aikana
143        /// </summary>
144        /// <param name="TimeInSeconds">Aika joka kuluu että efekti katoaa</param>
145        public void FadeOut(double TimeInSeconds)
146        {
147            if (visible)
148            {
149                originalAlpha = AlphaAmount;
150                fadeTimePassed = 0.0;
151                fadeOut = true;
152                fadeTime = TimeInSeconds;
153            }
154        }
155        /// <summary>
156        /// Muodostaja
157        /// </summary>
158        /// <param name="particleImage">Partikkelin kuva.</param>
159        /// <param name="maxAmountOfParticles">Suurin määrä partikkeleita mitä efektillä voi olla kerralla</param>
160        public ParticleSystem(Image particleImage, int maxAmountOfParticles)
161            : base(0, 0)
162        {
163            this.ParticleImage = particleImage;
164            AlphaAmount = 1.0;
165            IgnoreWind = false;
166            BlendMode = BlendMode.Alpha;
167
168            IsUpdated = true;
169            InitializeParticles();
170
171            particles = new LinkedList<Particle>();
172            freeParticles = new Queue<Particle>(maxAmountOfParticles);
173            for (int i = 0; i < maxAmountOfParticles; i++)
174            {
175                freeParticles.Enqueue(new Particle());
176            }
177        }
178
179        /// <summary>
180        /// Metodi joka asettaa partikkeleille attribuutit
181        /// Täytyy kutsua perityistä luokista
182        /// </summary>
183        protected virtual void InitializeParticles()
184        {
185        }
186
187        /// <summary>
188        /// Lisää efektin kentälle
189        /// </summary>
190        /// <param name="x">Efektin x-koordinaatti</param>
191        /// <param name="y">Efektin y-koordinaatti</param>
192        /// <param name="numberOfParticles">Partikkeleiden määrä efektissä</param>
193        public void AddEffect(double x, double y, int numberOfParticles)
194        {
195            AddEffect(new Vector(x, y), numberOfParticles);
196        }
197
198        public void AddEffect(Vector position, Angle angle, int numberOfParticles)
199        {
200            for (int i = 0; i < numberOfParticles && freeParticles.Count > 0; i++)
201            {
202                Particle p = freeParticles.Dequeue();
203                particles.AddLast(p);
204                InitializeParticle(p, position, angle);
205            }
206        }
207
208        /// <summary>
209        /// Lisää efektin kentälle
210        /// </summary>
211        /// <param name="position">Paikka.</param>
212        /// <param name="numberOfParticles">Partikkeleiden määrä efektissä</param>
213        public void AddEffect(Vector position, int numberOfParticles)
214        {
215            for (int i = 0; i < numberOfParticles && freeParticles.Count > 0; i++)
216            {
217                Particle p = freeParticles.Dequeue();
218                particles.AddLast(p);
219                InitializeParticle(p, position);
220            }
221        }
222
223        /// <summary>
224        /// Antaa satunnaisen suunnan
225        /// </summary>
226        /// <returns>Satunnainen suunta</returns>
227        protected virtual Vector GiveRandomDirection()
228        {
229            double angle = random.NextDouble() * MathHelper.TwoPi;
230            return new Vector(Math.Cos(angle), Math.Sin(angle));
231        }
232
233        /// <summary>
234        /// Alustaa yhden partikkelin
235        /// </summary>
236        /// <param name="p">Partikkeli joka alustetaan</param>
237        /// <param name="position">Sijainti johon alustetaan</param>
238        protected virtual void InitializeParticle(Particle p, Vector position)
239        {
240            Vector direction = GiveRandomDirection();
241
242            double scale = RandomGen.NextDouble(MinScale, MaxScale);
243            double rotation = RandomGen.NextDouble(MinRotation, MaxRotation);
244            double velocity = RandomGen.NextDouble(MinVelocity, MaxVelocity);
245            double lifetime = RandomGen.NextDouble(MinLifetime, MaxLifetime);
246            double acceleration = RandomGen.NextDouble(MinAcceleration, MaxAcceleration);
247            double rotationSpeed = RandomGen.NextDouble(MinRotationSpeed, MaxRotationSpeed);
248
249            p.Initialize(position, scale, rotation, rotationSpeed, velocity * direction, acceleration * direction, lifetime);
250        }
251
252        protected virtual void InitializeParticle(Particle p, Vector position, Angle angle)
253        {
254            Vector direction = GiveRandomDirection();
255
256            double rotation = angle.Degrees;
257            double scale = RandomGen.NextDouble(MinScale, MaxScale);
258            double velocity = RandomGen.NextDouble(MinVelocity, MaxVelocity);
259            double lifetime = RandomGen.NextDouble(MinLifetime, MaxLifetime);
260            double acceleration = RandomGen.NextDouble(MinAcceleration, MaxAcceleration);
261            double rotationSpeed = RandomGen.NextDouble(MinRotationSpeed, MaxRotationSpeed);
262
263            p.Initialize(position, scale, rotation, rotationSpeed, velocity * direction, acceleration * direction, lifetime);
264        }
265
266        /// <summary>
267        /// Kutsutaan kun luokka päivitetään
268        /// </summary>
269        /// <param name="time">Gametime</param>
270        public override void Update(Time time)
271        {
272            double t = time.SinceLastUpdate.TotalSeconds;
273
274            LinkedListNode<Particle> node = particles.First;
275            while (node != null)
276            {
277                Particle p = node.Value;
278                if (p.Alive)
279                {
280                    p.Update(t);
281                    node = node.Next;
282                }
283                else
284                {
285                    freeParticles.Enqueue(p);
286                    LinkedListNode<Particle> previous = node;
287                    node = node.Next;
288                    particles.Remove(previous);
289                }
290            }
291            if (fadeOut)
292            {
293                AlphaAmount = Math.Min(1 - fadeTimePassed / fadeTime, originalAlpha);
294                fadeTimePassed += t;
295                if (fadeTime <= fadeTimePassed)
296                {
297                    AlphaAmount = 0.0;
298                    fadeOut = false;
299                    visible = false;
300                }
301            }
302            if (fadeIn)
303            {
304                AlphaAmount = Math.Min(fadeTimePassed / fadeTime, originalAlpha);
305                fadeTimePassed += t;
306                if (fadeTime <= fadeTimePassed)
307                {
308                    AlphaAmount = originalAlpha;
309                    fadeIn = false;
310                    visible = true;
311                }
312            }
313
314            base.Update(time);
315        }
316
317
318        static readonly Vector2 textureTopLeft = new Vector2(0.0f, 0.0f);
319        static readonly Vector2 textureBottomLeft = new Vector2(0.0f, 1.0f);
320        static readonly Vector2 textureTopRight = new Vector2(1.0f, 0.0f);
321        static readonly Vector2 textureBottomRight = new Vector2(1.0f, 1.0f);
322
323        static readonly Vector3 topLeft = new Vector3(-0.5f, 0.5f, 0);
324        static readonly Vector3 bottomLeft = new Vector3(-0.5f, -0.5f, 0);
325        static readonly Vector3 topRight = new Vector3(0.5f, 0.5f, 0);
326        static readonly Vector3 bottomRight = new Vector3(0.5f, -0.5f, 0);
327
328        static readonly VertexPositionTexture[] vertices =
329        {
330            // Triangle 1
331            new VertexPositionTexture(topLeft, textureTopLeft),
332            new VertexPositionTexture(bottomLeft, textureBottomLeft),
333            new VertexPositionTexture(topRight, textureTopRight),
334
335            // Triangle 2
336            new VertexPositionTexture(bottomLeft, textureBottomLeft),
337            new VertexPositionTexture(bottomRight, textureBottomRight),
338            new VertexPositionTexture(topRight, textureTopRight),
339        };
340
341        private static BlendState ToXnaBlendState(BlendMode mode)
342        {
343            switch (mode)
344            {
345                case BlendMode.Alpha:
346                    return BlendState.AlphaBlend;
347                case BlendMode.Additive:
348                    return BlendState.Additive;
349                default:
350                    return BlendState.AlphaBlend;
351            }
352        }
353
354        internal void Draw(Matrix worldMatrix)
355        {
356            var device = Game.GraphicsDevice;
357            var effect = Graphics.BasicTextureEffect;
358
359            Texture2D texture = ParticleImage.XNATexture;
360
361            device.RasterizerState = RasterizerState.CullClockwise;
362            device.SamplerStates[0] = SamplerState.LinearClamp;
363            device.BlendState = ToXnaBlendState(BlendMode);
364           
365            effect.Texture = texture;
366            effect.CurrentTechnique.Passes[0].Apply();
367
368            if (OuterParticleImage == null)
369            {
370                foreach (Particle p in particles)
371                {
372                    double nTime = p.Lifetime.TotalMilliseconds / p.MaxLifetime.TotalMilliseconds;
373                    double alpha = 4 * nTime * (1 - nTime) * AlphaAmount;
374                    float scale = (float)(p.Scale * (.75f + .25f * nTime * ScaleAmount));
375
376                    Matrix matrix =
377                        Matrix.CreateScale(scale * texture.Width / texture.Height, scale, 1f)
378                        * Matrix.CreateRotationZ((float)p.Rotation)
379                        * Matrix.CreateTranslation((float)p.Position.X, (float)p.Position.Y, 0f)
380                        * worldMatrix
381                        ;
382
383                    effect.Alpha = (float)alpha;
384                    effect.World = matrix;
385                    effect.CurrentTechnique.Passes[0].Apply();
386
387                    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
388                }
389            }
390            else
391            {
392                Texture2D outerTexture = OuterParticleImage.XNATexture;
393
394                foreach (Particle p in particles)
395                {
396                    double nTime = p.Lifetime.TotalMilliseconds / p.MaxLifetime.TotalMilliseconds;
397                    double alpha = 4 * nTime * (1 - nTime) * AlphaAmount;
398                    float scale = (float)(p.Scale * (.75f + .25f * nTime * ScaleAmount));
399
400                    Matrix matrix =
401                        Matrix.CreateScale(scale, scale, 1f)
402                        * Matrix.CreateRotationZ((float)p.Rotation)
403                        * Matrix.CreateTranslation((float)p.Position.X, (float)p.Position.Y, 0f)
404                        * worldMatrix
405                        ;
406                    Matrix matrix2 =
407                        Matrix.CreateScale(0.65f, 0.65f, 1f)
408                        * matrix;
409
410                    effect.Texture = texture;
411                    effect.Alpha = (float)alpha;
412                    effect.World = matrix;
413                    effect.CurrentTechnique.Passes[0].Apply();
414
415                    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
416
417                    effect.Texture = outerTexture;
418                    effect.World = matrix2;
419                    effect.CurrentTechnique.Passes[0].Apply();
420
421                    device.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, 2);
422                }
423            }
424        }
425    }
426}
Note: See TracBrowser for help on using the repository browser.