ParticleSystem.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // ParticleSystem.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.Xml.Serialization;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Graphics;
  14. using Microsoft.Xna.Framework.Content;
  15. #endregion
  16. namespace NetRumble
  17. {
  18. /// <summary>
  19. /// A single-emitter particle system within a larger effect.
  20. /// </summary>
  21. public class ParticleSystem
  22. {
  23. #region Description Data
  24. /// <summary>
  25. /// The name of the particle system.
  26. /// </summary>
  27. private string name = "DefaultParticleSystem";
  28. /// <summary>
  29. /// The number of particles in this system.
  30. /// </summary>
  31. private int particleCount = 256;
  32. #endregion
  33. #region Graphics Data
  34. /// <summary>
  35. /// The current position of the system, relative to the effect.
  36. /// </summary>
  37. private Vector2 position = Vector2.Zero;
  38. /// <summary>
  39. /// The tint of the particles in the system.
  40. /// </summary>
  41. private Vector4 color =
  42. Microsoft.Xna.Framework.Color.White.ToVector4();
  43. /// <summary>
  44. /// The content name of the texture used with this system.
  45. /// </summary>
  46. private string textureName = "default_particle";
  47. /// <summary>
  48. /// The texture used with this system.
  49. /// </summary>
  50. private Texture2D texture = null;
  51. /// <summary>
  52. /// The center of the texture used with this system.
  53. /// </summary>
  54. /// <remarks>This is derived from the texture, calculated and stored.</remarks>
  55. private Vector2 textureOrigin;
  56. /// <summary>
  57. /// The blending mode for this system.
  58. /// </summary>
  59. private SpriteBlendMode blendMode = SpriteBlendMode.AlphaBlend;
  60. #endregion
  61. #region Status Data
  62. /// <summary>
  63. /// If true, the particle system is currently active.
  64. /// </summary>
  65. private bool active = false;
  66. [XmlIgnore()]
  67. public bool Active
  68. {
  69. get { return (active || (timeRemaining > 0.0f)); }
  70. }
  71. /// <summary>
  72. /// The amount of time that the particle system generates particles.
  73. /// </summary>
  74. private float duration = float.MaxValue;
  75. /// <summary>
  76. /// The time remaining for the particle system's generation.
  77. /// </summary>
  78. private float timeRemaining = float.MaxValue;
  79. /// <summary>
  80. /// The initial delay before the particle system starts generating particles.
  81. /// </summary>
  82. /// <remarks>Used for offsetting the start of particle systems.</remarks>
  83. private float initialDelay = 0f;
  84. /// <summary>
  85. /// The amount of time left on the initial delay.
  86. /// </summary>
  87. private float initialDelayRemaining = 0f;
  88. #endregion
  89. #region Particle Creation Data
  90. /// <summary>
  91. /// The number of particles that this system releases per second.
  92. /// </summary>
  93. private int particlesPerSecond = 128;
  94. /// <summary>
  95. /// The number of seconds between particle releases.
  96. /// </summary>
  97. /// <remarks>This is derived from particlesPerSecond.</remarks>
  98. private float releaseRate = 0.25f;
  99. /// <summary>
  100. /// The amount of time since the last particle was emitted.
  101. /// </summary>
  102. private float releaseTimer = 0f;
  103. /// <summary>
  104. /// The minimum lifetime possible for new particles.
  105. /// </summary>
  106. private float durationMinimum = 1f;
  107. /// <summary>
  108. /// The maximum lifetime possible for new particles.
  109. /// </summary>
  110. private float durationMaximum = 1f;
  111. /// <summary>
  112. /// The minimum initial velocity of new particles.
  113. /// </summary>
  114. private float velocityMinimum = 16f;
  115. /// <summary>
  116. /// The maximum initial velocity of new particles.
  117. /// </summary>
  118. private float velocityMaximum = 32f;
  119. /// <summary>
  120. /// The minimum acceleration of new particles.
  121. /// </summary>
  122. private float accelerationMinimum = 0f;
  123. /// <summary>
  124. /// The maximum acceleration of new particles.
  125. /// </summary>
  126. private float accelerationMaximum = 0f;
  127. /// <summary>
  128. /// The minimum initial scale of new particles.
  129. /// </summary>
  130. private float scaleMinimum = 1f;
  131. /// <summary>
  132. /// The maximum initial scale of new particles.
  133. /// </summary>
  134. private float scaleMaximum = 1f;
  135. /// <summary>
  136. /// The minimum initial opacity of new particles.
  137. /// </summary>
  138. private float opacityMinimum = 1f;
  139. /// <summary>
  140. /// The maximum initial opacity of new particles.
  141. /// </summary>
  142. private float opacityMaximum = 1f;
  143. /// <summary>
  144. /// The minimum release angle of new particles from the center.
  145. /// </summary>
  146. private float releaseAngleMinimum = 0f;
  147. /// <summary>
  148. /// The maximum release angle of new particles from the center.
  149. /// </summary>
  150. private float releaseAngleMaximum = 360f;
  151. /// <summary>
  152. /// The minimum release distance of new particles from the center.
  153. /// </summary>
  154. private float releaseDistanceMinimum = 0f;
  155. /// <summary>
  156. /// The maximum release distance of new particles from the center.
  157. /// </summary>
  158. private float releaseDistanceMaximum = 0f;
  159. #endregion
  160. #region Particle Updating Data
  161. /// <summary>
  162. /// The angular velocity of particles in this system.
  163. /// </summary>
  164. private float angularVelocity = 0f;
  165. /// <summary>
  166. /// The change in the scale per second for particles in this system.
  167. /// </summary>
  168. private float scaleDeltaPerSecond = 0f;
  169. /// <summary>
  170. /// The change in the opacity per second for particles in this system.
  171. /// </summary>
  172. private float opacityDeltaPerSecond = 0f;
  173. #endregion
  174. #region Particle Cache
  175. /// <summary>
  176. /// The cache of all particle objects in this system.
  177. /// </summary>
  178. private ParticleCache particles;
  179. #endregion
  180. #region Initialization Methods
  181. /// <summary>
  182. /// Construct a new particle system.
  183. /// </summary>
  184. public ParticleSystem() { }
  185. /// <summary>
  186. /// Clone a particle system.
  187. /// </summary>
  188. /// <returns>A clone of this particle system.</returns>
  189. public ParticleSystem Clone()
  190. {
  191. ParticleSystem clone = new ParticleSystem();
  192. clone.name = this.name;
  193. clone.particleCount = this.particleCount;
  194. clone.position = this.position;
  195. clone.color = this.color;
  196. clone.textureName = this.textureName;
  197. clone.blendMode = this.blendMode;
  198. clone.duration = this.duration;
  199. clone.initialDelay = this.initialDelay;
  200. clone.particlesPerSecond = this.particlesPerSecond;
  201. clone.releaseRate = this.releaseRate;
  202. clone.durationMinimum = this.durationMinimum;
  203. clone.durationMaximum = this.durationMaximum;
  204. clone.velocityMinimum = this.velocityMinimum;
  205. clone.velocityMaximum = this.velocityMaximum;
  206. clone.accelerationMinimum = this.accelerationMinimum;
  207. clone.accelerationMaximum = this.accelerationMaximum;
  208. clone.scaleMinimum = this.scaleMinimum;
  209. clone.scaleMaximum = this.scaleMaximum;
  210. clone.opacityMinimum = this.opacityMinimum;
  211. clone.opacityMaximum = this.opacityMaximum;
  212. clone.releaseAngleMinimum = this.releaseAngleMinimum;
  213. clone.releaseAngleMaximum = this.releaseAngleMaximum;
  214. clone.releaseDistanceMinimum = this.releaseDistanceMinimum;
  215. clone.releaseDistanceMaximum = this.releaseDistanceMaximum;
  216. clone.angularVelocity = this.angularVelocity;
  217. clone.scaleDeltaPerSecond = this.scaleDeltaPerSecond;
  218. clone.opacityDeltaPerSecond = this.opacityDeltaPerSecond;
  219. return clone;
  220. }
  221. /// <summary>
  222. /// Initialize the particle system.
  223. /// </summary>
  224. /// <param name="content">The content manager that owns the texture.</param>
  225. public virtual void Initialize(ContentManager content)
  226. {
  227. // calculate the release rate
  228. releaseRate = 1.0f / (float)particlesPerSecond;
  229. // create the cache
  230. particles = new ParticleCache(particleCount);
  231. // load the texture
  232. try
  233. {
  234. texture = content.Load<Texture2D>(textureName);
  235. }
  236. catch (ContentLoadException)
  237. {
  238. texture = content.Load<Texture2D>("Textures/Particles/defaultParticle");
  239. }
  240. // calculate the origin on the texture
  241. textureOrigin = new Vector2(texture.Width / 2f, texture.Height / 2f);
  242. // allow us to start updating and drawing
  243. active = true;
  244. }
  245. /// <summary>
  246. /// Resets the particle system.
  247. /// </summary>
  248. public virtual void Reset()
  249. {
  250. // reset the cache
  251. particles.Reset();
  252. // reset the timers
  253. timeRemaining = duration;
  254. initialDelayRemaining = initialDelay;
  255. // allow us to start updating and drawing
  256. active = true;
  257. }
  258. #endregion
  259. #region Updating Methods
  260. /// <summary>
  261. /// Update the particle system.
  262. /// </summary>
  263. /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
  264. public virtual void Update(float elapsedTime)
  265. {
  266. // if the system isn't active, dont' update at all
  267. if (!Active)
  268. return;
  269. // update the initial delay
  270. if (initialDelayRemaining > 0.0f)
  271. {
  272. initialDelayRemaining -= elapsedTime;
  273. return;
  274. }
  275. // generate new particles
  276. GenerateParticles(elapsedTime);
  277. // update the existing particles
  278. UpdateParticles(elapsedTime);
  279. // update the active flag, based on if there are any used particles
  280. active = particles.UsedCount > 0;
  281. }
  282. /// <summary>
  283. /// Generate new particles into the system.
  284. /// </summary>
  285. /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
  286. private void GenerateParticles(float elapsedTime)
  287. {
  288. if (timeRemaining <= 0.0f)
  289. {
  290. return;
  291. }
  292. // update the timer
  293. timeRemaining -= elapsedTime;
  294. // release some particles if it's time
  295. releaseTimer += elapsedTime;
  296. while (releaseTimer >= releaseRate)
  297. {
  298. // only get new particles if you can
  299. Particle particle = particles.GetNextParticle();
  300. if (particle == null)
  301. {
  302. break;
  303. }
  304. else
  305. {
  306. // initialize the new particle
  307. InitializeParticle(particle);
  308. // reduce the release timer for the release rate of a particle
  309. releaseTimer -= releaseRate;
  310. }
  311. }
  312. }
  313. /// <summary>
  314. /// Update all of the individual particles in this system.
  315. /// </summary>
  316. /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
  317. private void UpdateParticles(float elapsedTime)
  318. {
  319. for (int i = 0; i < particles.Particles.Length; ++i)
  320. {
  321. if (particles.Particles[i].TimeRemaining > 0.0f)
  322. {
  323. // update the timer on the particle
  324. particles.Particles[i].TimeRemaining -= elapsedTime;
  325. // if the particle just timed out on this update,
  326. // add it to the freed-list.
  327. if (particles.Particles[i].TimeRemaining <= 0.0f)
  328. {
  329. particles.ReleaseParticle(particles.Particles[i]);
  330. continue;
  331. }
  332. // update the particle
  333. particles.Particles[i].Update(elapsedTime, angularVelocity,
  334. scaleDeltaPerSecond, opacityDeltaPerSecond);
  335. }
  336. }
  337. }
  338. /// <summary>
  339. /// Initialize a new particle using the values in this system.
  340. /// </summary>
  341. /// <param name="particle">The particle to be initialized.</param>
  342. private void InitializeParticle(Particle particle)
  343. {
  344. // safety-check the parameter
  345. if (particle == null)
  346. {
  347. throw new ArgumentNullException("particle");
  348. }
  349. // set the time remaining on the new particle
  350. particle.TimeRemaining = RandomMath.RandomBetween(durationMinimum,
  351. durationMaximum);
  352. // generate a random direction
  353. Vector2 direction = RandomMath.RandomDirection(releaseAngleMinimum,
  354. releaseAngleMaximum);
  355. // set the graphics data on the new particle
  356. particle.Position = position + direction *
  357. RandomMath.RandomBetween(releaseDistanceMinimum,
  358. releaseDistanceMaximum);
  359. particle.Velocity = direction * RandomMath.RandomBetween(velocityMinimum,
  360. velocityMaximum);
  361. if (particle.Velocity.LengthSquared() > 0f)
  362. {
  363. particle.Acceleration = direction *
  364. RandomMath.RandomBetween(accelerationMinimum, accelerationMaximum);
  365. }
  366. else
  367. {
  368. particle.Acceleration = Vector2.Zero;
  369. }
  370. particle.Rotation = RandomMath.RandomBetween(0f, MathHelper.TwoPi);
  371. particle.Scale = RandomMath.RandomBetween(scaleMinimum, scaleMaximum);
  372. particle.Opacity = RandomMath.RandomBetween(opacityMinimum, opacityMaximum);
  373. }
  374. #endregion
  375. #region Drawing Methods
  376. /// <summary>
  377. /// Draw the particle system.
  378. /// </summary>
  379. /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
  380. public void Draw(SpriteBatch spriteBatch)
  381. {
  382. // only draw if we're active
  383. if (!Active)
  384. return;
  385. // draw each particle
  386. for (int p = 0; p < particles.Particles.Length; ++p)
  387. {
  388. Particle particle = particles.Particles[p];
  389. if (particle.TimeRemaining > 0.0f)
  390. {
  391. color.W = particle.Opacity;
  392. spriteBatch.Draw(texture, particle.Position, null, new Color(color),
  393. particle.Rotation, textureOrigin, particle.Scale,
  394. SpriteEffects.None, 1f);
  395. }
  396. }
  397. }
  398. #endregion
  399. #region Control Methods
  400. /// <summary>
  401. /// Stop the particle system.
  402. /// </summary>
  403. /// <param name="immediately">
  404. /// If true, particles are no longer drawn or updated.
  405. /// Otherwise, only generation is halted.
  406. /// </param>
  407. public void Stop(bool immediately)
  408. {
  409. // halt generation
  410. timeRemaining = 0.0f;
  411. // halt updating/drawing of the particles if requested
  412. if (immediately)
  413. {
  414. active = false;
  415. }
  416. }
  417. #endregion
  418. #region Serialization Interface
  419. public string Name
  420. {
  421. get { return name; }
  422. set { name = value; }
  423. }
  424. public int ParticleCount
  425. {
  426. get { return particleCount; }
  427. set { particleCount = value; }
  428. }
  429. public Vector2 Position
  430. {
  431. get { return position; }
  432. set { position = value; }
  433. }
  434. public Vector4 Color
  435. {
  436. get { return color; }
  437. set { color = value; }
  438. }
  439. public string TextureName
  440. {
  441. get { return textureName; }
  442. set { textureName = value; }
  443. }
  444. public SpriteBlendMode BlendMode
  445. {
  446. get { return blendMode; }
  447. set { blendMode = value; }
  448. }
  449. public float Duration
  450. {
  451. get { return duration; }
  452. set { duration = value; }
  453. }
  454. public float InitialDelay
  455. {
  456. get { return initialDelay; }
  457. set { initialDelay = value; }
  458. }
  459. public int ParticlesPerSecond
  460. {
  461. get { return particlesPerSecond; }
  462. set { particlesPerSecond = value; }
  463. }
  464. public float DurationMinimum
  465. {
  466. get { return durationMinimum; }
  467. set { durationMinimum = value; }
  468. }
  469. public float DurationMaximum
  470. {
  471. get { return durationMaximum; }
  472. set { durationMaximum = value; }
  473. }
  474. public float VelocityMinimum
  475. {
  476. get { return velocityMinimum; }
  477. set { velocityMinimum = value; }
  478. }
  479. public float VelocityMaximum
  480. {
  481. get { return velocityMaximum; }
  482. set { velocityMaximum = value; }
  483. }
  484. public float AccelerationMinimum
  485. {
  486. get { return accelerationMinimum; }
  487. set { accelerationMinimum = value; }
  488. }
  489. public float AccelerationMaximum
  490. {
  491. get { return accelerationMaximum; }
  492. set { accelerationMaximum = value; }
  493. }
  494. public float ScaleMinimum
  495. {
  496. get { return scaleMinimum; }
  497. set { scaleMinimum = value; }
  498. }
  499. public float ScaleMaximum
  500. {
  501. get { return scaleMaximum; }
  502. set { scaleMaximum = value; }
  503. }
  504. public float OpacityMinimum
  505. {
  506. get { return opacityMinimum; }
  507. set { opacityMinimum = value; }
  508. }
  509. public float OpacityMaximum
  510. {
  511. get { return opacityMaximum; }
  512. set { opacityMaximum = value; }
  513. }
  514. public float ReleaseAngleMinimum
  515. {
  516. get { return releaseAngleMinimum; }
  517. set { releaseAngleMinimum = value; }
  518. }
  519. public float ReleaseAngleMaximum
  520. {
  521. get { return releaseAngleMaximum; }
  522. set { releaseAngleMaximum = value; }
  523. }
  524. public float ReleaseDistanceMinimum
  525. {
  526. get { return releaseDistanceMinimum; }
  527. set { releaseDistanceMinimum = value; }
  528. }
  529. public float ReleaseDistanceMaximum
  530. {
  531. get { return releaseDistanceMaximum; }
  532. set { releaseDistanceMaximum = value; }
  533. }
  534. public float AngularVelocity
  535. {
  536. get { return angularVelocity; }
  537. set { angularVelocity = value; }
  538. }
  539. public float ScaleDeltaPerSecond
  540. {
  541. get { return scaleDeltaPerSecond; }
  542. set { scaleDeltaPerSecond = value; }
  543. }
  544. public float OpacityDeltaPerSecond
  545. {
  546. get { return opacityDeltaPerSecond; }
  547. set { opacityDeltaPerSecond = value; }
  548. }
  549. #endregion
  550. }
  551. }