Radial/omni directional shooting Problem

Hey guys.

Edit: Added images to bottom of post

I've currently managed to script my enemy shot upto a certain point, however I wish to have the enemy shoot in all directions, like a ring around it. From what I appear to understand, this will require s quick math algorithm, inputting the distance from the object, the number of projectiles and the angle for them.

So far I have this:

    using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour
{
    public float MinSpeed;
    public float MaxSpeed;

    public float currentSpeed;
    private float x, y, z;
    public GameObject Projectile;
    float firingRate = 1.0f; //delay between shots, in seconds
    float lastFired = -100f; //absolute time of last fired shot

    // Use this for initialization
    void Start()
    {
        //currentSpeed = Random.Range(MinSpeed, MaxSpeed);
        currentSpeed = 1;
        //x = Random.Range(1f, 1f);
        y = 7.0f;
        z = 0.0f;

        transform.position = new Vector3 (x, y, z);
    }

    // Update is called once per frame
    void Update()
    {
        float amtToMove = currentSpeed * Time.deltaTime;
        transform.Translate(Vector3.down * amtToMove);

        if (Time.time < lastFired + firingRate)
        {
            return;
        }

        if (transform.position.y <= -6.0)      
        {
            currentSpeed = 1    ;
            transform.position = new Vector3(x, y, z);
        }
        lastFired = Time.time;

        if (transform.position.y >= 0 && transform.position.y <= 1)
        {
            Vector3 rightposition = new Vector3(transform.position.x + transform.localScale.x * 8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, rightposition, transform.rotation);

            Vector3 right2position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right2position, Quaternion.Euler(0, 0, 20));

            Vector3 right3position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right3position, Quaternion.Euler(0, 0, 40));

            Vector3 right4position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right4position, Quaternion.Euler(0, 0, 60));

            Vector3 right5position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right5position, Quaternion.Euler(0, 0, 70));

            Vector3 right6position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right6position, Quaternion.Euler(0, 0, 80));

            Vector3 right7position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right7position, Quaternion.Euler(0, 0, 85));

            Vector3 right8position = new Vector3(transform.position.x + transform.localScale.x * -8, transform.position.y + transform.localScale.y * 5);
            Instantiate(Projectile, right8position, Quaternion.Euler(0, 0, 90));

        }

        }
}

After searching through the forums I came upon the following topic, in which someone asked a very similar question. Shooting in Multiple Directions

He was provided with the following code.

var speed : float = 1.0;

function Update ()
{
   transform.Translate(Vector3.forward * Time.deltaTime * speed);
}

var projectile : GameObject;
var amount : int = 16;

function Update () {
   if( Input.GetButtonUp( "Jump" ) )
   {
      for (var i = 1; i <= amount; i++){

         var instantiatedProjectile : GameObject = Instantiate( projectile, transform.position, transform.rotation );
         instantiatedProjectile.transform.eulerAngles = Vector3(0, 360/amount*i, 0);
      }
   }
} 

var projectile : GameObject;
var amount : int = 3;

function Update () {
   if( Input.GetButtonUp( "Jump" ) )
   {
      for (var i = 0; i <= amount; i++){

         var instantiatedProjectile : GameObject = Instantiate( projectile, transform.position + transform.forward*5, transform.rotation );
         instantiatedProjectile.transform.eulerAngles = Vector3(0,  (-30)+(60/amount*i)  , 0);
      }
   }
} 

Its clear that this script is precisely what I'm after, but unfortunately I've attempted to implement it and failed a few times. If someone could please help me to translate this script into my script it would be greatly appreciated.

http://mirror05.x264.nl/Dark/x264vsElecard/xvid.png Example2

You should consider describing what you want and what exactly about your results is not what you expect. It's very hard to provide solutions for "I tried but it isn't working" without knowing how it isn't working.

Your code

You're looking to generate a pattern of projectiles in 2D space around a center point? Well then you would do something much as you were before, but you would ensure that you are instantiating not only with new rotations, but at positions that are rotated about the center point as well.

I'm not sure if this was intentional, but as your code is posted, you are instantiating all projectiles but one on the top-left of the object (assuming positive x is right and positive y is up), one oriented the same as the object to which the script is attached and the others oriented at angles rotated about the z axis.

The following is designed with your posted implementation:

using UnityEngine;
using System.Collections;

public class Enemy : MonoBehaviour {
    public float MinSpeed = 1f; //min speed to move down at
    public float MaxSpeed = 1f; //max speed to move down at

    public float currentSpeed; //speed to move down at
    public Transform Projectile; //what to shoot
    public float[] angles = new float[] {-40f, -35f, -25f, -10f, 0, 10f, 25f, 35f, 40f};
    private float x; //random x position
    float firingRate = 1.0f; //delay between shots, in seconds
    float lastFired = -100f; //absolute time of last fired shot

    void Start() { //Initialization
        currentSpeed = Random.Range(MinSpeed, MaxSpeed); //Random down speed
        x = Random.Range(0f, 1f); //random x position
        transform.position = new Vector3(x, 7.0f, 0.0f);
    }

