Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


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

blog comments powered by Disqus

Month List

Popular Comments

Cocos2D HTML Tutorial 5: Menus , sounds and music
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


11. July 2012

 

In this tutorial we are going to cover playing sound effects and music files.  We are also going to implement a simple menu system to control everything.  The example sound effect and song are taken from the cocos2D HTML tests. 

 

First we need to preload our sound effect files.  Remember the preload in main.js?  Now we are going to actually use it.  Open up main.js and make the following change:

 

cc.AudioEngine.getInstance().init("mp3,ogg,wav");
   cc.Loader.shareLoader().preload([
    {type:"effect",src:"Resources/effect2"},
    {type:"bgm",src:"Resources/background"}
   ]);

 

Here we preload our song and sound effect for later playback.  It doesn’t have to be in this file, but it guarantees that it has happened when we need it.  You will need to add a pair of mp3 files in a subdirectory named Resources, background.mp3 and effect2.mp3.  Obviously you can use whatever files you want, but be sure not to add the file extension here. 

The out key call is the call to init(). This call initializes the AudioManager singleton.  The parameter are the supported file formats.  mp3, ogg and wav are the supported options.  This value determines what extension the loader will use when looking for your resources.  Each browser supports different file formats, so if you want to playback in as many browsers as possible, it is ideal to provide all three options.  For example, if you do not provide ogg, Chromium on Ubuntu will not play any sound.

 

Now lets take a look at MyFourthApp.js:

 

var MyFourthApp = cc.LayerColor.extend({
    init:function()
    {
        this.initWithColor(new cc.Color4B(0,0,0,255));
        var size = cc.Director.getInstance().getWinSize();


        cc.AudioEngine.getInstance().setEffectsVolume(0.5);
        cc.AudioEngine.getInstance().setBackgroundMusicVolume(0.5);

        var menuItem1 = new cc.MenuItemFont.create("Play Sound",this,this.playSound);
        var menuItem2 = new cc.MenuItemFont.create("Play Song",this,this.playSong);
        var menuItem3 = new cc.MenuItemFont.create("Stop Playing Song",this,this.stopPlayingSound);
        var menuItem4 = new cc.MenuItemFont.create("Exit",this,this.exit);

        menuItem1.setPosition(new cc.Point(size.width/2,size.height/2+50));
        menuItem2.setPosition(new cc.Point(size.width/2,size.height/2));
        menuItem3.setPosition(new cc.Point(size.width/2,size.height/2-50));
        menuItem4.setPosition(new cc.Point(size.width/2,size.height/2-100));

        var menu = cc.Menu.create(menuItem1,menuItem2,menuItem3,menuItem4);
        menu.setPosition(new cc.Point(0,0));

        this.addChild(menu);

        return this;
    },
    playSound:function(){
        cc.log("Playing sound");
        cc.AudioEngine.getInstance().playEffect("Resources/effect2");
    },
    playSong:function(){
        cc.log("Playing song");
        cc.AudioEngine.getInstance().playBackgroundMusic("Resources/background",false);
    },
    stopPlayingSound:function(){
        cc.log("Done playing song");
        if(cc.AudioEngine.getInstance().isBackgroundMusicPlaying())
        {
            cc.AudioEngine.getInstance().stopBackgroundMusic();
        }
    },
    exit:function(){
        document.location.href = "http://www.gamefromscratch.com";
    }
});


MyFourthAppScene = cc.Scene.extend({
    onEnter:function(){
        this._super();
        var layer = new MyFourthApp();
        layer.init();
        this.addChild(layer);
    }
});

 

The guts of this are identical to the last tutorial, with the obvious exception of changed names.

The following two lines set the background volume for both music and effects to 50%.  As you can see, AudioManager is available as a singleton object, just like Director.

 

cc.AudioEngine.getInstance().setEffectsVolume(0.5);
cc.AudioEngine.getInstance().setBackgroundMusicVolume(0.5);

 

Next up we create 4 different MenuItemFont objects:

var menuItem1 = new cc.MenuItemFont.create("Play Sound",this,this.playSound); var menuItem2 = new cc.MenuItemFont.create("Play Song",this,this.playSong); var menuItem3 = new cc.MenuItemFont.create("Stop Playing Song",this,this.stopPlayingSound); var menuItem4 = new cc.MenuItemFont.create("Exit",this,this.exit);

 

MenuItemFont is one of the available types of menuitems ( there is also a sprite based menu item ) that compose a menu.  For each one, you specify the text to display, the owning container as well as a callback function that is called when the menu is selected.  We then position each menuitem relative to the center of the screen, each one placed 50 pixels apart.   We then create our Menu item by passing in a variable number of MenuItem, like this:

var menu = cc.Menu.create(menuItem1,menuItem2,menuItem3,menuItem4);

 

Finally we position our menu and add it to our layer.

 

Next we have our 4 different callback function.  cc.log simple logs whatever text out to the console.  So if you have the developer console open in Chrome, or Firebug on Firefox, you will see the following when the code is run:

image

 

cc.Log is an effective and easy way to keep track of your code’s execution without requiring the debugger.

 

In the event of selecting “Play Sound” the playSound() method is fired, which calls:

 

cc.AudioEngine.getInstance().playEffect("Resources/effect2");

 

This uses the AudioManager singleton to play our preloaded effect “effect2” in the folder “Resources” using the playEffect() method.

 

In the event of selecting “Play Song”, the playSong method is fired, which plays a song using the function:

 

cc.AudioEngine.getInstance().playBackgroundMusic("Resources/background",false);

 

If “Stop Playing Song” is selected, the stopPlayingSound method is fired, which checks if a song is currently playing and stops it if one is.  Like this:

 

if(cc.AudioEngine.getInstance().isBackgroundMusicPlaying())
        {
            cc.AudioEngine.getInstance().stopBackgroundMusic();
        }

 

Finally, in the event Exit is called, the app simply redirects the user to a great website. Smile

 

 

As always, you can download the full project ( including all audio files ) right here.

 

Here is our application in action:

 

 

Read Tutorial 6

Programming , ,

blog comments powered by Disqus

Month List

Popular Comments