Author Topic: Basic Game Tutorial  (Read 75233 times)

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #20 on: Fri, Aug 7, 2009 »
well i only have the parts like in tutorial.

and it appears that hima was right
its there but its too fast that you only see a glimpse

I wouldn't say that it's 'too fast' ;) You certainly don't want a preloading screen to take 5, 10, 15+ seconds if the rest of your file would only take .01 seconds to load.

ahref

  • Guest
Re: Basic Game Tutorial
« Reply #21 on: Sat, Aug 8, 2009 »
Can i make a suggestion that this topic be split in two One for discussion and one for the actual tutorial :D

Adam Atomic

  • Founder
  • Key Contributor
  • *****
  • Posts: 852
  • Karma: +0/-0
  • new dad
    • View Profile
    • Adam Atomic
Re: Basic Game Tutorial
« Reply #22 on: Sat, Aug 8, 2009 »
Once it looks like its a pretty sound/solid tutorial for most new users I'm going to dedicate an actual page to it, instead of just the forum posts :)

pian0

  • New Member
  • *
  • Posts: 4
  • Karma: +0/-0
    • View Profile
Re: Basic Game Tutorial
« Reply #23 on: Mon, Aug 10, 2009 »
Have you considered something like a Wiki for Flixel developers? I think that would be a handy resource for us to share information and improve tutorials like this.

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #24 on: Mon, Aug 10, 2009 »
Have you considered something like a Wiki for Flixel developers? I think that would be a handy resource for us to share information and improve tutorials like this.
Wikis are fun. One of the things I've discovered while working on this tut is that nanodoc does not have a way to link to a specific 'page', since it's all in one page... a wiki would make it easy to link directly to the page for FlxG.follow, or whatever...
'Course, someone(s) would have to work on it and maintain it, which can be a pain...

Adam Atomic

  • Founder
  • Key Contributor
  • *****
  • Posts: 852
  • Karma: +0/-0
  • new dad
    • View Profile
    • Adam Atomic
Re: Basic Game Tutorial
« Reply #25 on: Mon, Aug 10, 2009 »
Yea a wiki might be kind of a cool thing, I've never set one up or administered one though, and initial setup with all the documentation would defintiely be a chore

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #26 on: Mon, Aug 10, 2009 »
Here's comes part IV! It's a big one... are you ready?

IV: Getting Ready for the PlayState

As you now know, the MenusState will call the PlayState Class when you press 'X', so we're going to need to build our PlayState Class. The Playstate Class is where all of the game objects are added, and to some degree, controlled. This State is going to load, hold, and control all of the assets we need for the game - sound, scores, sprites, blocks objects etc, as well as maintain their position and behaviors.

Before we create this wonderful Class file, we should create some content to add to it.

The first two pieces we will need to create to add to our PlayState are a Tilemap for the floors and walls, and a Player object to jump around on them.

The Player

For our Tutorial, and probably for most games made with Flixel, you're going to be dealing with "Sprites". Essentially, a sprite is simply an object that will be on the screen for the player to interact with in some way. The little guy that the player moves and jumps and collects stuff is the Player sprite. Enemies that the Player can collide with are also sprites. For the most part, Sprites behave independently of one another (except when colliding), but we have to define how each type of sprite will move, look, and interact with the game.

To create our Player Class, we want to first create the graphics for our Player. Open up your Image Editor, and create a new document that is 128 x 16 pixels. Our Player is not going to be a 128 pixel wide graphic on the screen - he's only really going to be 16 x 16, however, Flixel makes it very easy to create animations by splitting up images into equal-size squares. We're going to create 8 frames of our player's animation.

You can learn and practice making sprites, but its more of an art than a science. Here's the graphic Darthlupi created for this tutorial, feel free to use it or copy it:


Lets break down this image so we can see what we actually have:


When this graphic is going to be loaded into Flixel, it will be assigned a number for each 16x16 block, starting at 0 - which you can see above.

  • Frame 0 is going to be our standard, "idle" graphic for our Player.
  • When the player is moving, we will play frames 0, 1, 2, and 3 in succession.
  • When the player is jumping, we'll show frame 2.
  • When the player is attacking, we'll play frames 4, 5, and 6 in succession.
  • When the player is getting hurt, we'll play frame 2 then 7.
  • Finally, when the player dies, we'll show frame 7.

If you want to, you can make your own graphic for your player that has more or less frames, just make sure you have frames to use for all of the above animations.

If you look in your project's directory, under "data", you'll probably see a lot of files there. Those are all from Mode, the demo game that comes with Flixel. You can safely remove all of those files, since we'll be making all of our own files for this Tutorial.

Save your player's sprite graphic to this folder ("data") as "Player.png"

Creating the Player Class File

Now that we have our Player Graphic, we need to build an object to contain all of our Player's information. This will be the Player Class.

The Player Class is going to serve a couple of purposes. We're going to create a Class that contains within it all of the stuff that makes up the player: Health, animations, movement, etc. This will give us lots of control over how the player's sprite behaves and is controlled.

1:  Start by creating a new Class file in the com\Tutorial directory, name it "Player.as"

2:  Import Flixel's stuff:
Code: [Select]
import com.adamatomic.flixel.*;
3:  Tell this Class to extend the FlxSprite class:
Code: [Select]
extends FlxSprite
4:  We're only going to embed one file right now:

Code: [Select]
[Embed(source='../../data/Player.png')] private var ImgPlayer:Class;
5:  Next we need to define some variables. We want to set some constants to be used later on in the code:
Code: [Select]
private var _move_speed:int = 400;
private var _jump_power:int = 800;   
private var _max_health:int=10;
And we also want to set a counter which we will use to 'flash' the player for a few seconds after they get hurt:
Code: [Select]
private var _hurt_counter:Number = 0;
6:  Next, we need to setup the constructor for this class. When we create a new Player object we want to be able to pass the X and Y coordinates where the player should start. So, change
Code: [Select]
public function Player() to say
Code: [Select]
public function Player(X:Number,Y:Number):void
7:  On the first line inside the Constructor, we need to call the constructor of the FlxSprite class this class is extended from:

Code: [Select]
super(ImgPlayer, X, Y, true, true); If you look up the definition of FlxSprite in the documentation, you'll see that we're passing the image to use for this sprite, the x and y coordinates for this sprite, and we're setting "animated" and "reverse" to True.

