SHMUP Tutorial 7: Lives and Scoring

Welcome to part 7 of the SHMUP tutorial series! If you didn’t follow part 6 , you can download this project file to follow along! Today we are going to add in a scoring system, lives, and a graphical user interface to display both!

First off, we have a new image for this tutorial! Go ahead and save this in your images folder.

Next off we are going to open up our “Assets.as” and embed our new image!

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

Go ahead and add that in after all of the other images we’ve already embedded.

Now open up your “GC.as” file and add in this constant :

public static const LAYER_GUI:int = 0;

Go ahead and add that in after the other layer constants.

Next we need to update our “GV.as” file!

package  
{
	public class GV 
	{
		public static var PARTICLE_EMITTER:ParticleController;
		public static var CURRENT_GUI:GUI;
		public static var PLAYER:Player;
		public static var POINTS:Number;
		public static var LIVES:Number;
 
		public static function reset():void
		{
			POINTS = 0;
			LIVES = 3;
			PLAYER = new Player;
			CURRENT_GUI = new GUI;
			PARTICLE_EMITTER = new ParticleController;
		}
	}
}

In addition to adding in a few new variables that we will be using shortly, we also added in a new function that we can run to set default values for all of these variables when we want to start a new game. We were doing that manually before for a few of these in our GameWorld, but this makes it easier to manage and keep track of and doesn’t clutter up our GameWorld.

Now we are finally ready to make a new file “GUI.as”

package  
{
	import flash.display.BitmapData;
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Graphiclist;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.graphics.Spritemap;
	import net.flashpunk.graphics.Text;
 
	public class GUI extends Entity 
	{
 
		private var pointsText:Text;
		private var lives:Vector.<Spritemap> = new Vector.<Spritemap>;
 
		public function GUI() 
		{
 
			var glist:Graphiclist = new Graphiclist();
 
			var guiBackground:Image = new Image(new BitmapData(560, 20, false, 0x000000));
			guiBackground.scrollX = 0;
			guiBackground.scrollY = 0;
			guiBackground.alpha = 0.5;
			glist.add(guiBackground);
 
			pointsText = new Text("Points: 0");
			pointsText.width = 200;
			pointsText.x = FP.width - 200;
			pointsText.y = 2;
			pointsText.scrollX = 0;
			pointsText.scrollY = 0;
			glist.add(pointsText);
 
 
			for (var i:uint = 0; i < 3; i++) {
				var life:Spritemap = new Spritemap(Assets.GRAPHIC_LIVES, 16, 16);
				life.add("idle", [0]);
				life.add("die", [1, 2, 3 , 4], 6, false);
				life.play("idle");
				life.scrollX = 0;
				life.scrollY = 0;
				life.y = 2;
				life.x = 2 + i * 16;
				lives.push(life);
				glist.add(life);
			}
 
			graphic = glist;
			layer = GC.LAYER_GUI;
		}
 
		public function updateScore():void
		{
			pointsText.text = "Points: " + GV.POINTS;
		}
 
		public function playerDeath():void
		{
			if(GV.LIVES >= 0 && GV.LIVES < lives.length) lives[GV.LIVES].play("die");
		}
 
	}
 
}

Now it might look like there is a lot to explain here, but most of it we have already gone over in our previous tutorials.

private var lives:Vector.<Spritemap> = new Vector.<Spritemap>;

Alright so, this is the main thing that we haven’t gone over before… a Vector! To put it simply, think of a Vector as a container for a certain type of object- it can hold lots of them, but it will only accept objects of the type that’s it’s defined to use. When we add something to a vector it will be give a number to reference it, the first object will have a reference of 0, the next will have a reference 1 etc. We can then access the object that we added to the Vector with Vector[referencenumber], so Vector[0] will give us the first object that was added.

var glist:Graphiclist = new Graphiclist();

Here we are going to create an empty GraphicList so that we can use the .add function of the GraphicList class to add our images that will be rendered, an important thing to note is that the first image that is added will be rendered first, and the others on top of it.

var guiBackground:Image = new Image(new BitmapData(560, 20, false, 0x000000));
guiBackground.scrollX = 0;
guiBackground.scrollY = 0;
guiBackground.alpha = 0.5;
glist.add(guiBackground);

We’ve created new bitmaps before with our particle system, this time we are doing that with an Image, so there shouldn’t be anything new here, other than setting the image’s alpha to 0.5 so that it is semi-transparent. Oh wait! There is something important here! We are setting the scrollX and scrollY both to 0. This way when our camera moves, our guiBackground stays right where it was added.

for (var i:uint = 0; i < 3; i++) {
	var life:Spritemap = new Spritemap(Assets.GRAPHIC_LIVES, 16, 16);
	life.add("idle", [0]);
	life.add("die", [1, 2, 3 , 4], 6, false);
	life.play("idle");
	life.scrollX = 0;
	life.scrollY = 0;
	life.y = 2;
	life.x = 2 + i * 16;
	lives.push(life);
	glist.add(life);
}

