Pooled projectile adds unwanted offset to movement vector

Hi everyone,

in my current project I’m having tanks shooting at each other. For better resource management, I implemented the projectiles via Object Pools, i.e. they are instantiated when the level is initially loaded and then pooled when necessary.

// Generic pool class
public abstract class ObjectPool : MonoBehaviour
{
    [SerializeField] protected GameObject pooledObject;
    [SerializeField] protected int poolSize = 1;

    protected GameObject[] pooledObjects;
    public int PoolSize => pooledObjects.Length;


    protected virtual void Awake() => InitPool();

    protected virtual void InitPool()
    {
        pooledObjects = new GameObject[poolSize];
        AddPooledObjects();
    }

    protected virtual void AddPooledObjects()
    {
        GameObject tmp;
        for (int i = 0; i < poolSize; i++)
        {
            tmp = Instantiate(pooledObject, transform.parent);
            tmp.SetActive(false);
            pooledObjects[i] = tmp;
        }
    }

    public abstract bool GetPooledObject(out GameObject gameObject);
}

// Generic pool class with a fixed size
public abstract class CappedPool : ObjectPool
{
    public override bool GetPooledObject(out GameObject gameObject)
    {
        gameObject = null;
        for (int i = 0; i < poolSize; i++)
        {
            if (!pooledObjects[i].activeInHierarchy)
            {
                gameObject = pooledObjects[i];
                return true;
            }
        }
        return false;
    }
}

// The class actually pooling the projectiles. Each tank has its own Ammo component
public class Ammo : CappedPool { }

When a projectile is fired, it is fetched from the pool and given the current rotation of the tank to align to. From that point onward, the projectile starts moving on its own, only interacting with its environment based on the scripts it has attached. The only script manipulating its movement is this ProjectileMove script:

{
    [SerializeField] private ProjectileSpeed projectileSpeed;

    [SerializeField] private int wallBounces = 1;
    private int remainingBounces;

    private new Rigidbody rigidbody;
    private float speed = 0f;
    private Vector3 expectedVelocity = Vector3.zero;

    private void Awake()
    {
        rigidbody = gameObject.GetComponent<Rigidbody>();
        speed = projectileSpeed.Value;
    }

    private void OnEnable()
    {
        remainingBounces = wallBounces;
    }

    private void FixedUpdate()
    {
#if UNITY_EDITOR // allow runtime changes in the editor
        speed = projectileSpeed.Value;
#endif
        expectedVelocity = speed * transform.forward;
        rigidbody.AddForce(expectedVelocity - rigidbody.velocity, ForceMode.VelocityChange);
    }

    public bool HasBouncesRemaining() => remainingBounces > 0;

    public void BounceOffWall(Vector3 surfaceNormal)
    {
        remainingBounces--;
        Vector3 newDirection = Vector3.Reflect(transform.forward, surfaceNormal);
        Quaternion targetQuaternion = Quaternion.LookRotation(newDirection);
        rigidbody.MoveRotation(targetQuaternion);
    }
}

The problem is that after a couple of levels some bullets won’t fly in a straight line anymore but instead have some sort of offset in their movement vector as shown in the video below:

Some information about that bug:

  • It never occurs at the start of the game. It takes a few dozens levels to take effect. The exact time and level is always different, though.
  • The direction of the offset is always different.
  • The offset persists between pooling. As there are five projectiles for the player (blue tank), for this video specifically it’s always the second one that’s causing problems. Initially, it’s the second shot and then it reoccurs every time after being pooled and shot again.
  • For one bullet, the offset always has the same global direction, e.g. something like (-0.5, 0) in the video above. That means, when shooting upwards it drifts off to the left. When shooting to the left, it keeps its direction but becomes faster. Conversely, when shooting to the right, it becomes slower.

I tried logging values for projectiles to find the errror. I tried manually resetting each projectile’s movement vector when disabling/re-enabling them. I went through my complete project to find any other sources that might try to access the prtojectile’s Rigidbody. So far, nothing led to any results. Can anybody help me out here?

Thank you in advance!

Most likely you didn’t zero out the .velocity or .angularVelocity fields of the Rigidbody coming out of the pool.

Or maybe it’s some other value because clearly it’s a leaked piece of state.

Also, congratulations! You are now the manager of these items and it is on you to ensure 100% of their state gets reset the same way they would be if they were new.

Because you elected to use pooling, this now means you both have to write your game AND you have to manage these things. Every time into the future as you add fields or functionality, you gotta remember to zero / clear it all out. Otherwise… bugs! Bugs, Bugs, Bugs.

The costs and issues associated with object pooling / pools:

In very rare extremely-high-count object circumstances I have seen small benefits from pooling.

In MOST circumstances, object pooling is simply a source of complexity, bugs and edge cases while not giving any measurable benefit.