8:  Next, we're going to initialize our player with a bunch of information. The FlxSprite class is already setup with a lot of variables that we can use for various things:
Code: [Select]
//Max speeds
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            //Set the player health
            health = 10;
            //Gravity
            acceleration.y = 420;           
            //Friction
            drag.x = 300;
            //bounding box tweaks
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;

Now, we're adding bunch of stuff at once. Basically, what we're saying is this: when we create a Player object in our game, we're going to send it the X, Y coordinates where we want the Player to start on the screen, and then we're going to se all of these variables to these specific values instantaneously and before the Player can do anything else.
We're setting the speed of the player first, then the starting health of the player. Next the strength of gravity on the player, and the amount of friction. The Bounding box tweaks sort of shrink the boundaries of the sprite to better fit the size of the graphic so that if it collides with a wall, it will look better.

9:  Next we want to setup our sprites animations. We're going to use a Flixel Function called "addAnimation" to do this.

Remember when we were creating our player's graphic, and we layout which frame would be used for what? Well, here's where we define them:
Code: [Select]
addAnimation("normal", [0, 1, 2, 3], 10);
addAnimation("jump", [2]);
addAnimation("attack", [4,5,6],10);
addAnimation("stopped", [0]);
addAnimation("hurt", [2,7],10);
addAnimation("dead", [7,7,7],5);

10:  Finally, we'll set "facing" to True, which says that the player is facing to the right:
Code: [Select]
facing = true; and that's it for the constructor.

11: Now we need to override the FlxSprite "update" function. After the constructor, we'll add a new function with this code:
Code: [Select]
override public function update():void
{
}

Inside this function, we're going to do a couple of things in order:

  • Check to see if the player is dead or not.
  • Figure out if any buttons are being pressed by the player (left,right,jump), and act on them.
  • Change animation based on player's current status.


12: So, to check if the player is dead or not is simple:
Code: [Select]
if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }

FlxSprite has a "dead" variable which is true when the sprite's health is brought to 0. All this check is saying is:
If this sprite (the player) is marked as "dead", check to see if it's finished animating. If it is done animating, set this sprite's 'exists' property to false, which will remove it from the game. If the sprite is still animating, simply call the FlxSprite update function, and in either case, exit this function without going through the rest of the code.

13: Next, we know that if the player got past the first if statement that the player is still alive. When the player gets hurt by an enemy, we want a few things to happen: we want him to lose a health point, and then we want him to be invincible for a few seconds so that the player can get their bearing.

Our _hurt_counter variable is going to be used to find out if the player should be 'invincible' or not. Later on, when the player gets hurt, the _hurt_counter is going to be set to 1. Here we want to simply reduce the amount of time left on the counter:
Code: [Select]
if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }

Important:Notice instead of simply subtracting from _hurt_counter we are using some funky FlxG.elapsed*3 thing.  Why is that you ask?  FlxG.elapsed is a variable that represents REAL time in seconds ( or fractions of a second ) since the last FULL GAME FRAME.  We do this to keep everything that needs to remain accurate for every computer, no matter how fast, since it is time based.

If we simply subtracted 0.1 from _hurt_counter that would happen much faster for a very fast computer than a slower one because the faster computer.  We multiple FlxG.elapsed * 3 because we want the timer to go down a bit faster.  It is still accurate since still using the amount of time since the last frame!

14: Getting keyboard input is a very important part of the Player object.  The Flixel Class FlxG handles this for you by either setting the following boolean variables to true or false for pressed or not pressed:

  • FlxG.kLeft (for left arrow key)
  • FlxG.kRight (for right arrow key)
  • FlxG.kUp (for up arrow key)
  • FlxG.kDown (for down arrow key)
  • FlxG.A (for the X key)
  • FlxG.B (for the C key)

There is also a function that checks to see if a key has just been pressed this once:
FlxG.justPressed(FlxG.B)


Programming Tip:If you want to dig in deeper or change the keys you will need to look at the FlxGame Class, and look at it's onKeyUp and onKeyDown functions to see how they assignment of keys works in Flixel.

Now that you know which variables to check for to see if you are pressing a key, let's use it to move the player about.

15: First we check to see if the left arrow or right arrow is pressed and we set the direction the sprite is facing using facing.  Then we set the movement on the X axis using velocity.x.

The variable velocity.x represents how much to move along the x axis when the FlxSprite is updated.  Notice that we use _move_speed * FlxG.elapsed again when set the velocity.x.  Since we are again doing a calculation that adds over time, we are using the FlxG.elapsed variable make sure we are using the time since the last frame was drawn to keep it accurate for all computers.

Code: [Select]
if(FlxG.kLeft)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
            else if (FlxG.kRight)
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;               
            }

16: What is a platform game without jumping?

Code: [Select]
if(FlxG.justPressed(FlxG.A) && velocity.y == 0)
            {
                velocity.y = -_jump_power;
            }
     
   
17: Next, we want to handle Animations. In Flixel, all we need to do is tell the sprite to "play" the animation we named in the constructor depending on what's going on:
 
Code: [Select]
if (_hurt_counter > 0)
            {
                play("hurt");
            }
            else           
            {
                if (velocity.y != 0)
                {
                    play("jump");
                }
                else
                {
                    if (velocity.x == 0)
                    {
                        play("stopped");
                    }
                    else
                    {
                       play("normal");
                    }
                }
            }


18: Next we need to call update() on the FlxSprite Class that Player extends to update position and animation.
Code: [Select]
            super.update();
        }

19: The hurt function is a public function in the FlxSprite class that we want to change a little, so here we override it and add our changes.  We are simply adding the _hurt_counter = 1 in the event the player's hurt function called.  Also, we call the FlixelSprite's hurt function that we are overriding so we don't exlude anything that is in there.

Code: [Select]
        override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }       
}
       

That's it!  That is the player object, for now!

