2D Toolkit Forum

2D Toolkit => Support => Topic started by: fsadeq on August 14, 2012, 08:52:25 pm

Title: Recommended method for simple particle effects?
Post by: fsadeq on August 14, 2012, 08:52:25 pm
I am in the process of figuring out what is the best way to achieve "rain" effect or any other particles. Should I use 2D Toolkit for something like this, or am I better off using Unity's particle system? I'm trying for the most efficient method for mobile devices. I'm guessing I could have a sprite sheet with some particle textures, but not entirely sure what the best way to animate them would be.
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on August 15, 2012, 12:01:28 am
Well, I have some code somewhere for a tk2d based particle system. Its reasonably fast, but doesn't support animations (by that I mean no sprite animations, but the sprites do move). I was going to clean that up at some point, but it looks like I don't think I'll get a chance to do that. If you want I can describe the system. It really isn't that complicated to implement.
Title: Re: Recommended method for simple particle effects?
Post by: fsadeq on August 15, 2012, 02:11:50 am
Sounds like it could be useful, considering I don't need anything really complicated. I'd love to hear how to achieve it.
Title: Re: Recommended method for simple particle effects?
Post by: 39thstreet on August 15, 2012, 02:38:49 pm
I haven't really had an issue with Shuriken for most of the effects I've wanted to do.   I just make the emitter a really flat box and I get pretty good control.    About the only thing I couldn't do I really want to was use a 2d object as a mesh emitter (that is, have an effect appear over the visible surface of a sprite).

Is there a reason 2dtk would need it's own system?  Some reason to avoid Shuriken?
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on August 15, 2012, 07:38:11 pm
When I rolled my own, shuriken didn't exist. Anyway, shuriken still doesn't support a subuv region (and rotated regions) does it, which means you can't use sprites from a 2d Toolkit spritesheet. That was the main reason really.

Also, mine supported rewind at runtime (i.e. the sprites derive position from t every frame, rather than euler integrating) which I used for a prototype but never got further than that.

I'll write down some notes later when I get a chance.
Title: Re: Recommended method for simple particle effects?
Post by: fsadeq on August 18, 2012, 12:21:04 am
When you say the system didn't support animated sprites, could it still select different sprites from an atlas for each particle?
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on August 18, 2012, 10:11:53 am
Yes, each particle had its own unique sprite. The only controls I had on it was velocity, acceleration, size & start and end color.

There was a downside to this though - all the particles were drawn in one draw call - meaning you couldn't have some particles appear in different z depths and occluded differently. Well they could, but they wouldn't be occluded correctly by sprites at different depths...
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 11, 2012, 11:16:21 pm
Any thoughts how to implement something like the particle system to build things like 2D bullets & 2D explosions?
with collision events and all that stuff
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 12, 2012, 07:58:19 am
Those are two completely different class of effects aren't they? Bullets would want to collide with the world, and are high frequency, whereas explosions are likely to be lower frequency & might not need to collide...

The approach for both would really depend on how many you have - for example if you didn't have a crazy number of bullets then simple sprites would do the job. If you have LOADS of them (eg. bullet hell shooter), then you'd probably need to write a custom system for that.

I could probably make some more specific suggestions, but I'll need to have a lot more details...
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 12, 2012, 05:01:46 pm
Well, I plan to have up to 200 bullets for a given time. will depend on the number of on-screen enemies
and trails for a few bullets like rockets, lasers and such.
also plan to have dynamic explosions, fire +  smoke +  debri with a growing sphere collider for the splash damage.

[side note]
what would you suggest to propell bullets, use iTween or add a rigid body component and apply some force?
[end of side note]
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 13, 2012, 01:19:53 am
200 bullets at a time might be a bit expensive to use normal GameObjects with (I'm assuming you're targetting mobile).

I suggest writing a system to do this, and bypass Physx completely. If you have fast moving projectiles, physx is likely to miss them. Much easier to parametrically evaluate the positions based on a function, and then perform collision detection with this. You're not gonna have too many visible enemies / players right? Then just a brute force eval with sphere colliders on each should do the job, and will be fast.

