ParticleSystem.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // SmokePlumeParticleSystem.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Graphics;
  14. #endregion
  15. namespace RockRainIphone.Core
  16. {
  17. /// <summary>
  18. /// ParticleSystem is an abstract class that provides the basic functionality to
  19. /// create a particle effect. Different subclasses will have different effects,
  20. /// such as fire, explosions, and plumes of smoke. To use these subclasses,
  21. /// simply call AddParticles, and pass in where the particles should exist
  22. /// </summary>
  23. public abstract class ParticleSystem : DrawableGameComponent
  24. {
  25. // these two values control the order that particle systems are drawn in.
  26. // typically, particles that use additive blending should be drawn on top of
  27. // particles that use regular alpha blending. ParticleSystems should therefore
  28. // set their DrawOrder to the appropriate value in InitializeConstants, though
  29. // it is possible to use other values for more advanced effects.
  30. public const int AlphaBlendDrawOrder = 100;
  31. public const int AdditiveDrawOrder = 200;
  32. private Random random;
  33. protected SpriteBatch spriteBatch;
  34. // the texture this particle system will use.
  35. private Texture2D texture;
  36. // the origin when we're drawing textures. this will be the middle of the
  37. // texture.
  38. private Vector2 origin;
  39. // this number represents the maximum number of effects this particle system
  40. // will be expected to draw at one time. this is set in the constructor and is
  41. // used to calculate how many particles we will need.
  42. private int howManyEffects;
  43. // the array of particles used by this system. these are reused, so that calling
  44. // AddParticles will not cause any allocations.
  45. Particle[] particles;
  46. // the queue of free particles keeps track of particles that are not curently
  47. // being used by an effect. when a new effect is requested, particles are taken
  48. // from this queue. when particles are finished they are put onto this queue.
  49. Queue<Particle> freeParticles;
  50. /// <summary>
  51. /// returns the number of particles that are available for a new effect.
  52. /// </summary>
  53. public int FreeParticleCount
  54. {
  55. get { return freeParticles.Count; }
  56. }
  57. // This region of values control the "look" of the particle system, and should
  58. // be set by deriving particle systems in the InitializeConstants method. The
  59. // values are then used by the virtual function InitializeParticle. Subclasses
  60. // can override InitializeParticle for further
  61. // customization.
  62. #region constants to be set by subclasses
  63. /// <summary>
  64. /// minNumParticles and maxNumParticles control the number of particles that are
  65. /// added when AddParticles is called. The number of particles will be a random
  66. /// number between minNumParticles and maxNumParticles.
  67. /// </summary>
  68. protected int minNumParticles;
  69. protected int maxNumParticles;
  70. /// <summary>
  71. /// this controls the texture that the particle system uses. It will be used as
  72. /// an argument to ContentManager.Load.
  73. /// </summary>
  74. protected string textureFilename;
  75. /// <summary>
  76. /// minInitialSpeed and maxInitialSpeed are used to control the initial velocity
  77. /// of the particles. The particle's initial speed will be a random number
  78. /// between these two. The direction is determined by the function
  79. /// PickRandomDirection, which can be overriden.
  80. /// </summary>
  81. protected float minInitialSpeed;
  82. protected float maxInitialSpeed;
  83. /// <summary>
  84. /// minAcceleration and maxAcceleration are used to control the acceleration of
  85. /// the particles. The particle's acceleration will be a random number between
  86. /// these two. By default, the direction of acceleration is the same as the
  87. /// direction of the initial velocity.
  88. /// </summary>
  89. protected float minAcceleration;
  90. protected float maxAcceleration;
  91. /// <summary>
  92. /// minRotationSpeed and maxRotationSpeed control the particles' angular
  93. /// velocity: the speed at which particles will rotate. Each particle's rotation
  94. /// speed will be a random number between minRotationSpeed and maxRotationSpeed.
  95. /// Use smaller numbers to make particle systems look calm and wispy, and large
  96. /// numbers for more violent effects.
  97. /// </summary>
  98. protected float minRotationSpeed;
  99. protected float maxRotationSpeed;
  100. /// <summary>
  101. /// minLifetime and maxLifetime are used to control the lifetime. Each
  102. /// particle's lifetime will be a random number between these two. Lifetime
  103. /// is used to determine how long a particle "lasts." Also, in the base
  104. /// implementation of Draw, lifetime is also used to calculate alpha and scale
  105. /// values to avoid particles suddenly "popping" into view
  106. /// </summary>
  107. protected float minLifetime;
  108. protected float maxLifetime;
  109. /// <summary>
  110. /// to get some additional variance in the appearance of the particles, we give
  111. /// them all random scales. the scale is a value between minScale and maxScale,
  112. /// and is additionally affected by the particle's lifetime to avoid particles
  113. /// "popping" into view.
  114. /// </summary>
  115. protected float minScale;
  116. protected float maxScale;
  117. /// <summary>
  118. /// different effects can use different blend modes. fire and explosions work
  119. /// well with additive blending, for example.
  120. /// </summary>
  121. protected BlendState spriteBlendMode;
  122. #endregion
  123. /// <summary>
  124. /// Constructs a new ParticleSystem.
  125. /// </summary>
  126. /// <param name="game">The host for this particle system. The game keeps the
  127. /// content manager and sprite batch for us.</param>
  128. /// <param name="howManyEffects">the maximum number of particle effects that
  129. /// are expected on screen at once.</param>
  130. /// <remarks>it is tempting to set the value of howManyEffects very high.
  131. /// However, this value should be set to the minimum possible, because
  132. /// it has a large impact on the amount of memory required, and slows down the
  133. /// Update and Draw functions.</remarks>
  134. protected ParticleSystem(Game game, int howManyEffects)
  135. : base(game)
  136. {
  137. random = new Random(GetHashCode());
  138. this.howManyEffects = howManyEffects;
  139. }
  140. /// <summary>
  141. /// override the base class's Initialize to do some additional work; we want to
  142. /// call InitializeConstants to let subclasses set the constants that we'll use.
  143. ///
  144. /// also, the particle array and freeParticles queue are set up here.
  145. /// </summary>
  146. public override void Initialize()
  147. {
  148. InitializeConstants();
  149. // calculate the total number of particles we will ever need, using the
  150. // max number of effects and the max number of particles per effect.
  151. // once these particles are allocated, they will be reused, so that
  152. // we don't put any pressure on the garbage collector.
  153. particles = new Particle[howManyEffects * maxNumParticles];
  154. freeParticles = new Queue<Particle>(howManyEffects * maxNumParticles);
  155. for (int i = 0; i < particles.Length; i++)
  156. {
  157. particles[i] = new Particle();
  158. freeParticles.Enqueue(particles[i]);
  159. }
  160. base.Initialize();
  161. }
  162. /// <summary>
  163. /// this abstract function must be overriden by subclasses of ParticleSystem.
  164. /// It's here that they should set all the constants marked in the region
  165. /// "constants to be set by subclasses", which give each ParticleSystem its
  166. /// specific flavor.
  167. /// </summary>
  168. protected abstract void InitializeConstants();
  169. /// <summary>
  170. /// Override the base class LoadContent to load the texture. once it's
  171. /// loaded, calculate the origin.
  172. /// </summary>
  173. protected override void LoadContent()
  174. {
  175. // Initialize SpriteBatch
  176. spriteBatch = new SpriteBatch(GraphicsDevice);
  177. // make sure sub classes properly set textureFilename.
  178. if (string.IsNullOrEmpty(textureFilename))
  179. {
  180. string message = "textureFilename wasn't set properly, so the " +
  181. "particle system doesn't know what texture to load. Make " +
  182. "sure your particle system's InitializeConstants function " +
  183. "properly sets textureFilename.";
  184. throw new InvalidOperationException(message);
  185. }
  186. // load the texture....
  187. texture = Game.Content.Load<Texture2D>(textureFilename);
  188. // ... and calculate the center. this'll be used in the draw call, we
  189. // always want to rotate and scale around this point.
  190. origin.X = texture.Width / 2;
  191. origin.Y = texture.Height / 2;
  192. base.LoadContent();
  193. }
  194. /// <summary>
  195. /// AddParticles's job is to add an effect somewhere on the screen. If there
  196. /// aren't enough particles in the freeParticles queue, it will use as many as
  197. /// it can. This means that if there not enough particles available, calling
  198. /// AddParticles will have no effect.
  199. /// </summary>
  200. /// <param name="where">where the particle effect should be created</param>
  201. public void AddParticles(Vector2 where)
  202. {
  203. // the number of particles we want for this effect is a random number
  204. // somewhere between the two constants specified by the subclasses.
  205. int numParticles = random.Next(minNumParticles, maxNumParticles);
  206. // create that many particles, if you can.
  207. for (int i = 0; i < numParticles && freeParticles.Count > 0; i++)
  208. {
  209. // grab a particle from the freeParticles queue, and Initialize it.
  210. Particle p = freeParticles.Dequeue();
  211. InitializeParticle(p, where);
  212. }
  213. }
  214. // a handy little function that gives a random float between two
  215. // values. This will be used in several places in the sample, in particilar in
  216. // ParticleSystem.InitializeParticle.
  217. public float RandomBetween(float min, float max)
  218. {
  219. return min + (float)random.NextDouble() * (max - min);
  220. }
  221. /// <summary>
  222. /// InitializeParticle randomizes some properties for a particle, then
  223. /// calls initialize on it. It can be overriden by subclasses if they
  224. /// want to modify the way particles are created. For example,
  225. /// SmokePlumeParticleSystem overrides this function make all particles
  226. /// accelerate to the right, simulating wind.
  227. /// </summary>
  228. /// <param name="p">the particle to initialize</param>
  229. /// <param name="where">the position on the screen that the particle should be
  230. /// </param>
  231. protected virtual void InitializeParticle(Particle p, Vector2 where)
  232. {
  233. // first, call PickRandomDirection to figure out which way the particle
  234. // will be moving. velocity and acceleration's values will come from this.
  235. Vector2 direction = PickRandomDirection();
  236. // pick some random values for our particle
  237. float velocity = RandomBetween(minInitialSpeed, maxInitialSpeed);
  238. float acceleration = RandomBetween(minAcceleration, maxAcceleration);
  239. float lifetime = RandomBetween(minLifetime, maxLifetime);
  240. float scale = RandomBetween(minScale, maxScale);
  241. float rotationSpeed = RandomBetween(minRotationSpeed, maxRotationSpeed);
  242. // then initialize it with those random values. initialize will save those,
  243. // and make sure it is marked as active.
  244. p.Initialize(
  245. where, velocity * direction, acceleration * direction,
  246. lifetime, scale, rotationSpeed);
  247. }
  248. /// <summary>
  249. /// PickRandomDirection is used by InitializeParticles to decide which direction
  250. /// particles will move. The default implementation is a random vector in a
  251. /// circular pattern.
  252. /// </summary>
  253. protected virtual Vector2 PickRandomDirection()
  254. {
  255. float angle = RandomBetween(0, MathHelper.TwoPi);
  256. return new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
  257. }
  258. /// <summary>
  259. /// Check if the effect still active in screen
  260. /// </summary>
  261. public bool Active
  262. {
  263. get
  264. {
  265. // go through all of the particles...
  266. foreach (Particle p in particles)
  267. {
  268. if (p.Active)
  269. {
  270. return true;
  271. }
  272. }
  273. return false;
  274. }
  275. }
  276. /// <summary>
  277. /// overriden from DrawableGameComponent, Update will update all of the active
  278. /// particles.
  279. /// </summary>
  280. public override void Update(GameTime gameTime)
  281. {
  282. // calculate dt, the change in the since the last frame. the particle
  283. // updates will use this value.
  284. float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
  285. // go through all of the particles...
  286. foreach (Particle p in particles)
  287. {
  288. if (p.Active)
  289. {
  290. // ... and if they're active, update them.
  291. p.Update(dt);
  292. // if that update finishes them, put them onto the free particles
  293. // queue.
  294. if (!p.Active)
  295. {
  296. freeParticles.Enqueue(p);
  297. }
  298. }
  299. }
  300. base.Update(gameTime);
  301. }
  302. /// <summary>
  303. /// overriden from DrawableGameComponent, Draw will use ParticleSampleGame's
  304. /// sprite batch to render all of the active particles.
  305. /// </summary>
  306. public override void Draw(GameTime gameTime)
  307. {
  308. // tell sprite batch to begin, using the spriteBlendMode specified in
  309. // initializeConstants
  310. spriteBatch.Begin(SpriteSortMode.Deferred, spriteBlendMode);
  311. foreach (Particle p in particles)
  312. {
  313. // skip inactive particles
  314. if (!p.Active)
  315. continue;
  316. // normalized lifetime is a value from 0 to 1 and represents how far
  317. // a particle is through its life. 0 means it just started, .5 is half
  318. // way through, and 1.0 means it's just about to be finished.
  319. // this value will be used to calculate alpha and scale, to avoid
  320. // having particles suddenly appear or disappear.
  321. float normalizedLifetime = p.TimeSinceStart / p.Lifetime;
  322. // we want particles to fade in and fade out, so we'll calculate alpha
  323. // to be (normalizedLifetime) * (1-normalizedLifetime). this way, when
  324. // normalizedLifetime is 0 or 1, alpha is 0. the maximum value is at
  325. // normalizedLifetime = .5, and is
  326. // (normalizedLifetime) * (1-normalizedLifetime)
  327. // (.5) * (1-.5)
  328. // .25
  329. // since we want the maximum alpha to be 1, not .25, we'll scale the
  330. // entire equation by 4.
  331. float alpha = 4 * normalizedLifetime * (1 - normalizedLifetime);
  332. Color color = new Color(new Vector4(1, 1, 1, alpha));
  333. // make particles grow as they age. they'll start at 75% of their size,
  334. // and increase to 100% once they're finished.
  335. float scale = p.Scale * (.75f + .25f * normalizedLifetime);
  336. spriteBatch.Draw(texture, p.Position, null, color,
  337. p.Rotation, origin, scale, SpriteEffects.None, 0.0f);
  338. }
  339. spriteBatch.End();
  340. base.Draw(gameTime);
  341. }
  342. }
  343. }