Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

6. June 2017


There are several game engines out there that present a code free option such as Stencyl and Construct.  Today we are looking at an open source alternative, GDevelop.  It is a cross platform, open source 2D game engine with a visual programming interface that requires no previous experience.  However there is also the ability to extend the engine using the C++ programming language if desired.  If you are new to the Closer Look series, it is a combination of game engine review and getting started tutorial helping you decide if a game engine is right for you.


As always, there is a video version of this tutorial available here and embedded below.


Without further ado, let’s jump into GDevelop.


Meet GDevelop

As mentioned above, GDevelop is an open source 2D game engine.  It is released under the MIT license for the core engine, while the editor is available under the GPL v3 open source license.  The code is available on Github.

Of course you don’t ever have to touch the source code to work with GDevelop.  You can download binaries for Windows, Mac, Linux as well as Browsers, iOS and Android in the form of GDevApp.  I will not be covering GDevApp today however.  GDevelop is able to compile native applications, HTML5 pages as well as Android applications, which is currently an experimental feature.

The vast majority of your time is going to be spent in the GDevelop editor, shown below.

image

The center of the screen is currently showing the Scene editor.  There is where you can compose scenes.  It is a tabbed view that can contain multiple open scenes as well as Events, the programming model of GDevelop, which we will discuss more shortly.

On the right hand side is the Objects Editor, which contain the building blocks of your game.  On the left hand side is the Project Manager containing the assets and scenes that make up your game.


Let’s walk through creating a sprite in a game.  In the Objects editor, right click objects and select Add an object.

image

This will show us a list of possible objects:

image


For every object except the Sprite you need to enable it before you can use it (thus why they are grayed out).  It’s as simple as double clicking a grayed out object to enable it however, like so:

image


In this case however we are going to use the Sprite object, which is already enabled.  Simply double click Sprite.  Double clicking the newly created object in the Object editor will open up the sprite editor screen:

image


At the top right corner of the editor window you will see the Images bank’s editor, click the plus icon and add the images you will use for your sprite.  Next drag the selected images down to the images section, like so:

GIF


You can rename and configure the animation (if any) under the Animations panel.

image


Now you can create an instance of your Sprite object by simply dragging it into the scene.

GIF2


As we saw earlier, there are several built in objects in addition to the Sprite object, including Admob integration, a Text object, a tiled sprite (spritesheet), etc.   Next lets move on to adding some logic to our newly created object.


Scripting in GDevelop

Logic in GDevelop are implemented using Events.  Let’s look at an example of moving the sprite around when the mouse moves.  Click on the Events tab.

image


In the Events ribbon, click Add an Event:

image


In this case we have no conditions, we want this to happen every single frame.  Hover over No actions, then select Add an Action.

image


This is where the building block aspect of GDevelop programming comes into play.  Select All Objects->Position->Position of an object.

image


Now we can set the parameters for positioning our object.  First select the object to modify, then set each parameter.  In this case we set the X and Y values to those of the mouse cursor, like so:

GIF3


Upon completion, you will see we now have an event defined:

image


In this particular case we have no condition, so our event will fire every pass through the event loop.  We could however have set a condition which causes our event to fire or not.  Here for example is a condition that will start playing some music once the scene is loaded.

image


Beyond conditions, there are other control structures you can add to events:

image


Link enables you to break link to another event sheet, enabling you to modularize your code.  It is also possible to define variables, both globally, to the scene and at the object level.  For example, right click the Project and select Modify Global Variables.

image


This now enables us to define new variables:

image


This can also be done at the object level, right click an object and select Other Properties.  Then in the resulting panel select Click to Edit...

image


While we are here, notice the Behaviors option?  This enables us to add new functionality to game objects.  Click the Add... button:

image


You will notice once again, by default all behaviors are disabled.  Double click a behavior to add it in.  Let’s go ahead and add Top-down movement as an example.  You can now edit properties of the new behavior in the same dialog:

image


