Creating a Asteroids Flash Game Part 5: Creating and Firing bullets, and making objects “wrap around” the screen

Chris Moeller Asteroids Flash Game Part 5- bullets and wrapping

In the last section, we added keyboard input to the ship, so that we could rotate in any direction, and move forward.

In this section, we’ll create bullets that are fired from the front of the ship at a limited rate, and make both the ship and the bullets wrap around when they reach the edge of the screen.

Making the ship wrap to the other side of the screen

The first thing we’ll do is setup the ship to loop around the screen when it reaches the edge. To do this, we’ll need to modify “Ship.as“.

package Entities
{
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.geom.Rectangle;

	public class Ship extends GameSprite
	{
		private var rotate_amount:int;
		public var rotate_offset:Point;

		public var speed:Point;
		private var speed_multi:Number = .5;
		private var friction:Number = .95;

		public function Ship(x:int, y:int, width:int, height:int)
		{
			super(x, y, width, height);
			image_sprite = new Sprite();
			image_sprite.graphics.lineStyle(2, 0xFFFFFF);
			rotate_amount = 15;
			speed =  new Point(0, 0);
			rotate_offset = new Point(width / 2, height / 2);

			//create the ship drawing
			image_sprite.graphics.moveTo(0, height);
			image_sprite.graphics.lineTo(width / 2, 0);
			image_sprite.graphics.lineTo(width, height);

			//draw the line across
			image_sprite.graphics.moveTo((7*height/8 -height)/(-height/(width/2)), 7*height/8 );
			image_sprite.graphics.lineTo((7 * height / 8 -height) / (height / (width / 2)) + width, 7 * height / 8);
		}
		override public function Render():void
		{
			var matrix:Matrix = new Matrix();
			matrix.translate(-rotate_offset.x, -rotate_offset.y);
			matrix.rotate(angle);
			matrix.translate(rotate_offset.x, rotate_offset.y);
			matrix.translate(x, y);

			Game.Renderer.draw(image_sprite, matrix);
		}
		override public function Update():void
		{
			x += speed.x;
			y += speed.y;
			speed.x *= friction;
			speed.y *= friction;
			var angle_deg:int = angle * (180.0 / Math.PI);

			if (x + width <= 0)
				x = Game.Renderer.width - width;
			else if(x >= Game.Renderer.width)
				x = 0;

			if (y + height <= 0)
				y = Game.Renderer.height - height;
			else if(y >= Game.Renderer.height)
				y = 0;
		}
		public function RotateLeft():void
		{
			//first convert angle in rads
			var angle_deg:int = Math.round(angle * (180.0 / Math.PI));
			angle_deg -= rotate_amount;
			angle = angle_deg * (Math.PI / 180.0);
		}
		public function RotateRight():void
		{
			var angle_deg:int = angle * (180.0 / Math.PI);
			angle_deg += rotate_amount;
			angle = angle_deg * (Math.PI / 180.0);
		}
		public function Thrust(dir:int=1):void
		{
			var angle_deg:int = angle * (180.0 / Math.PI);
			if (dir == 1)
			{
				speed.x +=speed_multi * Math.sin(angle);
				speed.y -= speed_multi * Math.cos(angle);
			}
			else
			{
				speed.x -=speed_multi * Math.sin(angle);
				speed.y += speed_multi * Math.cos(angle);
			}
		}
	}
}

We only updated the Update.as function here.

line 53: Check to see if the right side of the ship is off of the left side of the screen, and place it on the right side(so it looks like it goes through the right side and comes through the left side)

line 58: We do the same thing witht he height- check if the top or bottom of the ship is off screen, then move it to the other side of the screen.

Creating Bullets and firing

The next thing we need to do is create a “bullet” class based on the GameSprite class, and have the ship fire them from the front of the ship, in the same angle it is facing.

So again, right click on the ‘Entities’ folder, add->new class, name it “Bullet’, browse for the base class (GameSprite) and click the checkbox for “Generate constructor matching base class”.

So we create Bullet.as:

package Entities
{
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	/**
	 * ...
	 * @author Chris
	 */
	public class Bullet extends GameSprite
	{
		private var speed:Point;
		private var max_speed:Number;
		public var life:Number;
		public function Bullet(x:int, y:int, life:Number, angle:Number = 0)
		{
			super(x, y, 2, 2, angle);
			max_speed = 10;
			image = new BitmapData(width, height, false, 0xFFFFFF);
			this.speed = new Point(max_speed*Math.sin(angle), -max_speed*Math.cos(angle));
			this.life = life;
			this.angle = angle;
		}
		override public function Render():void
		{
			Game.Renderer.copyPixels(image, new Rectangle(0, 0, width, height), new Point(x, y));
			super.Render();
		}
		override public function Update():void
		{

			x += speed.x;
			y += speed.y;	

			if (x + width <= 0)
				x = Game.Renderer.width - width;
			else if(x >= Game.Renderer.width)
				x = 0;

			if (y + height <= 0)
				y = Game.Renderer.height - height;
			else if(y >= Game.Renderer.height)
				y = 0;

			super.Update();
		}
	}
}

The only really new things we’re doing here is passing in the “life” variable. This is will just be the “current_time” variable from the Game class, and will let the bullet know when it was created.

Using this, we can see how long the bullet has been “alive”, and remove it when it has been around for too long.

The final step is updating Game.as to create the bullets when the spacebar has been pressed:

package
{
	import Entities.Bullet;
	import Entities.Ship;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.KeyboardEvent;
	import flash.geom.Rectangle;
	import flash.utils.getTimer;

	public class Game
	{
		public var bitmap:Bitmap;
		public static var Renderer:BitmapData;

		private var ship:Ship

		private var keys_down:Array

		private const LEFT:int = 37;
		private const UP:int = 38;
		private const RIGHT:int = 39;
		private const DOWN:int = 40;
		private const SPACE:int = 32;	

		public static var current_time:Number; //used to track the time of the current frame
		private var bullets:Array; //used to hold all the bullets
		private var firing_delay:Number; //the delay before creating a new bullet when the space bar is held down
		private var last_fired:Number; //the time the last bullet was created
		private var bullets_max_life:Number; //the max life of the bullets

		public function Game(stageWidth:int, stageHeight:int)
		{
			trace("Game created");
			Renderer = new BitmapData(stageWidth, stageHeight, false, 0x000000);
			bitmap = new Bitmap(Renderer);

			ship = new Ship(Renderer.width / 2 - 6, Renderer.height / 2 - 10, 10, 18);
			keys_down = new Array();
			bullets = new Array();
			firing_delay = 200;
			last_fired = 0;
			bullets_max_life = 1000;
		}
		public function Render():void
		{
			Renderer.lock();
			Renderer.fillRect(new Rectangle(0, 0, Renderer.width, Renderer.height), 0x000000);

			ship.Render();
			for (var i:int = 0; i < bullets.length; i++)
				bullets[i].Render();

			Renderer.unlock();
		}

		public function Update():void
		{
			current_time = getTimer();

			if (CheckKeyDown(LEFT))
				ship.RotateLeft();

			if (CheckKeyDown(RIGHT))
				ship.RotateRight();

			if (CheckKeyDown(UP))
				ship.Thrust(1);
			if (CheckKeyDown(DOWN))
				ship.Thrust( -1);

			if (CheckKeyDown(SPACE) && current_time-last_fired > firing_delay)
			{
				var x:int = 0 * Math.cos(ship.angle) + ship.rotate_offset.y * Math.sin(ship.angle)+ship.x+ship.rotate_offset.x;
				var y:int = 0 * Math.sin(ship.angle) - ship.rotate_offset.y * Math.cos(ship.angle)+ship.y+ship.rotate_offset.y;

				bullets.push(new Bullet(x, y, current_time, ship.angle));
				last_fired = current_time;
			}

			ship.Update();

			var bullets_to_delete:Array = new Array();
			for (var i:int = 0; i < bullets.length; i++)
			{
				bullets[i].Update();
				if (current_time-bullets[i].life > bullets_max_life)
					bullets_to_delete.push(i);
			}
			for (var j:int = 0; j < bullets_to_delete.length;j++ )
			{
				bullets.splice(bullets_to_delete[j], 1);
			}
		}

		public function KeyUp(e:KeyboardEvent):void
		{
			//position of key in the array
			var key_pos:int = -1;
			for (var i:int = 0; i < keys_down.length; i++)
				if (e.keyCode == keys_down[i])
				{
					//the key is found/was pressed before, so store the position
					key_pos = i;
					break;
				}
			//remove the keycode from keys_down if found
			if(key_pos!=-1)
				keys_down.splice(key_pos, 1);
		}

		public function KeyDown(e:KeyboardEvent):void
		{
			//check to see if the key that is being pressed is already in the array of pressed keys
			var key_down:Boolean = false;
			for (var i:int = 0; i < keys_down.length; i++)
				if (keys_down[i] == e.keyCode)
					key_down = true;

			//add the key to the array of pressed keys if it wasn't already in there
			if (!key_down)
				keys_down.push(e.keyCode);
		}

		public function CheckKeyDown(keycode:int):Boolean
		{
			var answer:Boolean = false;
			for (var i:int = 0; i < keys_down.length; i++)
				if (keys_down[i] == keycode)
				{
					answer = true;
					break;
				}
			return answer;
		}
	}
}

