Core HTML5 2D Game Programming book review

29. December 2014

 

Shortly before the holidays began I received a review copy of Core HTML5 2D Game Programming and amid all the holiday insanity, I’ve been slowly making my way through this title.  As with all reviews, I don’t give star ratings, I think the value of a book is determined mostly by the perspective and requirements of the reader.  OfCoreHTML5 course, some books are just simply bad.  Bad grammar, bad topic choice, bad humour.  Fortunately that is not the case here.  From a technical perspective this is a good book (with one glaring flaw).  Now the question is, is it a good book for you?

 

First let’s talk about the target audience.  This book is not aimed at complete beginners, prior experience with JavaScript and HTML5 is assumed.  Some JavaScript related topics are covered ( dealing with this, profiling/debugging in Chrome, simple inheritance, etc. ) but if you don’t already understand some JavaScript and haven’t ever touched on HTML5 or CSS work, you will be lost.  No prior game programming experience is assumed, although you may struggle a bit with some of the terminology if completely new.  There is however a fairly solid glossary that while get you through.  For more experienced game developers, this probably isn’t the title for you.

 

Ultimately this is a learn by doing book.  Through the course of the book you are putting together a basic platforming game called Snail Bait, built using the assets of the open source Android title Replica Island.  The game is available to be played online at http://corehtml5games.com… or at least, it’s supposed to be.  When I go to that site I get:

 

image

 

Hmmm, that’s unfortunate.  I am not sure if this is an ongoing problem, or just temporary.  Judging by an earlier review on Amazon about the server being unavailable, this is a recurring problem.  It is however a bit of a big problem, as many of the code listings in this book are actually partial, so having access to the complete project is very important.  The book repeatedly references this site, so with it down, so is a great deal of the appeal of this book.  Unfortunately the publisher doesn’t appear to make the code available anywhere else, at least not the book’s version.

 

Now back to the actual contents of the book.  This book covers pretty much all aspects of what you need to make a complete 2D HTML5 game.  One critical thing to understand with this title is everything is created from scratch.  The book makes use of no existing libraries, so you learn how to do things from scratch.  There is merit to learning how to do everything yourself at least initially.  That said, you will probably make a better game using libraries that have already dealt with all the various cross browser issues and optimizations for you.

 

The book does cover a surprising number of topics, starting with handling the game loop and ending with basic network programming.  For each topic there are a number of callout notes on game development or HTML5 idiosyncrasies.  For the most part, they are topical and rarely feel superfluous.  In between it covers animation, graphics, input, hit detection, dealing with mobile (controls and resolutions), particles, audio, easing and more.  The coverage of each topic is fairly comprehensive and easily understood.  One thing you might want to note, this book is entirely about using canvas for rendering, with absolutely no coverage of WebGL.  Given the increasing support for WebGL ( IE and Safari are both finally on board ), this could be a pretty big negative.

 

As I mentioned earlier, the majority of the book is about creating a single game step by step using what you’ve learned up till this point.  The code snippets are clear but without access to the finished whole, trying to figure out how it all fits together is difficult.  There is however one chapter dedicated to putting all the pieces you’ve learned together to make a simpler but complete  game, Bodega’s Revenge.  Unfortunately, these are also partial code listings, so without access to the source code, readers may struggle filling in the pieces.

 

What’s my verdict on this book then?  The book itself is quite good.  If you have some basic JavaScript knowledge and are looking at learning how to do HTML5 canvas based game development from scratch, it’s a very good resource.  There is an impressive amount of information jammed into this book with no obvious missing pieces.  If you are looking at purchasing this title, be certain to check if the site is available before you do! 

 

I would highly suggest the author or publisher make the code available on a much more reliable source, such as Github.

Programming , ,




LibGDX Video Tutorial: Spritesheets and TextureAtlases

19. December 2014

 

In this tutorial we look at the process of creating and using a Spritesheet in LibGDX.  This involves creating a series of sprites, putting them together with TexturePacker, then using a TextureAtlas and TextureRegion to display them with our Sprite.  We also quickly look at TexturePacker ( different product ) for those that prefer a UI.  Sample code and links to included assets below the video.

 

Once again, you can view the video in HD on YouTube by click here.

 

 

