Skip to content

Seifer Tim's Tutorial for Flixel v1.25 Part 3

Haledire edited this page Sep 13, 2010 · 5 revisions

Flixel Basic Platformer Game Tutorial

by SeiferTim & DarthLupi

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

Note: This tutorial is not for the current version of Flixel . The purpose of this older version is to familiarize you with the concepts of Flixel. If you want to follow this tutorial exactly, please make sure you have the 1.25 version of Flixel extracted. For a more recent version of this tutorial, please go here .

Updating the Playstate

1. We need to make a few changes to PlayState.as , so open that file. Somewhere in your variable declarations we want to add a new FlxArray to hold all the stars our player is going to throw.

private var _pStars:FlxArray;

2. Somewhere within the constructor, before you initialize the Player object, we want to setup this FlxArray :

_pStars = new FlxArray;
            for (var n:int = 0; n < 40; n += 1)
            {
                _pStars.add(lyrSprites.add(new NinjaStar(0,0,0,0)));
            }

We’re going to set _pStars to be a new (empty) FlxArray , and then we’re going to add 40 new stars to this array so that the game won’t have to create them on the fly. This is the array which we checked in the Player class to see if we can reuse a star or if we need to make a new one.

3. Next, we need to make the stars collide with our Tilemap so that they don’t fly through walls. Inside the update function, after super.update() , add:

FlxG.collideArray2(_map,_pStars);

This works the same as the collision with the enemies.

4. Now, we also want our stars to damage the enemies when they get hit. So we need to add an overlapArray check.

FlxG.overlapArrays(_pStars, _e, StarHitsEnemy);

This works the same way that collision between the player and the enemy works – we’re just going to call a different function if any star overlaps any enemy.

5. The StarHitsEnemy function is going to be pretty straightforward – if a star collides with an enemy we want to Kill that star , and hurt that enemy . Since our enemies (right now) only have 1 health each, this will kill them, too. In the future, we could add stronger enemies that take more hits, and/or stronger weapons that deal more damage.

private function StarHitsEnemy(colStar:FlxSprite, colEnemy:FlxSprite):void
        {
            colStar.kill();
            colEnemy.hurt(1);
        }

That’s it! Test out your code and you should be able to press C to kill the enemy!
Your PlayState.as should look like this:

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

    public class PlayState extends FlxState
    {
        [Embed(source = '../../data/Tiles.png')] private var ImgTiles:Class;
        [Embed(source = '../../data/map.txt', mimeType = "application/octet-stream")] private var DataMap:Class;

        private var _p:Player;
        private var _map:FlxTilemap;
        private var _pStars:FlxArray;

        private var _e:FlxArray;

        public static var lyrStage:FlxLayer;
        public static var lyrSprites:FlxLayer;
        public static var lyrHUD:FlxLayer;

        public function PlayState():void
        {
            super();

            lyrStage = new FlxLayer;
            lyrSprites = new FlxLayer;
            lyrHUD = new FlxLayer;

            _pStars = new FlxArray;
            for (var n:int = 0; n < 40; n += 1)
            {
                _pStars.add(lyrSprites.add(new NinjaStar(0,0,0,0)));
            }

            _p = new Player(48, 448, _pStars);
            lyrSprites.add(_p);

            FlxG.follow(_p,2.5);
            FlxG.followAdjust(0.5, 0.5);
            FlxG.followBounds(1,1,640-1,480-1);

            _map = new FlxTilemap(new DataMap, ImgTiles, 1);
            lyrStage.add(_map);

            this.add(lyrStage);
            this.add(lyrSprites);
            this.add(lyrHUD);

            _e = new FlxArray;
            _e.add(lyrSprites.add(new Enemy(432, 320, _p)));
        }

        override public function update():void
        {
            super.update();
            _map.collide(_p);
            FlxG.collideArray2(_map, _e);
            FlxG.overlapArray(_e, _p, EnemyHit);
            FlxG.collideArray2(_map, _pStars);
            FlxG.overlapArrays(_pStars, _e, StarHitsEnemy);
        }

        private function EnemyHit(E:Enemy, P:Player):void
        {
            FlxG.log(P._hurt_counter.toString());
            if (P._hurt_counter <= 0)
            {
                if (E.x > P.x)
                {
                    P.velocity.x = -100;
                    E.velocity.x = 100;
                }
                else
                {
                    P.velocity.x = 100;
                    E.velocity.x = -100;
                }
                P.hurt(1);
            }
        }

        private function StarHitsEnemy(colStar:FlxSprite, colEnemy:FlxSprite):void
        {
            colStar.kill();
            colEnemy.hurt(1);
        }

    }    
} 

