Ooops
November 28, 2020, 10:53:26 AM

Author Topic: [bmx] Particles! by matibee [ 1+ years ago ]  (Read 692 times)

Offline BlitzBot

  • Jr. Member
  • **
  • Posts: 1
[bmx] Particles! by matibee [ 1+ years ago ]
« on: June 29, 2017, 12:28:41 AM »
Title : Particles!
Author : matibee
Posted : 1+ years ago

Description : A simple 2d particle system complete with a useful (though a little cludged together) editor written in MaxGUI.  There's a collection of particle parameters such as alpha,red,green,blue,rotation,scale & weight, and 'factor' values that adjusts each parameter over time.

eg.  alpha = 1 and alphafactor -1 will fade alpha from 1 to 0 over one second.
alpha = 1 and alphafactor = -0.5 will fade it over two seconds.

Particles are tied to an emitter, which releases particles in 'Bursts'  [doBurst(x,y)].  Emitter parameters include random velocity range and number of particles per burst.  To maintain consistant results over different frame rates the bursts are limited to 20 per second.  So the editor results (timed to run at 20fps) will be the same as your game results no matter how often you call DoBurst.  If you want to override the 20fps limit (useful if you're interpolating the emitter position during one frame), it's as simple as DoBurst(x,y,False).

Screenshot:  www.matibee.co.uk/temp/particles.jpg

(I'm not sure how to use the code arc's, so this is an experiment!)...

The editor code..
Code: [Select]
SuperStrict
Import pub.freeprocess
Import MaxGui.Drivers
Include "Particles.bmx"

'==================================================================================================================
'
' Init controls
'
Global window:TGadget = CreateWindow:TGadget("Particle Editor",60,60,1050,640,Null,WINDOW_TITLEBAR| WINDOW_CLIENTCOORDS )

Local f$[] = [ "Point wieght", "Point scale", "Scale factor", "Rotation factor", ..
"Velocity Min", "Velocity Max", "Red", "Red Factor", "Green", "Green factor", ..
"Blue", "Blue factor", "Alpha", "Alpha factor", "Burst count", ..
"X Handle", "Y Handle", "Image file"   ]

Local d$[] = [ "1.0", "1.0", "-0.2", "720.0", ..
"1.0", "2.0", "1.0", "0.0", "1.0", "0.0", ..
"1.0", "0.0", "1.0", "-1.0", "20", ..
"2", "2", "" ]

Global labeln:TGadget[22]
Global textLn:TGadget[22]

For Local n:Int = 0 To Len( f ) - 1
labeln[n] = CreateLabel:TGadget( f[n],12,20 + n * 24,90,13,window:TGadget,Null)
textLn[n] = CreateTextField:TGadget(110,17 + n * 24,90,22,window:TGadget,Null)
textLn[n].SetText( d$[n] )
Next

Global loadImageButton:TGadget = CreateButton( "..", 204, 424, 30, 24, window:TGadget )
Global resetButton:TGadget = CreateButton( "Reset / Build", 110, 460, 90, 24, window:TGadget )

Global lab1:TGadget = CreateLabel( "Emitter file..", 8, 490, 60, 20, window:TGadget )
Global openButton:TGadget = CreateButton( "Open", 8, 510, 80, 24, window:TGadget )
Global saveButton:TGadget = CreateButton( "Save", 8, 540, 80, 24, window:TGadget )
Global saveasButton:TGadget = CreateButton( "Save As", 8, 570, 80, 24, window:TGadget )

Global loadBackgroundButton:TGadget = CreateButton( "Load background image", 10, 608, 190, 20, window:TGadget )
Global gameCanvas:TGadget = CreateCanvas( 240, 20, 800, 600, window:TGadget )


Global g_emitterFile$
Global gImage:TImage
Global gBackgroundImage:TImage
Global g_ImageFile$
CreateTimer( 20 )
Global emitMode:Int = False


Global mx:Int ' mouse position
Global my:Int

Global emitter:ParticleEmitter

Function buildEmitter()

emitter = New ParticleEmitter.Create()

emitter.m_fPointWeight = textLn[0].GetText().ToFloat()
emitter.m_baseParticle.m_fScale = textLn[1].GetText().ToFloat()
emitter.m_fScaleFactor = textLn[2].GetText().ToFloat()
emitter.m_fRotationFactor   = textLn[3].GetText().ToFloat()
emitter.m_fVelocityMin = textLn[4].GetText().ToFloat()
emitter.m_fVelocityMax = textLn[5].GetText().ToFloat()
emitter.m_baseParticle.m_fRed = textLn[6].GetText().ToFloat()
emitter.m_fRedFactor = textLn[7].GetText().ToFloat()
emitter.m_baseParticle.m_fBlue = textLn[8].GetText().ToFloat()
emitter.m_fBlueFactor = textLn[9].GetText().ToFloat()
emitter.m_baseParticle.m_fGreen = textLn[10].GetText().ToFloat()
emitter.m_fGreenFactor = textLn[11].GetText().ToFloat()
emitter.m_baseParticle.m_fAlpha = textLn[12].GetText().ToFloat()
emitter.m_fAlphaFactor = textLn[13].GetText().ToFloat()
emitter.m_iParticlesPerBurst = textLn[14].GetText().ToInt()
emitter.m_iXHandle = textLn[15].GetText().ToInt()
emitter.m_iYHandle = textLn[16].GetText().ToInt()

Local im:TImage = LoadImage( g_ImageFile$, FILTEREDIMAGE )
If ( im ) gImage = im

If ( gImage )
emitter.m_Image = gImage
SetImageHandle( gImage, emitter.m_iXHandle, emitter.m_iYHandle )
End If

End Function  

Function UpdateParams()
textLn[17].SetText( g_ImageFile$ )

If ( Not emitter ) Return

textLn[0].SetText( emitter.m_fPointWeight )
textLn[1].SetText( emitter.m_baseParticle.m_fScale )
textLn[2].SetText( emitter.m_fScaleFactor  )
textLn[3].SetText( emitter.m_fRotationFactor )
textLn[4].SetText( emitter.m_fVelocityMin )
textLn[5].SetText( emitter.m_fVelocityMax )
textLn[6].SetText( emitter.m_baseParticle.m_fRed )
textLn[7].SetText( emitter.m_fRedFactor )
textLn[8].SetText( emitter.m_baseParticle.m_fBlue )
textLn[9].SetText( emitter.m_fBlueFactor )
textLn[10].SetText( emitter.m_baseParticle.m_fGreen )
textLn[11].SetText( emitter.m_fGreenFactor )
textLn[12].SetText( emitter.m_baseParticle.m_fAlpha )
textLn[13].SetText( emitter.m_fAlphaFactor )
textLn[14].SetText( emitter.m_iParticlesPerBurst )
textLn[15].SetText( emitter.m_iXHandle )
textLn[16].SetText( emitter.m_iYHandle )
End Function




Function doSaveAs()
Local fn$ = RequestFile( "", "Text Files:txt", True )
If ( Len ( fn$ ) )
g_emitterFile$ = fn$
doSave()
End If
End Function

Function doSave()
If ( emitter )
If ( Len( g_emitterFile$ ) = 0 )
doSaveAs()
Else
Local fs:TStream = WriteStream( g_emitterFile$ )
If ( fs )
WriteLine( fs, "point_weight=" + emitter.m_fPointWeight )
WriteLine( fs, "point_scale=" + emitter.m_baseParticle.m_fScale )
WriteLine( fs, "point_scalefactor=" + emitter.m_fScaleFactor )
WriteLine( fs, "point_rotationfactor=" + emitter.m_fRotationFactor )
WriteLine( fs, "point_velocity_min=" + emitter.m_fVelocityMin )
WriteLine( fs, "point_velocity_max=" + emitter.m_fVelocityMax )
WriteLine( fs, "point_red=" + emitter.m_baseParticle.m_fRed )
WriteLine( fs, "point_redfactor=" + emitter.m_fRedFactor )
WriteLine( fs, "point_green=" + emitter.m_baseParticle.m_fGreen )
WriteLine( fs, "point_greenfactor=" + emitter.m_fGreenFactor )
WriteLine( fs, "point_blue=" + emitter.m_baseParticle.m_fBlue )
WriteLine( fs, "point_bluefactor=" + emitter.m_fBlueFactor )
WriteLine( fs, "point_alpha=" + emitter.m_baseParticle.m_fAlpha )
WriteLine( fs, "point_alphafactor=" + emitter.m_fAlphaFactor )
WriteLine( fs, "emitter_particles_per_burst=" + emitter.m_iParticlesPerBurst )
WriteLine( fs, "xhandle=" + emitter.m_iXHandle )
WriteLine( fs, "yhandle=" + emitter.m_iYHandle )
WriteLine( fs, "image=" + g_ImageFile$ )
CloseStream( fs )
Else
Notify( "Cannot write file: " + g_emitterFile$ )
End If
End If
Else
buildEmitter()
doSave()
End If
End Function

'==================================================================================================================
'
' Application loop
'
Repeat
WaitEvent()
Select EventID()
Case EVENT_APPTERMINATE
End
Case EVENT_WINDOWCLOSE
Select EventSource()
Case window
End
End Select
Case EVENT_GADGETACTION
Select EventSource()
Case saveasButton
doSaveAs()
Case resetButton
emitter = Null
buildEmitter()
Case saveButton
doSave()
Case loadImageButton
Local fn$ = RequestFile( "" )
If ( Len(fn$) )
gImage = LoadImage( fn$ )
If ( gImage And emitter )
emitter.m_Image = gImage
SetImageHandle( gImage, emitter.m_iXHandle, emitter.m_iYHandle )
End If
g_ImageFile$ = Right( fn$, Len( fn$ ) - fn$.FindLast( "" ) - 1 )
UpdateParams()
End If
Case openButton
Local fn$ = RequestFile( "", "Text Files:txt" )
If ( Len ( fn$ ) )
g_emitterFile$ = fn$
emitter = ParticleEmitter.Create( fn$, True )
Local fs:TStream = ReadFile( fn$ )
If ( fs )
While ( Not Eof( fs ) )
Local a$ = ReadLine( fs )
If Left( a$, 5 ) = "image"
g_ImageFile$ = Right( a$, Len( a$ ) - a$.FindLast( "=" ) - 1 )
End If
Wend
CloseStream( fs )
End If
UpdateParams()
buildEmitter()
End If
Case loadBackgroundButton
Local fn$ = RequestFile( "" )
If ( Len(fn$) )
gBackgroundImage = LoadImage( fn$ )
End If
End Select
Case EVENT_GADGETSELECT
Select EventSource()
End Select
Case EVENT_GADGETPAINT
         SetGraphics CanvasGraphics( gameCanvas )
Cls
SetColor( 255,255,255)
SetBlend( SOLIDBLEND )
SetAlpha( 1.0 )
SetScale( 1.0, 1.0 )
SetRotation( 0.0 )
If ( gBackgroundImage ) DrawImage( gBackgroundImage, 0, 0 )
SetBlend ( LIGHTBLEND )
If emitter emitter.Draw()
Flip
Case EVENT_KEYCHAR
If ( emitter ) emitter.DoBurst( mx, my ) ' press a key for a single burst
Case EVENT_MOUSEMOVE
mx = EventX()
my = EventY()
Case EVENT_MOUSEDOWN
If ( Not emitter ) buildEmitter()
If ( Not gImage )
Notify( "No image loaded" )
Else
emitMode = True ' hold the mouse down for continual burst
End If
Case EVENT_MOUSEUP
emitMode = False
Case EVENT_MOUSEENTER
Select EventSource()
Case gameCanvas
ActivateGadget( gameCanvas )
End Select
Case EVENT_TIMERTICK
If ( emitMode And emitter ) emitter.DoBurst( mx, my )
If ( emitter ) emitter.Update( 0.05 )
Local e:TEvent = New TEvent
e.source = gameCanvas
e.id = EVENT_GADGETPAINT
PostEvent( e )
Case EVENT_KEYUP
Select EventData()
Case KEY_DELETE
emitter = Null
Case KEY_SPACE
End Select
End Select
Forever



The sample code..
Code: [Select]
Graphics 800, 600

Include "Particles.bmx"

Local emitter:ParticleEmitter = ParticleEmitter.Create( "fire.txt" )

Local lastTime:Int

While Not AppTerminate()

Local timeNow:Int = MilliSecs()
emitter.Update( Float(timeNow - lastTime) / 1000.0 )
lastTime = timeNow
emitter.DoBurst( MouseX(), MouseY() )

Cls
SetRotation 0
SetScale 1,1
SetAlpha 1
SetColor 255,255,255
DrawText "Particle demo", 0, 0
SetBlend LIGHTBLEND
emitter.Draw()
Flip

Wend


The 'fire.txt' emitter used in the sample
Code: [Select]
point_weight=1.00000000
point_scale=1.00000000
point_scalefactor=2.00000000
point_rotationfactor=720.000000
point_velocity_min=-100.000000
point_velocity_max=100.000000
point_red=1.00000000
point_redfactor=1.00000000
point_green=1.00000000
point_greenfactor=-1.00000000
point_blue=0.000000000
point_bluefactor=0.000000000
point_alpha=0.500000000
point_alphafactor=-0.500000000
emitter_particles_per_burst=20
xhandle=8
yhandle=8
image=Particle1.tga


some other emitters
Code: [Select]
point_weight=10.0000000
point_scale=0.200000003
point_scalefactor=1.60000002
point_rotationfactor=600.000000
point_velocity_min=-350.000000
point_velocity_max=350.000000
point_red=1.00000000
point_redfactor=0.000000000
point_green=0.000000000
point_greenfactor=1.00000000
point_blue=0.000000000
point_bluefactor=1.00000000
point_alpha=1.00000000
point_alphafactor=-1.00000000
emitter_particles_per_burst=40
xhandle=8
yhandle=8
image=Particle1.tga

Code: [Select]
point_weight=40.0000000
point_scale=1.00000000
point_scalefactor=-0.200000003
point_rotationfactor=0.000000000
point_velocity_min=1.00000000
point_velocity_max=80.0000000
point_red=1.00000000
point_redfactor=0.000000000
point_green=0.000000000
point_greenfactor=1.00000000
point_blue=1.00000000
point_bluefactor=0.000000000
point_alpha=1.00000000
point_alphafactor=-1.00000000
emitter_particles_per_burst=2
xhandle=8
yhandle=8
image=Particle1.tga

Code: [Select]
point_weight=10.0000000
point_scale=1.00000000
point_scalefactor=-0.200000003
point_rotationfactor=720.000000
point_velocity_min=-1.000000000
point_velocity_max=30.0000000
point_red=1.00000000
point_redfactor=0.000000000
point_green=0.500000000
point_greenfactor=-0.500000000
point_blue=0.000000000
point_bluefactor=0.800000012
point_alpha=1.00000000
point_alphafactor=-0.600000024
emitter_particles_per_burst=2
xhandle=-2
yhandle=-2
image=Particle1.tga


My .tga file can be found here
 www.matibee.co.uk/temp/Particle1.tga

It's not the most thorough particle system in the world, but along with the editor, it might serve as a useful learning aid or start point to improve upon.

Cheers
Matt [/i]

Code :
Code: BlitzMax
  1. Const MAX_PARTICLES_PER_EMITTER%        = 600
  2.  
  3.  
  4. Type Particle
  5.         Field m_fAge:Float
  6.         Field m_fScale:Float
  7.         Field m_fRotation:Float
  8.         Field m_fVelocityX:Float
  9.         Field m_fVelocityY:Float
  10.         Field m_fRed:Float
  11.         Field m_fBlue:Float
  12.         Field m_fGreen:Float
  13.         Field m_fAlpha:Float
  14.         Field m_fX:Float
  15.         Field m_fY:Float
  16. End Type
  17.  
  18.  
  19. Type ParticleEmitter
  20.  
  21.         Field m_freeParticles:TList
  22.         Field m_liveParticles:TList
  23.        
  24.         Field m_baseParticle:Particle
  25.        
  26.         Field m_fPointWeight:Float = 1.0
  27.         Field m_fScaleFactor:Float = -0.2
  28.         Field m_fRotationFactor:Float = 720.0
  29.         Field m_fVelocityMin:Float = 9.0
  30.         Field m_fVelocityMax:Float = 100.0
  31.         Field m_fRedFactor:Float = 0.0
  32.         Field m_fBlueFactor:Float = 0.0
  33.         Field m_fGreenFactor:Float = 0.0
  34.         Field m_fAlphaFactor:Float = -1.0
  35.        
  36.         Field m_iParticlesPerBurst:Int = 20
  37.        
  38.         Field m_Image:TImage
  39.         Field m_iXHandle:Int, m_iYHandle:Int
  40.        
  41.         Field m_lastBurstTime:Int
  42.        
  43.         Function Create:ParticleEmitter( strUrl:String = "", IgnoreImage:Int = False )
  44.                 Local emitter:ParticleEmitter = New ParticleEmitter
  45.                 emitter.m_freeParticles = New TList
  46.                 emitter.m_liveParticles = New TList
  47.                 emitter.m_baseParticle = New Particle
  48.                 For Local t:Int = 0 To MAX_PARTICLES_PER_EMITTER - 1
  49.                         emitter.m_freeParticles.AddLast( New Particle )
  50.                 Next
  51.                 If ( Len (strUrl) )
  52.                         Local inFile:TStream = ReadFile( strUrl )
  53.                         While ( inFile And Not Eof( inFile ) )
  54.                                 'Debugstop
  55.                                 Local strLine:String = ReadLine( inFile )
  56.                                 If ( Len(strLine) > 0 And Left( strline, 1 ) <> "'" )
  57.                                         Local key:String = Left( strLine, Instr( strLine, "=" ) - 1 )
  58.                                         Local value:String = Right( strLine, Len( strLine ) - Instr( strLine, "=" ) )
  59.                                         If ( key = "point_weight" )
  60.                                                 emitter.m_fPointWeight = value.ToFloat()
  61.                                         Else If ( key = "point_scalefactor" )
  62.                                                 emitter.m_fScaleFactor = value.ToFloat()
  63.                                         Else If ( key = "point_scale" )
  64.                                                 emitter.m_baseParticle.m_fScale = value.ToFloat()
  65.                                         Else If ( key = "point_rotationfactor" )
  66.                                                 emitter.m_fRotationFactor = value.ToFloat()
  67.                                         Else If ( key = "point_velocity_min" )
  68.                                                 emitter.m_fVelocityMin = value.ToFloat()
  69.                                         Else If ( key = "point_velocity_max" )
  70.                                                 emitter.m_fVelocityMax = value.ToFloat()
  71.                                         Else If ( key = "point_redfactor" )
  72.                                                 emitter.m_fRedFactor = value.ToFloat()
  73.                                         Else If ( key = "point_red" )
  74.                                                 emitter.m_baseParticle.m_fRed = value.ToFloat()
  75.                                         Else If ( key = "point_greenfactor" )
  76.                                                 emitter.m_fGreenFactor = value.ToFloat()
  77.                                         Else If ( key = "point_green" )
  78.                                                 emitter.m_baseParticle.m_fGreen = value.ToFloat()
  79.                                         Else If ( key = "point_bluefactor" )
  80.                                                 emitter.m_fBlueFactor = value.ToFloat()
  81.                                         Else If ( key = "point_blue" )
  82.                                                 emitter.m_baseParticle.m_fBlue = value.ToFloat()
  83.                                         Else If ( key = "point_alphafactor" )
  84.                                                 emitter.m_fAlphaFactor = value.ToFloat()
  85.                                         Else If ( key = "point_alpha" )
  86.                                                 emitter.m_baseParticle.m_fAlpha = value.ToFloat()
  87.                                         Else If ( key = "emitter_particles_per_burst" )
  88.                                                 emitter.m_iParticlesPerBurst = value.ToInt()
  89.                                         Else If ( key = "image" And Not IgnoreImage )
  90.                                                 emitter.m_Image = LoadImage( value, FILTEREDIMAGE )
  91.                                         Else If ( key = "xhandle" )
  92.                                                 emitter.m_iXHandle = value.ToInt()
  93.                                         Else If ( key = "yhandle" )
  94.                                                 emitter.m_iYHandle = value.ToInt()
  95.                                         End If                         
  96.                                 End If
  97.                         End While
  98.                         CloseFile ( inFile )
  99.                         If emitter.m_Image SetImageHandle( emitter.m_Image, emitter.m_iXHandle, emitter.m_iYHandle )
  100.                 End If         
  101.                 Return emitter
  102.         End Function
  103.        
  104.         Method Draw()
  105.                 If ( Not m_Image ) Return
  106.                 For Local p:Particle = EachIn m_liveParticles
  107.                         SetScale p.m_fScale, p.m_fScale
  108.                         SetRotation p.m_fRotation
  109.                         SetAlpha p.m_fAlpha
  110.                         SetColor( p.m_fRed * 255.0, p.m_fGreen * 255.0, p.m_fBlue * 255.0 )
  111.                         DrawImage( m_Image, p.m_fX, p.m_fY )
  112.                 Next
  113.         End Method
  114.        
  115.         Method Update( fTime:Float )
  116.                 For Local p:Particle = EachIn m_liveParticles
  117.                         p.m_fAlpha :+ m_fAlphaFactor * fTime
  118.                         p.m_fScale :+ m_fScaleFactor * fTime
  119.                         If ( p.m_fAlpha <= 0 Or p.m_fScale <= 0 )
  120.                                 m_freeParticles.AddLast( p )
  121.                                 m_liveParticles.Remove( p )
  122.                         Else
  123.                                 p.m_fAge :+ fTime
  124.                                 p.m_fRotation :+ m_fRotationFactor * fTime
  125.                                 p.m_fX :+ p.m_fVelocityX * fTime
  126.                                 p.m_fY :+ p.m_fVelocityY * fTime
  127.                                 p.m_fVelocityX :- m_fPointWeight * fTime
  128.                                 p.m_fVelocityY :- m_fPointWeight * fTime
  129.                                 p.m_fY :+ m_fPointWeight * (p.m_fAge * p.m_fAge)
  130.                                 p.m_fRed :+ m_fRedFactor * fTime
  131.                                 p.m_fBlue :+ m_fBlueFactor * fTime
  132.                                 p.m_fGreen :+ m_fGreenFactor * fTime
  133.                         End If
  134.                 Next
  135.                 Assert ( m_freeParticles.Count() + m_liveParticles.Count() = MAX_PARTICLES_PER_EMITTER ) 'ensure particles don't exist in both lists!!
  136.         End Method
  137.        
  138.         Method DoBurst( iX:Int, iY:Int, fpsSync:Int = True )
  139.                 If ( fpsSync )
  140.                         Local timeNow:Int = MilliSecs()
  141.                         If ( timeNow - m_lastBurstTime ) >= 20
  142.                                 m_lastBurstTime = timeNow
  143.                         Else
  144.                                 Return
  145.                         End If
  146.                 EndIf
  147.                
  148.                 Local count:Int = m_iParticlesPerBurst
  149.                 For Local p:Particle = EachIn m_freeParticles
  150.                         MemCopy( p, m_baseParticle, SizeOf( Particle ) )
  151.                         p.m_fVelocityX = m_fVelocityMin + ( RndFloat() * ( m_fVelocityMax - m_fVelocityMin ) )
  152.                         p.m_fVelocityY = m_fVelocityMin + ( RndFloat() * ( m_fVelocityMax - m_fVelocityMin ) )
  153.                         p.m_fX = iX
  154.                         p.m_fY = iY
  155.                         m_liveParticles.AddLast( p )
  156.                         m_freeParticles.Remove( p )
  157.                         count :- 1
  158.                         If ( count <= 0 )
  159.                                 Return
  160.                         End If
  161.                 Next
  162.         End Method
  163.  
  164. End Type


Comments :


pmc(Posted 1+ years ago)

 This is great. I'm having some good fun playing with it. Thank you.


christian223(Posted 1+ years ago)

 Really nice!. I have been playing with it and I really like it, I'm going to use it in my future games, I hopefully. I do not have MaxGui though, could you make the editor available in other ways?.


matibee(Posted 1+ years ago)

 Sure. The full download is available here...<a href="http://www.matibee.co.uk/wpsite/?p=15" target="_blank">http://www.matibee.co.uk/wpsite/?p=15[/url]


hub(Posted 1+ years ago)

 Very nice code.Could you explain more matibee what's the ftime parameter into the update function ? Thanks !


matibee(Posted 1+ years ago)

 The fTime parameter is a floating point delta time of your frame rate.  ie 60 fps = 1/60 or 0.016666.


hub(Posted 1+ years ago)

 Many thanks !


hub(Posted 1+ years ago)

 Is there a way to set a time parameter so particules are created for example during 5 seconds (particule age ?). (i use this code to create an explosion). Thanks !


matibee(Posted 1+ years ago)

 <div class="quote"> Is there a way to set a time parameter so particules are created for example during 5 seconds (particule age ?) </div>Not in this system. Why not store the time you last called "DoBurst", then call it again 5 seconds later.  This code is more concerned with the behaviour of the particles and updating them rather than controlling the emitters.


 

SimplePortal 2.3.6 © 2008-2014, SimplePortal