SHMUP Tutorial 3: Moving Through Space

Welcome to part 3 of my SHMUP tutorial series! We are going to jump right in from part 2 of the series. If you didn’t follow part 2 you can download this project file to follow along.

When we last left off, we added in an enemy that moves back and forth and makes you teleport when you get hit.
Let’s start off with a new image for this tutorial!

Go ahead and save that in the same folder that you saved the ship image. Next we will go into our “Assets.as” file and add in the stars.

[Embed(source = "Images/stars.png")]
public static const GRAPHIC_STARS:Class;

It should look something like this. Just make sure that you select the right path for your Embedding.

Next, let’s make a large update to our “GameWorld.as”

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Backdrop;
	import net.flashpunk.World;
 
	public class GameWorld extends World 
	{
 
		public static var _player:Player = new Player;
		private var cameraSpeed:Number = 50; 
		private var nextSpawn:Number = 1;
 
		public function GameWorld() 
		{
			_player.x = 64;
			_player.y = 240;
			add(_player);
 
			var b:Backdrop = new Backdrop(Assets.GRAPHIC_STARS, true, true);
			b.scrollX = 0.5;
			b.scrollY = 0.5;
 
			var e:Entity = new Entity;
			e.graphic = b;
			e.layer = 100;
			add(e);
 
		}
 
		override public function update():void
		{
			super.update();
			camera.x += cameraSpeed * FP.elapsed;
			_player.x += cameraSpeed * FP.elapsed;
			nextSpawn -= FP.elapsed;
			if (nextSpawn <= 0) {
				nextSpawn = 1;
				var _enemy:Enemy = create(Enemy) as Enemy;
				_enemy.x = FP.camera.x + FP.width + 100;
				_enemy.y = FP.rand(FP.height);
				add(_enemy);
			}
 
		}
 
	}
}

Don’t worry, I’ll walk you through all of the changes!

public static var _player:Player = new Player;
private var cameraSpeed:Number = 50; 
private var nextSpawn:Number = 1;

First off, we are moving the Player variable out of the GameWorld function and turning it into a static so that other classes can easily get a reference to our player later. We also declare a couple number variables that we will be using in our update function.

var b:Backdrop = new Backdrop(Assets.GRAPHIC_STARS, true, true);
b.scrollX = 0.5;
b.scrollY = 0.5;
 
var e:Entity = new Entity;
e.graphic = b;
e.layer = 100;
add(e);

Hopefully you noticed that I removed the code to add a new enemy here, since we will be spawning enemies in our update function.
Next I declare a Backdrop, which is a very handy class in Flashpunk that takes an image and then if you so desire, tiles it horizontally, and vertically (the two trues in the initialization). If it’s set to tile, it will tile to cover the entire screen. Next you will notice that I set it’s scrollX and scrollY to 0.5, this makes it so that when we start moving our World’s camera, this image will only move 0.5 pixels for ever 1 pixel that the camera is moved. This can be very handy for creating a nice parallax effect.
After all of that, we create a new entity, set it’s graphic to the Backdrop we made, and set it’s layer to 100 so that it will render behind our Player and enemies.

override public function update():void
{
	super.update();

Now we are overriding our world’s update function just like we did in our Player and Enemy entities, but there is something important to note here. Unlike our Player and Enemy, we also call a super to the original update function. If we don’t do this, your game will not run. This is the function that loops through all of the entities that are added to the world and then updates them, so if you wan’t to add onto it, make sure you add in a super.update(); to call it’s original function.

camera.x += cameraSpeed * FP.elapsed;
_player.x += cameraSpeed * FP.elapsed;

This is what makes our world move. This will also make enemies move towards our player right now since their X locations are not being updated with the camera.

nextSpawn -= FP.elapsed;
if (nextSpawn <= 0) {
	nextSpawn = 1;
	var _enemy:Enemy = create(Enemy) as Enemy;
	_enemy.x = FP.camera.x + FP.width + 100;
	_enemy.y = FP.rand(FP.height);
	add(_enemy);
}

This is a really easy way to use a variable for a timer in flashpunk, all you are doing is subtracting the time elapsed between updates- this number will add up to 1 after a full second of updating. Then when it reaches 0 we run some code that resets it’s value to 1 so it will start the process all over again after another second.
The rest of the code is very similar to what we deleted from our GameWorld function for adding a single enemy, but this time instead of using new Enemy, we are using the create method with flashpunk. This is an extremely useful method and will be crucial if you want to spawn a lot of enemies and bullets later on. Instead of creating a new enemy everytime this is run, it will see if there are any instances of the enemy class that aren’t being used and add recycle that to be used instead. If it can’t find a free instance it will just create a new one.
Setting the x is just taking our current camera’s location, adding the width of the screen, and another 100 pixels to make sure it’s off the screen when it’s added. The y is using the Flashpunk rand function for a random number between 0 and the height of our game.

Now, if you tried to run the game right now, you would notice something right away. Our player cannot keep up with the camera since it’s movement is locked into a box that our camera quickly scrolls past. Let’s do some updates to our “Player.as” file next.

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.utils.Input; 
	import net.flashpunk.utils.Key;
	public class Player extends Entity 
	{
 
		private var moveSpeed:Number = 200;
		private var _image:Image;
 
		public function Player() 
		{
			_image = new Image(Assets.GRAPHIC_SHIP_PLAYER);
			_image.centerOO();
			graphic = _image;
			Input.define("right", Key.RIGHT, Key.D); 
			Input.define("left", Key.LEFT, Key.A);
			Input.define("down", Key.DOWN, Key.S);
			Input.define("up", Key.UP, Key.W);
			type = "Player";
			setHitbox(32, 32, 16, 16);
			FP.console.enable();
		}
 
		override public function update():void
		{
			if (Input.check("right")) x += moveSpeed * FP.elapsed; 
			if (Input.check("left")) x -= moveSpeed * FP.elapsed; 
			if (Input.check("down")) y += moveSpeed * FP.elapsed; 
			if (Input.check("up")) y -= moveSpeed * FP.elapsed; 
			if (x < 32 + FP.camera.x) x = 32 + FP.camera.x;
			if (x > FP.width - 32 + FP.camera.x) x = FP.width - 32 + FP.camera.x;
			if (y < 32) y = 32 ;
			if (y > FP.height - 32) y = FP.height - 32;
			if(_image.alpha >= 1) {
				if (collide("Enemy", x, y)) {
					x = FP.camera.x + 32;
					y = FP.halfHeight;
					_image.alpha = 0;
				}
			} else {
				_image.alpha += FP.elapsed;
			}
		}
	}
}