This will instantly add arrow key navigation abilities to your object.  When you play your game, arrow keys will cause your object to move around screen.  Speaking of playing your game, hit the Preview button in the Scene ribbon to launch your game in your browser.

image


You can control application level settings of your project by right clicking your project and selecting Edit the property of the game.

image


This will bring up the next dialog.

image


Finally, if your project has Native extensions enabled, under the File menu you have the option to build a native version of your application.

image


Then simply click the Compile button.

image



Documentation and Community

GDevelop is reasonably well documented, with a Getting Started tutorial, several other tutorials and a decent number of starter templates to choose from.

image 


There is also a manual available online.  If you are intending to extend GDevelop using C++ however, the documentation is almost non-existent.  However being an open source project, all of the code is available.  All of the behaviors and objects we used in this example are available in source form on Github in the extensions folder:

image


GDevelop has a dedicated forum available here.  It is reasonably active with a decent sized community.  Forums are available in both English and French.


Conclusion

If open source, free and a visual programming interface are important to you, GDevelop is definitely an engine to consider.  The documentation is adequate, the engine is mostly feature complete, although annoyingly some features such as Tiled support are only available for native targets.  The entire thing is designed around extensibility and if you are willing to dive into C++, the sky’s the limit on what you can do.  My biggest complaint is a lack of polish on the UI layer, experiencing a few crashes, some UI glitches that went away on a reload and some buttons that simply do nothing.  Most annoyingly, the engine is basically unusable on a high DPI display.

It is however an easy engine to jump into and use if you are willing to deal with some UI warts.  An MIT license around the core engine is always an excellent feature.


The Video

Programming , , , ,

8. May 2017

 

Ever needed to loop a background forever in your 2D game and wondered the best way to do it?  That’s exactly what we are going to look at today, with all the code samples done using HaxeFlixel, which I recently covered in this tutorial series if you wish to learn more.  One key aspect is to have a background that is seamless.  That is the ending edge and beginning edge need to blend seamlessly together.  For this example I am using this graphic (click for the full resolution version).

BloodRed

 

There are two approaches we are going to cover today.  The first one is the simplest, essentially we update our camera when we get within half a screens height of the edge and loop it to the bottom, like so:

CameraLoop0001-0060

 

Let’s take a look at the code.

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

class PlayState extends FlxState
{
   var bg:FlxSprite;
   var camTarget:FlxObject;
   override public function create():Void
   {
      super.create();
      bg = new FlxSprite(0,0,AssetPaths.BloodRed__png);
   
      add(bg);

      camTarget = new FlxObject();
      camTarget.setPosition(FlxG.width/2, FlxG.height/2);
      FlxG.camera.target = camTarget;

      add(camTarget);
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      camTarget.y+=25;

      // Move the camera to loop forever  
      if(camTarget.y >  bg.height - FlxG.height/2){
         camTarget.setPosition(FlxG.width/2, FlxG.height/2);
      }
   }
}

 

The downside to this approach is you also need to reset the Y coordinate of all the entities in your scene as your camera resets.

Another option is to use two instances of the background and as you approach the edge you raise the lower most version to the top and repeat forever.  Like so:

CameraLoop20001-0060

 

Here is the code for this approach.

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

class PlayState extends FlxState
{
   var bg:FlxSprite;
   var bg2:FlxSprite;
   var camTarget:FlxObject;
   override public function create():Void
   {
      super.create();
      bg = new FlxSprite(0,0,AssetPaths.BloodRed__png);
      bg2 = new FlxSprite(0,bg.height,AssetPaths.BloodRed__png);
   
      add(bg);
      add(bg2);

      camTarget = new FlxObject();
      camTarget.setPosition(FlxG.width/2, FlxG.height/2);
      FlxG.camera.target = camTarget;

      add(camTarget);
   }

