Skip to content

Seifer Tim's Tutorial Part 3

TheHanna edited this page Sep 13, 2010 · 12 revisions

Flixel Basic Platformer Game Tutorial

by SeiferTim & DarthLupi

Please Note: This tutorial was recently updated for flixel 1.4x (at the time of this writing: 1.47). Please visit this thread for help or to report errors. -Rybar

Contents:

PART 1
I. Introduction II. Getting Started III. Creating the Menu
i. MenuState
PART 2
IV. Getting Ready for the Playstate
i. Player
ii. Tilemaps – Creating the Game World
iii. Setting up the Playstate
V. Adding the Enemy to the Game VI. Fighting the Enemies
i. Make the Weapon
ii. Equip the Player
iii. Update the PlayState
PART 3
VII. The Enemies Fight Back! VIII. Keeping Score
i. Health
IX. Spawning Enemies

VII: The Enemies Fight Back!

So now we want the enemies to start throwing their own stars at the player . For the most part this is going to work just like adding the Player’s stars , except we want the computer to trigger them itself. Fortunately, we can re-use the NinjaStar class we already wrote, we just need to hold stars thrown by the enemies in a new array to track their collision separately.

1. Let’s start with PlayState.as . Go ahead and open this up now.

2. Add a new variable declaration to define our enemy star array: [code]private var _eStars:Array;[/code]

3. Right beneath where we build our initial stars for the player we want to do the same for the enemy stars:

            _eStars = new Array;
            for (var en:int = 0; en < 40; en += 1)
            {
                _eStars.push(lyrSprites.add(new NinjaStar(0,0,0,0)));
            }

4. In the place where you add your enemy , we need to now also pass it the _eStars array (we’ll change the enemy class later to allow this).

_e.push(lyrSprites.add(new Enemy(432, 320, _player, _eStars)));

5. Next, we need to make sure the enemy stars collide with the wall and the player . Inside the update function add:

            _map.collideArray( _eStars);
            FlxG.overlapArray(_eStars, _player, StarHitsPlayer);

6. Now we need to add the StarHitsPlayer function :

private function StarHitsPlayer(colStar:FlxSprite, P:Player):void
        {
            if (P._hurt_counter <= 0)
            {
                if (colStar.x > P.x)
                {
                    P.velocity.x = -100;
                }
                else
                {
                    P.velocity.x = 100;
                }
                P.hurt(1);
                colStar.kill();
            }
        }

This is basically going to work the same as when the player runs into an enemy : if the player ‘s not still recovering from being hurt , then it’s going to be knocked back depending on which way the star came from, and then take 1 damage while the star is destroyed.

That’s it for this file!
Next we want to make the Enemy class be able to actually throw stars when they see the Player .

1: Open up Enemy.as .

2: First we want to add a variable to attach to the stars array in the PlayState , so add this declaration :

private var _eStars:Array;

3. We also want a counter to prevent the enemy from attacking all the time, so add this declaration :

private var _attack_counter:Number = 0;

4. Next, we need to change the constructor to accept the star Array , so change the top of the constructor to:

public function Enemy(X:Number,Y:Number,ThePlayer:Player, EnemyStars:Array):void

5. Within the constructor , right after "**super()** ", add this:

_eStars = EnemyStars;

6. Next we want to check the attack counter and if it’s more than 0, reduce it closer to 0. In the update function, after we check if the enemy’s dead or not, add:

if (_attack_counter > 0)
            {
                _attack_counter -= FlxG.elapsed*3;
            }

7. Now we want to see if we should and can throw a star . Still in update , add this code:

if(_attack_counter <= 0 && _player.y > y - 1 && _player.y < y + 1)
            {
                _attack_counter = 2;
                play("attack");
                throwStar(facing);
            }   

8. Now we need to change our logic to show animations around to look like this:

if (_hurt_counter > 0)
            {
                play("hurt");
            }
            else            
            {
                if (_attack_counter > 0)
                {
                    play("attack");
                }
                else
                {
                    if (velocity.y != 0)
                    {
                        play("jump");
                    }
                    else
                    {
                        if (velocity.x == 0)
                        {
                            play("stopped");
                        }
                        else
                        {
                           play("normal");
                        }
                    }
                }
            }

9. Finally we need to add our ThrowStar function :

        private function throwStar( dir:uint ):void
        {
            var XVelocity:Number;
            if (dir == RIGHT) XVelocity = 150;
            else XVelocity = -150;
            for(var i:uint = 0; i < _eStars.length; i++)
                if(!_eStars[i].exists)
                {
                    _eStars[i].resetStar(x, y + 2,XVelocity, 0);
                    return;
                }
            var star:NinjaStar = new NinjaStar(x, y + 2, XVelocity, 0);
            star.resetStar(x, y,XVelocity, 0)
            _eStars.push(PlayState.lyrSprites.add(star) );    
        }