Your code should look like this:

Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;
   
    public class Player extends FlxSprite
    {
        [Embed(source='../../data/Player.png')] private var ImgPlayer:Class;
       
        private var _move_speed:int = 400;
        private var _jump_power:int = 800;   
        private var _max_health:int = 10;
        private var _hurt_counter:Number = 0;
       
        public function Player(X:Number,Y:Number):void
        {
            super(ImgPlayer, X, Y, true, true);//Max speeds
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            //Set the player health
            health = 10;
            //Gravity
            acceleration.y = 420;           
            //Friction
            drag.x = 300;
            //bounding box tweaks
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;addAnimation("normal", [0, 1, 2, 3], 10);
            addAnimation("jump", [2]);
            addAnimation("attack", [4,5,6],10);
            addAnimation("stopped", [0]);
            addAnimation("hurt", [2,7],10);
            addAnimation("dead", [7, 7, 7], 5);
        }
   
        override public function update():void
        {

            if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }
            if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }

            if(FlxG.kLeft)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
            else if (FlxG.kRight)
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;               
            }
            if(FlxG.justPressed(FlxG.A) && velocity.y == 0)
            {
                velocity.y = -_jump_power;
            }
            if (_hurt_counter > 0)
            {
                play("hurt");
            }
            else           
            {
                if (velocity.y != 0)
                {
                    play("jump");
                }
                else
                {
                    if (velocity.x == 0)
                    {
                        play("stopped");
                    }
                    else
                    {
                       play("normal");
                    }
                }
            }

            super.update();
        }

        override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }       
    }
}

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #27 on: Mon, Aug 10, 2009 »
Tilemaps - Creating the Game World

Now we need something for the player to stand on. Make a new image document that's 48x16 Pixels. We're going to keep it simple and just make 2 different 16 x 16 block sprites:


You might not realize it, but the first tile we want to keep 'blank'.
The first tile is *not* going to collide with sprites, but the rest are. In the future, you can make some neat effects by having more than 1 non-colliding tile.

Tiles work similarly to the Player's graphics: every square block will be assigned a number, starting at 0. You can make as many tiles as you want. Save this file as "Tiles.png" in the data directory.

Next, we're going to create our Tilemap. There's already a great tutorial on how to do this here. Using a tilemap is a simple way to create a map that looks the way you want. When you setup your map, we're going to want to make it twice as wide and tall as the game screen, so make sure you define the tile size as 16 x 16 and that you want your map to be 40 x 30 tiles in size.


Once you start your new map, Import your tiles.png file, and go wild! Just make sure that there are no gaps along the edge of the map - we don't want anywhere where the player can leave the map.


Once you finish making your map, save it, and then you can choose "Custom->Export Flixel Tilemap". Name it "map.txt" and save it to your src\data directory. When it asks for a number to adjust the tiles by, leave it at "-1".

And there you go! You now have everything ready to start setting up the PlayState!

Setting up the PlayState

The PlayState is going to be our State that says: "The player is currently playing the game", ie, they're not on the MenuScreen, etc.

1:  You should already have the PlayState.as file in your com\Tutorial directory, open it up now.

2:  First, import the Flixel library right after the initial package declaration:
Code: [Select]
import com.adamatomic.flixel.*;
3:  Extend PlayState by adding
Code: [Select]
extends FlxState to the end of the public class declaration.

4:  Next, we need to Embed the assets we want to use in the state. In our case we want to embed 2 files: the Tiles.png and the Map.txt. In a new row after the Class declaration add:
Code: [Select]
[Embed(source = '../../data/Tiles.png')] private var ImgTiles:Class;
[Embed(source = '../../data/map.txt', mimeType = "application/octet-stream")] private var DataMap:Class;
What this does is actually include a file into the finished SWF file that's created, while assigning it a name so it can be accessed programattically. We can do this for images, sounds, and much more later on.

5:  We also need to define 2 of the objects that are going to live in our PlayState: the Player and the map. Flixel has a class called FlxTilemap which lets you pass it a map (like the one we created with Mappy), and some other parameters and then draws them on the screen. When we define a variable to hold the FlxTilemap, we can also do some other things (like test collision) with that Object. The Player object is going to be a Player class, which we created earlier. In a new row right beneath the Embeds, add:
Code: [Select]
private var _p:Player;
private var _map:FlxTilemap;

6: Finally, we're going to define 3 FlxLayers to hold display our items in. FlxLayers let you specify the order that you want to display your graphics in. Anything placed in the topmost layer will be shown abover everything in the other layers.
Code: [Select]
public static var lyrStage:FlxLayer;
        public static var lyrSprites:FlxLayer;
        public static var lyrHUD:FlxLayer;

7: Now, within the PlayState constuctor, the first thing we want to do is call the FlxState constructor by saying:
Code: [Select]
super();.

8: Next, we need to initialize some of our variables that we defined earlier:
First, the layers:
Code: [Select]
lyrStage = new FlxLayer;
            lyrSprites = new FlxLayer;
            lyrHUD = new FlxLayer;
This is telling the system to make a new FlxLayer object for each of our layers, and assign them to the variables we specified.

9: Next, we're going to set up the Player object:
Code: [Select]
_p = new Player(48, 448);[./code]
this is just saying that our _p variable is going to be a player object, and it's going to start at 28,29 on the map.

10: Now we're going to attach the player to the Sprites layer:
[code]lyrSprites.add(_p);

11: Next, we want the game's camera to follow the player. We do that by using the FlxG function: "follow". We will also want to set some options related to the camera to give the player a smoother experience:
Code: [Select]
FlxG.follow(_player,2.5);
            FlxG.followAdjust(0.5, 0.5);
            FlxG.followBounds(1,1,640-1,480-1);

12: Next, we're going to create our Tilemap object, and place it into lyrStage:
Code: [Select]
_map = new FlxTilemap(new DataMap, ImgTiles, 1);
            lyrStage.add(_map);

13: Finally, we attach our layers (in order) to the State:
Code: [Select]
this.add(lyrStage);
            this.add(lyrSprites);
            this.add(lyrHUD);

You may notice that we did not put anything into lyrHUD - we'll add score and health to that layer later on.

14: Now we need to override the update function of FlxState, so add this function to PlayState:
Code: [Select]
override public function update():void
        {
            super.update();
            _map.collide(_p);
        }
This should make some sense to you by now: we're calling the FlxState's update function first, and then we're telling Flixel to collide our Player object with our tilemap object.

Once this is done, you can test your project, and you should be able to run around the map you created!

At this point, your PlayState.as file should look like this:
Code: [Select]
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;
       
        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;
           
            _p = new Player(48, 448);
            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);
        }
       
        override public function update():void
        {
            super.update();
            _map.collide(_p);
        }
    }   
}

Tune in for our next installment when we add Enemies and Collision![/code]
« Last Edit: Mon, Aug 10, 2009 by SeiferTim »