    void Update() { //Every Frame
        float amtToMove = currentSpeed * Time.deltaTime; //how far to move this frame
        transform.Translate(-Vector3.up * amtToMove); //move down

        if (Time.time < lastFired + firingRate) return; //we just fired

        //reset position if we've fallen too far
        //You might consider a level manager script elsewhere to destroy enemies
        //that go off the screen and spawn new ones as appropriate
        if (transform.position.y <= -6.0) {
            currentSpeed = Random.Range(MinSpeed, MaxSpeed);
            transform.position = new Vector3(x, 7.0f, 0.0f);
        }

        lastFired = Time.time; //We're firing
        //If we're off the screen, should we really be firing?
        if (transform.position.y < 0f || transform.position.y > 1f) return;
        //We'll only ever fire once with this condition at a speed of 1
        //And we'll fire at the exact same time every pass as the fire rate is also 1

        //Rotating the shot position and orientation is easier if they are aligned
        //get the distance from the center
        //I assume this is to avoid the projectile overlapping the object
        //and uses scale to appropriately scale as the object scales
        Vector3 localShotPos = new Vector3(0, -((new Vector2(transform.localScale.x*8f,
                                       transform.localScale.y*5f)).magnitude));
        //Since your scene appears to only be 1-7 units large,
        //aren't these sizes (8 and 5) a little big?

        foreach(float angle in angles) {
            Quaternion rotation = Quaternion.AngleAxis(angle, transform.forward);
            Vector3 shotPosition = transform.position + rotation * localShotPos;
            Instantiate(Projectile, shotPosition, rotation * transform.rotation);
        }
    }
}

If you wanted a circular spread, you would only need to set a number of shots and change the foreach loop to something like the following:

float degree = 360f/numberOfShots;
for(float i = -180f; i < 180f; i += degree) {
    Quaternion rotation = Quaternion.AngleAxis(i, transform.forward);
    Vector3 shotPosition = transform.position + rotation * localShotPos;
    Instantiate(Projectile, shotPosition, rotation * transform.rotation);
}

To constrain the circle by some amount and evenly distribute the shots, you would just calculate based on the constraint:

float degree = constraint/ numberOfShots;
for(float i = -constraint/2f; i < constraint/2f; i += degree) {
    Quaternion rotation = Quaternion.AngleAxis(i, transform.forward);
    Vector3 shotPosition = transform.position + rotation * localShotPos;
    Instantiate(Projectile, shotPosition, rotation * transform.rotation);
}

Essentially by controlling your angle increments, you could divine any kind of shot pattern. If you wanted a spiral, at each shot you would just add the extra rotation to your loop controls (or rotate your enemy).

for(float i = -constraint/2f + adjustment; i < constraint/2f; i += degree){
    Quaternion rotation = Quaternion.AngleAxis(i, transform.forward);
    Vector3 shotPosition = transform.position + rotation * localShotPos;
    Instantiate(Projectile, shotPosition, rotation * transform.rotation);
}
adjustment += degree/spiralSteps; //number of rotations before returning to the original

If you wanted your shots centered on some target, there are slightly smarter ways, but the simple approach is to just preform the rotation by the amount to aim at the target first by setting it as the adjustment above:

adjustment = Vector3.Angle(-transform.up, target.position - transform.position);

For the first example, you would add the adjustment to the angle when you calculate the rotation:

Quaternion rotation = Quaternion.AngleAxis(angle+adjustment, transform.forward);

The code you found

As with the code you found and included though, it is actually quicker and easier to instantiate the objects at the center of the transform and then move/rotate from there rather than instantiating at the correct position/rotation. You would do much the same, but with less work:

//You don't need to worry about calculating any positions or anything first
foreach(float angle in angles) {
    Transform instance = (Instantiate(Projectile, transform.position, transform.rotation)
                         as GameObject).transform;
    instance.eulerAngles.z += angle; //rotate
    instance.position += -instance.up * (new Vector2(transform.localScale.x * 8f,
                                         transform.localScale.y * 5f)).magnitude; //offset
}

Also, using that method, translating the script included would give you something like:

//Circle
for(int i = 0; i < numberOfShots; i++) {
    Transform instance = (Instantiate(Projectile, transform.position, transform.rotation)
                         as GameObject).transform;
    instance.eulerAngles.z += i * 360 / numberOfShots; //rotate
    //They didn't offset and they are dividing and multiplying for every shot
}

//Circle subsection
for(int i = 0; i < numberOfShots; i++) {
    Transform instance = (Instantiate(Projectile, transform.position, transform.rotation)
                         as GameObject).transform;
    instance.eulerAngles.z += i * sectionSize/numberOfShots - sectionSize/2; //rotate
    //The extra division is so that the section aligns with your original direction
}

//Note that the method in the previous section does fewer divisions/multiplications
//at the expense of using floating point numbers to control the loop.
//Circle which only does one division
float degree = 360f/numberOfShots;
for(float i = 0f; i < 360f; i += degree) {
    Transform instance = (Instantiate(Projectile, transform.position, transform.rotation)
                         as GameObject).transform;
    instance.eulerAngles.z += i; //rotate
}
//Likewise for the subsection