【译】一个基本的粒子系统

发表于2016-03-07
评论0 2.5k浏览
版权:作者版权声明为http://www.codeproject.com/info/cpol10.aspx,可以翻译

介绍下粒子系统的基本概念,诸如如何创建基本的粒子效果诸如爆炸和水的喷泉。


简介
  粒子系统早已进入游戏引擎,变成模拟现实环境的基本特征和基础。在这篇文章中,我讲给你介绍粒子系统的基本思想,并将向你介绍如何创建基本的效果,诸如爆炸和喷泉。本文不涉及图形方面的知识,并且假设你已经有了一个粒子系统,你可以用它以任何你喜欢的方式进行展示。

单个粒子
  一个粒子系统实际是一组有相同行为的粒子阻止在一起。这些粒子可以是任何东西,从车撞墙飞溅的部分到下雨时候落下的水滴。
  所有粒子都有一些共同点-位置、方向、颜色和生命期。每个粒子记录其在空间的位置、方向、颜色和可以存活的时间。
  在我们开始观察粒子之前,我们需要一个类来保持位置和方向。因为我们是在3D世界中处理问题,一个简单的3D向量应该够了。你可以在附件中找到一个工作正常的向量类。现在对我们来说一个向量是一个封装了三个浮点变量的类,有函数的加法、减法和乘法。
现在让我们看一下基本粒子:
using System;
using System.Drawing;
namespace Particles
{
   ///
   /// Summary description for Particle.
   ///
   public class Particle
   {
        public static readonly int MAX_LIFE =1000;       
        // Position of the particle
        private Vector m_Position;
        // Direction and speed the particle ismoving
        private Vector m_Velocity;
        // Age of the particle
        private int m_Life;
        // Color of the particle
        private Color m_Color 
        ///
        /// Default constructor
        ///
        public Particle() : this(Vector.Zero,Vector.Zero, Color.Black, 0)
        { } 
        ///
        /// Constructor
        ///
        ///
       ///   ofnewly created particle
        ///
        ///   of newly createdparticle
        ///
        ///
        public Particle(Vector pos, Vector vel,Color col, int life)
        {
            // Create particle at givenposition
            m_Position = pos;
            // Set particle's speed to givenspeed
            m_Velocity = vel
            // Set particle's color to givencolor
            m_Color = col;
            // Make sure starting age is valid
            if (life < 0)
                m_Life = 0;
            else
                m_Life = life;
        }
        ///
        /// Update position, velocity and ageof particle
        ///
        /// False - if particleis too old and should be killed
        /// True - otherwise
        public bool Update()
        {
            // Update particle's movementaccording to environment
            m_Velocity = m_Velocity -Environment.getInstance().Gravity
                                    +Environment.getInstance().Wind;
            // Update particle's positionaccording to movement
            m_Position = m_Position +m_Velocity;
            // Update particle's age
            m_Life++;
            // If particle if too old
            if (m_Life > MAX_LIFE)
                // Notify caller to killparticle
                return false;
            return true;
        }
        #region Accesors
        ///
        /// Read Only - Position
        ///
        public Vector Position
        {
            get { return m_Position; }
        }
        ///
        /// Read Only - Velocity
        ///
        public Vector Velocity
        {
            get { return m_Velocity; }
        }
        ///
        /// Read Only - Age of the particle
        ///
        public int Life
        {
            get { return m_Life; }
        }
        ///
        /// Read Only - Color of the particle
       ///
        public Color Color
        {
            get { return m_Color; }
        }
        #endregion Accessors
   }
}
代码非常容易,我认为需要解释的只有下面一段:
// Update particle's movementaccording to environment
m_Velocity = m_Velocity -Environment.getInstance().Gravity
                +Environment.getInstance().Wind;
  因为我们的粒子只是我们的世界里一个小的实体,它受到外部力量,如重力和风力的影响。在下一节中,我们将讨论环境问题。

环境
  我们的环境包括了所有影响粒子的外部力。这些力包括微不足道的重力和封,还可以包括温度以及任何你在乎的东西。因为我们只需要一个环境实例,所以我用单例方式实现了它:
using System; 
namespace Particles
{
   ///
   /// Summary description for Enviroment.
   ///
   public class Environment
   {
        ///
        /// Single instance of the Environment
        ///
        private static Environment m_Instance =new Environment(); 
        // Default Gravity vector in our world
        private Vector m_Gravity = Vector.Zero;
        // Default Wind vector in our world
        private Vector m_Wind = Vector.Zero; 
        ///
        /// Protected constructor
        ///
        protected Environment()
        {
        } 
        // Public accessor function to get aninstance of the Environment
        public static Environment getInstance()
        {
            return m_Instance;
        }
        ///
        /// Accessor for the Gravity Vector
        ///
        public Vector Gravity
        {
            get { return m_Gravity; }
            set { m_Gravity = value; }
        }
        ///
        /// Accessor for the Wind Vector
        ///
        public Vector Wind
        {
            get { return m_Wind; }
            set { m_Wind = value; }
        }
   }
}
应该没有东西让你不好理解