george

  • Guest
Re: Basic Game Tutorial
« Reply #28 on: Mon, Aug 10, 2009 »
great work guys!

HI5 Studios

  • Guest
Re: Basic Game Tutorial
« Reply #29 on: Mon, Aug 10, 2009 »
@hima pastebin did not help me fiigure out what an identifier is because my question has not been answered can someone in the on this post tell me what I did wrong? Error below

(8): col: 10 Error: Syntax error: expecting identifier before extends.

hima

  • Member
  • **
  • Posts: 96
  • Karma: +0/-0
    • View Profile
    • My Dev Blog
Re: Basic Game Tutorial
« Reply #30 on: Mon, Aug 10, 2009 »
@hima pastebin did not help me fiigure out what an identifier is because my question has not been answered can someone in the on this post tell me what I did wrong? Error below

(8): col: 10 Error: Syntax error: expecting identifier before extends.
Pastebin is used to paste the code and then you post the link to the code so people can see your code and check where's the error at. You could also post the code here too.


Also, yayh for the new tutorial chapter! This one is quite a long one and more than I expect! Will be trying it this afternoon :)

HI5 Studios

  • Guest
Re: Basic Game Tutorial
« Reply #31 on: Tue, Aug 11, 2009 »
Okay I changed my code for tutorial.as and now I get this error

8: col: 35 Error: The definition of base class FlxGame was not found.

darthlupi

  • Active Member
  • ***
  • Posts: 241
  • Karma: +0/-0
  • All Smiles
    • View Profile
    • LupiGames.com
Re: Basic Game Tutorial
« Reply #32 on: Wed, Aug 12, 2009 »
We are getting pretty close to finishing the section on enemy objects, and once we have shooting for the player and the scores added we'll bundle the project in a zip file that you will be able to open and follow along with the tutorial.

That should help new people get a better handle on things as well as give you a function project start learning from.  For now, most if not all of the errors you come across can be explained through the wonder of Google.  You will definitely want to start researching these types of errors on your own, because they will always be popping up!

H15 Studios,

Make sure you are importing FlxGame properly!  It is important to understand that when you import something it then becomes available to add or extend.

Also, keep in mind that the com.whatever.whatever in it's simplest explanation is just the directory structure.

Code: [Select]
import com.adamatomic.flixel.FlxGame;
Think of this like open file com/adamatomic/flixel/FlxGame.  If the file isn't there prepare for more woes!

Make sure your package definitions and imports look like this:

Code: [Select]
package {
import com.adamatomic.flixel.FlxGame;
import com.Tutorial.MenuState;

To take care of that not so fresh feeling: #flixel on irc.freenode.net.

Use your favorite IRC client or  http://webchat.freenode.net/

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #33 on: Wed, Aug 12, 2009 »
What the what? Part V?!

V: Enemies

At this point you should start to see a game coming together.  What we are lacking is any sort of challenge or interaction, and a simple way to add challenge is with enemies.

In this section we will be setting up a very simple enemy using a new Class named Enemy that extends FlxSprite.  This Enemy class is going to be a near copy of the Player class that we already created so some portions of the class will not be covered in detail since we have already done so.

The first thing you are going to want to do is create an image file for new Enemy object with the same basic frame setup that we used for the Player.  We are doing this because for this tutorial we are going to be using the animation layout we used for the Player.


Notice that this image is exactly the same as the player with little recoloring job applied.  Simple stuff right?

Alright, let get down to coding!

1:  Start by creating a new Class file in the com\Tutorial directory, name it "Enemy.as"
2:  Next we define the package structure, import all of Fixel, and set this new public class Enemy to extend Flxsprite.
Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;
    public class Enemy extends FlxSprite
    {

3:  We will want to also embed the new image we created for this Enemy class
Code: [Select]
[Embed(source='../../data/Enemy.png')] private var ImgEnemy:Class;
4:  Next we declare our variables and define our constructor.
Code: [Select]
private var _move_speed:int = 400;
        private var _jump_power:int = 800;   
        private var _max_health:int = 10;
        private var _hurt_counter:Number = 0;
        private var _can_jump:Boolean = true;
        private var _last_jump:Number = 0;
        private var _p:FlxSprite;       
        public function Enemy(X:Number,Y:Number,ThePlayer:FlxSprite):void
        {
            super(ImgEnemy, X, Y, true, true);//Max speeds
             _p = ThePlayer;
            maxVelocity.x = 200;
            maxVelocity.y = 200;
       
  There are four differences here from how we setup out Player Class.
  • The _can_jump and _last_jump variables that will be used to tell the Enemy if it is ok to jump now or not.
  • The _p variable which is set to the type of FlxSprite.  This allows the variable to hold the id, index or value of a FlxSprite Class.  This one in particular is going to hold the value of the Player class that is added in PlayState.
  • In the constructor we are passing a variable ThePlayer FlxSprite that will allow us to pass the id of the Player object to this Class from the PlayState class.
  • Below the call of super, FlxSprite's constructor, we see where we assign ThePlayer to private variable _p we initialized earlier.
      We can now use it through out the class to do checks against the player!

5:  Now let's finish the Enemy's constructor.  Again, this section is exactly like the Player Class. We setup health, gravity, friction, bounding boxes for collisions, and the animations.
Code: [Select]
health = 1;
            acceleration.y = 420;           
            drag.x = 300;
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;
            addAnimation("normal", [0, 1, 2, 3], 10);
            addAnimation("jump", [2]);
            addAnimation("attack", [4,5,6],10);
            addAnimation("stopped", [0]);
            addAnimation("hurt", [2,7],10);
            addAnimation("dead", [7, 7, 7], 5);
        }

6:  Now again the first section of the update function is a carbon copy our Player Class.  First we override FlxSprite's update function, verify that this object is not "dead" and that it "exists", and setup the counter being hurt.
Code: [Select]
override public function update():void
        {
            if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }
            if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }

7:  In this next section we are going to add horizontal movement for the Enemy.  Notice that we are using the _p variable that holds the Player's id that we added to the PlayState.

What we are doing here is saying that if the Player's X coordinate is less than the Enemy's X coordinate then we need to move the Enemy to the left and change the facing for animations.
Code: [Select]
if(_p.x < x)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
If it is not, then we need to move the enemy to the right and change the facing for animations.
Code: [Select]
else
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;               
            }