At this point, you can try to add some more enemies to the game to see how it works. We’re next going to cover how to give the enemies stars to throw as well.

Back to Contents

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:FlxArray;[/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 FlxArray;
            for (var en:int = 0; en < 40; en += 1)
            {
                _eStars.add(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.add(lyrSprites.add(new Enemy(432, 320, _p, _eStars)));

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

            FlxG.collideArray2(_map, _eStars);
            FlxG.overlapArray(_eStars, _p, 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:FlxArray;

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 FlxArray , so change the top of the constructor to:

public function Enemy(X:Number,Y:Number,ThePlayer:Player, EnemyStars:FlxArray):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 && _p.y > y - 1 && _p.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:Boolean ):void
        {
            var XVelocity:Number;
            if (dir) XVelocity = 150;
            else XVelocity = -150;
            for(var i:uint = 0; i < _eStars.length; i++)
                if(!_eStars[i].exists)
                {
                    _eStars[i].reset(x, y + 2,XVelocity, 0);
                    return;
                }
            var star:NinjaStar = new NinjaStar(x, y + 2, XVelocity, 0);
            star.reset(x, y,XVelocity, 0)
            _eStars.add(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, 40, FlxG.score.toString(), 0xffffffff, null, 16, "right");
            _scoreDisplay.scrollFactor.x = _scoreDisplay.scrollFactor.y = 0;
            lyrHUD.add(_scoreDisplay);[/code]

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(_p.dead) FlxG.score = 0;
            if(_old_score != FlxG.score)
            {
                _scoreDisplay.setText(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 a FlxArray to hold our hearts .
bc. private var _hearts:FlxArray;

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

_hearts = new FlxArray();
            var tmpH:FlxSprite;
            for (var hCount:Number = 0; hCount < _p._max_health; hCount++)
            {
                tmpH = new FlxSprite(ImgHearts, 2 + (hCount * 10), 2, true, false);
                tmpH.scrollFactor.x = tmpH.scrollFactor.y = 0;
                tmpH.addAnimation("on", [0]);
                tmpH.addAnimation("off", [1]);
                tmpH.play("on");
                _hearts.add(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 might want 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 = _p.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(_p.health != _old_health)
            {
                for(var i:Number = 0; i < _p._max_health; i++)
                {
                    if (i >= _p.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 com.adamatomic.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 _p:Player;
        private var _e:FlxArray;    
        private var _eStars:FlxArray;        
        private var _create_counter:Number = 5;

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

        public function Spawner(X:Number, Y:Number, Enemies:FlxArray,ThePlayer:Player,Stars:FlxArray):void
        {
            super(ImgSpawner, X, Y, true, true);
            _e = Enemies;
            _p = 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 FlxArray 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].reset(x, y);
                    return;
                }
            }
            var enemy:Enemy = new Enemy(x, y , _p,_eStars);
            _e.add(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 reset(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 FlxArray to contain any Spawners you would like to create.

        private var _spawners:FlxArray;

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

           _spawners = new FlxArray;
            _spawners.add(lyrStage.add(new Spawner(432, 320, _e,_p,_eStars)));   

Run it, and enjoy the many new enemies!

This is what your final Spawner class will look like:

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

    public class Spawner extends FlxSprite
    {
        [Embed(source = '../../data/Spawner.png')] private var ImgSpawner:Class;

        private var _p:Player;
        private var _e:FlxArray;    
        private var _eStars:FlxArray;        
        private var _create_counter:Number = 5;

        public function Spawner(X:Number, Y:Number, Enemies:FlxArray,ThePlayer:Player,Stars:FlxArray):void
        {
            super(ImgSpawner, X, Y, true, true);
            _e = Enemies;
            _p = 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].reset(x, y);
                    return;
                }
            }
            var enemy:Enemy = new Enemy(x, y , _p,_eStars);
            _e.add(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