Great, I did more than just fix that didn’t I? Well I’ll walk you through all of the updates I did here too!

private var _image:Image;
 
	public function Player() 
	{
		_image = new Image(Assets.GRAPHIC_SHIP_PLAYER);
		_image.centerOO();
		graphic = _image;

First off, we are no longer making setting our graphic to a new image directly and now have it declared as a variable that can easily be accessed later.

if (x < 32 + FP.camera.x) x = 32 + FP.camera.x;
if (x > FP.width - 32 + FP.camera.x) x = FP.width - 32 + FP.camera.x;

Adding in our camera’s X location fixes our movement problems and now our bounding box that keeps us from moving off the screen stays locked in place with the camera.

if(_image.alpha >= 1) {
	if (collide("Enemy", x, y)) {
		x = FP.camera.x + 32;
		y = FP.halfHeight;
		_image.alpha = 0;
	}
} else {
	_image.alpha += FP.elapsed;
}

Since our enemies are no longer staying on the right side of the screen, I though it would be a good idea to toss in a little invulnerability and visual indication after dying. Instead of creating a new variable for this, we can just use our images alpha value for when to run our check for colliding with enemies. Also we set our images alpha to 0 when we get hit, and we updated the x location to keep up with the camera movement. If our image isn’t fully visible, then we will just increase it’s visibility by the elapsed time between updates, this gives us a full second of invulnerability after dying.

This is all fine and dandy, but see if you notice something wrong when you compile your game. First off you might notice that some of the enemies are getting stuck on the top and bottom of the screen. You should also notice that we are constantly creating new enemies… but none of them are being removed once they go off screen! Let’s fix those two problems right now. Open up your “Enemy.as” and update it with this :

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Image;
 
	public class Enemy extends Entity 
	{
		private var _image:Image;
		private var speedY:Number = 50;
 
		public function Enemy() 
		{
			_image = new Image(Assets.GRAPHIC_SHIP_PLAYER);
			_image.centerOO();
			_image.color = 0xFF0000;
			_image.tinting = 0.7;
			_image.flipped = true;
			graphic = _image;
			type = "Enemy";
			setHitbox(32, 10, 16, 5);
		}
 
		override public function update():void 
		{
			if (y < 32 && speedY < 0) speedY *= -1;
			if (y > FP.height - 32 && speedY > 0) speedY *= -1; 
			y += speedY * FP.elapsed;
 
			if (x < FP.camera.x - 100 - width) {
				FP.world.recycle(this);
			}
 
		}
	}
}

Not too much changed here! But let’s go ahead and start going over what was updated.

setHitbox(32, 10, 16, 5);

First I made a small change to the Enemy’s hit box. It’s now a lot shorter, and it’s y offset has been updated to keep it centered. I did this because of all of the transparent space on the ships make it so that the collisions were happening before the graphics would touch each other, making the game a lot more difficult and unfair.

if (y < 32 && speedY < 0) speedY *= -1;
if (y > FP.height - 32 && speedY > 0) speedY *= -1;

This is the fix to make sure that our enemies are moving up before flipping their speed when they reach the top of the screen, and then making sure that they are moving down before flipping their speed when they reach the bottom of the screen.

if (x < FP.camera.x - 100 - width) {
	FP.world.recycle(this);
}

And that brings us to the last update in this tutorial! We now are checking if our enemy’s x location is less than the camera’s x location, minus 100 pixels, and minus it’s width. The reason I added width in there is just in case you decide to make your enemies really long, they still won’t remove themselves before they are off the screen. Next let’s cover the recycle method. This is one of my favorite functions in Flashpunk. What this does is removes the entity from the world so it stops updating, but instead of marking it for garbage collection, it keeps the instance of this class ready to be reused later with the create function. Not only do you get to save resources from not having to always create new instances of classes, but this also keeps the garbage collector from having to be called as often which also really helps performance.

You can download a zip containing the entire project for this tutorial here.

Bookmark the permalink.

Comments are closed.

Comments are closed