Split arrow spell not rotating all arrows, and moving in wrong direction

Hello. I’m trying to create a split arrow spell with code by spawning, adding spacing, and rotations to the arrows. The arrows spawn and move forward, but there are two issues I can’t figure out:

  1. Only the first arrow is rotating.
  2. The arrows aren’t moving in the direction they were rotated to.

Here is my code (See screenshot for output):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SplitArrow : MonoBehaviour
{
    [SerializeField] private GameObject arrow;
    [SerializeField] private Transform spawnPoint;

    [SerializeField] private float projectileSpeed = 10.0f;

    [SerializeField] private float xSpacing = 2.0f;
    [SerializeField] private float yRot = 35.0f;

    public List<GameObject> arrowList = new List<GameObject>();

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            for (int i = 0; i < arrowList.Count; i++)
            {
                GameObject projectile = Instantiate(
                    arrowList[i],
                    new Vector3(spawnPoint.position.x + xSpacing * i, spawnPoint.position.y, spawnPoint.position.z + 5.0f),
                    new Quaternion(arrowList[i].transform.rotation.x, arrowList[i].transform.rotation.y + (yRot * i), arrowList[i].transform.rotation.z, arrowList[i].transform.rotation.w)
                    );
                projectile.GetComponent<Rigidbody>().velocity = arrowList[i].transform.forward * projectileSpeed;
                Destroy(projectile, 2f);
            }
        }
    }
}

Unity will call Updates once in each engine frame.

Your Update will evaluate at some point, and will proceed to check the state of the keyboard.
If the key was pressed in this particular Update (so its state changed since the last call), only then it’ll evaluate the things inside the block.

What are the things inside the block?
Well, you go through some list, which is presumably empty, and then you populate that list by instantiating objects, and modifying their transforms.
Then you destroy them as soon as they’re created, practically.

At this point, it becomes apparent that it’s a miracle you get anything at all.

I’m guessing you’re following some tutorial, but you got this all wrong. Instantiate should refer to some prefab, a fixed game object that you drag’n’dropped to your inspector field, and should instantiate clones of this “master” object, and hold the references to these clones inside the list. This helps you maintain some order and address these clones individually. Otherwise, the references would be lost from your immediate coding environment, and you’ll have to grab them through other means, which are much more expensive.

This is why you have private GameObject arrow;
Please call this arrowPrefab. Drag’n’drop your arrow object in the inspector.
This is why you have public List<GameObject> arrowList = new List<GameObject>();
This lets you keep references to your clones. Please make this private (or simply remove public), so that it’s not serializable. This is live data, not something you want to tweak in inspector, and this is likely the error that lets you see that one arrow to begin with. It’s a well-mannered, good-willed error so to speak.

Then change Instantiate to var projectile = Instantiate(arrowPrefab, ....);
And remove Destroy. No you don’t destroy the objects immediately, what would be the purpose of spawning them?

Edit: Ah ok, you have set the delay with Destroy. Ok, it’s ok for testing.

1 Like

@orionsyndrome Not following a tutorial. Trying to do all this from docs with trial and error. New to using lists, but now I see why what I’m doing is terrible. I added prefabs to the list in the inspector, which is why it works… haha!

Thanks for going through that, your thoughts actually helped me debug my code. It’s now shooting like a split arrow and working properly.

Side note:
The reason it wasn’t moving in the direction it was facing was because I was applying velocity to the arrowPrefab and not the instantiated projectile.

Cleaner updated code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SplitArrow : MonoBehaviour
{
    [SerializeField] private GameObject arrowPrefab;
    [SerializeField] private Transform spawnPoint;
    [SerializeField] private float projectileSpeed = 10.0f;
    [SerializeField] private float xSpacing = 2.0f;
    [SerializeField] private float yRot = 35.0f;
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Alpha2))
        {
            for (int i = 0; i < 3; i++)
            {
                // Center arrow group with player
                float spawnPointShiftPos = spawnPoint.position.x + xSpacing * i;
                // Space arrows out evenly on the x-axis.  Added 2.0f to z-axis for extra distance from player for testing.
                Vector3 arrowPosition = new Vector3(spawnPointShiftPos, spawnPoint.position.y, spawnPoint.position.z + 2.0f);
                // Rotate each arrow separately.  -45.0f to create a rotation starting point.
                Quaternion arrowRotation = Quaternion.Euler(0, arrowPrefab.transform.rotation.y -  45.0f + yRot * i, 0);
                //Instantiate arrows and add seperate rotations
                GameObject projectile = Instantiate(arrowPrefab, arrowPosition, arrowRotation);
                //move arrows in direction of arrow rotation
                projectile.GetComponent<Rigidbody>().velocity = projectile.transform.forward * projectileSpeed;
                Destroy(projectile, 2f);
            }
        }
    }
}
1 Like

Nice, that’s better.
Although now you don’t keep references to your projectiles, but ok, maybe you don’t need to.

1 Like