Don't bother with iTween or any other tweening system for this, I think going custom with this will give you the best results, and with not much more work really.
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 13, 2012, 01:30:53 am
Yes we are targetting for mobile devices.

The bullets doesnt move at a high-ish speed as we want to give the player a small chance to dodge them
On our previous project we used up to 25 characters at a time, we might want to stretch that number up a little further. the final number will be set after playtesting the prototype based on dificulty.

Could you elaborate on the custom system idea?, as I have no clue where to start if not using GameObjects with tk2dSprite scripts attached.

Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 13, 2012, 05:15:55 am
I suggest building it in such a way that is super-convenient to start with, but will be easy enough to switch to a fully custom solution should the need arise.

Something like this:

// This wil create a GameObject with a sprite (i.e. the projectile sprite)
ProjectileManager.CreateProjectile( ProjectileType.RedFireball, lifeTime, projectilePos, projectileRotation, ProjectileMotionType.CurveRadius, projectileMotionRadius);

Because this doesn't directly interface with gameObjects, etc. you can later switch fairly quickly to any other solution.

Attach a monobehaviour to the projectile which has a few parameters and an update function.
startTime = Time.time; // assign time when projectile is created
normalizedProjectileTime = (Time.time - startTime) / lifeTime;
if (normalizedProjectileTime > 1.0f) KillProjectile();
transform.position = Interpolate(projectileStartPos, projectileEndPos, normalizedProjectileTime);

This can later be executed on an entire array without having the gameObjects.

You should now have moving projectiles. Using gameObjects, but very easily switchable to a fully custom solution later.

The important concept here is that the projectile motion types are fixed, containted within the enum ProjectileMotionType. What this gives you is fully deterministic projectile motion with no accumulation error. This also lets you create a chain of projectiles "in the past", which is super important for a chain of projectiles emiting from a source. Otherwise, you end up with non-uniformly staggered projectiles. You can apply all sorts of easing functions to this to make it look nicer at very little cost.

Additionally, because the motion is procedural, collision detection becomes considerably easier to perform later. Post again later if you decide to try this out and I'll go into more detail for the subsequent stages.
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 13, 2012, 05:36:04 am
This sounds like the thing I need!!!
more detail would be appreciated
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 13, 2012, 12:53:12 pm
I suggest getting the first stage working first as per the previous post. Once you have the core system working I'll post in more detail to help with the next stages. If you have specific implementation questions I'll be happy to help :)
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 13, 2012, 04:43:04 pm
Hi unikron,

I already have a Bullet9mm prefab which consists of a tk2dSprite with a Bullet script attached, the script has properties as damage, range, firedById, etc.
I also have a ObjectPoolComponent gameObject with an ObjectPool script attached where I am pre-instantiating 200 Bullet9mm.
On the update method of the bullet script I test if the distance between startPosition and the currentPosition is greater than range, then the object is returned to the pool.
On my Weapon script I get a Bullet gameObject from the pool and apply a force to it +  a random slight change on the startPosition and force direction to generate a spread of bullets instead of a single line.
On my CharacterController script I test for any Trigger Enter event and apply the damage if necesary and return the bullet to the pool.

Could you explain how to build the deterministic path?, especially for parabolic ones (as I plan to have grenades) and I guess I didnt get the chain of projectiles thing, does that mean the spread of bullets? or bullets + trail?
should I still use the trigger event to test for collisions? or should I use some sort of function of the collider to test if it contains a specific point/vector
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 14, 2012, 12:28:32 am
Deterministic paths

What usually happens in Unity, especially when using physics, is positions are evaluated every frame from velocity, and that from acceleration using Euler integration. Another downside is you can't create something "in the past". Lets say you have an enemy who rapid fires 10 bullets in rapid succession, followed by a 0.5s pause. Using Unity physics, you'd be limited to each of this bullets firing per physics step. You'd have to do some pretty funky stuff to get bullets firing at a higher rate.

So with this other system, the position is evaluated every frame from a function. The start time of the bullet is passed in to the bullet when it is fired - so all other derivations are performed with this time. Normally you'd pass current time, but you could just as easily pass in time in the past or in the future.

Code: [Select]
float startTime; // start time of the behaviour
float lifeTime; // how long the bullet lives for
float3 startPosition; // store the start position of the bullet