8:  Jumping is pretty simple to pull off.
First, we don't want the Enemy jumping all the time - we're going to give him a little bit of a delay between jumps:
Code: [Select]
if (_last_jump > 0)
            {
                _last_jump -= FlxG.elapsed;
            }
Plus, we want to make sure the enemy is on the ground. If he's moving up or down, or if the _last_jump variable is greater than 0, we set his "_can_jump" variable to false:
Code: [Select]
if(velocity.y != 0 || _last_jump > 0)
    {
        _can_jump = false;
    }
Next, we check to see if the Enemy wants to jump.  We do this by checking to see if the Player is above the Enemy, and if the Enemy's _can_jump variable is set to true.
Code: [Select]
if(_p.y < y && _can_jump)
            {

Now we want to make that sucker jump!  We do this by setting the velocity on the up and down or Y axis to a negative number.  Notice we do not use FlxG.elapsed here because we are not adding to the jump speed over time.  Just one burst.
Code: [Select]
velocity.y = -_jump_power;Set the variable _can_jump to false to avoid the enemy trying to jump mid air, and set the _last_jump variable to 2 so that he can't jump again right away and close the if statement.
Code: [Select]
_can_jump = false;
                _last_jump = 2;
            }

9:  Set up the animations, call FlxSprite's update function, and close the update function just like you did for the Player Class.
Code: [Select]
if (_hurt_counter > 0)
            {
                play("hurt");
            }
            else           
            {
                if (velocity.y != 0)
                {
                    play("jump");
                }
                else
                {
                    if (velocity.x == 0)
                    {
                        play("stopped");
                    }
                    else
                    {
                       play("normal");
                    }
                }
            }
            super.update();
        }

10: Next we will want to override the hitFloor function that is part of the FlxCore Class, which FlxSprite extends.  The function is called when ever there is a collision against a block on the top side of the block.  We are overriding to set the _can_jump variable to true.  Now the Enemy can jump again!  We also want to return the value of FlxSprite's hitFloor so we call the super.hitFloor().
Code: [Select]
override public function hitFloor():Boolean
        {
            _can_jump = true;
            return super.hitFloor();
        }

11:  Finish up the Enemy Class by overriding the hurt function just like the Player Class does, and closing the Class and Package definitions.
Code: [Select]
override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }       
    }
}

This is what your final Enemy class will look like:
Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;
    public class Enemy extends FlxSprite
    {
        [Embed(source='../../data/Enemy.png')] private var ImgEnemy:Class;
        private var _p:Player;
        private var _move_speed:int = 400;
        private var _jump_power:int = 800;   
        private var _max_health:int = 10;
        private var _hurt_counter:Number = 0;
        private var _can_jump:Boolean = true;
        private var _last_jump:Number = 0;
        public function Enemy(X:Number,Y:Number,ThePlayer:Player):void
        {
            super(ImgEnemy, X, Y, true, true);//Max speeds
            _p = ThePlayer;
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            health = 1;
            acceleration.y = 420;
            drag.x = 300;
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;
            addAnimation("normal", [0, 1, 2, 3], 10);
            addAnimation("jump", [2]);
            addAnimation("attack", [4,5,6],10);
            addAnimation("stopped", [0]);
            addAnimation("hurt", [2,7],10);
            addAnimation("dead", [7, 7, 7], 5);
        }
   
        override public function update():void
        {
            if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }
            if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }
            if (_last_jump > 0)
            {
                _last_jump -= FlxG.elapsed;
            }
            if (velocity.y != 0 || _last_jump > 0)
            {
                _can_jump = false;
            }
            if(_p.x < x)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
            else
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;               
            }
            if(_p.y < y && _can_jump)
            {
                velocity.y = -_jump_power;
                _last_jump = 2;
                _can_jump = false;
            }
            if (_hurt_counter > 0)
            {
                play("hurt");
            }
            else           
            {
                if (velocity.y != 0)
                {
                    play("jump");
                }
                else
                {
                    if (velocity.x == 0)
                    {
                        play("stopped");
                    }
                    else
                    {
                       play("normal");
                    }
                }
            }
            super.update();
        }

        override public function hitFloor():Boolean
        {
            _can_jump = true;
            return super.hitFloor();
        }
       
        override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }       
    }
}

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #34 on: Wed, Aug 12, 2009 »
Adding the Enemy to the Game

So now you have an enemy class, but we need to get it into the game for it to do any good (or evil, as the case may be...) so open up the PlayState.as file so we can add a few things.

1:  First, we want to define an Array to hold all of our enemies. Go up to where you defined your other variables (for _p and _map), and add a new line:
Code: [Select]
private var _e:FlxArray;Now, a FlxArray is a Class built into Flixel which is like a standard array++, it can hold just about anything, and works great. This FlxArray is going to be full of our enemies.

Programming Tip:FlxArray extends Array, and it adds on a few nice touches.  One thing that does that is very helpful is that it will return the ID or Index of the object that you add to the array.  Check out how it works if you are interested by opening up the FlxArray.as Class file.

