// 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!