3
\$\begingroup\$

Basically, I'm making a bullethell / I wanna be the guy inspired game for where I'm currently creating different methods of spawning bullets into the game. One of the few methods I've created is creating a shockwave of bullets with defined number of bullets in a circular shape. The method works excellently with a for-loop and is very simple to use.

for (double angle = (0 + rotation); angle < (Math.PI * 2 + rotation); angle += Math.PI / (numberOfBullets / 2))
{
     Bullet nextBullet = new Bullet(bulletDef, position, speed, acceleration, rotation, texturePriority, color, bulletID);
     nextBullet.speed.X = (float)Math.Cos(angle) * speed.X;
     nextBullet.speed.Y = (float)Math.Sin(angle) * speed.Y;
     nextBullet.acceleration.X = (float)Math.Cos(angle) * acceleration.X;
     nextBullet.acceleration.Y = (float)Math.Sin(angle) * acceleration.Y;
     nextBullet.position = position;
     nextBullet.isRemoved = false;
     Data.tempEntities.Add(nextBullet);
}

However, I wanted to extend this method a little further with the ability of creating shockwaves in other shapes, like Triangle, Square, Pentagon and Hexagon. I however have no specific idea of how to set this up with a for-loop.

The only lead I have so far of what I could do is calculate the amount of sides each shape have (they're all going to be equilateral as having differently long sides would be a waste of time to program) so we'll take a Triangle for example; has 3 sides. We'll decide that this triangular shockwave will shoot 6 bullets so we'll define what the first two bullets speed will be with these (probably wrongly written code).

int calculateBullets = (numberOfBullets / 3);
int divider = numberOfBullets % calculateBullets;

I tried using this in a for-loop, but it gave me OutOfMemory error without any explaination of what I did that was wrong. What would be the best way to approach this predicament? Should I still use a for-loop and how will I need to set it up? Or should I attempt to use something else?

\$\endgroup\$

1 Answer 1

11
\$\begingroup\$

To maintain the shape while spreading, each bullet needs to have a different speed. Suppose you are going to create a "wave" of bullets originating from a centerpoint, what you need to do is to calculate the speed each bullet needs to have to maintain the contour of the shape. See below: the red bullet moves slower than the green bullet in order to enlarge the triangle.

Triangle shape, with red arrow a slow moving bullet, green a faster moving bullet

So this is how one could do it:

  • Define the shape as a bunch of lines.

Now there are two options:

  1. For a radial distribution: Determine the intersect vector of the line from the origin to the edge of the shape (cast rays for each 2PI/numberbullets).
  2. For an even distribution: Determine how many bullets per segment, evenly distribute (Lerp) between the endpoints of the segment.

Then based on the method (radial or evenly)

  • Create the pattern.
  • Spawn bullets at the directionVector multiplied by the speed you want the shape to grow.

If you set point a,b and c at distance=1 from the origin, bullets that travel towards a,b and c will have the vector lenght 1 and thus will travel at the speed you set. Other bullets will travel relative to that in order to maintain the shape.

If you define the shapes with lines you can create complex shapes such as stars, hearts or whatever you define.

I made some sample code to provide some insight how to do it:

Step 1: Define the polygon in lines

For now let's do simple polygons like triangles, squares and hexagons: /// /// Holds a linesegment /// private struct Linesegment { public Vector2 point1; public Vector2 point2; }

    /// <summary>
    ///  Creates a list of linesegments around (0,0) to create a polygon. The points generated have a distance of 1 from the center.
    /// </summary>
    /// <param name="segments">Number of linesegement that create the polygon, should be at least 3</param>
    /// <param name="offsetangle">Angle to rotate the generated polygon</param>
    /// <returns></returns>
    private List<Linesegment> CreateRadialPolygon(int segments, double offsetangle)
    {
        if (segments < 3)
            throw new Exception("Polgon should have at least 3 segments");

        List<Linesegment> result = new List<Linesegment>();
        double theta = (2 * Math.PI) / (double)segments;

        for(int i=0;i<segments;i++)
        {
            result.Add(new Linesegment()
            {
                point1 = new Vector2((float)Math.Cos(offsetangle + (i * theta)), (float)Math.Sin(offsetangle + (i * theta))),
                point2 = new Vector2((float)Math.Cos(offsetangle + ((i + 1) * theta)), (float)Math.Sin(offsetangle + ((i + 1) * theta)))
            });

        }
        return result;
    }

Step 2, Radial distribution

This is basic line intersection calculation. I altered it so it calculates the intersectionpoint of a segment with a ray spawning from the origin and a direction angle.

    /// <summary>
    /// The result of the intersection function.
    /// </summary>
    private struct IntersectionResult
    {
        public bool intersection;
        public bool coincident;
        public Vector2 intersectionPoint;
    }

    /// <summary>
    /// Find the intersectionpoint (if any) for a ray starting from a point in the direction of theta.
    /// </summary>
    /// <param name="point1">start of linesegment</param>
    /// <param name="point2">end of linesegment</param>
    /// <param name="origin">origin of the ray</param>
    /// <param name="theta">angle of the ray</param>
    /// <returns>an intersectionresult. If the ray intersects the line intersection is set to true, intersectionPoint contains the found intersection</returns>
    private IntersectionResult ProcessIntersection(Linesegment linesegment, Vector2 origin, double theta)
    {
        //since this is linesegment versus linesegment code; transform the ray into a point somewhere far away...
        Vector2 rayend = origin+new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * 1000;

        float ua = (rayend.X - origin.X) * (linesegment.point1.Y - origin.Y) - (rayend.Y - origin.Y) * (linesegment.point1.X - origin.X);
        float ub = (linesegment.point2.X - linesegment.point1.X) * (linesegment.point1.Y - origin.Y) - (linesegment.point2.Y - linesegment.point1.Y) * (linesegment.point1.X - origin.X);
        float denominator = (rayend.Y - origin.Y) * (linesegment.point2.X - linesegment.point1.X) - (rayend.X - origin.X) * (linesegment.point2.Y - linesegment.point1.Y);

        IntersectionResult result = new IntersectionResult { intersection = false, coincident=false,intersectionPoint=Vector2.Zero };

        if (Math.Abs(denominator) <= 0.00001f) // epsilon, check if the point is on the line (within a very small distance for rounding errors).
        {
            if (Math.Abs(ua) <= 0.00001f && Math.Abs(ub) <= 0.00001f)
            {
                result.intersection = true;
                result.coincident = true;
                result.intersectionPoint = (linesegment.point1 + linesegment.point2) / 2;
            }
        }
        else
        {
            ua /= denominator;
            ub /= denominator;

            if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
            {
                result.intersection = true;
                result.intersectionPoint.X = linesegment.point1.X + ua * (linesegment.point2.X - linesegment.point1.X);
                result.intersectionPoint.Y = linesegment.point1.Y + ua * (linesegment.point2.Y - linesegment.point1.Y);
            }
        }
        return result;
    }

Step 3, Radial distribution

Tying this all together, in this part the pattern is generated; a shape with segments is made (using step 1) and the intersections are calculated (step 2). The result is an array of Vector2 that holds the directionvector of each bullet in the pattern.

    /// <summary>
    /// Creats a pattern of bullets in a polygon shape with 'segment' sides.
    /// </summary>
    /// <param name="segments">sides the polygon should have (must be >=3)</param>
    /// <param name="offset">rotation of the polygon</param>
    /// <param name="numberofbullets">number of bullets in this pattern</param>
    /// <returns></returns>
    private Vector2[] CreateBulletPattern(int segments, double offset, int numberofbullets)
    {
        List<Linesegment> polygon = CreateRadialPolygon(segments , offset);
        List<Vector2> bulletdirections = new List<Vector2>();

        double bulletangle = (2 * Math.PI) / numberofbullets;
        for(int i=0;i<numberofbullets;i++)
        {
            foreach(Linesegment l in polygon)
            {
                //do an intersection; if the line intersects add a bullet to the pattern.
                IntersectionResult r = ProcessIntersection(l, Vector2.Zero, i * bulletangle);
                if(r.intersection)
                {
                    //check if the intersectionpoint already is in the result- 
                    //this may be if the intersectionpoint is one of the endpoints of the line segment.
                    if (!bulletdirections.Contains(r.intersectionPoint))
                        bulletdirections.Add(r.intersectionPoint);
                }
            }
        }
        return bulletdirections.ToArray();
    }

Step 4, making it work it together

Line intersections are relatively fast- you could do precalculations and store the vectors for a given shape and number of bullets for example in a Dictionary:

Dictionary<string, Vector2[]> _pattern = new Dictionary<string, Vector2[]>();

This enables you to fill it like this:

        _pattern.Add("triangle60", CreateBulletPattern(3, 0, 60));
        _pattern.Add("square80", CreateBulletPattern(4, 0, 80));
        _pattern.Add("hexagon30", CreateBulletPattern(6, 0, 30));

now each bullet can travel at a speed in the directionVector and the shape will remain the polygon you have defined. If an enemy spawns bullets in a pattern do something like this:

foreach(Vector2 directionvector in _pattern["triangle60"])
{
    SpawnBullet(startposition,directionvector,speed);
}

Improvements could be, change polygon code to create more complex shapes. Add delays to the bulletspawning code to have the pattern appear.

Using this code I produced this result (looks better when moving ofcourse):

Bullets in triangle, square and hexagon shape

Evenly spaced option

Another option is, to evenly space the bullets over the line segments. This does not require intersection calculations. By using code like this:

    /// <summary>
    /// Creats a pattern of bullets in a polygon shape with 'segment' sides.
    /// </summary>
    /// <param name="segments">sides the polygon should have (must be >=3)</param>
    /// <param name="offset">rotation of the polygon</param>
    /// <param name="numberofbullets">number of bullets in this pattern</param>
    /// <returns></returns>
    private Vector2[] CreateBulletPatternEvenlySpacedOverSegments(int segments, double offset, int numberofbullets)
    {
        List<Linesegment> polygon = CreateRadialPolygon(segments, offset);
        List<Vector2> bulletdirections = new List<Vector2>();

        int bulletspersegment = numberofbullets / polygon.Count;

        foreach(Linesegment l in polygon)
        {
            for(int i=0;i<bulletspersegment;i++)
            {
                bulletdirections.Add(Vector2.Lerp(l.point1, l.point2, (float)i / (float)bulletspersegment));
            }
        }

        return bulletdirections.ToArray();
    }

You generate the polygon in the same way as before, however the bullets are evenly spaced. This results in:

Bullets are evenly spaced

Both methods work; the outcome may look prettier for one shape than the other so you may want to experiment what looks best for you. If you create a tool to generate the shapes/linesegments you can create complex patterns.

Note that traditional bullethell type shooters often have pattern generators that spawn bullets based on angle, angle increments and delays. However since you wanted polygonlike shapes, this method suits your need.

\$\endgroup\$
5
  • \$\begingroup\$ Alright, I added the Dictionary list as you posted above since that was the only thing I had some experience with, it's setup the way you explained it and it appears to be correct. I have never programmed with "lines" as you refer to here, since I'm still a beginner student (sort of). I feel like I need to look into how line intersections and etc work, but I have not been able to find anything of use. Know any source of where I can look into this? \$\endgroup\$
    – A. Ben
    Commented Jun 30, 2016 at 21:38
  • \$\begingroup\$ This covers it: stackoverflow.com/questions/14307158/… theta is the angle for your bullet, origin is well the origin of the bullet. Then you check for each segment if it intersects, if so: the directionvector equals the intersectionpoint minus origin. \$\endgroup\$
    – Felsir
    Commented Jul 1, 2016 at 5:58
  • \$\begingroup\$ I create a quick test application, the code is in the answer. \$\endgroup\$
    – Felsir
    Commented Jul 1, 2016 at 9:18
  • \$\begingroup\$ Also, added code to create an even distribution- this produces nice results without the need for calculating intersection points. \$\endgroup\$
    – Felsir
    Commented Jul 1, 2016 at 11:44
  • \$\begingroup\$ You sir, are a scholar. Absolutely incredible and extremely useful for the game I'm developing. Massive thanks for introducing me to this and teaching me some. I'll definitely look into this as I go along with programming, and again thank you. \$\endgroup\$
    – A. Ben
    Commented Jul 1, 2016 at 14:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .