Pretty dam fast projectile manager (also works on iphone)

// Pretty dam fast projectile manager for rigidbody-based projectiles.
// Create a projectile prefab and attach this script to it.
// From some world object (singleton?), call Init() once; and UpdateAll()
// every frame.
// Use Projectile.Launch() to launch all the projectiles you want :)
//
// author: rozgo ([url]www.rozgo.com[/url])
//

using UnityEngine;
using System.Collections;

public class Projectile : MonoBehaviour {
	
	// struct for better data locality
	struct ProjectileData {
		public float life;
		public int nextFree;
	}
	
	static int maxNumProjectiles = 100;
	static Projectile[] projectiles = null;
	static ProjectileData[] projectilesData = null;
	static int free = 0;
	
	// call this only once, it will initialize all projectiles
	public static void Init () {
		// pre-allocate to avoid runtime allocations
		projectiles = new Projectile[maxNumProjectiles];
		projectilesData = new ProjectileData[maxNumProjectiles];
		for (int i=0; i<maxNumProjectiles; ++i) {
			// somewhere in Resources folder, get the projectile asset
			// by resusing the asset, unity will do automatic baching
			GameObject projectileObject = (GameObject)Instantiate((GameObject)Resources.Load("Projectile"));
			projectileObject.active = false;
			// prepare storage for O(1) lookup time
			Projectile projectile = (Projectile)projectileObject.GetComponent(typeof(Projectile));
			if (i<(maxNumProjectiles-1)) {
				projectilesData[i].nextFree = i+1;
			}
			projectile.index = i;
			projectiles[i] = projectile;
		}
	}
	
	// call this for every projectile you want to launch
	public static Projectile Launch (Vector3 position, Vector3 velocity, float life) {
		// O(1) lookup time
		if (free<0) {return null;}
		Projectile projectile = projectiles[free];
		projectile.gameObject.active = true;
		projectile.transform.position = position;
		projectile.rigidbody.velocity = velocity;
		projectilesData[free].life = life;
		free = projectilesData[free].nextFree;
		return projectile;
	}
	
	// call this only once per frame, it handles all projectiles at once
	public static void UpdateAll () {
		float dt = Time.deltaTime;
		// iterate on struct, not component array
		for (int p=0; p<maxNumProjectiles; ++p) {
			if (projectilesData[p].life >= 0) {
				projectilesData[p].life -= dt;
				if (projectilesData[p].life < 0) {
					projectiles[p].gameObject.active = false;
					projectilesData[p].nextFree = free;
					free = p;
				}
			}
		}
	}
    
    new Transform transform;
    new Rigidbody rigidbody;
    int index;
	
	void Awake () {
		// cache component lookup, not really sure this works
		transform = gameObject.transform;
		rigidbody = gameObject.rigidbody;
	}
	
	void OnCollisionEnter () {
		// schedule for death
		projectilesData[index].life = 0;
	}
}

Hey this is pretty cool code. Thanks!
I’ve been working parts of it into my projectile system. I have a question for you. I’m not sure if this issue I’m having is related to my lack of understanding or knowledge of c#…but…
I’m basically trying to not have per frame polling using update functions. I am trying to work in a coroutine to set up a life timer on fired projectiles. I’m putting the StartCoroutine function within the Projectile Launch function in your code. On calling the coroutine, I’m passing the index # to that coroutine so it knows which projectile I’m referring to.

added this:

int i = projectile.index;
StartCoroutine(TimerKill(int i));

Then in a new function putting this:

IEnumerator TimerKill(int p)
{
yield return new WaitForSeconds(timeOut);
Kill(p);
}

(I added a seperate Kill function using your code block of projectiles[p].gameObject.active = false etc etc.)

Thanks for any help or tips.

-p

[/code]

i’ll check the code and try to implement.
Thank you!

your kill function needs to be something like this:

void Kill (int projectileIndex) {
	projectiles[projectileIndex].gameObject.active = false; 
	projectilesData[projectileIndex].nextFree = free; 
	free = projectileIndex;
}

everything else looks like it should work

i got it working with…

	public static Projectile Launch (Vector3 position, Vector3 velocity, float life) {
		// O(1) lookup time
		if (free<0) {return null;}
		Projectile projectile = projectiles[free];
		projectile.gameObject.active = true;
		projectile.transform.position = position;
		projectile.rigidbody.velocity = velocity;
		projectile.StartCoroutine(projectile.Kill(free, life));
		free = projectilesData[free].nextFree;
		return projectile;
	}
	
	IEnumerator Kill (int p, float life) {
		yield return new WaitForSeconds(life);
		projectiles[p].gameObject.active = false;
		projectilesData[p].nextFree = free;
		free = p;
	}

cool. thanks again, That successfully calls the coroutine.
I’m just wondering why you need to put in the ‘projectile’
instance at the head of coroutine?

projectile.StartCoroutine(projectile.Kill(free, life));

Thanks!
-p

because Launch() is a class function, not a member function

the function finds the next available projectile and uses that instance to do the setup

StartCoroutine is part of MonoBehaviour, which requires a MonoBehaviour-derived instance, and the coroutine also has to be part of the same instance

any chance of a javascript version of this?

This looks cool, man. But I’m lost when it comes to C#… any chance you could point out how to call these functions from a javascript? Thanks!