2:  Go down to the constructor, and somewhere before the end of the function (doesn't really matter where, just after you set the player object), add this code:
Code: [Select]
_e = new FlxArray;
            _e.add(lyrSprites.add(new Enemy(432, 320, _p)));

So, we're first telling Flixel that we want _e to be a new FlxArray, and then we're adding a new Enemy to the lyrSprites FlxLayer while simultaneously adding that Enemy to the _e FlxArray. 432 and 320 are the X and Y coordinates where we want to place the enemy (you may want to use different values based on your map - make sure he's not inside a wall), and _p is the player object.

3:  Now, in the update function we need to check 2 more things. First, we want to make sure all of the enemies inside our _e FlxArray (right now just one) will collide with the walls, and not fall through the floor. Then we want the enemies to damage the player if they run into each other. So add this underneath our player's collision:
Code: [Select]
FlxG.collideArray2(_map, _e);
    FlxG.overlapArray(_e, _p, EnemyHit);
So, FlxG.collideArray2 is a function that takes an object (in our case _map, our FlxTilemap), and a FlxArray (_e) and 'collides' them. Any object within _e will be unable to pass through the _map object.
FlxG.overlapArray is a similar function that takes a FlxArray (_e), and an object (_p, our Player), and if any object inside the FlxArray touches the object, it will call a function we define (EnemyHit, in our case).

4:  So, we need to add the EnemyHit function. Somewhere after the update function, and before the end of the class:
Code: [Select]
private function EnemyHit(E:Enemy, P:Player):void
        {
        }

5:  Now, before we actually handle our EnemyHit function, we need to go back to our Player.as and make a few changes. Open that file up. We're going to change _hurt_counter from Private to Public so that we can access it from outside the Player class, so change:
Code: [Select]
private var _hurt_counter:Number = 0 to
Code: [Select]
public var _hurt_counter:Number = 0.

6:  Next, we want to overide the FlxSprite's standart hurt function so that we can trigger our _hurt_counter. Somewhere before the end of the class, add:
Code: [Select]
override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }
All this is going to do, is when our player gets Hurt, it will set our _hurt_counter to 1 before calling the normal FlxSprite hurt function. That's it! Now you can go back to the PlayState.as file.

7: We're going to do a couple of things in our EnemyHit function. First, we're going to see if the player is currently hurt or not. If they are, we're not going to do anything - give the player a moment to recover:
Code: [Select]
if(P._hurt_counter==0)
{

8: If they are not hurt, then we're going to see which direction the enemy came from that hit them.
Code: [Select]
if (E.x > P.x)
                {
                    P.velocity.x = -100;
                    E.velocity.x = 100;
                }
                else
                {
                    P.velocity.x = 100;
                    E.velocity.x = -100;
                }
If the enemy came from the right, we're going to bounce the player left and the enemy right, and vice versa.

9: Finally, we're going to hurt the player by saying:
Code: [Select]
P.hurt(1); and then close your if statement and function:
Code: [Select]
}
    }

That should be it! Give your game a test and you should be able to go head-to-head against another Ninja (which you can't kill just yet...)

You might notice that after your player gets hit enough times, he'll disappear. This is because the FlxSprite.hit() function will actually reduce the player's health by the amount specified, and if it hits 0, we're removing the player from the game. Pretty neat, huh?
Next time we're going to talk about how to fight back against the enemy Ninja!

Your Player.as should now look like this:
Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;
    public class Player extends FlxSprite
    {
        [Embed(source = "../../data/Player.png")] private var ImgPlayer:Class;
        private var _move_speed:int = 400;
        private var _jump_power:int = 800;   
        private var _max_health:int = 10;
        public var _hurt_counter:Number = 0;
        public function  Player(X:Number,Y:Number):void
        {
            super(ImgPlayer, X, Y, true, true);
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            health = 10;
            acceleration.y = 420;
            drag.x = 300;
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;
            addAnimation("normal", [0, 1, 2, 3], 10);
            addAnimation("jump", [2]);
            addAnimation("attack", [4,5,6],10);
            addAnimation("stopped", [0]);
            addAnimation("hurt", [2,7],10);
            addAnimation("dead", [7, 7, 7], 5);
            facing = true;
        }
        override public function update():void
        {
            if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }
            if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }
            if(FlxG.kLeft)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
            else if (FlxG.kRight)
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;               
            }
            if(FlxG.justPressed(FlxG.A) && velocity.y == 0)
            {
                velocity.y = -_jump_power;
            }
            if (_hurt_counter > 0)
            {
                play("hurt");
               
            }
            else           
            {
                if (velocity.y != 0)
                {
                    play("jump");
                }
                else
                {
                    if (velocity.x == 0)
                    {
                        play("stopped");
                    }
                    else
                    {
                        play("normal");
                    }
                }
            }
            super.update();
        }
       
        override public function hitFloor():Boolean
        {
            return super.hitFloor();
        }
       
        override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }     
    }
}

And your PlayState.as file should look like this:
Code: [Select]
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 _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;
           
            _p = new Player(48, 448);
            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);
        }
       
        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);
            }
        }
       
    }   
}

darthlupi

  • Active Member
  • ***
  • Posts: 241
  • Karma: +0/-0
  • All Smiles
    • View Profile
    • LupiGames.com
Re: Basic Game Tutorial
« Reply #35 on: Fri, Aug 14, 2009 »
Tim just finished part 6 - madness!

VI: Fighting back!

    Now that you have the player and the enemy on the screen, you need to give the player a way to fight back! We're going to give him some Ninja Stars too throw.

1:  First, draw a simple sprite for your Ninja Star. This doesn't have to be complicated.
- this is really trans, just put black background to see better.



Name this file "NinjaStar.png", and place it in your data directory.

2:  We're also going to create a "spark" graphic for when the Star hits something.



Link to above tiny sprite: http://darthlupi.com/images/Spark.png  << Right click save as

Name this file "Spark.png" and place it in the same directory.

3:  We're going to create a new Class for our stars. Name it "NinjaStar.as", then import Flixel and make it extend FlxSprite.

4:  Right before the constructor, you're going to Embed the NinjaStar and Spark graphics:
Code: [Select]
[Embed(source = "../../data/NinjaStar.png")] private var ImgStar:Class;
        [Embed(source = "../../data/Spark.png")] private var ImgSpark:Class;

5: We're also going to setup a variable to be a FlxEmitter to give a little special effect to our star.
Code: [Select]
private var _sparks:FlxEmitter;
6:  Change the constructor's heading to look like this:
Code: [Select]
function NinjaStar(X:Number, Y:Number, XVelocity:Number, YVelocity:Number ):void
7:  Then within the constructor, we're going to call super, and then setup a few different things:
Code: [Select]
super(ImgStar, X, Y, true, true);
            
            //Basic movement speeds
            maxVelocity.x = 200;  //How fast left and right it can travel
            maxVelocity.y = 200;  //How fast up and down it can travel
            angularVelocity = 100; //How many degrees the object rotates            
            //bounding box tweaks
            width = 5;  //Width of the bounding box
            height = 5;  //Height of the bounding box
            offset.x = 6;  //Where in the sprite the bounding box starts on the X axis
            offset.y = 6;  //Where in the sprite the bounding box starts on the Y axis
            addAnimation("normal", [0]);  //Create and name and animation "normal"
            _sparks = FlxG.state.add(new FlxEmitter(0,0,0,0,null,-0.1,-150,150,-200,0,-720,720,400,0,ImgSpark,10,true,PlayState.lyrSprites)) as FlxEmitter;  
            facing = true;
            //The object now is removed from the render and update functions.  It returns only when reset is called.
            //We do this so we can precreate several instances of this object to help speed things up a little
            exists = false;
