Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon

25. May 2017


I featured the PlayCanvas engine a couple years ago in this Closer Look review.  PlayCanvas is a 3D HTML5 powered game engine with a full suite of editing tools.  In the time since that initial review, PlayCanvas has advanced a great deal, so I have decided to revisit the engine.  Much of the engine has remained the same as it was, just generally improved all around.  Therefore I have decided instead of another review, I will do a hands on tutorial showing how to create a simple game using PlayCanvas.  In this case, a bowling game.  For Patreon backers all the assets used to make this tutorial are now available including Blender files, FBX, textures and more.

There is a video version of this tutorial available here or embedded below. [Coming Soon]


Spoiler alert... here is the “game” we are about to create in this tutorial.

Click the window to focus.

Press spacebar or W to throw bowling ball.

Press left | A and right | D to move the bowling ball.

Press R to reset the table.

Press B to reset just the ball.


Creating your Project

Alright, lets jump in and build this game.  First head on over to http://playcanvas.com.  Now you either need to create an account or log in to your existing account.

image


A free account is sufficient for what we are doing here.  Once your account is created and you’ve logged in, click the New button

image


Select  Blank Project:

image


Now name your game and hit Create.  Unless you have a premium account, you can’t make private projects.

image


This will open your project after it’s created.  Now head into the settings.

image

You can set the resolution, change the name, description, etc...  but what we want to do in this case is enable physics for our project.

image

Next scroll to the bottom right corner and click the save button.

image


Now we are ready to fire up the editor.  Scroll back up and locate the Editor button.

image


Creating your Game

Welcome to the default editor!

image

Now that we are in the editor, it’s time to start creating our game.  You will notice on the left there is the Hierarchy, this is your games scene graph.

image

We have no need of the Box or Plane, simply right click each and delete them.

Now let’s get our assets into the game.  In Blender I created the bowling lane and pin textured meshes, then exported them as FBX ready for importing.  Let’s import them for use into our game, along with the required textures.  Locate the Assets window, click the + Icon, select Upload then select your FBX file(s).  Repeat the process with any texture maps you require.

image

This process will create a .json for each of your 3D models.  It also creates an empty material for both.

image


Now let’s set the texture for each material.  Simply select the Material, then in the Material tab to your right, scroll down and select Diffuse.  Click ... then select your texture map from the asset pane, like so:

image


I also have a normal map for the bowling lane, I repeat the above process, but instead of Diffuse I select Normal:

image


Creating the Bowling Lane

Ok, now that we have our raw assets and our textures linked, lets start creating some game objects.  Simply drag the lane into the scene like so:

CreateEnt


You will notice this creates a new entity in our scene graph:

image


PlayCanvas takes the traditional Entity Component model that is rapidly becoming the norm in the world of game development. 

image

You will notice that we have a Transform component, which enables us to position our entity in the world.  It also automatically created a Model component for us.  Now we want to add some physics to the game.  Since this is the lane, it wont be moving, so we create it as a static physics object.  To accomplish this we need to add two components, one for collisions that define the shape of our object to the physics engine, the second a RigidBody that defines our physical properties.  Let’s create the Collision shape first.

First click the Add Component button:

image

Then select Collision

image


Select Box then set the dimensions to surround your mesh as tightly as possible (note that PlayCanvas is a Y-up game engine):

image

image


Now Add Component again, this time selecting Rigid Body.  The default value should be Static, which is what we want.

image

Static means the object will be part of the physics simulation, but wont be moved by it.  Friction determines how well other objects slide over this surface, while Restitution determines how objects “bounce” off of it.


That’s it!  Our lane is completed.  Now it’s on to creating the bowling pin object.


Creating the Bowling Pins

Now it’s time to create the bowling pin.  Follow the exact same process you did earlier, drag the .json file onto the scene.  Position and scale it appropriately for your scene.

image

Just like last time, we want to create a collision component for this object.  This time instead of a cube, a cylinder is a better fit.

image

image

We also need to create a rigid body to control physics for this entity.  This time we create our entity as Dynamic ( static means stationary, dynamic is fully simulated, while kinematic takes part in the simulation but expects the movement to come directly from code, not the simulation).

image

Finally we are going to set a tag under the Entity section so we can identify all pins via code.  In the Tags section, enter “Pin” then hit enter to add a tag.  You can add multiple tags to an entity, a quick easy way to create ad-hoc groups of objects as we will see in a moment.

image


We are simulating 5 pin bowling here, so we need 4 more of them.  In the Hierarchy review, right click the pin and select Duplicate (not copy!).  This creates a copy of the entity and all of it’s components.

image

Repeat this process 3 more times, then go ahead and position the pins in a V like so.

image

Obviously if you are a 10 pin fan, other than being a heathen, you simply have to duplicate and position 5 more pins.


This post is getting pretty long so I’ve decided to split it into two parts.  Click here to continue on to part two. [Coming Soon]

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

Love2D Using Sprites Tutorial
Subscribe to GameFromScratch on YouTube Support GameFromScratch on Patreon


22. March 2016

 

This page is in support for the video tutorial on using Sprites/Images in Love as part of the GameDev For Complete Beginners tutorial series.  It contains the code and images used in the tutorial.  There is also a copy of the tutorial embedded below.

 

Images

The graphics are from this pack of images, made transparent using this process.

ALC-17AttackChoppers

 

Simply right click and save to your project directory.

 

Source Code

Drawing an Image

local imageFile

function love.load()
    imageFile = love.graphics.newImage("ALC-17.PNG")
end

function love.draw()
    love.graphics.draw(imageFile)
end

 

Using a Spritesheet

local imageFile
local frames = {}

local activeFrame
local currentFrame = 1
function love.load()
    imageFile = love.graphics.newImage("AttackChoppers.PNG")
    frames[1] = love.graphics.newQuad(0,0,128,64,imageFile:getDimensions())
    frames[2] = love.graphics.newQuad(128,0,128,64,imageFile:getDimensions())
    frames[3] = love.graphics.newQuad(0,64,128,64,imageFile:getDimensions())
    frames[4] = love.graphics.newQuad(128,64,128,64,imageFile:getDimensions())
    activeFrame = frames[currentFrame]
    print(select(4,activeFrame:getViewport())/2)
end

function love.draw()
    --love.graphics.draw(imageFile,activeFrame)
--[[    love.graphics.draw(imageFile,activeFrame,
        love.graphics.getWidth()/2 - (select(3,activeFrame:getViewport())/2) * 2,
        love.graphics.getHeight()/2 - (select(4,activeFrame:getViewport())/2) * 2,

            0,
            2,
            2)
]]--
    -- draw image 4x size centered
    love.graphics.draw(imageFile,activeFrame,
        love.graphics.getWidth()/2 - ({activeFrame:getViewport()})[3]/2 * 4,
        love.graphics.getHeight()/2 - ({activeFrame:getViewport()})[4]/2 * 4,
        0,
        4,
        4)
end

local elapsedTime = 0
function love.update(dt)
    elapsedTime = elapsedTime + dt

    if(elapsedTime > 1) then
        if(currentFrame < 4) then
            currentFrame = currentFrame + 1
        else
        currentFrame = 1
        end
        activeFrame = frames[currentFrame]
        elapsedTime = 0
        end
end

 

 

The Video

Programming , ,

blog comments powered by Disqus

Month List

Popular Comments