In Update/FixedUpdate, all you need to do is move the position. Lets start with something really simple.

Code: [Select]
float normalizedTime = (Time.time - startTime) / lifeTime;
float clippedNormalizedTime = Mathf.Clamp01(normalizedTime);
transform.position = startPosition + (endPosition - startPosition) * clippedNormalizedTime;
The bullet moves from start position -> endPosition over its lifetime. I've missed out the bit where you kill the gameObject once normalizedTime > 1, but that is easy to add. You know the exact position of the bullet at any given time, accurately and without error.

Now that you have this information, you can then do some more exciting stuff with it. For example, you can change how time affects the function. You can find some functions here: http://wiki.unity3d.com/index.php/Mathfx

Code: [Select]
transform.position = startPosition + (endPosition - startPosition) * FunkyFunction(clippedNormalizedTime);

Instead of directly going from start -> end position, you could apply some basic physics to it.

Code: [Select]
float t = FunkyFunction(clippedNormalizedTime);
transform.position = startPosition + velocity * t + 0.5f * acceleration * t * t;

Or indeed any other function.


For now set the rigidbody to kinematic and simply use the trigger. At a later date, you could try doing custom collision detection, etc. Again, build up the system in such a way that it is simple to switch bits to custom code - it is easy in Unity to code stuff in such a way that is impossible to break away from normal Unity behaviour. It really is up in the air if you will actually need more complicated collision detection than is offered in Unity, so don't bother writing a complex system until you know you need it.

Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 14, 2012, 05:26:34 pm
Thanks, let me implement those functions on my code instead of the physics. I'll post here my results
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on December 19, 2012, 03:19:41 am
Hi

I implemented the proposed solution to my bullet system, it works great! thanks

I also applied some of the gathered knlowdege to build a basic Particle Engine script component. hope this code can help someone.

Code: [Select]
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[ExecuteInEditMode]
public enum EmitterType{
Point
}

