Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

21. April 2017

 

Earlier on in the ongoing HaxeFlixel Tutorial series we covered drawing sprites, a fairly critical concept to any 2D game.  Today we are going to take that one step further and look at the process of animating them.  If you look at the FlxSprite class you will notice there is a member animation which is a FlxAnimationController.  This is the class we are going to be focusing on today.  However in order to animate sprites, we need sprites to animate.  2D animation is a lot like those flipbook animations you may have drawn on the corner of books.  Like the following:

 

 

This is exactly what we are going to do here.  Except instead of multiple pages of animations, we are going to put all of our sprites in a single image file.  This one in fact.  (Click to view full size)

walk

 

At it’s full size this image is composed of 16 frames of our sprite, each one 512x384 in size.  When we create our FlxSprite, we use a slightly different setup than we did in the past.

      spritesheet = new FlxSprite();
      spritesheet.loadGraphic(AssetPaths.walk__png,true,512,384);

 

Here when we call loadGraphic we pass in the width and height of the rectangle within the larger image that represents our individual sprite.  Next we need to define our animation. 

      spritesheet.animation.add("walkRight",[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],5,true);

Here we are adding an animation with the name “walkRight”, then we pass in the individual frame indexes that represent this animation.  These represent the index of the sub-rectangle within our source image, starting at the top left, going across to the right and down a row.  Therefore in this case 0 is the animation frame at the top left corner, 1 would be the one to it’s right, 4 would be the first image on the second row, 15 would be the final image in the sequence, etc.  After the array of indices, we pass in the frame rate.  In this case we are saying to play this animation at a rate of 5 frames per second.

One cool thing we can do is actually flip the animation on the X or Y axis, enabling us to use the same images for a walkLeft animation, like so:

      spritesheet.animation.add("walkLeft",[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],5,true,true);

It’s the exact same parameters, except with a different name and we pass in true to the FlipX parameter.

 

After we have defined our animation(s) and add() it, we need to tell the game engine which one to play, like so:

      spritesheet.animation.play("walkRight");

 

Let’s put some code in the update() callback that flips the direction of animation when the player presses the spacebar.

      if(FlxG.keys.justReleased.SPACE)
         if(spritesheet.animation.name == "walkRight")
            spritesheet.animation.play("walkLeft");
         else
            spritesheet.animation.play("walkRight");

 

You can also easily alter the speed an animation plays after it has been loaded, like so:

      if(FlxG.keys.justReleased.UP)
         spritesheet.animation.curAnim.frameRate++;
      if(FlxG.keys.justReleased.DOWN)
         spritesheet.animation.curAnim.frameRate--;   

We also have the ability to stop a playing animation, like this code that does it when ESC is hit.

      if(FlxG.keys.justReleased.ESCAPE){
         spritesheet.animation.curAnim.curFrame = 0;
         spritesheet.animation.finish();
      }

You do not have to set the frame to the beginning, but it helps if you want to transition to another animation or restart the existing one.

The animation controller also has a pair of callback functions that can be called each frame of an animation, or when an animation ends.  Please note that in this case we created our animation to loop by default, so the callback will only be called if you hit the ESC key to stop the animation manually.  Or you turn looping off when you define the animation sequence.

      spritesheet.animation.callback = function(s:String, frame:Int, index:Int){
         trace("Animation:" + s + " -- frame: " + frame + " index: " + index);
      }

      spritesheet.animation.finishCallback = function(s:String){
         trace ("------------------------ " + s + " anim finished ------------------");
      }

 

Ok, let’s look at that as one complete example now.

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.text.FlxText;
import flixel.util.FlxColor;

class PlayState extends FlxState
{

   var spritesheet:FlxSprite;
   var text:FlxText;
   override public function create():Void
   {
      super.create();

      this.bgColor = FlxColor.GRAY;
      text = new FlxText(0,0,300);
      add(text);

      spritesheet = new FlxSprite();
      spritesheet.loadGraphic(AssetPaths.walk__png,true,512,384);
      spritesheet.animation.add("walkRight",[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],5,true);
      spritesheet.animation.add("walkLeft",[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],5,true,true);
      add(spritesheet);

      spritesheet.animation.play("walkRight");
      

      spritesheet.animation.callback = function(s:String, frame:Int, index:Int){
         trace("Animation:" + s + " -- frame: " + frame + " index: " + index);
      }

      spritesheet.animation.finishCallback = function(s:String){
         trace ("------------------------ " + s + " anim finished ------------------");
      }
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      if(FlxG.keys.justReleased.SPACE)
         if(spritesheet.animation.name == "walkRight")
            spritesheet.animation.play("walkLeft");
         else
            spritesheet.animation.play("walkRight");

      if(FlxG.keys.justReleased.UP)
         spritesheet.animation.curAnim.frameRate++;
      if(FlxG.keys.justReleased.DOWN)
         spritesheet.animation.curAnim.frameRate--;   

      if(FlxG.keys.justReleased.ESCAPE){
         spritesheet.animation.curAnim.curFrame = 0;
         spritesheet.animation.finish();
      }
      text.text = "Cur speed: " + spritesheet.animation.curAnim.frameRate;    
   }
}

 

Here is the results.  Press SPACE to flip direction, UP and DOWN to control animation speed and ESC to stop the animation all together.

 

The Video

[Coming Soon]

Programming , , ,

17. April 2017

 

Up until this point in the HaxeFlixel Tutorial Series all of our examples have been confined to a single screen.  What happens however when your game world is larger than the screen?  Well, that’s where cameras come in.  In this tutorial we are going to look at creating a camera and navigating around the game world.  Cameras in HaxeFlixel are also capable of much more then this, including special effects and even split screen, all of which we will look at in this tutorial. 

There is one important concept to understand before we get too far.  The camera and the screen resolution are two completely different things.  The screen resolution, which we covered in this tutorial, determines the resolution and size of the applications window.  A camera on the other hand is a view into our game world.  They do not have to be the same, nor do they have to even use the same units.

In the example we are going to create I have a single large image to use as a background, an image many times larger than the screen, then another sprite to represent our player within the game world.  Let’s get the setup out of the way.  Don’t worry, I’ll do a full source listing later in the tutorial.

class PlayState extends FlxState
{
   var background:FlxSprite;
   var jet:FlxSprite;
   var blur:Bool = false;
   override public function create():Void
   {
      super.create();
      background = new FlxSprite();
      background.loadGraphic(AssetPaths.aerialcropped__jpg,false);

      jet = new FlxSprite();
      jet.loadGraphic(AssetPaths.jet__png);
      
      camera = new FlxCamera(0,0,640,480);

      camera.bgColor = FlxColor.TRANSPARENT;
      camera.setScrollBoundsRect(0,0,640,background.height);
      
      // Replace default camera with our newly created one
      FlxG.cameras.reset(camera);
      
      jet.angle = 90.0;
      jet.x = 320 - jet.width/2;
      jet.y = 0;

      camera.target = jet;
      add(background);
      add(jet);
   }
}

 

We run this game and get:

image

 

Nothing too exciting so far.  We setup and add our two sprites and most importantly create our FlxCamera object.  We create it with a top left position of 0,0 and size of 640x480.  We then set our camera as active with a call to FlxCamera.reset().  One important thing to be aware of, by default Flixel will create a camera for you in FlxG.camera that matches the size of your application’s resolution.  In this example there is no reason why we couldn’t just use the default created camera.  The other critical line of code to be aware of is

camera.target = jet;

This tells our camera to follow the FlxObject passed in, in this case our jet sprite.  The other key line here is

camera.setScrollBoundsRect(0,0,640,background.height);

This line sets the range that our camera will navigate.  As you will see in a moment when our jet sprite exceeds the bounds the camera will travel no further.  On that topic, let’s get some movement going and see what happens!

Add the following code to your project:

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      jet.y+= 300.0 * elapsed;
   }

 