This is how we create our extra lives that will be displayed on our GUI. Since we need more than one life, we are running it through a simple for loop that runs 3 times. This time we are creating spritemaps with more than one animation, we also tell our “die” animation that it not loop by passing the last parameter as false, this way when we tell our graphic to play, it will only run once and then stop on the transparent frame. Like the guiBackground we also make sure to set the scrollX and scrollY of each spritemap to 0 so they will not move with the camera. You might want to note that I use the i variable when setting the x location of our spritemap, that way each spritemap will be 16 pixels apart from each other as i increase with each run of the loop. Finally we push our spritemap into the lives Vector so we can access it later, and we add it to our graphic list so that it will render.

public function updateScore():void
{
	pointsText.text = "Points: " + GV.POINTS;
}

This function will need to be called every time we change GV.POINTS that way our players get real time updates when they acquire points.

public function playerDeath():void
{
	if(GV.LIVES >= 0 && GV.LIVES < lives.length) lives[GV.LIVES].play("die");
}

And that brings us to our death animation for our lives. The reason we check if GV.LIVES is greater than 0 and less than the length of our lives Vector is to make sure that we aren’t trying to reference something that doesn’t exist. If GV.LIVES was -1 and our first spritemap added to our lives vector is at 0, then it will cause an error when it tries to access it. Likewise if we somehow had 4 lives and tries to access the 4th spritemap added, it would cause an error as well. If you were curious what lives.length is, well it’s just the total number of objects that are being stored in the Vector.

Great, with our GUI all explained, let’s add it to our “GameWorld.as”! But wait, since we added in that reset function to our GV class, let’s do some updates to the beginning of our GameWorld function while we are at it.

public function GameWorld() 
{
	GV.reset();
 
	add(GV.CURRENT_GUI);
	GV.PLAYER.x = 64;
	GV.PLAYER.y = 240;
	add(GV.PLAYER);
	add(GV.PARTICLE_EMITTER);

Now we call our reset function first, and then we add our GUI, and since we don’t need to reset the GV variables a second time we removed the redundant lines of code.

If you compile the game now, you should see the GUI up at the top of the screen.. wait no, sorry! First you should go into your “Main.as” and go ahead and remove FP.console.enable, otherwise the console will be overlapping your GUI.
Now if you compile the game, you should see the GUI up at the top of the screen.. but it doesn’t do anything right now does it? That’s not very fun, so let’s first give our player some points when he kills an enemy. Yep that’s right, open up your “Enemy.as” file because we have some changes to make!

First, after :

private var speedY:Number = 50;

add the line :

private var killed:Boolean = false;

We will be using this Boolean to determine if our enemy was killed by our player, or died from natural causes, like moving off the screen.

Next we need to add in a new overridden function :

override public function added():void
{
	killed = false;
}

This function is run every time our entity is added to the world. This is really handy because when we recycle entities, all of their current variables don’t get reset, so we can use this function to reset those variables!

And finally we are going to update our destroy function :

public function destroy():void
{
	if (killed) {
		GV.POINTS += 100;
		GV.CURRENT_GUI.updateScore();
		GV.PARTICLE_EMITTER.explosion(x, y);
	}
	FP.world.recycle(this);
}

Now, we do a check to see if we were killed, and if we were we give our player 100 points, update our GUI’s text, and then create an explosion. This is also handy because it’s saving us resources for when our enemies aren’t killed and fly off the screen they will no longer create a particle explosion off screen.

Awesome! Now we get points for killing enemies, and we even made our enemies a little more efficient. But we still have those shiny 3 lives to destroy don’t we?

Right, so before we update our player, let’s add in a screen for running out of lives. Go ahead and make a new file named “GameOver.as”

package  
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Text;
	import net.flashpunk.utils.Input;
	import net.flashpunk.World;
	public class GameOver extends World 
	{
 
		public function GameOver() 
		{
			var t:Text = new Text("Your final score was " + GV.POINTS + "\n\nPress space to retry.");
			t.align = "center";
			var e:Entity = new Entity(FP.halfWidth - (t.width / 2), FP.halfHeight - (t.height / 2), t);
			add(e);
		}
 
		override public function update():void
		{
			super.update();
			if (Input.pressed("fire")) FP.world = new GameWorld;
		}
 
	}
}

This is almost exactly like our MainMenu.as, except this time we are telling the player how many points they earned with their last game session.

Alright, with that out of the way, we are finally ready to make some changes to our “Player.as”

First let’s add in a new function for our Player :

public function kill():void 
{
	GV.PARTICLE_EMITTER.explosion(x, y);
	x = FP.camera.x + 32;
	y = FP.halfHeight;
	_image.alpha = 0;
	GV.LIVES--;
	GV.CURRENT_GUI.playerDeath();
	if (GV.LIVES < 0) FP.world = new GameOver;
}

Most of this code should look familiar, because it’s going to be replacing what happens when we collide into an enemy, this way we can use this later when we collide into bullets or whatever else later very easily. So all that’s new is we subtract one away from our GV.LIVES, we tell our GUI to play the death animation, and then we make a check to see if we are out of lives, if we have -1 lives after subtracting from our lives then we change the world to the GameOver screen.

Now that we have that function, let’s update our collision in our update Function to this :

if (collide("Enemy", x, y)) {
	kill();
}

Now we just call the function to kill our player and we should lose a life, have a particle explosion, teleport the player to the starting position, and check and see if we should end the game! And that’s it. We should have a working score system, death system, and game over screen that tells your players their score!

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

Bookmark the permalink.

Comments are closed.

Comments are closed