   override public function update(elapsed:Float):Void
   {
      super.update(elapsed);
      camTarget.y+=25;

      var greater,lesser;
      if(bg.y > bg2.y){
         greater = bg;
         lesser = bg2;
      }
      else{
         greater = bg2;
         lesser = bg;
      }
      if(camTarget.y > greater.y + bg.height - FlxG.height/2){
         trace("Flip");
         lesser.y = greater.y + bg.height;
      }

   }
}

 

This approach has the advantage of enabling you to keep coordinates consistent, but has the downside of requiring a second identical sprite.  Since the second sprite is simply an instance of the same texture though, the memory impact should be almost non-existent.

Programming , , ,

26. April 2017

 

Welcome back to our ongoing HaxeFlixel Tutorial Series, today we are going to cover playing sound and music in HaxeFlixel.  This title is a bit misleading, as they are both basically the same thing, they are all ultimately FlxSound objects.  The single biggest challenge you are going to have with HaxeFlixel audio is file formats, so let’s start there. 

The file format of your audio file depends entirely on the platform your code is going to run on.  On the Flash player platform, all of your audio files need to be in mp3 format.  However on other platforms the mp3 file format has a number of licensing issues, so they instead use Ogg Vorbis (ogg) files for long audio files, and WAV for shorter effects.  Why two different formats?  WAV files are much faster to load, but take up more system memory.  They are ideal for frequently used but shorter sound files.  Ogg files on the other hand are much more compressed, taking more CPU power to process but a great deal less space.  This makes them ideal for longer audio files, such as your game’s music.  In addition to the file format limitations, your sounds need to be encoded at 11025, 22050 or 44100kHz frequency.  If you need to switch encoding frequencies, Audacity is a great free program (you can learn more about here) that can also be used to convert between the various file formats.

Using a default project, HaxeFlixel will automatically create sound and music folders in your asset directory.  One thing you will notice in this example is when you start including multiple different audio files for different platforms, the warnings can become extremely irritating.  Hearing that your mp3 file isn't compatible with your platform, or that your WAV file wont work on Flash gets irritating quickly.  Thankfully we have the ability to filter our files based on platform.  In your Project.xml file, locate the Path Settings area and edit it like so:

   <!-- _____________________________ Path Settings ____________________________ -->

   <set name="BUILD_DIR" value="export" />
   <classpath name="source" />
   <!-- <assets path="assets" /> -->
   <assets path="assets/music" include="*.mp3" if="flash" />
   <assets path="assets/music" include="*.ogg|*.wav" unless="flash" />
   <assets path="assets/sounds" include="*.mp3" if="flash" />
   <assets path="assets/sounds" include="*.ogg|*.wav" unless="flash" />

This will cause Flash builds to only see mp3 files from the music and sound directories, while all other platforms will only see the ogg and wav format files.  Ok, now lets move on to some actual code.

Playing music and sound effects is trivial in HaxeFlixel, but once again platform becomes a factor.  In this case we are using conditional compilation to solve this problem, like so:

#if flash
   FlxG.sound.playMusic(AssetPaths.techno__mp3);   
   soundEffect = FlxG.sound.load(AssetPaths.gunshot__mp3);
#else
   FlxG.sound.playMusic(AssetPaths.techno__ogg);
   soundEffect = FlxG.sound.load(AssetPaths.gunshot__wav);  
#end

 

In this case the music file will play automatically, while the soundEffect needs to be triggered manually.  Let’s take a look at how to do that in our update() method.

if(FlxG.keys.justPressed.G)
   soundEffect.play(true);

The true parameter causes the sound effect to replace any running instances.  This will result in the sound starting from the beginning if you press the G key multiple times before a sound effect has finished playing. 

You also have a fair bit of control over sound playback.  The following code shows how to pause/resume sound as well as increasing and decreasing the volume.  Volume is a value that ranges from 0.0 to 1.0, with 0.0 being complete silence while 1.0 is maximum volume.

if(FlxG.keys.justPressed.P)
   if(FlxG.sound.music.active)
      FlxG.sound.music.pause();
   else
      FlxG.sound.music.resume();