Most of this should be familiar to you from our other FlxSprites. However, the FlxEmitter is a little new. This object is going to actually create a bunch of sprites at once when we call it - using the ImgSpark graphic - to look like a little explosion of sparks. There's a lot of neat effects you can do with FlxEmitters.

8: Next, we're going to override some of the FlxSprites functions so that whenever our Star hits a wall or something, it will call the "Kill" function, and basically kill itself.
Code: [Select]
override public function hitFloor():Boolean
        {
            kill();
            return super.hitFloor();
        }
        override public function hitWall():Boolean
        {
            kill();
            return super.hitWall();
        }        
        override public function hitCeiling():Boolean
        {
            kill();
            return super.hitCeiling();
        }

9: Next, our kill function. FlxSprite has a Kill function which instantly kills the sprite, regardless of any health they might have or whatever. We're going to override this function to let our Star die in glory (using our Spark FlxEmitter):
Code: [Select]
override public function kill():void
        {
            if(dead)
                return;            
            _sparks.x = x + 5;
            _sparks.y = y + 5;
            _sparks.reset();
            super.kill();
        }
What this says is:
    If this object is already dead, don't do anything - get out. Otherwise, place our FlxEmitter object at the location of this sprite (+5 in x and y), and then "reset" our Emitter - which tells it to explode all of it's particles right then. Then we call the actual kill function of FlxSprite which will remove this sprite.

10: Finally, in order to save resources, we're going to let the system 'reuse' dead Stars and place them back in the game, so we're going to create a 'reset' function:
Code: [Select]
public function reset(X:Number,Y:Number,XVelocity:Number,YVelocity:Number):void
        {
            x = X;
            y = Y;
            dead = false;
            exists = true;
            visible = true;
            velocity.x = XVelocity; //Set the left and right speed
            play("normal"); //Play the animation
        }
This will just 'undo' all the stuff that happens when you Kill a sprite so that the game can use this Star again.

That's it for this Class! Your NinjaStar.as file should look like this:
Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;
    
    public class NinjaStar extends FlxSprite
    {
        [Embed(source = "../../data/NinjaStar.png")] private var ImgStar:Class;
        [Embed(source = "../../data/Spark.png")] private var ImgSpark:Class;
        
        private var _sparks:FlxEmitter;
        public function NinjaStar(X:Number, Y:Number, XVelocity:Number, YVelocity:Number ):void
        {
            super(ImgStar, X, Y, true, true);
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            angularVelocity = 100;
            width = 5;
            height = 5;
            offset.x = 6;
            offset.y = 6;
            addAnimation("normal", [0]);
            _sparks = FlxG.state.add(new FlxEmitter(0,0,0,0,null,-0.1,-150,150,-200,0,-720,720,400,0,ImgSpark,10,true,PlayState.lyrSprites)) as FlxEmitter;            
            facing = true;
            exists = false;
        }
        override public function hitFloor():Boolean
        {
            kill();
            return super.hitFloor();
        }
        override public function hitWall():Boolean
        {
            kill();
            return super.hitWall();
        }        
        override public function hitCeiling():Boolean
        {
            kill();
            return super.hitCeiling();
        }
        
        override public function kill():void
        {
            if(dead)
                return;            
            _sparks.x = x + 5;
            _sparks.y = y + 5;
            _sparks.reset();
            super.kill();
        }
        
        public function reset(X:Number,Y:Number,XVelocity:Number,YVelocity:Number):void
        {
            x = X;
            y = Y;
            dead = false;
            exists = true;
            visible = true;
            velocity.x = XVelocity;
            play("normal");
        }
        
    }
    
}
« Last Edit: Fri, Aug 14, 2009 by darthlupi »
To take care of that not so fresh feeling: #flixel on irc.freenode.net.

Use your favorite IRC client or  http://webchat.freenode.net/

darthlupi

  • Active Member
  • ***
  • Posts: 241
  • Karma: +0/-0
  • All Smiles
    • View Profile
    • LupiGames.com
Re: Basic Game Tutorial
« Reply #36 on: Fri, Aug 14, 2009 »
1:  We now need to change the Player.as file to allow the player to actually throw these stars. Open this file now.

2:  We need to add 2 more variables to the top of the class. First, a FlxArray which will connect to the Array we will later setup in PlayState to hold all of the stars the player throws. Then we'll also need a counter to track when the player can throw another star - we want to create a pause.
Code: [Select]
private var _stars:FlxArray;
        private var _attack_counter:Number = 0;

3:  Next, we're going to need to pass the Array of stars to the Player object when it initializes. So, change the top of the constructor class to this:
Code: [Select]
private function  Player(X:Number,Y:Number,Stars:FlxArray):void
4: Somewhere within the constructor, after "super", we need to actually connect the Stars array we pass to the Stars array we created.
Code: [Select]
_stars = Stars;
5:  Next, in the update function, we want to see if our attack counter is > 0,and if so, subtract from it to make it closer to 0. The player will only be able to throw a Star if the counter is at 0. You can place this code right around the hurt counter code:
Code: [Select]
if (_attack_counter > 0)
            {
                _attack_counter -= FlxG.elapsed*3;
            }

6:  Now we want to respond to the player pressing the attack key. Still in update, and below the other keypress checks, add this:
Code: [Select]
if(FlxG.justPressed(FlxG.B) && _attack_counter <= 0)
            {
                _attack_counter = 1;
                play("attack");
                throwStar(facing);
            }  
So what this says is: If the player presses the B Key (c), and the attack counter is 0 or less, then set the counter to 1, play the "attack" animation, and then call a function called "throwStar", passing data based on which direction the player is facing. We'll write this function later.

7:  Next, we're going to make sure the attack animation plays while the attack counter is going down. This helps show the player that they can't attack again just yet. In the update function, right beneath the _hurt_counter check to play("hurt"), we're going to add this clause to the if statement:
Code: [Select]
else if (_attack_counter > 0)
            {
                play("attack");
            }