And now when we run it (focus and press spacebar to begin):

 

As you can see, the camera smoothly follows the jet until the jet sprite exceeds the predefined bounds.  Neat.  So that’s the basics of a camera, but by no means all it can do.  Let’s look at adding zoom functionality next.  Add the following code to your update:

if(FlxG.keys.justReleased.UP)
   camera.zoom += 0.1;
if(FlxG.keys.justReleased.DOWN)
   if(camera.zoom > 1)
      camera.zoom -= 0.1;

Now when you press the update and down arrows:

zoomGifResized

 

You can also perform special effects, such as camera shake:

if(FlxG.keys.justReleased.SPACE)
   camera.shake(0.01,1);   

Resulting in:

Gif4Optimized

 

You can even apply special effect filters, such as blur:

if(FlxG.keys.justReleased.ESCAPE){
   blur = !blur;
   if(blur){
      var filter = new BlurFilter();
      camera.setFilters([filter]);
   }
   else {
      camera.setFilters([]);
   }
}

Resulting in:

GIF5

 

Now let’s look at a complete example.  In addition to all of the code above this one has a couple more features.  You can flash the screen red by pressing the A key.  Also when the jet goes off screen, the screen will fade to black then restart a few seconds later. 

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxCamera;
import flixel.FlxState;
import flixel.util.FlxColor;
import flash.filters.BlurFilter;

class PlayState extends FlxState
{
   var background:FlxSprite;
   var jet:FlxSprite;
   var blur:Bool = false;
   var start:Bool = false;
   override public function create():Void
   {
      super.create();
      background = new FlxSprite();
      background.loadGraphic(AssetPaths.aerialcropped__jpg,false);

      jet = new FlxSprite();
      jet.loadGraphic(AssetPaths.jet__png);
      
      camera = new FlxCamera(0,0,640,480);

      camera.bgColor = FlxColor.TRANSPARENT;
      camera.setScrollBoundsRect(0,0,640,background.height);
      
      // Replace default camera with our newly created one
      FlxG.cameras.reset(camera);
      
      jet.angle = 90.0;
      jet.x = 320 - jet.width/2;
      jet.y = 0;

      camera.target = jet;
      add(background);
      add(jet);
   }



   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      if(FlxG.keys.justReleased.SPACE)
         start = true;
         
      if(FlxG.keys.justReleased.UP)
         camera.zoom += 0.1;
      if(FlxG.keys.justReleased.DOWN)
         if(camera.zoom > 1)
            camera.zoom -= 0.1;

      if(FlxG.keys.justReleased.S)
         camera.shake(0.01,1);
      
      if(FlxG.keys.justReleased.ESCAPE){
         blur = !blur;
         if(blur){
            var filter = new BlurFilter();
            camera.setFilters([filter]);
         }
         else {
            camera.setFilters([]);
         }
      }              

      if(FlxG.keys.justReleased.A)
         camera.flash(flixel.util.FlxColor.RED,0.5);

      if(start){
         if(jet.y > background.height)
            camera.fade(FlxColor.BLACK,2.0,false, function(){
               FlxG.resetGame();
               });
         jet.y+= 300.0 * elapsed;
      }
   }
}

Click the window below and press Space to begin. S to shake, Up and Down arrows to zoom.  Unfortunately the Blur filter depends on the Flash target so does not work in the browser.

 

 

One final thing we are going to cover is supporting multiple cameras.  Let’s just jump in with a code example.

   override public function create():Void
   {
      super.create();
      background = new FlxSprite();
      background.loadGraphic(AssetPaths.aerialcropped__jpg,false);

      jet = new FlxSprite();
      jet.loadGraphic(AssetPaths.jet__png);
      
      var camera = new FlxCamera(0,0,320,480);

      camera.bgColor = FlxColor.TRANSPARENT;
      camera.setScrollBoundsRect(0,0,640,background.height);
      
      var camera2 = new FlxCamera(320,0,320,480);
      camera2.focusOn(new FlxPoint(320/2,480/2));

      // Replace default camera with our newly created one
      FlxG.cameras.reset(camera);
      FlxG.cameras.add(camera2);

      
      
      jet.angle = 90.0;
      jet.x = 320/2 - jet.width/2;
      jet.y = 0;

      camera.target = jet;
      add(background);
      add(jet);
   }

Now when run you see the screen is now split:

 

The second camera stays stationary while the camera on the left behaves exactly as the original did.

fin

 

The Video

Programming , , ,

6. April 2017

 

Following up on our keyboard handling tutorial, we carry on the HaxeFlixel tutorial series looking at how to handle mouse input.  Unlike keyboard input, polling isnt our only option, we can also have event driven mouse input.  Let’s take a look at how to do both.

Let’s start of with an example polling the mouse.  That is each frame we check for the status of the mouse and respond accordingly.  OK, code time.

import flixel.FlxState;
import flixel.FlxSprite;
import flixel.FlxG;

class PlayState extends FlxState
{
   var sprite:FlxSprite;
   var scaleFactor = 0.1;
   override public function create():Void
   {
      super.create();

      sprite = new FlxSprite();
      sprite.loadGraphic(AssetPaths.enemy__png);
      sprite.x = FlxG.width/2 - sprite.width/2;
      sprite.y = FlxG.height/2 - sprite.height/2;
      add(sprite);


   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);

      if(FlxG.mouse.overlaps(sprite)){
         if(FlxG.mouse.pressed){
            sprite.setPosition(FlxG.mouse.getPosition().x - sprite.width /2   ,
            FlxG.mouse.getPosition().y - sprite.height /2);
         }
      }
      if(FlxG.mouse.justReleasedRight){
         sprite.x = FlxG.width/2 - sprite.width/2;
         sprite.y = FlxG.height/2 - sprite.height/2;
         sprite.scale.set(1,1);
      }

      if(FlxG.mouse.wheel != 0){ 
         sprite.scale.add(FlxG.mouse.wheel * scaleFactor,FlxG.mouse.wheel * scaleFactor);
      }
   }
}

 

The following application is the result of the code above.  Left click and drag with left mouse button to move the sprite, right click to reset its location and use scroll wheel to scale.

You will notice that all our code resides in the update() method, meaning we will check the status of the mouse every frame.  Notice once again that all of the input code is contained in the FlxG class.  Next we use the handy built in overlaps() method which checks if the current position of the mouse overlaps the sprite.  We then check to see if mouse is pressed, by default this will refer to either a left click (or a touch).  Our next test is to see if the right mouse button was pressed then released with the method justReleasedRight(), which will be true once the mouse button is released.  Just like with the keyboard there is a difference between pressed and released in that pressed will be true constantly while the button is down, while released will only be true once per click.  Finally we check the status of the wheel.  FlxG.mouse.wheel represents the amount the wheel moved this frame.

 

Next we look at event driven mouse input.  Instead of checking the status of the mouse over and over like we do with polling, in this example we give HaxeFlixel a series of functions that will be called when input happens. 

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.input.mouse.FlxMouseEventManager;

class PlayState extends FlxState
{
   var sprite:FlxSprite;
   override public function create():Void
   {
      super.create();

      sprite = new FlxSprite();
      sprite.loadGraphic(AssetPaths.enemy__png);
      sprite.x = 200;
      sprite.y = 200;
      add(sprite);


      FlxMouseEventManager.add(sprite,
         function(s:FlxSprite){ trace("MouseDown");},
         function(s:FlxSprite){ trace("MouseUp");},
         function(s:FlxSprite){ trace("MouseOver");},
         onMouseOut
         );
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
   }

   public function onMouseOut(s:FlxSprite){
      trace("Mouse Out, sprite at: " + s.x + "," + s.y + " Mouse at: " + FlxG.mouse.getPosition());
   }
}

 

In this example we wire a number of event handlers up to our sprite object.  This is done via the FlxMouseEventManager for which we pass in the sprite we want to track mouse input for and a series of 4 functions that will be called when various different actions occur.  Those actions are mouse down (any button), mouse up (any button released), mouse over ( the mouse is within the sprite’s bounds) and mouse out ( the mouse leaves the sprite’s bounds).  In this case I used 3 anonymous functions which simply print out the event that occurred.  For the onMouseOut event I instead implemented a member function, mostly just to show that different options you have.  You’ll notice in onMouseOut we ask for the mouse’s position using FlxG.mouse.getPosition().  In a similar manner you can poll the mouse for a great deal of additional information such as it’s location, button and wheel status and more.

 

The choice between polled input, event driven input or a hybrid of both is completely up to you. 

 

The Video

Programming , , ,

30. March 2017

 

Now that we have the basics of drawing graphics on the screen down in the ongoing Haxe and HaxeFlixel tutorial series now is a good time to move on to giving our player some control over the game.  Today we are going to look at how you handle keyboard input in a HaxeFlixel game.  It’s a fairly straight forward task, so this will be a fairly short tutorial consisting of a single code sample.  Without further ado, let’s jump right in with the code!

 

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.input.keyboard.FlxKey;


class PlayState extends FlxState
{
   var sprite:FlxSprite;
   override public function create():Void
   {
      super.create();

      // Create a sprite and center it to the middle of the screen
      sprite = new FlxSprite(AssetPaths.enemy__png);
      sprite.x = FlxG.width/2 - sprite.width/2;
      sprite.y = FlxG.height/2 - sprite.height/2;
      add(sprite);
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);

      if(FlxG.keys.enabled){
         if(FlxG.keys.pressed.LEFT)
            sprite.x--;
         if(FlxG.keys.pressed.RIGHT)
            sprite.x++;

         if(FlxG.keys.justReleased.UP)
            sprite.y--;
         if(FlxG.keys.justReleased.DOWN)
            sprite.y++;

         if(FlxG.keys.anyPressed([FlxKey.ESCAPE,FlxKey.SPACE])){
            sprite.x = FlxG.width/2 - sprite.width/2;
            sprite.y = FlxG.height/2 - sprite.height/2;
         }
      }
   }
}

This example draws a sprite centered to the screen and enables you to control it using the arrow keys.  The majority of logic happens in the update function, where we poll the status of various keys using the object FlxG.keys.  Notice that we start by checking if keys is enabled, this is because many platforms Haxe runs on may not have keyboard input at all.  Next we check the status of keys two different ways, using pressed() and justReleased().  There is a critical difference between these two approaches.  Pressed will have a list of all the keys that are currently being pressed, while released will only have a list of keys that were just released in this pass through the game loop.  The end result is, pressed will be true over and over as a key is held down, while justReleased will only be true once per individual key/press cycle.  This means to move up and down you have to keep hitting the UP/DOWN arrows over and over, while you will move left and right constantly as the LEFT/RIGHT arrows are held down.  Keep in mind the game loop takes only a few milliseconds to run, so even though it feels like a single key press, pressed will be true for several frames. 

 

The final portion of this example illustrates how you can query multiple key presses at a single time.  In this case we check if the ESC or SPACE BAR are pressed and reset our sprites position back to the center of the screen if they are. In addition to anyPressed() there are also methods to check just pressed, and just released.  There are also additional methods to check other values such as firstPressed(), firstJustReleased() and more.  You can also get an array of the currently pressed keys using getIsDown().  Ultimately FlxG.keys implements FlxKeyManager, so that is the class to check out to see the full scope of functionality available.

 

One interesting omission from HaxeFlixel is event driven (as opposed to polled) keyboard input.  The underlying OpenFL and Lime libraries do however support it.

 

The Video

Programming , , ,

20. March 2017

 

In this tutorial in our ongoing HaxeFlixel tutorial series we are going to look at using Sprites in HaxeFlixel.  Sprites are fundamental to HaxeFlixel development and can be simply thought of as a graphic that can move.  We are going to look at a couple different examples, one procedurally creating the graphic to draw, while the other loads the sprite from an image.  We are also going to quickly take a look at how we can move our sprite around the screen.  If you were curious, the term Sprite was coined by Texas Instruments for one of their early graphics chips.  The reason the term Sprite was chosen according to Wikipedia:

The term was derived from the fact that sprites, rather than being part of the bitmap data in the framebuffer, instead "floated" around on top without affecting the data in the framebuffer below, much like a ghost or "sprite". By this time, sprites had advanced to the point where complete two-dimensional shapes could be moved around the screen horizontally and vertically with minimal software overhead.

It was common in the early days of consoles and graphics cards to have dedicated hardware for drawing sprites (non-stationary images) and the number of supported sprites was a key selling feature for computers and consoles.  In HaxeFlixel, Sprite is actually a very important class, the FlxSprite.  Behind the scenes there is no special hardware for drawing sprites anymore, in fact a sprite is ultimately a 3D object that is parallel with the camera, but the terminology has stuck.  Ok, enough history, let’s actually jump in with some code.

 

In this first example, we are simply going to create a FlxSprite programmatically.

package;

import flixel.FlxSprite;
import flixel.FlxState;

class PlayState extends FlxState
{
   var sprite:FlxSprite;
   override public function create():Void
   {
      super.create();
      sprite = new FlxSprite();
      sprite.makeGraphic(300,300,flixel.util.FlxColor.WHITE);
      for(y in 0...300){
         for(x in 0...300){
            if(x%2 == 1 && y%2 == 1)
               sprite.pixels.setPixel(x,y,0x0000ff);
            if(x < 5 || y < 5 || x > 295 || y > 295 )
               sprite.pixels.setPixel(x,y,0xffffff);
         }
      }
      add(sprite);
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      sprite.x += elapsed * 100;
   }
}

When we run this code we should see:

image

 

As the program runs the sprite will slowly advance to the right until it is off the screen.  Let’s just in and figure out exactly what is happening here. 

First thing to notice is the base class of our sprite is FlxSprite.  We allocate a new one then call it’s makeGraphic() method.  This gives us a 2D image to work with, in this case one that’s 300 pixels wide by 300 pixels high and filled with the colour WHITE.  As you can see HaxeFlixel has a handy utility class containing the numeric value of several of the most common colours available in flixel.util.FlxColor.

Now that we have a 300x300 white canvas to play with we simply loop over all the available pixels in the graphic, looping row by row, then through each pixel in that row.  We can then directly set the color value of a given pixel by accessing the setPixel() method of the pixel member of FlxSprite.  The parameters are the x and y location of the pixel within the graphic, relative to the top left corner, as well as the color to set it.  Notice in this example instead of using a predefined colour (which we could have if we preferred) we instead us a hexadecimal encoded number.  If you’ve ever done any HTML coding, this color scheme should be immeidately familiar.  If not each two digits represents first the red, then green, then blue component of the color from 0 to 255 ( or 0 to ff in hexidecimal).  So in this case we set every other pixel of blue ( no red, no green, full blue == 0x0000ff ).  We also have another check to see if we are within 5 pixels of the image edge and if so, draw the pixels in white.  This causes a 5 pixel wide border to appear around our image.

After creating our simple white bordered checkboard image you will notice a call to add().  This call is EXTREMELY important, as it adds the FlxSprite to the object collection of our FlxState derived class PlayState.  Being in this collection will cause the sprite to automatically be drawn and updated each pass through the game engines main loop.  Speaking of updating, notice we have also overridden the update method.  This is called every pass through the game loop and is where you should update the logic of your game world.  In this case we simply increase the x value of our sprite by elapsed * 100.

So... why did we do this?  What exactly are we saying with the line:

sprite.x += elapsed * 100;

This is actually a really common tactic in game development.  One of the challenge of creating games is getting them to run the same speed on various different computers.  If you are running a fixed game rate loop, say a game running at 60fps, and it never runs below that rate, it’s not a problem, you know how fast things are going to update.  However on faster or slower computers this is trickier to determine.  This is why elapsed is so important.  This is a value passed in to the update() function each pass through the game loop and it tells our game how long has elapsed in seconds since the last pass through the game loop.  You can then multiply this value by the amount you want to move by to get a consistent update regardless to the machine you are running on.

For example, if your game was running at 10 frames per second, the value of elapsed will be 0.1.  So to hit our target of 100 pixels per second, this means in updates we will move by 100 * 0.1 or 10 pixels.  However if we were running at 60 fps instead of 10fps, the value of elapsed will instead be 1/60 or 0.016.  So to hit our target of 100pixels per second we update instead by 0.016 * 100 or 1.66 pixels.  Over the course of a second, regardless to framerates, the sprite will be updated by the same total amount.

Before we move on it’s important to know the process of working directly with each pixel in a sprite like we just did is an extremely costly process!  This is the kind of task you want to perform very occasionally, not every frame!

Next, instead of creating an ugly graphic programmatically, lets load a sprite from an image file.  Let’s jump straight into the code.

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.tweens.FlxTween;

class PlayState extends FlxState
{

   var sprite:FlxSprite;
   override public function create():Void
   {
      super.create();
      sprite = new FlxSprite();
      sprite.loadGraphic(AssetPaths.enemy__png);
      sprite.x = 100;
      sprite.y = 0;
      add(sprite);

      FlxTween.tween(sprite, { x: FlxG.width - sprite.width, 
                         y: FlxG.height - sprite.height, 
                         angle : 360.0 }, 5, { type:FlxTween.PINGPONG });
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
   }
}

Now when we run the code, we see:

GIF

 

Notice in this case that our sprite is still a FlxSprite, although instead of creating it from scratch using makeGraphic() we load if from file using loadGraphic passing in the value AssetPaths.enemy__png.  In this case enemy.png is the name of the image we are using for our sprite.  If yours has a different filename, this value will change.  A bit of a note on the image, I added it to the directory assets/images that was automatically created when we created our project.

image

 

As we add new assets to these folders, Haxe is smart enough to automatically create a reference to it for us.  This is a handy feature of the Haxe language and is invoked by this code in AssetPaths.hx:

package;

@:build(flixel.system.FlxAssets.buildFileReferences("assets", true))
class AssetPaths {}

Throwing any applicable asset in the assets directory will automatically generate file references that we can then use in code, like so:

image

 

Very cool.  Behind the scenes loadGraphic is actually doing a lot more than you might think.  In addition to loading the file into memory, it’s actually caching it.  This means that if other code loads the same asset it will get the version from cache instead of creating yet another version.  This improves performance and reduces memory usage.

So now that we have our sprite loaded from an image we set it’s initial x position to 100 pixels.  Notice that graphics are drawn relative to their top left corner and the top left corner of the applications window.  So an image with x = 100 and y = 0 is drawn starting at it’s top left corner 100 pixels right of the top left corner of the window and 0 pixels down from the very top of the application window. 

The final detail of this example is the use of a Tween, called via FlxTween.tween().  FlxTween is another cool way of handling updating your game objects, but it takes care of the timing and calculations for us.  Instead of moving our sprite each pass through the game loop via update() like we did earlier, we simply fire off a Tween and it takes care of things for us.  Tween comes from  “In-Between” and basically it’s a function that you give an end goal and a timeline and it figures out all the in-between calculations required.  In this case we are saying “take this sprite, move it to the bottom corner of the screen, rotating 360 degrees over a period of 5 seconds” and HaxeFlixel figures out how much it needs to move each frame and does it for us.  The final parameter we are saying we want to ping pong the tween, which is to say repeat it, but in reverse forever.  There is a ton more we can do with Tweens and we will cover them in more detail later.

 

That’s it for sprites for now, but there is a lot more we will cover later including texture atlases, animations and more, stay tuned!

 

The Video

Programming , , ,

Month List

Popular Comments