Example’s Source

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class SpritesheetDemo extends ApplicationAdapter implements InputProcessor {
   SpriteBatch batch;
   TextureAtlas textureAtlas;
   Sprite sprite;
   TextureRegion textureRegion;
   int currentFrame = 1;
   int MAX_FRAMES = 19;
   
   @Override
   public void create () {
      batch = new SpriteBatch();
      textureAtlas = new TextureAtlas(Gdx.files.internal("ss.txt"));
      textureRegion = textureAtlas.findRegion("0001");
      sprite = new Sprite(textureRegion);
      sprite.setPosition(Gdx.graphics.getWidth()/2 - sprite.getWidth()/2,
            Gdx.graphics.getHeight()/2 - sprite.getHeight()/2);

      Gdx.input.setInputProcessor(this);
   }

   @Override
   public void render () {
      Gdx.gl.glClearColor(0, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      batch.begin();
      sprite.draw(batch);
      batch.end();
   }

   @Override
   public boolean keyDown(int keycode) {
      if(keycode == Input.Keys.UP){
         currentFrame++;
         if(currentFrame > MAX_FRAMES)
            currentFrame = 1;
         sprite.setRegion(textureAtlas.findRegion(String.format("%04d",currentFrame)));
      }
      if(keycode == Input.Keys.DOWN){
         currentFrame--;
         if(currentFrame < 1)
            currentFrame = MAX_FRAMES;

         sprite.setRegion(textureAtlas.findRegion(String.format("%04d",currentFrame)));
      }
      return true;
   }

   @Override
   public boolean keyUp(int keycode) {
      return false;
   }

   @Override
   public boolean keyTyped(char character) {
      return false;
   }

   @Override
   public boolean touchDown(int screenX, int screenY, int pointer, int button) {
      return false;
   }

   @Override
   public boolean touchUp(int screenX, int screenY, int pointer, int button) {
      return false;
   }

   @Override
   public boolean touchDragged(int screenX, int screenY, int pointer) {
      return false;
   }

   @Override
   public boolean mouseMoved(int screenX, int screenY) {
      return false;
   }

   @Override
   public boolean scrolled(int amount) {
      return false;
   }
}

 

The sprite art used for this example was rendered using this Blender file.

The texture packing application (near the end) was CodeAndWeb’s TexturePacker.

Programming , , , ,




LibGDX Video Tutorial: Gestures–Panning, Zooming, Pinch, Tapping and more

16. December 2014

 

This video tutorial covers handling gestures in LibGDX, this includes:

  • pinch
  • zoom
  • tap
  • pan
  • long press

 

You can view the video in full resolution on Youtube here.  The source is included below.

 

Source from this tutorial example:

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;

public class GestureDemo extends ApplicationAdapter implements GestureDetector.GestureListener {
   SpriteBatch batch;
   Sprite sprite;
   OrthographicCamera camera;
   GestureDetector gestureDetector;
   
   @Override
   public void create () {
      batch = new SpriteBatch();
      sprite = new Sprite(new Texture(Gdx.files.internal("storm_trooper.png")));
      sprite.setPosition(-sprite.getWidth()/2,-sprite.getHeight()/2);
      sprite.setCenter(0.5f,0.5f);

      camera = new OrthographicCamera(1280,720);
      camera.update();

      gestureDetector = new GestureDetector(this);
      Gdx.input.setInputProcessor(gestureDetector);
   }

   @Override
   public void render () {
      Gdx.gl.glClearColor(0, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
      batch.setProjectionMatrix(camera.combined);
      batch.begin();
      sprite.draw(batch);
      batch.end();
   }

   @Override
   public boolean touchDown(float x, float y, int pointer, int button) {
      return false;
   }

   @Override
   public boolean tap(float x, float y, int count, int button) {
      if(count > 1){
         sprite.setPosition(-sprite.getWidth()/2,-sprite.getHeight()/2);
         sprite.setSize(256f,256f);
         sprite.setRotation(0f);
      }
      else {
         Vector3 touchPos = new Vector3(x, y, 0);
         camera.unproject(touchPos);
         sprite.setPosition(touchPos.x, touchPos.y);
      }
      return true;
   }

   @Override
   public boolean longPress(float x, float y) {

      Vector3 touchPos = new Vector3(x,y,0);
      camera.unproject(touchPos);

      if(sprite.getBoundingRectangle().contains(touchPos.x,touchPos.y)) {
         float alpha = sprite.getColor().a;

         if (alpha >= 0.f)
            sprite.setAlpha(alpha - 0.25f);
         else
            sprite.setAlpha(1f);
      }
      return true;
   }

   @Override
   public boolean fling(float velocityX, float velocityY, int button) {
      return false;
   }

   @Override
   public boolean pan(float x, float y, float deltaX, float deltaY) {
      Vector3 touchPos = new Vector3(x,y,0);
      camera.unproject(touchPos);

      sprite.setPosition(touchPos.x - sprite.getWidth()/2,touchPos.y - sprite.getHeight()/2);

      return true;
   }

   @Override
   public boolean panStop(float x, float y, int pointer, int button) {
      return false;
   }

   @Override
   public boolean zoom(float initialDistance, float distance) {
      sprite.setSize(distance,distance);
      return true;
   }

   @Override
   public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2)
{
float deltaX = pointer2.x - pointer1.x; float deltaY = pointer2.y - pointer1.y; float angle = (float)Math.atan2((double)deltaY,(double)deltaX) * MathUtils.radiansToDegrees; angle += 90f; if(angle < 0) angle = 360f - (-angle); sprite.setRotation(-angle); return true; } }

Programming , ,




LibGDX Video Tutorial: Cameras and Viewports. Handling multiple resolutions and aspect ratios

9. December 2014

 

In this video tutorial we look at using the different types of Cameras available, using a Camera to position your world in a device independent way.  Next we discuss the various Viewport options available for making your render results look best across a number of devices.  Since this video was released at the same time as the text version of the tutorial, I will be linking to those tutorials for code examples.

 

You can see the full 1080p video directly on YouTube or embedded below.

 

 

For the text tutorials, or for the code or assets used in this tutorial, check this tutorial on Cameras and this tutorial on Viewports.

 

Additionally, the text only tutorial also covers converting coordinates too and from your world coordinates, something I forgot to do in the video tutorial.

Programming , , ,




LibGDX Tutorial Part 17: Viewports

9. December 2014

 

 

 

In the previous tutorial we looked at how to use a camera with LibGDX to abstract away resolution differences so you are no longer using pixel coordinates.  This however doesn’t really help you all that much if your aspect ratios are massively different.  Fortunately LibGDX implements the concepts of Viewports, which can be considered the coding equivalent of the Aspect button on your HDTV, controlling how non-native content is scaled to be displayed on your TV.

 

There are a number of different Viewports available:

 

Or of course you can inherit Viewport and create your own.

 

We are going to use the following image ( click for full resolution, no squished version ) taken from here.

Aspect

 

 

Here is the code for creating a FillViewport, which results in behavior very similar to using no Viewport at all:

 

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.viewport.FillViewport;
import com.badlogic.gdx.utils.viewport.Viewport;

public class ViewportDemo extends ApplicationAdapter {
   SpriteBatch batch;
   Sprite aspectRatios;
   OrthographicCamera camera;
   Viewport viewport;

   @Override
   public void create () {
      batch = new SpriteBatch();
      aspectRatios = new Sprite(new Texture(Gdx.files.internal("Aspect.jpg")));
      aspectRatios.setPosition(0,0);
      aspectRatios.setSize(100,100);

      camera = new OrthographicCamera();
      viewport = new FillViewport(100,100,camera);
      viewport.apply();

      camera.position.set(camera.viewportWidth/2,camera.viewportHeight/2,0);
   }

   @Override
   public void render () {

      camera.update();
      Gdx.gl.glClearColor(1, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

      batch.setProjectionMatrix(camera.combined);
      batch.begin();
      aspectRatios.draw(batch);
      batch.end();
   }

   @Override
   public void dispose(){
      aspectRatios.getTexture().dispose();
   }

   @Override
   public void resize(int width, int height){
      viewport.update(width,height);
      camera.position.set(camera.viewportWidth/2,camera.viewportHeight/2,0);
   }
}

 

The code is pretty straight forward but has a few caveats to be aware of.  Most importantly, you absolutely need to update the viewport in the ApplicationAdapter’s resize method or the viewport will not work.  Standard process is to create your camera, then create the viewport passing in the viewport resolution as well as the camera to apply to.  For the technically minded, the Viewport ultimately manipulates the GL viewport behind the scenes.  Now let’s look at the various viewport options, we simply replace one line of code in each example:

 

Here are the results of various options running at 900x600 resolution:

 

No Camera or Viewport

image

 

 

viewport = new ExtendViewport(100,100,camera);

image

 

 

viewport = new FillViewport(100,100,camera);

image

 

viewport = new FitViewport(100,100,camera);

image

 

viewport = new StretchViewport(100,100,camera);

image

 

viewport = new ScreenViewport(camera);

image

 

 

The behavior of most of those viewports should be evident from the results but a few certainly require a bit of explanation.  The oddest result is most likely ScreenViewport.  When you use ScreenViewport you are simply saying “create a viewport the same size as the screen resolution”.  However, we also told our game that the sprite is 100x100 in size, but instead of being treated in our arbitrary camera units, this value is now relative to actual pixels, so as a result, our background is drawn as a 100 x 100 pixel rectangle.

 

You may also be wondering what the difference between Fill and Stretch is.  It isn’t obvious because our source image and our resolution are both widescreen.  When you run in a 4:3 aspect ratio, such as 1024x768 of the iPad, the results become more obvious:

 

FillViewport @ 1024x768

image

 

StetchViewport @ 1024x768

image

 

Fill will always fill the viewport, even if it means have to crop part of the image.  As you can see in the first image, the blue border at the top and bottom is not visible.  IMHO, this is the worst looking of all scaling options.

 

Stretch on the other hand, stretches the result to fit the screen width and height, however it also results in some distortion.  This mode is probably the easiest to implement, but the results depend heavily on your games art style.

 

 

Mapping To And From Camera to Screen Coordinates

 

One last quick thing to touch upon with Cameras and Viewports… how do you handle converting coordinates, such as touch or click events from our arbitrary camera coordinates to screen coordinates?  Fortunately it’s quite easy.

 

package com.gamefromscratch;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.viewport.StretchViewport;
import com.badlogic.gdx.utils.viewport.Viewport;

public class mouseproject extends ApplicationAdapter implements InputProcessor {
   SpriteBatch batch;
   Sprite aspectRatios;
   OrthographicCamera camera;
   Viewport viewport;

   @Override
   public void create () {
      batch = new SpriteBatch();
      aspectRatios = new Sprite(new Texture(Gdx.files.internal("Aspect.jpg")));
      aspectRatios.setPosition(0,0);
      aspectRatios.setSize(100,100);

      camera = new OrthographicCamera();
      viewport = new StretchViewport(100,100,camera);
      viewport.apply();

      camera.position.set(camera.viewportWidth/2,camera.viewportHeight/2,0);
      Gdx.input.setInputProcessor(this);
   }

   @Override
   public void render () {

      camera.update();
      Gdx.gl.glClearColor(1, 0, 0, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

      batch.setProjectionMatrix(camera.combined);
      batch.begin();
      aspectRatios.draw(batch);
      batch.end();
   }

   @Override
   public void dispose(){
      aspectRatios.getTexture().dispose();
   }

   @Override
   public void resize(int width, int height){
      viewport.update(width, height);
      camera.position.set(camera.viewportWidth / 2, camera.viewportHeight / 2, 0);
   }

   @Override
   public boolean keyDown(int keycode) {
      return false;
   }

   @Override
   public boolean keyUp(int keycode) {
      return false;
   }

   @Override
   public boolean keyTyped(char character) {
      return false;
   }

   @Override
   public boolean touchDown(int screenX, int screenY, int pointer, int button) {

      Gdx.app.log("Mouse Event","Click at " + screenX + "," + screenY);
      Vector3 worldCoordinates = camera.unproject(new Vector3(screenX,screenY,0));
      Gdx.app.log("Mouse Event","Projected at " + worldCoordinates.x + "," + worldCoordinates.y);
      return false;
   }

   @Override
   public boolean touchUp(int screenX, int screenY, int pointer, int button) {
      return false;
   }

   @Override
   public boolean touchDragged(int screenX, int screenY, int pointer) {
      return false;
   }

   @Override
   public boolean mouseMoved(int screenX, int screenY) {
      return false;
   }

   @Override
   public boolean scrolled(int amount) {
      return false;
   }
}

 

It's simply a matter of using Camera.unproject to convert a screen coordinate to your world coordinate, and Camera.project to do the reverse.  There is more to Viewports and Cameras, but that covers enough to get you going in most cases.  Keep in mind, you don’t need to use either, but they both certainly make making a game that runs across devices a lot easier.

 

Programming , ,