Skip to content

Instantly share code, notes, and snippets.

@prime31
Created May 1, 2014 17:42
Show Gist options
  • Save prime31/d8b4f830eee2c8bb8051 to your computer and use it in GitHub Desktop.
Save prime31/d8b4f830eee2c8bb8051 to your computer and use it in GitHub Desktop.
Simple (unoptimized!) particle flock. Stick it on a GameObject with a ParticleSystem and play with inspector for some interesting results. Clicking anywhere on screen will set the target position for all boids.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[RequireComponent( typeof( ParticleSystem ) )]
public class ParticleFlock : MonoBehaviour
{
public Vector3 targetPosition;
public int totalBoids = 15;
[Range( 0.1f, 50f )]
public float alignMaxDistance = 0.2f;
[Range( 0.1f, 50f )]
public float desiredSeperationDistance = 5f;
[Range( 0.1f, 50f )]
public float cohesionMaxDistance = 2f;
[Range( 0.1f, 2f )]
public float maxSpeed = 0.1f;
[Range( 0.001f, 0.1f )]
public float maxForce = 0.01f;
[Range( 0.1f, 5f )]
public float alignWeight = 1f;
[Range( 0.1f, 5f )]
public float seperationWeight = 3f;
[Range( -10f, 3f )]
public float cohesionWeight = 1f;
[Range( 0.1f, 5f )]
public float tendTowardPositionWeight = 1f;
private ParticleSystem _particleSystem;
private ParticleSystem.Particle[] _particles;
void Awake()
{
_particleSystem = GetComponent<ParticleSystem>();
// set our total boids. we limit the value entered in the inspector by the particleSystem max
totalBoids = totalBoids > _particleSystem.maxParticles ? _particleSystem.maxParticles : totalBoids;
_particleSystem.Emit( totalBoids );
_particles = new ParticleSystem.Particle[_particleSystem.particleCount];
_particleSystem.GetParticles( _particles );
}
void OnGUI()
{
if( GUILayout.Button( "Scatter" ) )
{
var tempCohesionWeight = cohesionWeight;
var tempPositionWeight = tendTowardPositionWeight;
cohesionWeight = -25f;
tendTowardPositionWeight = 0.3f;
StartCoroutine( resetSomeValueAfterDelay( 1.5f, () =>
{
cohesionWeight = tempCohesionWeight;
tendTowardPositionWeight = tempPositionWeight;;
} ) );
}
if( Event.current.isMouse && ( Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag ) )
{
var mouse = new Vector3( Input.mousePosition.x, Input.mousePosition.y, 10f );
targetPosition = Camera.main.ScreenToWorldPoint( mouse );
}
}
IEnumerator resetSomeValueAfterDelay( float delay, System.Action action )
{
yield return new WaitForSeconds( delay );
action();
}
void Update()
{
for( var i = 0; i < _particles.Length; i++ )
{
var acceleration = Vector3.zero;
// acquire our forces and weight them
var sep = separate( i ) * seperationWeight;
var ali = align( i ) * alignWeight;
var coh = cohesion( i ) * cohesionWeight;
var tow = tendTowardLocation( i ) * tendTowardPositionWeight;
// add to acceleration
acceleration += sep;
acceleration += ali;
acceleration += coh;
acceleration += tow;
_particles[i].velocity += acceleration;
// limit max velocity
_particles[i].velocity = limitVectorMagnitude( _particles[i].velocity, maxSpeed );
// move
_particles[i].position += _particles[i].velocity;
}
_particleSystem.SetParticles( _particles, _particles.Length );
}
#region boid flocking
Vector3 limitVectorMagnitude( Vector3 vec, float limit )
{
if( vec.magnitude > limit )
return vec.normalized * limit;
return vec;
}
private Vector3 seek( int boidIndex, Vector3 target )
{
var desired = target - _particles[boidIndex].position; // A vector pointing from the location to the target
// scale to maximum speed
desired.Normalize();
desired *= maxSpeed;
// Steering = Desired minus Velocity
var steer = desired - _particles[boidIndex].velocity;
// limit our force
if( steer.magnitude > maxForce )
steer = steer.normalized * maxForce;
return steer;
}
// check for nearby boids and steers away
public Vector3 separate( int boidIndex )
{
var steer = Vector3.zero;
var count = 0;
for( var i = 0; i < _particles.Length; i++ )
{
if( i == boidIndex )
continue;
var d = Vector3.Distance( _particles[boidIndex].position, _particles[i].position );
if( d < desiredSeperationDistance )
{
// calculate vector pointing away from neighbor
var diff = _particles[boidIndex].position - _particles[i].position;
diff.Normalize();
diff /= d;
steer += diff;
count++; // keep track of how many we are avoiding
}
}
// average -- divide by how many
if( count > 0 )
steer /= count;
// As long as the vector is greater than 0
if( steer.sqrMagnitude > 0 )
{
// Reynolds: Steering = Desired - Velocity
steer.Normalize();
steer *= maxSpeed;
steer -= _particles[boidIndex].velocity;
// limit our force
steer = limitVectorMagnitude( steer, maxForce );
}
return steer;
}
// for every nearby boid in the system, calculate the average velocity
public Vector3 align( int boidIndex )
{
var sum = Vector3.zero;
int count = 0;
for( var i = 0; i < _particles.Length; i++ )
{
if( i == boidIndex )
continue;
var d = Vector3.Distance( _particles[boidIndex].position, _particles[i].position );
if( d < alignMaxDistance )
{
sum += _particles[i].velocity;
count++;
}
}
if( count > 0 )
{
sum /= count;
// Reynolds: Steering = Desired - Velocity
sum.Normalize();
sum *= maxSpeed;
var steer = sum - _particles[boidIndex].velocity;
// limit our force
return limitVectorMagnitude( steer, maxForce );
}
else
{
return Vector3.zero;
}
}
// for the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
public Vector3 cohesion( int boidIndex )
{
var sum = Vector3.zero; // Start with empty vector to accumulate all locations
int count = 0;
for( var i = 0; i < _particles.Length; i++ )
{
if( i == boidIndex )
continue;
var d = Vector3.Distance( _particles[boidIndex].position, _particles[i].position );
if( d < cohesionMaxDistance )
{
sum += _particles[i].position;
count++;
}
}
if( count > 0 )
{
sum /= count;
return seek( boidIndex, sum ); // Steer towards the location
}
return Vector3.zero;
}
Vector3 tendTowardLocation( int boidIndex )
{
return ( targetPosition - _particles[boidIndex].position ) * 0.02f;
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment