Thursday, 26 March 2015

Introduction To Playground and CanvasQuery

Welcome back to building a 2D mobile game with Cordova and Canvasquery.

We will continue on from our last post, by creating a simple 2D game that shows a box on the screen. The box will travel to any point you click on the canvas. Groundbreaking stuff! The fancy stuff will come later once we get the basics in.

You can see a running version of the app we are trying to build in this post here

Here is a simple screenshot of what we are trying to achieve at this very basic level:


I have included the source code of the project we are working on in tandem with this blog, and will link to the tags where necessary.

Here is the tagged version of the codebase, relating to this blog post.
And here is the current codebase.

Now that we have our project set up, it is time to add some libraries to do some heavy lifting for us.
We will add the canvasquery library.

Canvasquery is a wrapper for a raw html5 canvas.
It works together with playground to provide some really nice utility methods for creating animations etc.
Playground translates key codes into human readable strings to help us keep our code clean and readable.
Canvasquery provides the ability to chain methods together, when drawing on a canvas.
In addition, canvasquery provides us with the very handy stars() method. See Here

I placed the Javascript files for the library into cgsrc/src/javascripts/lib.
Again, these will be compiled into our main Javascript file when we run the Grunt requireJS task.

We then define a template for our game using what are known in playground as game states.
A state will represent different screens in your game, such as "main menu" or "game screen".
This allows you to keep your project modular and reduces complexity.

Let's continue to create a simple game that allows you to control an on screen object with the keys.

For this we will create two games states: main_menu and game_screen.
However, we must first initialize playground.

Create a file called game.coffee in the src/coffeescripts directory with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
define ["require", "playground"], (require, playground) ->

  game = playground
    mouseToTouch: true

    width: 1600
    height: 900
    scaleToFit: true

    create: ->


    ready: ->
      @setState require("main_menu")

  return game

This will define the skeleton for our game and set it to fix into a 16:9 ratio.
the ready callback basically sets the initial state of the game.
We will create the main_menu_state next.

Create a file main_menu.coffee in the src/coffeescripts directory with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
define ["require", "game"], (require, game) ->
  main_menu =
    create: ->

    ready: ->

    step: (delta) ->

    render: (delta) ->
      game.layer.clear "#FFF"
      game.layer.fillStyle("#000").font("64px Arial")
        .fillText "Click The Mouse To Play!!!", 100 / game.scale, 100 / game.scale

    mousedown: (event) ->

    mouseup: (event) ->

    mousemove: (event) ->

    keydown: (event) ->

    keyup: (event) ->

    touchstart: (event) ->

    touchend: (event) ->
      game.setState require("game_screen")

    touchmove: (event) ->

  return main_menu

This is a skeleton that you can use to create any game state.

Basically, a game state is just a JSON object that has some callbacks and properties in it.
It has callbacks for different events that you can hook on to relating to keyboard and mouse input.

You respond however you like to any of the events, but for now, all this does is displays some text and responds to the click event by setting the games state to the main game screen which we will create later.

Note here that we have a property mouseToTouch: true defined in our game.coffee file.
This will tell playground to translate all of our mouse events to touch events so that we only have to worry about touch events and our app will work across both mobile and desktop.
This is why you see that we are using the touchEnd callback in the main_menu to transition to the game screen.
When you click the mouse it will be translated into a touch event.
Handy!

The 'ready' function gets called when this state is loaded.

The two functions step and render are really important for the main game screen.
Basically, the idea is that we have a game loop which is handled by playground.
This loop runs constantly, and is responsible for calling a sequence of callbacks in our state JSON in order for you to provide your game logic.
The step callback is basically where you put your game logic, update your game world/process user input/make any calculations for the users score etc.

Then the render callback is where you draw your game state. Under the covers, playground uses the requestAnimationFrame API provided by our browser, to ensure we have sharp animations. More on this in a future post.

See here for more information on the game loop.

Now we need to hook our menu state up to our game object that we defined above in our first file game.coffee.
To do this, we go back to our app.coffee from the previous tutorial.

In this file we simply printed out some text when the app loaded.
Now, instead, we would like to load in "game" and set some states on it.
Replace the content of app.coffee with:

1
2
3
4
define ["require", "jquery", "canvasquery", "playground", "game", "main_menu", "game_screen"],
(require, $, cq, playground, game, main_menu, game_screen) ->
 game.main_menu = main_menu
 game.game_screen = game_screen

We will also hook up our game_screen state here that we will create later.
To transition to any state, you simply call game.setState(stateObject), where stateObject is the JSON representing the state you wish to transition to.

Notice we also pull in dependencies for our libraries at the top here.
For our libraries to play nicely with requireJS, we must declare to requireJS that these libraries export globals.

