基本粒子系统(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/a-basic-particles-system-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 10 分钟阅读 - 4673 个词 阅读量 0基本粒子系统(译文)
原文地址:https://www.codeproject.com/Articles/10003/A-basic-Particles-System
原文作者:Itay Sagui
译文由本站 robot-v1.0 翻译
前言
Introduction to the basic idea of the particle systems, and how to create basic effects such as explosions and water fountains.
介绍粒子系统的基本概念,以及如何创建基本效果,例如爆炸和喷泉.
介绍(Introduction)
很久以前,Particle Systems一直侵入游戏引擎,成为现实环境的基本特征和基础之一.在本文中,我将向您介绍粒子系统的基本概念,并向您展示如何创建基本效果,例如爆炸和喷泉.本文在图形方面没有涉及太多内容,并假设一旦拥有了粒子系统本身,就可以随意以任何方式显示它.(Particle Systems have long ago intruded into game engines, to become one of the basic features and foundations of a realistic environment. In this article, I will introduce you to the basic idea of the particle systems, and will show you how to create basic effects such as explosions and water fountains. This article does not cover much on the graphics side, and assume that once you have the particle system itself, you’re free to display it in whatever way pleases you.)
单颗粒(The single particle)
粒子系统实际上只是一组粒子,这些粒子被分组在一起并具有相同的一般行为.这些微粒可以是任何东西,从撞到墙壁上的汽车零件到下雨时的水滴都可以.(A particle system is actually just a group of particles that are grouped together and have the same general behavior. These particles can be anything, from parts of a car when it hits a wall, to drops of water when there’s rain.)
所有粒子都有一些共同点-位置,方向,颜色和年龄.每个粒子都在空间中保持其位置,行进的方向,自身的颜色以及存活的时间.(All particles have a couple of things in common - position, direction, color and age. Each particle keeps its location in the space, the direction where it’s going, its own color, and how long it has been alive.)
在开始查看粒子之前,我们需要一个类来保留有关位置和方向的信息.由于我们正在处理3D世界,因此简单的3D向量就足够了.您可以在附件中找到一个可以正常使用的向量类.现在我们已经足够了解(Before we start looking at the particle, we need a class to keep information about the location and direction. Since we’re dealing with a 3D world, a simple 3D-vector should be enough. You can find a fully working vector class in the attached files. It’s enough for us now to understand that a) Vector
是一个封装了三个的类(is a class that encapsulates three) float
变量,具有用于相加,相减和相乘向量的函数.(variables, with functions for adding, subtracting and multiplying vectors.)
现在让我们看一下我们的基本粒子:(Now let’s have a look at our basic particle:)
using System;
using System.Drawing;
namespace Particles
{
/// <SUMMARY>
/// Summary description for Particle.
/// </SUMMARY>
public class Particle
{
public static readonly int MAX_LIFE = 1000;
// Position of the particle
private Vector m_Position;
// Direction and speed the particle is moving
private Vector m_Velocity;
// Age of the particle
private int m_Life;
// Color of the particle
private Color m_Color
/// <SUMMARY>
/// Default constructor
/// </SUMMARY>
public Particle() : this(Vector.Zero, Vector.Zero, Color.Black, 0)
{ }
/// <SUMMARY>
/// Constructor
/// </SUMMARY>
/// <PARAM name="pos">Position
/// <SEE cref="Vector" /> of newly created particle</PARAM>
/// <PARAM name="vel">Velocity
/// <SEE cref="Vector" /> of newly created particle</PARAM>
/// <param name="col">Color of newly created particle</param>
/// <PARAM name="life">Starting age of newly created particle</PARAM>
public Particle(Vector pos, Vector vel, Color col, int life)
{
// Create particle at given position
m_Position = pos;
// Set particle's speed to given speed
m_Velocity = vel
// Set particle's color to given color
m_Color = col;
// Make sure starting age is valid
if (life < 0)
m_Life = 0;
else
m_Life = life;
}
/// <SUMMARY>
/// Update position, velocity and age of particle
/// </SUMMARY>
/// <RETURNS>False - if particle is too old and should be killed
/// True - otherwise</RETURNS>
public bool Update()
{
// Update particle's movement according to environment
m_Velocity = m_Velocity - Environment.getInstance().Gravity
+ Environment.getInstance().Wind;
// Update particle's position according 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 kill particle
return false;
return true;
}
#region Accesors
/// <SUMMARY>
/// Read Only - Position <SEE cref="Vector" /> of the particle
/// </SUMMARY>
public Vector Position
{
get { return m_Position; }
}
/// <SUMMARY>
/// Read Only - Velocity <SEE cref="Vector" /> of the particle
/// </SUMMARY>
public Vector Velocity
{
get { return m_Velocity; }
}
/// <SUMMARY>
/// Read Only - Age of the particle
/// </SUMMARY>
public int Life
{
get { return m_Life; }
}
/// <summary>
/// Read Only - Color of the particle
/// </summary>
public Color Color
{
get { return m_Color; }
}
#endregion Accessors
}
}
该代码非常不言自明,并且我认为唯一需要解释的部分是以下行:(The code is pretty self-explanatory, and I believe that the only part that needs explanation is the following line:)
// Update particle's movement according to environment
m_Velocity = m_Velocity - Environment.getInstance().Gravity
+ Environment.getInstance().Wind;
自从我们(Since our) Particle
只是我们世界中的一个小实体,它受到重力和风等外部力量的影响.在下一节中,我们将介绍环境.(is just a small entity in our world, it is affected by outside forces such as gravity and wind. In the next section, we’ll cover the Environment.)
环境(The Environment)
using System;
namespace Particles
{
/// <SUMMARY>
/// Summary description for Enviroment.
/// </SUMMARY>
public class Environment
{
/// <SUMMARY>
/// Single instance of the Environment
/// </SUMMARY>
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;
/// <SUMMARY>
/// Protected constructor
/// </SUMMARY>
protected Environment()
{
}
// Public accessor function to get an instance of the Environment
public static Environment getInstance()
{
return m_Instance;
}
/// <SUMMARY>
/// Accessor for the Gravity Vector
/// </SUMMARY>
public Vector Gravity
{
get { return m_Gravity; }
set { m_Gravity = value; }
}
/// <SUMMARY>
/// Accessor for the Wind Vector
/// </SUMMARY>
public Vector Wind
{
get { return m_Wind; }
set { m_Wind = value; }
}
}
}
这里没有什么可以让您扬眉吐气的.(Nothing here should make you even raise an eye-brow.)
系统抽象类(The System Abstract Class)
到目前为止,我们仅看到单个粒子.观看屏幕上的单个点的移动可能会带来很多乐趣,即使您不愿意尝试,也不是真正的嗡嗡声.只有当我们有大量的粒子一起运动时,才能看到粒子系统的美丽.在本节中,我们将为系统创建基本类.该类实际上是一个抽象类,它将处理粒子列表,并要求从其继承的每个类实现创建新粒子的功能以及更新这些粒子的功能.让我们看一下代码:(Until now we’ve seen only single particles. As much fun as it might have been for you to watch a single dot move around on the screen, if you even bothered to try it, it’s no real buzz. The beauty of particle systems can only be seen when we have large numbers of particles moving together. In this section, we will create the basic class for a system. This class, which is actually an abstract class, will handle the list of particles, and will require each class that inherit from it to implement a function to create new particles, and a function to update those particles. Let’s have a look at the code:)
using System;
using System.Collections;
using System.Drawing;
namespace Particles
{
/// <SUMMARY>
/// Summary description for ParticlesList.
/// </SUMMARY>
public abstract class ParticlesSystem
{
// Array to keep all the particles of the system
protected ArrayList m_Particles = new ArrayList();
// Should the particles regenerate over time
protected bool m_Regenerate = false;
// Central position of the system
protected Vector m_Position;
// Default color of a particle
protected Color m_Color;
/// <SUMMARY>
/// Generate a single particle in the system.
/// This function is used when particles
/// are first created, and when they are regenerated
/// </SUMMARY>
/// <RETURNS>New particle</RETURNS>
protected abstract Particle GenerateParticle();
/// <SUMMARY>
/// Update all the particles in the system
/// </SUMMARY>
/// <RETURNS>False - if there are no more particles in system
/// True - otherwise</RETURNS>
public abstract bool Update();
/// <SUMMARY>
/// Draw all the particles in the system
/// </SUMMARY>
/// <PARAM name="g">Graphics object to be painted on</PARAM>
public virtual void Draw(Graphics g)
{
Pen pen;
int intense;
Particle part;
// For each particle in the system
for (int i = 0; i < m_Particles.Count; i++)
{
// Get the current particle
part = this[i];
// Calculate particle intensity
intense = (int)((float)part.Life / PARTICLES_MAX_LIFE);
// Generate pen for the particle
pen = new Pen(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();
}
}
/// <SUMMARY>
/// Indexer allowing access to each particle in the system
/// </SUMMARY>
public Particle this[int index]
{
get
{
return (Particle)m_Particles[index];
}
}
/// <SUMMARY>
/// Accessor to the number of particles in the system
/// </SUMMARY>
public int CountParticles
{
get { return m_Particles.Count; }
}
/// <SUMMARY>
/// Accessor to the maximum life of particles in the system
/// </SUMMARY>
public virtual int PARTICLES_MAX_LIFE
{
get { return particleMaxLife; }
}
}
}
这三个构造函数很容易理解.的(The three constructors are easy to understand. The) GenerateParticle()
当创建新粒子时,无论是全新粒子还是当粒子死亡并且我们希望将其替换为新粒子时,都会使用该函数.的(function will be used when a new particle is created, whether it’s a completely new particle, or when a particle dies and we wish to replace it with a new one. The) Update()
将用于更新系统中的粒子.(will be used to update the particles in the system.) Update()
将需要决定是否以及何时创建新粒子.最后,(will need to decide if and when to create new particles. And last,) Draw()
将用于显示给定的粒子系统(will be used to display the particle system on a given) Graphics
目的.(object.)
2个基本粒子系统(2 basic particle systems)
现在我们已经了解了需要实现的基本接口,我们需要开始实现粒子系统.两个更基本的系统是爆炸和喷泉.我会在这里示范.(Now that we’ve seen the basic interface that we need to implement, we need to start implementing particle systems. Two of the more basic systems are an explosion and a fountain. I’ll demonstrate them here.)
爆炸(Explosion)
在爆炸中,粒子正飞到各处.这非常容易实现-我们只需将所有粒子设置为从系统的中心开始,然后以随机速度向随机方向移动.重力会照顾其他一切.(In an explosion, particles just fly everywhere. This is quite easy to implement - we just set all the particles to start at the center of the system, and move to a random direction, with a random speed. Gravity will take care of everything else.)
using System;
namespace Particles
{
/// <SUMMARY>
/// Summary description for Explosion.
/// </SUMMARY>
public class PSExplosion : ParticlesSystem
{
private static readonly int DEFAULT_NUM_PARTICLES = 150;
// Random numbers generator
private Random m_rand = new Random();
/// <SUMMARY>
/// Default constructor
/// </SUMMARY>
public PSExplosion() : this(Vector.Zero, Color.Black)
{ }
/// <SUMMARY>
/// Constructor
/// </SUMMARY>
/// <PARAM name="pos">Starting position of system</PARAM>
public PSExplosion(Vector pos) : this(pos, Color.Black)
{ }
/// <SUMMARY>
/// Constructor
/// </SUMMARY>
/// <PARAM name="pos">Starting position of system</PARAM>
/// <PARAM name="col">Color of the particles in the system</PARAM>
public PSExplosion(Vector pos, Color col)
{
// Set system's position at given position
m_Position = pos;
// Set system color to given color
m_Color = col;
// Create all the particles in the system
for (int i = 0; i < DEFAULT_NUM_PARTICLES; i++)
{
// Create particle, and add it to the list of particles
m_Particles.Add(GenerateParticle());
}
}
/// <SUMMARY>
/// Update all the particles in the system
/// </SUMMARY>
/// <RETURNS>False - if there are no more particles in system
/// True - otherwise</RETURNS>
public override bool Update()
{
Particle part;
// Get number of particles in the system
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 check age
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 particles in the system
if (m_Particles.Count <= 0)
return false;
return true;
}
/// <SUMMARY>
/// Generate a single particle in the system.
/// This function is used when particles
/// are first created, and when they are regenerated
/// </SUMMARY>
/// <RETURNS>New particle</RETURNS>
protected override Particle GenerateParticle()
{
// 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's starting position
Particle part = new Particle(m_Position,
// With generated direction and speed
new Vector(rndX, rndY, rndZ),
// And a random starting life
m_rand.Next(50));
// Return newly created particle
return part;
}
}
}
在此示例中,我们在创建系统时创建了所有粒子.我们已经将它们完全放在系统的起点,尽管为了更逼真的外观,我们可能还会在其中添加一些随机性.每个新粒子都有一个随机的年龄-这样,粒子不会同时全部死亡.我们还决定杀死大于150的粒子.我们可以选择其他条件,例如仅在它们离开显示视图或撞到某些物体时杀死它们.(In this example, we’ve created all the particles when the system was created. We’ve placed them all at exactly the starting point of the system, although for a more realistic look, we might have added a little bit of randomness there too. Each new particle is given a random age - this way the particles don’t die all at the same time. We’ve also decided to kill particles that are older than 150. We could have chosen another criteria, such as to kill particles only when they leave the display view, or they bumped into something.)
喷泉(Fountain)
由于两个原因,此处给出了喷泉示例.首先,喷泉再生死掉的颗粒,以便继续"喷泉"或其他任何喷泉工作.其次,并非一次创建所有粒子-我们首先创建几个粒子,随着时间的流逝,我们向系统中添加了越来越多的粒子.(The fountain example is given here due to two reasons. First, the fountain regenerates particles that die, in order to continue “fountaining” or whatever else fountains do. Secondly, not all the particles are created at once - we first create a few particles, and as time goes on, we add more and more particles to the system.)
using System;
namespace Particles
{
/// <SUMMARY>
/// Summary description for Firework.
/// </SUMMARY>
public class PSFountain : ParticlesSystem
{
private static readonly int DEFAULT_NUM_PARTICLES = 250;
// Random numbers generator
private Random m_rand = new Random();
/// <SUMMARY>
/// Default constructor
/// </SUMMARY>
public PSFountain() : this(Vector.Zero, Color.Black)
{ }
/// <SUMMARY>
/// Constructor
/// </SUMMARY>
/// <PARAM name="pos">Starting position of system</PARAM>
public PSFountain(Vector pos) : this(pos, Color.Black)
{ }
/// <SUMMARY>
/// Constructor
/// </SUMMARY>
/// <PARAM name="pos">Starting position of system</PARAM>
/// <PARAM name="col">Color of the particles in the system</PARAM>
public PSFountain(Vector pos, Color col)
{
// Mark that this system regenerates particles
m_Regenerate = true;
// Set system's position at given position
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 it to the list of particles
m_Particles.Add(GenerateParticle());
}
}
/// <SUMMARY>
/// Generate a single particle in the system.
/// This function is used when particles
/// are first created, and when they are regenerated
/// </SUMMARY>
/// <RETURNS>New particle</RETURNS>
protected override Particle GenerateParticle()
{
// Generate random direction & speed for new particle
// In a fountain, particles move almost 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's starting position
Particle part = new Particle(m_Position,
// With generated direction and speed
new Vector(rndX, rndY, rndZ),
// And a random starting life
m_rand.Next(50));
// Return newly created particle
return part;
}
/// <SUMMARY>
/// Update all the particles in the system
/// </SUMMARY>
/// <RETURNS>False - if there are no more particles in system
/// True - otherwise</RETURNS>
public override bool Update()
{
Particle part;
// Get number of particles in the system
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 check age
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 enough particles
if (m_Particles.Count < DEFAULT_NUM_PARTICLES)
// Add another particles
m_Particles.Add(GenerateParticle());
// Always return true, since system is regenerating
return true;
}
}
}
如您所见,Explosion类的更改很小.在这里,我们在创建系统时仅创建了几个粒子,并且在每次系统更新时都添加了一个新粒子.我们还改变了粒子运动的数学原理-现在它们几乎笔直向上移动,而向侧面移动一点.(As you can see, the changes from the Explosion class are quite minor. Here we’ve created only a few particles when the system is created, and add a new particle every time the system is updated. We’ve also changed a bit the math for the movement of the particles - now they move almost straight up, and just a bit to the sides.)
更多系统(More systems)
创建更多系统非常简单.其他系统的示例包括雨雪,龙卷风,冲水,落叶,烟雾等等.选项是无止境的.在随附的演示中,我包括了另一个系统-烟火.(Creating more systems is quite simple. Examples of other systems include rain and snow, tornados, water flushing, falling leaves, smoke and more. The options are endless. In the attached demo, I’ve included another system - a firework.)
结论(Conclusion)
我在附件中包含了所描述系统的一个简单示例.我使用的显示非常简单-每个粒子都有一个椭圆.但是,如果考虑到每个粒子的年龄,只需更改粒子的大小和透明度,就可以得出惊人的效果.(I’ve included in the attached files a simple example of the described systems. The display I’ve used is very simple - single ellipse for each particle. But if you take into account each particle’s age, you can come up with amazing effects just by changing the particles' size and transparency.) 使用描述的模型,只需几分钟即可创建新系统,非常欢迎您发送给我自己的系统以添加到本文中.(Creating new systems can be done in just minutes using the described model, and you are more than welcome to send me your own systems to add to this article.)
历史(History)
- 3(3)rd(rd)2005年4月(April 2005)-演示项目已更新(感谢Mark Treadwell).(- Demo project updated (Thanks to Mark Treadwell).)
- 9(9)日(th)2005年4月(April 2005)-修复了(- Fixed a bug in the)
Vector
类.为粒子增加颜色.包含一个(class. Added color to the particles. Included a)Draw()
在功能(function in the)ParticlesSystem
抽象类.在附加项目中添加了烟花系统.(abstract class. Added firework system to attached project.) - 13(13)rd(rd)2005年4月(April 2005)-修复了性能问题(感谢John Fisher).(- Performance issue fixed (Thanks to John Fisher).)
- 19(19)日(th)2005年4月(April 2005)-Junai建议改善构造函数.(- Constructors improvement suggested by Junai.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# Win2K WinXP Windows .NET1.1 Visual-Studio GDI GDI+ VS.NET2003 Dev 新闻 翻译