There's several new things here.
Line 73: Used to check if the spacebar is held down, and enough time has passed since the last bullet was fired
Line 75 and 76: Used to find the coordinates of the tip of the ship when rotated, to generate the bullet at
Line 84: The start of the area to look through the bullets, find the ones that are old enough to need to be deleted, then added to the array to then delete.
Line 93: Removing the expired bullets from the bullets array

Resouce for rotation around a point:
http://en.wikipedia.org/wiki/Rotation_(mathematics)

Tutorial Demo

In the next section we’ll make the asteroids, so that we have some "opponents!".

Download the source Code and please leave me any comments or feedback you have.

Other Articles in this Series

Bookmark the permalink.

23 Comments

  1. Why do the controls allow nearly every combination except moving forward, shooting, and turning left and the same time?
    I can’t figure out why this would be since moving backwards while shooting and turning works, and moving forwards while shooting and turning right works.

    • I’m not sure either- looking online I’m not finding much information, and the way I’m capturing the keys shouldn’t be limited to the number of keys being held down.

      Someone said it might be a limit of the keyboard- allowing x number of keys to be held down at a time- not sure why it sometimes works and sometimes doesn’t (if I hold down forward, then turn, then space, it will work on all three, but not pushing space last…)

    • Yeah, here’s an online program app – pressing ‘dfg’ at once doesn’t work, but ‘asd’ does…. it would be worth looking into, if I would want a multiplayer game on the same keyboard.

  2. I made a small modification in Game.as to the bullet firing so instead of firing 1 bullet at a time, it spawns 3 to simulate a 3 way shot, like so:

    bullets.push(new Bullet(x,y,current_time,ship.angle-0.1));
    bullets.push(new Bullet(x,y,current_time,ship.angle));
    bullets.push(new Bullet(x,y,current_time,ship.angle+0.1));

    For some reason, one of the 3 bullets fired gets killed off early, mostly if SPACE is released then pressed rather than constatly held down.

    Any ideas why?

    • Yeah, the reason that happens is because of how it is setup to remove bullets.

      A better way to remove the bullets is to go through the array backwards- because right now if it removes a bullet at the beginning of the array, and has one after that also needs to be removed, the index will change.

      So imagine:
      0 1 2 3 4 [position in the array]
      o o x x o [x will be removed, o will not]

      if it removes position 2, position 4 will no longer exist, and position 4 will become position 3, so 3 should have been deleted, but won’t, and 4 will get deleted instead.

      The way to fix it is to replace:
      for (var j:int = 0; j < bullets_to_delete.length;j++ ) with: var bullets_to_delete_length:int = bullets_to_delete.length; for (var j:int = bullets_to_delete_length-1; j > -1;j–)

      which will instead go through the array backwards, deleting things from the end first, so it won’t change the next ones indexes.

      • Ah!

        I see exactly what you mean, I should have realised – doh!

        Cheers for both the quick reply and taking the time to write such a detailed tutorial in the first place 😀

  3. I’m a bit puzzled why the ‘this’ keyword is used in the Bullet constructor for this.speed, this.life and this.angle. Is ‘this’ not implied by the fact that these variables are declared in the constructor? Would it not be enough to just say speed, life and angle?

    • When you use the keywords ‘this’ – it is talking about that objects version of the variables.

      I passed in variables of the same name in the function- so if you referred to life’ in the constructor, it would think you are talking about the passed in ‘life’ variable, not the ‘life’ variable of the class, so your objects ‘life’ would be null, and it would only assign the passed in ‘life’ variable back onto itself.

      You could get around this by using different names- such as:

      function DoSomething(x1:int, y1:int, life1:int):void
      {
      x=x1;
      y=y1;
      life=life1;
      }

      And it would know that you are talking about the objects. But generally, if you want to pass in a variable with the same name as one that is defined in the class, you have to use the ‘this’ keyword to let it know you’re talking about your class’s version.

      • Thanks for the help. Nice tutorial, I’m new to ActionScript & think I’m getting a bit stretched at this point!
        Just thought it might be helpful to point out there are a lot of ‘&&’ instead of ‘&&’ in the class sections on this page and the rest of the tutorial.

        • amp;amp; instead of && !

          • Son of a… :O

            Thanks for letting me know, I just went through and removed all the & lt; and & gt;, and didn’t realize it also had converted the plain amp; as well 😮

            Let me know if you come across any others- I’ll go through the series and change them out, but any time I edit it, wp-syntax, or wordpress changes out all the symbols in the code with html symbols 😮

  4. I am wondering then if I am having the same issue on part of this.
    For some reason my bullets are never deleting. If I shoot,
    they continue to trail across the screen indefinitly
    until I close the game. The code I see on my end while
    doing the tutorial is:

    if (CheckKeyDown(SPACE) && current_time-last_fired > firing_delay)

    Not sure if thats what is causing the issue, but I get no
    syntax errors, and the game starts fine up to this point.

    • The code to delete them is:

      if (current_time-bullets[i].life > bullets_max_life)
      bullets_to_delete.push(i);

      and:
      bullets.splice(bullets_to_delete[j], 1);

      so to check what is going wrong, you can trace out the values to see what they are, such as above the ‘if’ statement:

      trace(” bullet age: “+(current_time-bullets[i].life+” , bullets_max_life: “+bullets_max_life);

      so then you have to check if the bullet age is increasing like it should, or if the max life is accidentally set very high. When the bullet is older than the life, it will splice them out of the array, and they won’t be able to render any more.

  5. Can’t seem to get any bullets to fire even though my codes the same. Checked multiple times. Maybe something to do with the ‘render’ or &&?
    Help?

    • You can send me your code if you can’t figure it out, and I can take a look.

      It’s best to ‘trace’ things out to see to make sure things are happening when they are supposed to.
      I’d say when you add a bullet to the array have it trace(‘added bullet’);, when you update/render the bullet, trace out :trace(‘bullet updating’);

      That is the best way to track down the problem- validate each thing. Even try not rendering the ship to see if the bullets are being rendered behind it, and just aren’t updating and moving across the screen.

      Let me know!

  6. Hey, I recently discovered a rather severe flaw in the removal system shown in this tutorial. When an object is removed, every other object shifts a place, so, in the event that two objects are to be removed at the same time, a glitch will occur. I have discovered a solution to this, and it is as follows: Rather than iterating through the *object*storemove array and splicing from the main array at that number, iterate backwards through the *object*s array checking if each object is within the *object*storemove array via indexof and splicing it out if so.

  7. First off, AWESOME tutorial!

    One question:

    I’m trying to figure out how to modify the code to have different sized bullets and ships. I’ve got this working fine. However, when I change the size of bullets or ships, the bullets don’t originate from the point of the ship any longer. Instead, depending on the ship’s angle, they come out slightly to the left or right of the ship’s front point.

    Would you mind helping me figure out how to make the bullets come from the front point of the ship regardless of ship / bullet size?

    Thanks!

  8. my code in the lines “Line 75 and 76: Used to find the coordinates of the tip of the ship when rotated, to generate the bullet at” the program says

    COL:103 Eror: Attenpted access f inaccessible property rotate_offset through a reference with static type

    What should I do?? I tried everything, but I didn’t understand these especifcs lines of the code

    Thanks

  9. Whenever I run the game it works just fine until I press the spacebar to shoot. Then the entire thing freezes and I have to close it.
    I get this error:
    [Fault] exception, information=ArgumentError: Error #2015: Invalid BitmapData.
    Any ideas on why this is happening?

    • It sounds like the bullet bitmap data is invalid somehow.Check the code for it, even try setting it up like the ship, just to make sure it is drawing correctly. Trace out the value, it shouldn’t be null, and should have a width/ height

  10. In:

    0 * Math.cos(ship.angle) + ship.rotate_offset.y * Math.sin(ship.angle)+ship.x+ship.rotate_offset.x;

    What does the “0 * Math.cos(ship.angle)” part do? I get the same result without it aswell…

    • Yeah, that is more for understanding than anything- the 0 part just cancels it out. But it is just showing the complete formula for figuring out where the bullets should spawn (If I remember correctly). If you try changing 0 to something else, you’ll see the bullets spawn at a different position (so you can eliminate it to save a little processing power, but if yuou want to use that formula again in the future and look back, it might be good to have it there for understanding, or even to keep it as a comment above those lines)

Leave a Reply

Your email address will not be published. Required fields are marked *