if(FlxG.keys.justPressed.PLUS)
   FlxG.sound.changeVolume(0.1);
if(FlxG.keys.justPressed.MINUS)
   FlxG.sound.changeVolume(-0.1);

 

HaxeFlixel also supports positional audio.  Both by changing the X and Y position of a sound effect, or positioning it relative to another object.  The latter is the approach we are going to take here, creating a virtual ear that is centered to the screen.

soundX = FlxG.width/2;  
soundY = FlxG.height/2;
ear = new FlxObject();
ear.setPosition(FlxG.width/2, FlxG.height/2);   

Now we can relocate the sound relative to the ear and it will change accordingly.

 

There is also

if(FlxG.keys.justPressed.LEFT)
   soundX -= 25;
if(FlxG.keys.justPressed.RIGHT)
   soundX += 25;        
if(FlxG.keys.justPressed.UP)
   soundY -= 25;
if(FlxG.keys.justPressed.DOWN)
   soundY += 25;                 
FlxG.sound.music.proximity(soundX,soundY, ear,300,true);

It is also possible to tween audio, fading it in and out over time, like so:

if(FlxG.keys.justPressed.S)
   FlxG.sound.music.fadeOut(3.0);

You can also set up callback functions when a sound stops playing.  This can be used to easily create a music or sound effect management system.

FlxG.sound.music.onComplete = function() {
   // This will only ever fire if you hit L to turn looping off
   trace("Song ended");
}

 

Now let’s show the complete source listing, which also illustrates a couple other features such as looping, muting and pausing audio.

package;

import flixel.FlxG;
import flixel.FlxSprite;
import flixel.FlxState;
import flixel.system.FlxSound;
import flixel.FlxObject;

class PlayState extends FlxState
{
   var soundEffect:FlxSound;
   var soundX:Float;
   var soundY:Float;
   var ear:FlxObject;

   override public function create():Void
   {
      super.create();
      #if flash
         FlxG.sound.playMusic(AssetPaths.techno__mp3);   
         soundEffect = FlxG.sound.load(AssetPaths.gunshot__mp3);
      #else
         FlxG.sound.playMusic(AssetPaths.techno__ogg);
         soundEffect = FlxG.sound.load(AssetPaths.gunshot__wav);  
      #end

         soundX = FlxG.width/2;  
         soundY = FlxG.height/2;
         ear = new FlxObject();
         ear.setPosition(FlxG.width/2, FlxG.height/2);   

      FlxG.sound.music.onComplete = function() {
         // This will only ever fire if you hit L to turn looping off
         trace("Song ended");
      }

      FlxG.sound.muted = true;
   }

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

if(FlxG.keys.justPressed.G)
   soundEffect.play(true);

      if(FlxG.keys.justPressed.P)
         if(FlxG.sound.music.active)
            FlxG.sound.music.pause();
         else
            FlxG.sound.music.resume();
      if(FlxG.keys.justPressed.PLUS)
         FlxG.sound.changeVolume(0.1);
      if(FlxG.keys.justPressed.MINUS)
         FlxG.sound.changeVolume(-0.1);

      if(FlxG.keys.justPressed.LEFT)
         soundX -= 25;
      if(FlxG.keys.justPressed.RIGHT)
         soundX += 25;        
      if(FlxG.keys.justPressed.UP)
         soundY -= 25;
      if(FlxG.keys.justPressed.DOWN)
         soundY += 25;                 
      FlxG.sound.music.proximity(soundX,soundY, ear,300,true);

      
      if(FlxG.keys.justPressed.L)
         FlxG.sound.music.looped = false;

      if(FlxG.keys.justPressed.S)
         FlxG.sound.music.fadeOut(3.0);

      if(FlxG.keys.justPressed.M)
         FlxG.sound.muted = !FlxG.sound.muted;
   }
}

 

The Video

Programming , , ,

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 , , ,

Month List

Popular Comments