Do this by adding a shim section to the config.coffee file:

1
2
3
4
5
6
7
8
shim:
    playground:
      deps: ["canvasquery"]
      exports: "playground"
    canvasquery:
      exports: "cq"
    brain:
      exports: "brain"

At this point, lets compile our project with grunt dist.
Now if we run our rails server and go to /game you should see a screen that asks you to click the mouse to start!
Finally we can start seeing some progress on screen for our efforts!

However, clicking the mouse causes an error relating to our missing game state, let's resolve that!

Just like before, create a skeleton game state in a file src/coffeescripts/game_screen.coffee with the content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
define ["game"]
, (game) ->

  game_screen =
    gameWidth: 1600
    gameHeight: 900

    x: 10
    y: 10

    lastTouchX: 0
    lastTouchY: 0

    followLastTouch: false

    enter: ->

    ready: ->

    step: (delta) ->
      @checkKeyPresses()
      @checkTouches()

      #make sure we don't go outside the screen bounds
      if @x < 0
        @x = 0

      if @x > @gameWidth - 60
        @x = @gameWidth - 60

      if @y < 0
        @y = 0

      if @y > @gameHeight - 60
        @y = @gameHeight - 60

    render: (delta) ->
      game.layer.clear("#7EC0EE")
      .fillStyle("red")
      .fillRect(@x, @y, 50, 50)

      #game.layer.drawRegion player.getImage(), player.getNextSprite(), x, y

    mousedown: (event) ->

    mouseup: (event) ->

    mousemove: (event) ->

    keydown: (event) ->
      @followLastTouch = false

    keyup: (event) ->

    touchstart: (event) ->
      @followLastTouch = true
      @lastTouchX = event.x
      @lastTouchY = event.y

    touchend: (event) ->

    touchmove: (event) ->

    checkKeyPresses: ->
      if game.keyboard.keys["right"]
        @x+=20
      else if game.keyboard.keys["left"]
        @x-=20

      if game.keyboard.keys["up"]
        @y-=20
      else if game.keyboard.keys["down"]
        @y+=20

    checkTouches: ->
      if @followLastTouch
        console.log(@lastTouchX + " " + @lastTouchY)
        #move the object towards the current touch

        if @lastTouchX >= @x
          @x+=20

        if @lastTouchX < @x
          @x-=20

        if @lastTouchY >= @y
          @y+=20

        if @lastTouchY < @y
          @y-=20

  return game_screen

There is a lot here, but fret not, all will be revealed.
There is commented line in the render function that we will get back to, where we draw an image from an image sprite to represent the current player.
For now, you will see that we are simply drawing a rectangle at the current point (x, y).
We will update this x, y coordinate by listening to the arrow keys on the keyboard, and we will update the rectangle on screen to reflect the new position.

As explained above, canvasquery simplifies parsing the input for us by allowing us to test for keypresses with concice statements such as:

1
if game.keyboard.keys["right"]

We do not have to deal with character codes.
You will see if conditions in the checkKeyPresses method that test for the arrow keys on our keyboard.
If a keypress is detected, we modify the x, y pair to reflect the direction that was pressed.

You will see that the checkKeyPresses function is called from our step callback which is in turn called once per game loop.

Now, in our render step, we would like to draw the square at the current x, y.

We do this with the following line:

1
game.layer.clear("#7EC0EE").fillStyle("red").fillRect(@x, @y, @x + 500, @y + 500)

This is another handy shortcut for drawing rectangles provided to us by canvasquery.
To do this using a raw html5 canvas would require more fiddly code!
Note, that if you want to access the underlying canvas object or context, you can still do so.

See Here For More Info

You will see that we are drawing a red square of 500x500.
So, in essence, we should now see a square on screen that we control on screen with our arrow keys.
One thing to note is that we do not allow the square to go outside the bounds of the screen.
We do this at the end of the step method, where we simply cap the maximum and minimum values of x and y.

Another thing to note here is that our game is running in scaled mode, scaleToFit: true
Canvasquery is clever enough to scale our input events also when you resize the screen and also when you are drawing something on to the canvas!

So, now we have already hooked up our game screen to our game in the app.coffee file.
This screen will be shown after we click the mouse on the main menu!

You can use the arrow keys to move the square around within the bounds of your game canvas!

For a little extra fun, you will see that we are also responding to touch events.
If you run the app on a touch compatible screen, you will be able to make the square travel to the last point of contact.
The code is pretty self explanatory, it is all contained within the checkTouches method.

So, this is a really simple, lame example of a game you can program using the above technologies, but it serves as an introduction to the possibilities available to you when you leverage the playground API.

We will get on to more complex examples of what you can create in future blogs!

Stay posted!

No comments:

Post a comment