系统的抽象类
  直到现在我们值看到一个粒子。它很有趣,但只给你看一个点在屏幕上移动你可能甚至懒得尝试它。粒子系统的美只能看到当我们看到大量的粒子一起移动才能感受到。在本节中,我们将创建一个系统的基本类。它实际上是一个抽象类,将处理粒子集合,并将要求每一个类继承它来实现一个函数用来创建新的粒子。让我们来看看代码:
using System;
using System.Collections;
using System.Drawing; 
namespace Particles
{
   ///
   /// Summary description for ParticlesList.
    ///
   public abstract class ParticlesSystem
   {
        // Array to keep all the particles ofthe system
        protected ArrayList m_Particles = newArrayList();
        // Should the particles regenerate overtime
        protected bool m_Regenerate = false;
        // Central position of the system
        protected Vector m_Position;
        // Default color of a particle
        protected Color m_Color; 
        ///
        /// Generate a single particle in thesystem.
       /// This function is usedwhen particles
        /// are first created, and when theyare regenerated
        ///
        /// Newparticle
        protected abstract ParticleGenerateParticle(); 
        ///
       /// Update all the particlesin the system
        ///
        /// False - if there areno more particles in system
        /// True - otherwise
        public abstract bool Update(); 
        ///
        /// Draw all the particles in thesystem
        ///
        ///
        public virtual void Draw(Graphics g)
        {
            Pen pen;
            int intense;
            Particle part; 
            // For each particle in the system
            for (int i = 0; i
            {
                // Get the current particle
                part = this[i];
                // Calculate particle intensity
                intense = (int)((float)part.Life /PARTICLES_MAX_LIFE);
                // Generate pen for theparticle
                pen = newPen(Color.FromArgb(intense * m_Color.R ,
                         intense * m_Color.G,
                         intense * m_Color.B));
                // Draw particle
                g.DrawEllipse(pen,part.Position.X, part.Position.Y,
                  Math.Max(1,4 * part.Life /PARTICLES_MAX_LIFE),
                  Math.Max(1,4 * part.Life /PARTICLES_MAX_LIFE));
               pen.Dispose();
            }
        }
        ///
        /// Indexer allowing access to eachparticle in the system
        ///
        public Particle this[int index]
        {
            get
            {
                return (Particle)m_Particles[index];
            }
        } 
        ///
        /// Accessor to the number of particlesin the system
        ///
        public int CountParticles
        {
            get { return m_Particles.Count; }
        } 
        ///
        /// Accessor to the maximum life ofparticles in the system
        ///
        public virtual int PARTICLES_MAX_LIFE
        {
            get { return particleMaxLife; }
        }
   }
}
  这三个构造函数很容易理解。当需要创建一个新的粒子的时候,GenerateParticle()会被调用,无论是一个全新的粒子,还是一个死去的粒子我们希望代替它。Update()将用于更新系统中的粒子。Update()将需要决定是否创建新的粒子。最后Draw()用来显示粒子系统。

2个基本的粒子系统
  现在,我们已经看到我们需要实现的基本界面,我们要开始实现粒子系统了。两个基本的系统是爆炸和喷泉。我将演示它们。

爆炸
  在爆炸的时候,粒子四处飞。这很容易实现-我们把所有粒子从中心开始运动,然后随机一个方向以随机的速度飞行。重力会让它们看起来像真的。
using System; 
namespace Particles
{
   ///
   /// Summary description for Explosion.
   ///
   public class PSExplosion : ParticlesSystem
   { 
        private static readonly intDEFAULT_NUM_PARTICLES = 150;
        // Random numbers generator
        private Random m_rand = new Random();
       ///
        /// Default constructor
        ///
        public PSExplosion() :this(Vector.Zero, Color.Black)
        { } 
        ///
        /// Constructor
        ///
        ///
        public PSExplosion(Vector pos) :this(pos, Color.Black)
        { } 
        ///
        /// Constructor
        ///
        ///
        ///
        public PSExplosion(Vector pos, Colorcol)
        {
            // Set system's position at givenposition
            m_Position = pos;
            // Set system color to given color
           m_Color = col;
            // Create all the particles in thesystem
            for (int i = 0; i
            {
                // Create particle, and add itto the list of particles
                m_Particles.Add(GenerateParticle());
            }
        }  
        ///
        /// Update all the particles in thesystem
        ///
        /// False - if there areno more particles in system
        /// True - otherwise
        public override bool Update()
        {
            Particle part;
            // Get number of particles in thesystem
            int count = m_Particles.Count;
 
            // For each particle
            for (int i=0; i < count; i++)
            {
               // Get particle fromlist
                part =(Particle)m_Particles[i];
                // Update particle and checkage
                if ((!part.Update()) ||(part.Life > 150))
                {
                    // Remove old particles
                    m_Particles.RemoveAt(i);
                    // Update counter and index
                    i--;
                    count = m_Particles.Count;
                }
            }
            // If there are no more particlesin the system
            if (m_Particles.Count <= 0)
                return false;
            return true;
        } 
        ///
        /// Generate a single particle in thesystem.
        /// This function is used whenparticles
        /// are first created, and when theyare regenerated
        ///
        /// Newparticle
        protected override ParticleGenerateParticle()
        {
            // Generate random direction &speed for new particle
            float rndX = 2 *((float)m_rand.NextDouble() - 0.5f);
            float rndY = 2 *((float)m_rand.NextDouble() - 0.5f);
            float rndZ = 2 *((float)m_rand.NextDouble() - 0.5f); 
            // Create new particle at system'sstarting position
            Particle part = new Particle(m_Position,
                // With generated direction andspeed
                new Vector(rndX, rndY, rndZ),
                // And a random starting life
                m_rand.Next(50)); 
            // Return newly created particle
            return part;
        }
   }
}
  在这个粒子中,当系统创建的时候我们已经创建好了所有的粒子系统。我们把所有的粒子都放在合适的位置上,虽然一点点增加随机行能够营造一个更加真实的场景。每个新粒子存活的时间随机。这样他们就不会同时死亡。但是超过150的粒子也会死掉。我们可以选择其他的标准,比如当他们离开视野或者撞上了什么东西。

喷泉
  在这里给喷泉作为例子有2个原因。首先,喷泉会重复产生例子,以营造喷的效果。其次,并不是所有的粒子都被一次创建好,我们会在一开始创建一些粒子。然后逐步增加。
using System;
namespace Particles
{
   ///
   /// Summary description for Firework.
   ///
   public class PSFountain : ParticlesSystem
   {
        private static readonly intDEFAULT_NUM_PARTICLES = 250; 
        // Random numbers generator
        private Random m_rand = new Random(); 
        ///
        /// Default constructor
        ///
        public PSFountain() : this(Vector.Zero,Color.Black)
        { } 
        ///
        /// Constructor
        ///
        ///
        public PSFountain(Vector pos) :this(pos, Color.Black)
        { } 
        ///
        /// Constructor
        ///
        ///
        /// Colorof the particles in the system
        public PSFountain(Vector pos, Colorcol)
        {
            // Mark that this systemregenerates particles
            m_Regenerate = true;
            // Set system's position at givenposition
           m_Position = pos;
            // Set system color to given color
            m_Color = col;
            // Create ONLY 5 particles
            for (int i = 0; i < 5; i++)
            {
                // Create particle, and add itto the list of particles
               m_Particles.Add(GenerateParticle());
            }
        } 
        ///
        /// Generate a single particle in thesystem.
        /// This function is used whenparticles
        /// are first created, and when theyare regenerated
        ///
        /// Newparticle
        protected override ParticleGenerateParticle()
        {
            // Generate random direction &speed for new particle
            // In a fountain, particles movealmost straight up
            float rndX = 0.5f *((float)m_rand.NextDouble() - 0.4f);
            float rndY = -1 - 1 *(float)m_rand.NextDouble();
            float rndZ = 2 *((float)m_rand.NextDouble() - 0.4f); 
            // Create new particle at system'sstarting position
            Particle part = newParticle(m_Position,
                // With generated direction andspeed
                new Vector(rndX, rndY, rndZ),
                // And a random starting life
                m_rand.Next(50)); 
            // Return newly created particle
            return part;
        } 
        ///
        /// Update all the particles in thesystem
        ///
        /// False - if there areno more particles in system
        /// True - otherwise
        public override bool Update()
        {
            Particle part;
            // Get number of particles in thesystem
            int count = m_Particles.Count; 
            // For each particle
            for (int i=0; i < count; i++)
            {
                // Get particle from list
                part =(Particle)m_Particles[i];
                // Update particle and checkage
                if ((!part.Update()) ||(part.Life > 150))
                {
                    // Remove old particles
                    m_Particles.RemoveAt(i);
                    // Update counter and index
                    i--;
                    count = m_Particles.Count;
                }
            }
           // If there aren't enoughparticles
            if (m_Particles.Count < DEFAULT_NUM_PARTICLES)
                // Add another particles
               m_Particles.Add(GenerateParticle());
            // Always return true, since systemis regenerating
            return true;
        }
   }
}
  如你所见,爆炸类的变化非常小。当我们创建例子系统的时候创建了一些例子并且在每次更新的时候添加一些粒子。我们也用数学改变了粒子的运动状态-现在它们的运动轨迹比较直,并且有一点点靠边。

更多的系统
  创造更多的系统非常简单。其他系统的例子包括雨雪、龙卷风、冲浪、落叶、烟雾等。有无穷无尽的可能。在附件中,我包含了另一个系统——烟花。

结论
  我在附件中包含了一个简单的例子。我用单个椭圆这种非常简单的元素来进行展示。但是如果你把粒子的生命周期这个因素考虑进去,你可以得到惊人的效果。
可以用仅仅几分钟来创建新的粒子系统,并欢迎你把新作的系统发给我添加到这篇文章里

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引