8:  For the throwStar function, we're going to do a couple of things. First, we need it to recieve the direction we want to first determine which way to throw the star. "facing" is a variable build into FlxSprite. If an object is 'facing' to the right, then it's true, otherwise, it's false. We pass facing to this function, and use it to determine which direction the star will move.
Then we're going to see if we have any stars that we can reuse: stars that are in our array of stars, but aren't currently on the screen. If we do, we're going to use one of those stars, or we'll make a new star if we have to.
Then we set the star's velocity, and add our star to the lyrSprites layer, so it starts moving in the direction specified.
Code: [Select]
private function throwStar( dir:Boolean ):void
        {
            var XVelocity:Number;
            if (dir) XVelocity = 200;
            else XVelocity = -200;
            for(var i:uint = 0; i < _stars.length; i++)
                if(!_stars[i].exists)
                {
                    _stars[i].reset(x, y + 2,XVelocity, 0);
                    return;
                }
            var star:NinjaStar = new NinjaStar(x, y + 2, XVelocity, 0);
            star.reset(x, y,XVelocity, 0)
            _stars.add(PlayState.lyrSprites.add(star) );    
        }

That's it for the Player.as file for now! It should looks like this:
Code: [Select]
package com.Tutorial
{
    import com.adamatomic.flixel.*;

    public class Player extends FlxSprite
    {
        [Embed(source = "../../data/Player.png")] private var ImgPlayer:Class;
        private var _move_speed:int = 400;
        private var _jump_power:int = 800;  
        public var _max_health:int = 10;
        public var _hurt_counter:Number = 0;
        private var _stars:FlxArray;
        private var _attack_counter:Number = 0;
        
        public function  Player(X:Number,Y:Number,Stars:FlxArray):void
        {
            super(ImgPlayer, X, Y, true, true);
            
            _stars = Stars;
            maxVelocity.x = 200;
            maxVelocity.y = 200;
            health = 10;
            acceleration.y = 420;            
            drag.x = 300;
            width = 8;
            height = 14;
            offset.x = 4;
            offset.y = 2;
            
            addAnimation("normal", [0, 1, 2, 3], 10);
            addAnimation("jump", [2]);
            addAnimation("attack", [4,5,6],10);
            addAnimation("stopped", [0]);
            addAnimation("hurt", [2,7],10);
            addAnimation("dead", [7, 7, 7], 5);
            facing = true;
        }
        override public function update():void
        {
            if(dead)
            {
                if(finished) exists = false;
                else
                    super.update();
                return;
            }
            if (_hurt_counter > 0)
            {
                _hurt_counter -= FlxG.elapsed*3;
            }
            if (_attack_counter > 0)
            {
                _attack_counter -= FlxG.elapsed*3;
            }
            if(FlxG.kLeft)
            {
                facing = false;
                velocity.x -= _move_speed * FlxG.elapsed;
            }
            else if (FlxG.kRight)
            {
                facing = true;
                velocity.x += _move_speed * FlxG.elapsed;                
            }
            if(FlxG.justPressed(FlxG.A) && velocity.y == 0)
            {
                velocity.y = -_jump_power;
            }

            if(FlxG.justPressed(FlxG.B) && _attack_counter <= 0)
            {
                _attack_counter = 1;
                play("attack");
                throwStar(facing);

            }            
            
            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");
                    }
                }
            }
            super.update();
            
        }
        
        override public function hitFloor():Boolean
        {
            return super.hitFloor();
        }
        
        override public function hurt(Damage:Number):void
        {
            _hurt_counter = 1;
            return super.hurt(Damage);
        }        
            
        private function throwStar( dir:Boolean ):void
        {
            var XVelocity:Number;
            if (dir) XVelocity = 200;
            else XVelocity = -200;
            for(var i:uint = 0; i < _stars.length; i++)
                if(!_stars[i].exists)
                {
                    _stars[i].reset(x, y + 2,XVelocity, 0);
                    return;
                }
            var star:NinjaStar = new NinjaStar(x, y + 2, XVelocity, 0);
            star.reset(x, y,XVelocity, 0)
            _stars.add(PlayState.lyrSprites.add(star) );    
        }
            
    }
    
}

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 through.
Code: [Select]
private var _pStars:FlxArray;
2:  Somewhere within the constructor, before you initialize the Player object, we want to setup this FlxArray:
Code: [Select]
_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:
Code: [Select]
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.
Code: [Select]
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.
Code: [Select]
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:
Code: [Select]
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.
« Last Edit: Tue, Aug 18, 2009 by darthlupi »
To take care of that not so fresh feeling: #flixel on irc.freenode.net.

Use your favorite IRC client or  http://webchat.freenode.net/

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #37 on: Mon, Aug 17, 2009 »
VII: More Stars!

    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: [Select]
private var _eStars:FlxArray;
3:  Right beneath where we build our initial stars for the player we want to do the same for the enemy stars:
Code: [Select]
_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).
Code: [Select]
_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:
Code: [Select]
FlxG.collideArray2(_map, _eStars);
            FlxG.overlapArray(_eStars, _p, StarHitsPlayer);

6:  Now we need to add the StarHitsPlayer function:
Code: [Select]
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:
Code: [Select]
private var _eStars:FlxArray;
3:   We also want a counter to prevent the enemy from attacking all the time, so add this declaration:
Code: [Select]
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:
Code: [Select]
public function Enemy(X:Number,Y:Number,ThePlayer:Player, EnemyStars:FlxArray):void
5:  Within the constructor, right after "super()", add this:
Code: [Select]
_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:
Code: [Select]
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:
Code: [Select]
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:
Code: [Select]
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:
Code: [Select]
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.

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #38 on: Mon, Aug 17, 2009 »
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:
Code: [Select]
private var _scoreDisplay:FlxText;
3:  Within the constructor somewhere, we want to define the scoreDisplay object and set it to never scroll.
Code: [Select]
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);

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:
Code: [Select]
var _old_score:uint = FlxG.score;
5: Next, near the end of the update function (after ALL of the collision checks) add this:
Code: [Select]
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:
Code: [Select]
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.

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:
Code: [Select]
[Embed(source = '../../data/hearts.png')] private var ImgHearts:Class;
3:  We're going to add a FlxArray to hold our hearts.
Code: [Select]
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:
Code: [Select]
_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 8x8 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()):
Code: [Select]
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':
Code: [Select]
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!

SeiferTim

  • Contributor
  • ****
  • Posts: 253
  • Karma: +0/-0
    • View Profile
    • Tim's World
Re: Basic Game Tutorial
« Reply #39 on: Mon, Aug 17, 2009 »
So... we're getting close to being done with out tutorial... our next plan is to show how to make enemy spawners... and then...? What else do you guys want to know?