That’s all! If you test it out, you’ll see that the enemy Ninja now throw stars back at you!
With a few small changes to your NinjaStar.as and your 2 ThrowStar functions , you can easily make it so that your player can differentiate between stars thrown by the player and stars thrown by the enemy. I’ll leave that to you to figure out.

Back to Contents

VIII: Keeping Score

Now that we can kill enemy Ninja (and they can kill us), it might be a good idea to add a way to see how much health the player has left, as well as give them a score for killing enemies.
Flixel already has variables built in for score and health, all we need to do is access them and show them.

Score

1. Open up the PlayState.as file.

2. We’re going to add a FlxText object which will show the player’s score . So add this variable declaration :

private var _scoreDisplay:FlxText;

3. Within the constructor somewhere, we want to define the scoreDisplay object and set it to never scroll .

_scoreDisplay = new FlxText(FlxG.width - 50, 2, 48, FlxG.score.toString());
_scoreDisplay.setFormat(null, 16, 0xffffffff, "right");
_scoreDisplay.scrollFactor.x = _scoreDisplay.scrollFactor.y = 0;
lyrHUD.add(_scoreDisplay);

4. Now, within our update function , we want to change the score text, but ONLY if its been changed. So, at the very top of the update function (before super() ) add this:

var _old_score:uint = FlxG.score;

5. Next, near the end of the update function (after ALL of the collision checks) add this:

      if(_player.dead) FlxG.score = 0;
            if(_old_score != FlxG.score)
            {
                _scoreDisplay.text = FlxG.score.toString();
            }

This just says:

If the player ’s dead, make his score = 0. Then, if the score has been changed since last time, show the new score in the scoreDisplay object .

So, how do we actually change the score ? Well, when an enemy dies, we want to give the player 10 points, so we’re going to override the Kill function within the enemy class.

1. Open up Enemy.as , and go all the way to the bottom to add this function :

        override public function kill():void
        {
            if (dead)
                return;
            FlxG.score += 10;
            super.kill();
        }

All this says is that when this enemy is going to die, give the player 10 points (As long as it’s not already dead).

That’s it! Give it a try.

Back to Contents

Health

So now we just want to let the player know how close they are to being dead . We’re going to show a bunch of hearts at the top of the screen , and every time the player gets hurt , one of the hearts will be emptied.

1. First, open up your drawing program and make a simple heart sprite – make one sprite for ‘full’ heart, and one for ‘empty’ . Name this file "**hearts.png** ".

2. Next open up PlayState.as , and embed our heart image :

[Embed(source = '../../data/hearts.png')] private var ImgHearts:Class;

3. We’re going to add an Array to hold our hearts .

private var _hearts:Array;

4. Next, go into the constructor and somewhere after we initialize the player , add this code to show all the hearts :

_hearts = new Array();
            var tmpH:FlxSprite;
            for (var hCount:Number = 0; hCount < _player._max_health; hCount++)
            {
                tmpH = new FlxSprite(2 +(hCount * 10), 2)
		tmpH.loadGraphic(ImgHearts, true, false, 8, 8);
                tmpH.scrollFactor.x = tmpH.scrollFactor.y = 0;
                tmpH.addAnimation("on", [0]);
                tmpH.addAnimation("off", [1]);
                tmpH.play("on");
                _hearts.push(lyrHUD.add(tmpH));
            }

So what this does is loop through based on the players maxHealth variable and adds a ‘full’ heart to the screen. In the spot that says: “(hCount * 10)”, keep in mind that the graphic I posted above is set to have 2 8×8 hearts. If your image is a different size, you will need to change this value.

5. Now we want to update our hearts if the player ever gets hurt . So go into our Update function and place this at the top (before super() ):

var _old_health:uint = _player.health;

6. After super() and after all the collision checks, we want to see if the player’s health changed – and if it did, we’re going to redraw our hearts to show all the ones that are ’_empty_ ’:

            if(_player.health != _old_health)
            {
                for(var i:Number = 0; i < _player._max_health; i++)
                {
                    if (i >= _player.health)
                    {
                        _hearts[i].play("off");
                    }
                    else
                    {
                        _hearts[i].play("on");
                    }
                }
            }

The final thing you want to do is to open up Player.as and change “_maxHealth” from “private” to “public” , and then that’s it! Now you can watch your Ninja’s health fall away with each hit!

Back to Contents

IX: Spawning Enemies

Sure, you can populate your levels with pre-placed enemies , but what if you want your enemies to constantly flood onto the screen? Well, we’re going to add some enemy spawn-points to do just that!

1. First thing to do is to create a new class named Spawner that extends FlxSprite, and imports all of Flixel , as usual.

 package com.Tutorial
{
    import org.flixel.*;

    public class Spawner extends FlxSprite
    { 

2. Create and embed a new image to represent the Spawner on the screen. In this example we are using an image that looks like a doorway to the BEYOND.

        [Embed(source = '../../data/Spawner.png')] private var ImgSpawner:Class;

3. As you should know by now, we are going to need to declare some variables. This Class will need variables to reference the Player , the Enemies , and EnemyStars that are all created in the PlayState . Also , we are setting up another counter for Enemy creation.

        private var _player:Player;
        private var _e:Array;    
        private var _eStars:Array;        
        private var _create_counter:Number = 5;

4. Now we setup our Constructor allowing for the passing of the Player , Enemies Array , and EnemyStars arrays from the PlayState to the private variables we declared above.

        public function Spawner(X:Number, Y:Number, Enemies:Array,ThePlayer:Player,Stars:Array):void
        {
	    super(X, Y);
	    loadGraphic(ImgSpawner, true, true, 16, 16);
            _e = Enemies;
            _player = ThePlayer;
            _eStars = Stars;
        }

5. The update function should be VERY familiar to you by this point. So familiar, in fact, the following explanation may induce vomiting. As you can see, we are overriding FlxSprite’s update function , calling the parent Class FlxSprite’s update function, and then we have a counter that runs every five seconds and calls the spawn function to create new baddies!

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

            if (_create_counter > 0)
            {
                _create_counter -= FlxG.elapsed;
            }    
            if(_create_counter <= 0)
            {
                _create_counter = 5;
                spawn();
            }             
        } 

6. The spawn function works very similiar to the ninja star creating functions you setup for the Player and Enemy Classes. It loops through all indexes in the _e Array which will contain all of the enemies , and then either resets them if they are there and exist is false , or it adds a new Enemy to that array!

         private function spawn():void
        {
            for (var i:uint = 0; i < _e.length; i++)
            {
                if(!_e[i].exists)
                {
                    _e[i].resetEnemy(x, y);
                    return;
                }
            }
            var enemy:Enemy = new Enemy(x, y , _player,_eStars);
            _e.push(PlayState.lyrSprites.add(enemy) );    
        }        

    }

} 

If you are a super genius, then you will remember that we never added a reset function the Enemy class! How can we reset the Enemy in this Spawner Class if it doesn’t exist? Why, we create the reset function !

Add this to your Enemy class after the last function . That last function should be the override of the kill function.

        public function resetEnemy(X:Number, Y:Number):void
        {
            x = X;
            y = Y;
            dead = false;
            exists = true;
            visible = true;
            play("normal");
        }

Finally, in order to create the Spawner object you will need to modify the PlayState class, so go ahead and open up your PlayState class for editing and do the following:

1. We will first want to declare a new Array to contain any Spawners you would like to create.

        private var _spawners:Array;

2. The last thing to modify in the PlayState class is adding a Spawner to a layer . We need to pass in the Array that contains the Enemies , the variable representing the Player , and finally the Array that holds the EnemyStars . To do this we have to add the following the very end of the PlayState constructor .

           _spawners = new Array;
            _spawners.push(lyrStage.add(new Spawner(432, 320, _e,_player,_eStars)));   

Run it, and enjoy the many new enemies!

This is what your final Spawner class will look like:

 package com.Tutorial 
{
	import org.flixel.*;
	import org.flixel.data.FlxAnim;
	
	public class Spawner extends FlxSprite
	{
	[Embed(source = '../../data/Spawner.png')] private var ImgSpawner:Class;
	
	private var _player:Player;
	private var _e:Array;
	private var _eStars:Array;
	private var _create_counter:Number = 5;
	

		public function Spawner(X:Number, Y:Number, Enemies:Array,ThePlayer:Player, Stars:Array):void 
		{
			super(X, Y);
			loadGraphic(ImgSpawner, true, true, 16, 16);
            _e = Enemies;
            _player = ThePlayer;
            _eStars = Stars;
		}
		override public function update():void
        {
            super.update();

            if (_create_counter > 0)
            {
                _create_counter -= FlxG.elapsed;
            }    
            if(_create_counter <= 0)
            {
                _create_counter = 5;
                spawn();
            }             
        }
		private function spawn():void
        {
            for (var i:uint = 0; i < _e.length; i++)
            {
                if(!_e[i].exists)
                {
                    _e[i].resetEnemy(x, y);
                    return;
                }
            }
            var enemy:Enemy = new Enemy(x, y , _player,_eStars);
            _e.push(PlayState.lyrSprites.add(enemy) );    
        }        
		
	}

}
} 

At this point there should be enough detailed information for you to be able to create your own little game.

Back to Contents