public class ParticleEngine : MonoBehaviour {

public class tk2dParticle{
private ParticleEffect effect;
public tk2dSprite Shape;
public float LifeTime;
public float StartTime;
public Vector3 StartPosition;
public Vector3 Velocity;
public Vector3 Acceleration;
public float Speed;
public Vector3 SpinFactor;
public Vector3 Scale;
public float StartScaleFactor;
public float FinalScaleFactor;
public float StartAlpha;
public float FinalAlpha;
public Color Tint;
public Color FinalTint;

public tk2dParticle(ParticleEffect effect, tk2dSprite shape){
this.effect = effect;
this.Shape = shape;
}

public void Update(float totalElapsedTime){
float localElapsedTime = (totalElapsedTime - this.StartTime);
float normalizedTime = localElapsedTime / this.LifeTime;
float clippedNormalizedTime = Mathf.Clamp01(normalizedTime);

if(localElapsedTime > this.LifeTime){
this.effect.killParticle(this);
return;
}

Vector3 position = this.StartPosition + this.Velocity * this.Speed * localElapsedTime + 0.5f * this.Acceleration * localElapsedTime * localElapsedTime;
this.Shape.transform.position = position;
this.Shape.transform.Rotate(this.SpinFactor * Time.deltaTime);
this.Shape.scale = this.Scale * MathS.Lerp(this.StartScaleFactor, this.FinalScaleFactor, clippedNormalizedTime);
Color shapeColor = this.Shape.color;
shapeColor = MathS.ColorLerp(this.Tint, this.FinalTint, clippedNormalizedTime);
shapeColor.a = Mathf.Clamp(MathS.Lerp(this.StartAlpha, this.FinalAlpha, clippedNormalizedTime), 0f, 255f);
this.Shape.color = shapeColor;
}
}

[System.Serializable]
public class ParticleEffect{
public bool isEnabled;
//EmiterProperties
public float EmitStart;
public float EmitStop;
public float Rate = 3.0f;
public bool InheritMovement = false;
public EmitterType EmitterType;

//ParticleProperties
public tk2dSprite Shape;
public float LifeSpan = 30.0f;
public float LifeSpanVariation = 20.0f;

public Vector3 Direction;
public Vector3 DirectionVariation;
public float Speed = 1.0f;
public float SpeedVariation = 0.0f;
public Vector3 Acceleration;
public Vector3 Spin;
public Vector3 SpinVariation;
public Vector3 Rotation;
public Vector3 RotationVariation;

public float Scale = 1.0f;
public float ScaleVariation = 0.0f;
public float FinalScale = 1.0f;
public float FinalScaleVariation;

public float Alpha = 255.0f;
public float AlphaVariation;
public float FinalAlpha = 255.0f;
public float FinalAlphaVariation;

public Color Tint = Color.white;
public Color FinalTint = Color.white;


private List<tk2dParticle> availableParticles;
private List<tk2dParticle> activeParticles;
private List<tk2dParticle> particlesToDeactivate;

private float timePerParticleEmision;
private float timeSinceLastEmision;
private ParticleEngine engine;

public ParticleEffect(){
this.EmitStart = 0;
this.EmitStop = 10;
this.Rate = 3.0f;
}

public void Initialize(ParticleEngine engine){
this.engine = engine;
float maxParticles = 0.0f;
if(this.EmitStop == -1)
maxParticles = (this.LifeSpan + LifeSpanVariation +1) * this.Rate;
else
maxParticles = (this.EmitStop - this.EmitStart) * this.Rate;

this.timeSinceLastEmision = 0.0f;
this.timePerParticleEmision = 1.0f / this.Rate;

this.availableParticles = new List<tk2dParticle>((int)maxParticles);
this.activeParticles = new List<tk2dParticle>((int)maxParticles);
this.particlesToDeactivate = new List<tk2dParticle>((int)maxParticles);

tk2dParticle tempParticle = null;
for(int i=0; i < maxParticles; i++){
tk2dSprite obj = (tk2dSprite)Instantiate(this.Shape);
tempParticle = new tk2dParticle(this, obj);
this.killParticle(tempParticle);
}
}

internal void EmitParticle(){
if(this.availableParticles.Count == 0)
return;
tk2dParticle particle = this.availableParticles[0];
this.availableParticles.RemoveAt(0);
this.activeParticles.Add(particle);

GameObject obj = particle.Shape.gameObject;
obj.SetActiveRecursively(true);
if(!this.InheritMovement)
obj.transform.parent = null;
obj.transform.position = this.engine.selfTransform.position;

particle.LifeTime = Random.Range(this.LifeSpan - this.LifeSpanVariation, this.LifeSpan + this.LifeSpanVariation);
particle.StartTime = Time.time;
particle.StartPosition = obj.transform.position;
particle.Velocity = Quaternion.Euler(
Random.Range(this.DirectionVariation.x * -1, this.DirectionVariation.x),
Random.Range(this.DirectionVariation.y * -1, this.DirectionVariation.y),
Random.Range(this.DirectionVariation.z * -1, this.DirectionVariation.z)) * this.Direction;
particle.Velocity.Normalize();
particle.Speed = Random.Range(this.Speed - this.SpeedVariation, this.Speed + this.SpeedVariation);
particle.Acceleration = this.Acceleration;
particle.Shape.transform.Rotate(
Random.Range(this.Rotation.x - this.RotationVariation.x, this.Rotation.x + this.RotationVariation.x),
Random.Range(this.Rotation.y - this.RotationVariation.y, this.Rotation.y + this.RotationVariation.y),
Random.Range(this.Rotation.z - this.RotationVariation.z, this.Rotation.z + this.RotationVariation.z));
particle.SpinFactor = new Vector3(
Random.Range(this.Spin.x - this.SpinVariation.x, this.Spin.x + this.SpinVariation.x),
Random.Range(this.Spin.y - this.SpinVariation.y, this.Spin.y + this.SpinVariation.y),
Random.Range(this.Spin.z - this.SpinVariation.z, this.Spin.z + this.SpinVariation.z));

particle.Shape.scale = this.Shape.scale;
particle.Scale = particle.Shape.scale;
particle.StartScaleFactor = Random.Range(this.Scale - this.ScaleVariation, this.Scale + this.ScaleVariation);
particle.FinalScaleFactor = Random.Range(this.FinalScale - this.FinalScaleVariation, this.FinalScale + this.FinalScaleVariation);

particle.StartAlpha = Random.Range(this.Alpha - this.FinalAlphaVariation, this.Alpha + this.AlphaVariation);
particle.FinalAlpha = Random.Range(this.FinalAlpha - this.FinalAlphaVariation, this.FinalAlpha + this.FinalAlphaVariation);

particle.Tint = this.Tint;
particle.FinalTint = this.FinalTint;
}

internal void killParticle(tk2dParticle particle){
this.particlesToDeactivate.Add(particle);
}

private void deactivateParticle(tk2dParticle particle){
GameObject obj = particle.Shape.gameObject;
obj.SetActiveRecursively(false);
obj.transform.parent = this.engine.selfTransform;
this.activeParticles.Remove(particle);
this.availableParticles.Add(particle);
}

internal void Update (float elapsedTime) {
if(this.engine.isRunning){
bool isEmitting = false;
if(this.EmitStop == -1)
isEmitting = elapsedTime >= this.EmitStart;
else
isEmitting = elapsedTime >= this.EmitStart && elapsedTime <= EmitStop;
if(isEmitting){
this.timeSinceLastEmision += Time.deltaTime;
if(this.timeSinceLastEmision >= this.timePerParticleEmision){
float numOfEmisions = this.timeSinceLastEmision / this.timePerParticleEmision;
numOfEmisions = Mathf.CeilToInt(numOfEmisions);
for(int i=0; i< numOfEmisions; i++)
this.EmitParticle();
this.timeSinceLastEmision = 0.0f;
}
}
}
if(this.activeParticles.Count>0){
foreach(tk2dParticle particle in this.activeParticles)
particle.Update(elapsedTime);
}
if(this.particlesToDeactivate.Count >0){
foreach(tk2dParticle particle in this.particlesToDeactivate)
this.deactivateParticle(particle);
this.particlesToDeactivate.Clear();
}
}
}


public bool AutoStartEngine = true;
public ParticleEffect[] Effects;

internal Transform selfTransform;
internal bool isRunning;
private float startTime;

public void StartEngine(){
this.startTime = Time.time;
this.isRunning = true;
}

public void StopEngine(){
this.isRunning = false;
}

// Use this for initialization
void Start () {
this.selfTransform = this.gameObject.transform;
if(Effects == null)
return;
for(int i=0 ; i< this.Effects.Length; i++)
if(this.Effects[i].isEnabled)
this.Effects[i].Initialize(this);
}

void Awake(){
if(this.AutoStartEngine)
this.StartEngine();
}

// Update is called once per frame
void Update () {
if(Effects == null)
return;
float elapsedTime = (Time.time - this.startTime);
for(int i=0 ; i< this.Effects.Length; i++)
if(this.Effects[i].isEnabled)
this.Effects[i].Update(elapsedTime);
}
}


Any improvement or sugestion would be appreciated
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on December 20, 2012, 03:10:24 am
Thats awesome. You only need to do any further work if you run into performance issues with this current one.
Title: Re: Recommended method for simple particle effects?
Post by: Tochas on February 21, 2013, 05:10:34 am
Hi Unikron,

We have been working for a few weeks now and we have a working prototype now, so we just did a few tests with real devices
on iphone and ipads everything is ok between 40 to 60 fps but with 4g ipods we sometimes have drops below 20 fps.
We are still using the built in physics collision detection for bullets

You mentioned earlier there could be another ways to test if a sprite (aka a bullet) hits another sprite (aka a character)
could you please elaborate on those, so we can see if we can get better performance on older devices

Thanks
Title: Re: Recommended method for simple particle effects?
Post by: unikronsoftware on February 21, 2013, 11:55:08 pm
Easy thing to try is to use raycasts for bullets instead of colliders. Hopefully they are small enough that you'll get away with doing this. Basically for each frame step raycast forward to where the bullet will be in the current frame and see if it hits anything. If you set up your collision masks properly (i.e. don't try to cast bullets with other bullets and so on) your performance should improve. There are other options but will require more work than this, so I'd try this first to see if it helps.