I came up with this method while designing my particle engine (and many others) and thought I'd like to share the idea cause I believe it could be very useful to others (or to tell me I wasted my time cause theres a better way). Modules are just bits of functionality created from a system, basically they just break the system into smaller pieces but also optimize it at the same time by cutting down on un-used variables and adding more functionality. I'll use my particle engine to explain this:
Let's look at a basic particle emitter type (not mine):
type UDT.ParticleEmitter
Life as float
Position as UDT.Vector
Rotation as UDT.Vector
Acceleration as UDT.Vector
Velocity as UDT.Vector
Size as UDT.Vector
Colour as UDT.RGBA
ParticleImage as integer
SpawnRate as float
endtype
The emitter sets up how it's particles will be emitted but is pretty baisc. If you wanted to add more functionality you'd just add more params to the type right. But when your emitter becomes as complex as mines is (known as a super emitter) you'd find that you don't use half of the params for each emitter. All these params are just sitting there wasting memory. Here's where modules come in.
Now with modules it would look like this:
type UDT.ParticleEmitter.Module
Life as integer
Acceleration as integer
Velocity as integer
Size as integer
Colour as integer
endtype
type UDT.ParticleEmitter
Position as UDT.Vector
Rotation as UDT.Vector
ParticleImage as integer
SpawnRate as float
Module as UDT.ParticleEmitter.Module
endtype
I define modules as anything specific to the emitted particle, this is why life is a module cause the emitter doesn't use life, only it's particles. So let's look at a module:
type UDT.ParticleEmitterModule.Life
lifetime as float
endtype
global dim _PEMod_Life() as UDT.ParticleEmitterModule.Life
This module defines the life of the emitted particles, it's pretty basic but can be improved which I'll show you later. So lets assign a module to an emitter:
//Note: you'd make functions for these
// create a life for a particle
_PEMod_Life(1).lifetime = 1.0
// assign life to are emitter
_ParticleEmitter(1).Module.Life = 1
Particle Emitter Module's just reference the module arrays. So _ParticleEmitter(1).Module.Life would be the index reference for _PEMod_Life() array. When a particle emitter is created all the module refs are set to -1, this tells the engine that a module hasn't been assigned to it, so skip this module on updating. So when the emitter is emitting a particle is would check if a module has been assigned and set the particle by the reference array. Example:
function EmitParticle( EmitterIndex )
particle = FindFreeParticle()
// < set intial particle params based on the emitter, like image, position and rotation >
if _ParticleEmitter( EmitterIndex ).Module.Life > -1
particle.life = _PEMod_Life( _ParticleEmitter( EmitterIndex ).Module.Life ).lifetime
endif
endfunction
So whats the advantage of this, well if you created 1000 particle emitters, it would have 1000 life float variables attached to it which would then be assigned to the particle or the particle references the emitter. Now if all of the 1000 lifes = 1.0, with modules you could assign 1000 lifes to a single float variable, so they all use a single array index. This mean's we've cut away 999 float variables and free'd up a bunch of memory, or another way to put it, we use 1000 integers for the references and 1 float, which is still less than 1000 floats variables. Take into account that there will be a ton more modules full of variables and then you'd saved a ton of memory. Even if you wanted modules only specific to the emitter you still save memory since you not using all of them. There are many others advantages, you could have a group of emitters that share modules, or you could swap in and out modules on the fly by changing a single value, the references. Cool huh.
Once the system is in place, we can then start adding more complex modules and improving on other for instance are life one like mine:
type UDT.ParticleEmitter.Module.Life
DistributionType as dword
UniformFloat as UDT.UniformFloat
UniformFloatCurve as integer
endtype
Quick explaintion of this. The DistributionType defines how the life float variable will be distribution. The types are: Float, FloatCurve, UniformFloat and UniformFloatCurve. There's also vector types but there not needed for this. Float is just a constant float value, FloatCurve is a float based on a keyframe curve, UniformFloat are two floats - MIN and Max and will pick a random value between them and UniformFloatCurve is just like UniformFloat, but based between Min and Max keyframe curves. All this does is add a ton of functionality to my life module. The idea being once the the base system is in place, you only have to improve on the modules and not edit a ton of code.
Heres some of my other modules I use in my particle engine:
Acceleration
AccelerationByLife
LineAttractor
ParticleAttractor
PointAttractor
BeamModifier
Noise
Source
Target
Collision
InitialColor
ParameterColor
ColorOverLife
AlphaOverLife
ScaleColorByLife
Life
InitialLocation
EmitterInitialLocation
Cylinder
Sphere
InitialRotation
InitialRotRate
LockAxis
MeshData
InitialSize
SizeByLife
SizeByVelocity
SizeScale
InitialVelocity
VelocityByLife
SubImageIndex
// there's about 16 more
And what my particle system looks like if you made it in the IDE:
// Particle System - Test
// ------------------------------------------------------------------------------------
tParticleSystem.Name = "FX.ParticleSys.Test"
tParticleSystem.UpdateType = c_PS_UpdateType_RealTime
tParticleSystem.UpdateTimeFPS = 60.0 // Only if UpdateType = FixedTime
tParticleSystem.WarmupTime = 0.0
tParticleSystem.SecondsBeforeInactive = 0.0
tParticleSystem.LODDistanceCheckTime = 5.0
tParticleSystem.bUseRelativeBoundingBox = 0 // For updating
tUniformVector.Min.x = -1.0 : tUniformVector.Min.y = -1.0 : tUniformVector.Min.z = -1.0
tUniformVector.Max.x = 1.0 : tUniformVector.Max.y = 1.0 : tUniformVector.Max.z = 1.0
tParticleSystem.BoundingBox = tUniformVector
CreateParticleSystem()
// LOD List ¬
CreateParticleSystem_LODListItem( 0.0 )
CreateParticleSystem_LODListItem( 256.0 )
// Particle System - Muzzle Flash 01 - Emitters ¬
// ------------------------------------------------------------------------------------
tParticleEmitter.Name = "Temp_01"
tParticleEmitter.Material = "Mat.DiffuseFlare1Additive"
tParticleEmitter.ScreenAlignment = c_PSA_Square
tParticleEmitter.Duration = 3.0
tParticleEmitter.DurationLow = 0.0
tParticleEmitter.bUseDurationRange = 0
tParticleEmitter.Loops = 0
tParticleEmitter.EmitterDelay = 0.0
tParticleEmitter.SubImage_HorizontalCount = 1
tParticleEmitter.SubImage_VerticalCount = 1
tParticleEmitter.bUseMaxDrawCount = 1
tParticleEmitter.MaxDrawCount = 500
tParticleEmitter.SpawnRate.DistributionType = c_DT_ConstantFloat
tParticleEmitter.SpawnRate.ConstantFloat = 0.0
CreateParticleEmitter( tParticleEmitter )
// Burst List ¬ (Count, CountLow, Time)
CreateBurstListItem( 4, 2, 0.0 )
CreateBurstListItem( 2, 1, 1.5 )
// Modules ¬
// LifeTime
PE_Module_LifeTime( c_DT_UniformFloat, 3.0, 3.0 )
tUniformFloatARGB.A.Min = 1.0 : tUniformFloatARGB.R.Min = 0.75 : tUniformFloatARGB.G.Min = 2.0 : tUniformFloatARGB.B.Min = 10.0
tUniformFloatARGB.A.Max = 1.0 : tUniformFloatARGB.R.Max = 0.75 : tUniformFloatARGB.G.Max = 2.0 : tUniformFloatARGB.B.Max = 10.0
PE_Module_InitialColor( c_DT_UniformFloat )
PE_Module_InitialRotation( -0.15, 0.6 )
// Initial Size
tUniformVector.Min.x = 14.0 : tUniformVector.Min.y = 0.0 : tUniformVector.Min.z = 0.0
tUniformVector.Max.x = 14.0 : tUniformVector.Max.y = 0.0 : tUniformVector.Max.z = 0.0
PE_Module_InitialSize( c_DT_UniformVector )
// Initial Velocity
tUniformVector.Min.x = -25.0 : tUniformVector.Min.y = 40.0 : tUniformVector.Min.z = -25.0
tUniformVector.Max.x = 25.0 : tUniformVector.Max.y = 80.0 : tUniformVector.Max.z = 25.0
tUniformFloat.Min = 0.0 : tUniformFloat.Max = 0.0 // radial velocity
PE_Module_InitialVelocity( c_DT_UniformVector, c_DT_UniformFloat )
// Acceleration
tUniformVector.Min.x = 0.0 : tUniformVector.Min.y = 0.0 : tUniformVector.Min.z = 0.0
tUniformVector.Max.x = 0.0 : tUniformVector.Max.y = 0.0 : tUniformVector.Max.z = 0.0
// PE_Module_Acceleration( c_DT_UniformVector )
// Particle System - Muzzle Flash 01 - Emitters ¬
// ------------------------------------------------------------------------------------
tParticleEmitter.Name = "Temp_02"
tParticleEmitter.Material = "Mat.DiffuseFlare2Additive"
tParticleEmitter.ScreenAlignment = c_PSA_Square
tParticleEmitter.Duration = 1.0
tParticleEmitter.DurationLow = 0.0
tParticleEmitter.bUseDurationRange = 0
tParticleEmitter.Loops = 0
tParticleEmitter.EmitterDelay = 0.0
tParticleEmitter.SubImage_HorizontalCount = 1
tParticleEmitter.SubImage_VerticalCount = 1
tParticleEmitter.bUseMaxDrawCount = 1
tParticleEmitter.MaxDrawCount = 500
tParticleEmitter.SpawnRate.DistributionType = c_DT_ConstantFloat
tParticleEmitter.SpawnRate.ConstantFloat = 4.0
CreateParticleEmitter( tParticleEmitter )
// Burst List ¬ (Count, CountLow, Time)
CreateBurstListItem( 16, 12, 0.0 )
// Modules ¬
// LifeTime
PE_Module_LifeTime( c_DT_ConstantFloat, 0.5, 0.5 )
// Initial Size
tUniformVector.Min.x = 14.0 : tUniformVector.Min.y = 0.0 : tUniformVector.Min.z = 0.0
tUniformVector.Max.x = 14.0 : tUniformVector.Max.y = 0.0 : tUniformVector.Max.z = 0.0
PE_Module_InitialSize( c_DT_UniformVector )
// Initial Velocity
tUniformVector.Min.x = 0.0 : tUniformVector.Min.y = 0.0 : tUniformVector.Min.z = 0.0
tUniformVector.Max.x = 0.0 : tUniformVector.Max.y = 0.0 : tUniformVector.Max.z = 0.0
tUniformFloat.Min = 0.0 : tUniformFloat.Max = 0.0 // radial velocity
PE_Module_InitialVelocity( c_DT_UniformVector, c_DT_UniformFloat )
// Size By Life (linear for now, cause i'm not messing with tangents without a visual editor)
tConstantVectorLine.x = CreateFloatConstantCurve()
tFloatConstantCurvePoint.Time = 0.0 : tFloatConstantCurvePoint.OutValue = 1.0
AddFloatConstantCurvePoint()
tFloatConstantCurvePoint.Time = 1.0 : tFloatConstantCurvePoint.OutValue = 0.0
AddFloatConstantCurvePoint()
tConstantVectorLine.y = CreateFloatConstantCurve()
tFloatConstantCurvePoint.Time = 0.0 : tFloatConstantCurvePoint.OutValue = 1.0
AddFloatConstantCurvePoint()
tFloatConstantCurvePoint.Time = 1.0 : tFloatConstantCurvePoint.OutValue = 0.0
AddFloatConstantCurvePoint()
tConstantVectorLine.z = CreateFloatConstantCurve()
tFloatConstantCurvePoint.Time = 0.0 : tFloatConstantCurvePoint.OutValue = 0.0
AddFloatConstantCurvePoint()
PE_Module_SizeByLife( c_DT_ConstantVectorCurve )
// Emitter Initial Location - Emits from the emitted particles of another emitter
tEmitterInitialLocation.EmitterName = "Temp_01"
tEmitterInitialLocation.SelectionMethod = 0
tEmitterInitialLocation.bInheritSourceVelocity = 1
tEmitterInitialLocation.bInheritSourceRotation = 0
tEmitterInitialLocation.InheritSourceVelocityScale = 1.0
tEmitterInitialLocation.InheritSourceRotationScale = 1.0
PE_Module_EmitterInitialLocation()
So now we could add more to the system, interaction between present assigned modules and un-assigned modules, module lists instead fixed modules which work perfectly for semi-script systems. I use modules or what I call cell layers on grid systems, so if I had a world based off a grid system and I wanted a paticullar cell to have something like... strong gravity, I could add a gravity layer to it to alter the gravity of that cell. These layers are parts of module lists. So I've added a cell module to the world grid that allows me to add layers to a define cell instead of every single cell on a grid having un-used layer lists. Basically modules within modules.
Just incase this Module stuff confused the hell out of you, I'll show you an example:
gosub declareObjects
sync on : sync rate 60
hide mouse
// Create a bunch of objects (only boxes for now unless you want to make a primitives module)
for n = 0 to 500
CreateObject()
// add some modules to it
ObjectModule_Position( rnd(512)-256, rnd(512)-256, rnd(512)-256 )
ObjectModule_Size( rnd(25), rnd(25), rnd(25) )
if rnd(500) > 250
ObjectModule_Color( rnd(255), rnd(255), rnd(255) )
endif
if rnd(500) > 350
ObjectModule_RotationRate( rnd(4), rnd(4), rnd(4) )
endif
next n
// finalize object
BuildObjects()
do
cr# = 0 : cf# = 0
speed# = 1.0
sm# = 5.0
if rightkey()=1 or KEYSTATE(32)=1 then cr# = -speed#
if leftkey()=1 or KEYSTATE(30)=1 then cr# = speed#
if upkey()=1 or KEYSTATE(17)=1 then cf# = speed#
if downkey()=1 or KEYSTATE(31)=1 then cf# = -speed#
ncr#=curvevalue(cr#,ncr#,sm#)
ncf#=curvevalue(cf#,ncf#,sm#)
cx#=cx#+mousemovey()*0.2
cy#=cy#+mousemovex()*0.2
if cx#>80 then cx#=80
if cx#<-80 then cx#=-80
ncx#=curveangle(cx#,ncx#,sm#)
ncy#=curveangle(cy#,ncy#,sm#)
move camera ncf#
rotate camera 0,wrapvalue(ncy#-90),0
move camera ncr#
rotate camera 0,wrapvalue(ncy#+90),0
rotate camera ncx#,ncy#,0
UpdateObjects()
sync
loop
// Object Functions
///////////////////////////////////////////////////////////////////////////////////////////////
type UDT.Object.Modules
Position as integer
RotationRate as integer
Size as integer
Color as integer
endtype
type UDT.Object
ID as integer
Module as UDT.Object.Modules
endtype
type UDT.Object.Module.Position
x as float
y as float
z as float
endtype
type UDT.Object.Module.Size
x as float
y as float
z as float
bValid as boolean
endtype
type UDT.Object.Module.Color
R as byte
G as byte
B as byte
endtype
type UDT.Object.Module.RotationRate
x as float
y as float
z as float
endtype
declareObjects:
global dim _Object() as UDT.Object
global g_CurrentObject as integer
global dim _ObjMod_Position() as UDT.Object.Module.Position
global dim _ObjMod_Size() as UDT.Object.Module.Size
global dim _ObjMod_Color() as UDT.Object.Module.Color
global dim _ObjMod_RotationRate() as UDT.Object.Module.RotationRate
return
function CreateObject()
local out as integer
array insert at bottom _Object()
out = array count( _Object() )
_Object().Module.Position = -1
_Object().Module.Size = -1
_Object().Module.Color = -1
_Object().Module.RotationRate = -1
g_CurrentObject = out
endfunction out
function ObjectModule_Position( x as float, y as float, z as float )
local bCheck as boolean
local index as integer
local out as integer
// check to see if the array holds the same params
if array count( _ObjMod_Position() ) > -1
for index = 0 to array count( _ObjMod_Position() )
if ( _ObjMod_Position(index).x = x && _ObjMod_Position(index).y = y && _ObjMod_Position(index).z = z )
_Object(g_CurrentObject).Module.Position = index : exitfunction
endif
next index
endif
array insert at bottom _ObjMod_Position()
out = array count( _ObjMod_Position() )
_ObjMod_Position().x = x
_ObjMod_Position().y = y
_ObjMod_Position().z = z
_Object(g_CurrentObject).Module.Position = out
endfunction
function ObjectModule_Size( x as float, y as float, z as float )
local bCheck as boolean
local index as integer
local out as integer
// check to see if the array holds the same params
if array count( _ObjMod_Size() ) > -1
for index = 0 to array count( _ObjMod_Size() )
if ( _ObjMod_Size(index).x = x && _ObjMod_Size(index).y = y && _ObjMod_Size(index).z = z )
_Object(g_CurrentObject).Module.Size = index : exitfunction
endif
next index
endif
array insert at bottom _ObjMod_Size()
out = array count( _ObjMod_Size() )
_ObjMod_Size().x = x
_ObjMod_Size().y = y
_ObjMod_Size().z = z
_ObjMod_Size().bValid = 1
if (_ObjMod_Size().x = 0 && _ObjMod_Size().y = 0 && _ObjMod_Size().z = 0)
_ObjMod_Size().bValid = 0
endif
_Object(g_CurrentObject).Module.Size = out
endfunction
function ObjectModule_Color( R as byte, G as byte, B as byte )
local bCheck as boolean
local index as integer
local out as integer
// check to see if the array holds the same params
if array count( _ObjMod_Color() ) > -1
for index = 0 to array count( _ObjMod_Color() )
if ( _ObjMod_Color(index).R = R && _ObjMod_Color(index).G = G && _ObjMod_Color(index).B = B )
_Object(g_CurrentObject).Module.Color = index : exitfunction
endif
next index
endif
array insert at bottom _ObjMod_Color()
out = array count( _ObjMod_Color() )
_ObjMod_Color().R = R
_ObjMod_Color().G = G
_ObjMod_Color().B = B
_Object(g_CurrentObject).Module.Color = out
endfunction
function ObjectModule_RotationRate( x as float, y as float, z as float )
local bCheck as boolean
local index as integer
local out as integer
// check to see if the array holds the same params
if array count( _ObjMod_RotationRate() ) > -1
for index = 0 to array count( _ObjMod_RotationRate() )
if ( _ObjMod_RotationRate(index).x = x && _ObjMod_RotationRate(index).y = y && _ObjMod_RotationRate(index).z = z )
_Object(g_CurrentObject).Module.RotationRate = index : exitfunction
endif
next index
endif
array insert at bottom _ObjMod_RotationRate()
out = array count( _ObjMod_RotationRate() )
_ObjMod_RotationRate().x = x
_ObjMod_RotationRate().y = y
_ObjMod_RotationRate().z = z
_Object(g_CurrentObject).Module.RotationRate = out
endfunction
function BuildObjects()
local index as integer
local NewObject as integer
local SizeIndex as integer
local PositionIndex as integer
local ColorIndex as integer
if array count( _Object() ) > -1
for index = 0 to array count( _Object() )
SizeIndex = _Object(index).Module.Size
PositionIndex = _Object(index).Module.Position
ColorIndex = _Object(index).Module.Color
repeat
inc NewObject
until not object exist(NewObject)
_Object(index).ID = NewObject
if SizeIndex > -1 and _ObjMod_Size(SizeIndex).bValid
make object box NewObject,_ObjMod_Size(SizeIndex).x,_ObjMod_Size(SizeIndex).y,_ObjMod_Size(SizeIndex).z
else
// if size is invalid set it to deflaut size
make object box NewObject,1,1,1
endif
if PositionIndex > -1
position object NewObject,_ObjMod_Position(PositionIndex).x,_ObjMod_Position(PositionIndex).y,_ObjMod_Position(PositionIndex).z
endif
if ColorIndex > -1
set object diffuse NewObject, RGB(_ObjMod_Color(ColorIndex).R,_ObjMod_Color(ColorIndex).G,_ObjMod_Color(ColorIndex).B)
endif
next index
endif
// clear up initials
undim _ObjMod_Position(0)
undim _ObjMod_Size(0)
undim _ObjMod_Color(0)
endfunction
// Update object with modules that need updating
function UpdateObjects()
local index as integer
if array count( _Object() ) > 0
for index = 0 to array count( _Object() )
if _Object(index).Module.RotationRate > -1
x# = wrapvalue(object angle x(_Object(index).ID)+_ObjMod_RotationRate(_Object(index).Module.RotationRate).x)
y# = wrapvalue(object angle y(_Object(index).ID)+_ObjMod_RotationRate(_Object(index).Module.RotationRate).y)
z# = wrapvalue(object angle z(_Object(index).ID)+_ObjMod_RotationRate(_Object(index).Module.RotationRate).z)
rotate object _Object(index).ID,x#,y#,z#
endif
next index
endif
endfunction
I wouldn't use it in this way, but this is just an example of how it works. You would mostly use it for referencing, for instances my particle emitters are just references for my particle emitter actors, which just use the emitter ref ID and emit particle based on the ref emitter and the modules attached to it. Anyway, hope this helps some, and as always, have fun programming. (Let the flames of terror begin)
A dream is a fantasy, if you achieve that fantasy it was never a dream to begin with.