ParticleSystem.cs 17 KB

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