Canvas UI Interface

Now that Toobz is more or less complete (just the levels to finish), I thought it was about time to start on the next project. There’s so many ideas at the moment it’s difficult to know which to start.

Actually,one thing that did come to light during development using HTML canvas was the issue of input, especially around the so called virtual joysticks. You know, the ones where there is a template for movement and a single button adds well.

The problem with these is the lack of tactile feedback, which means it’s all to easy to drag your fingers off these points and end up being unable to control the character. More often than not,this is usually around the action parts where you need it most.

So, before anything else in turns of game development, let’s get this solved once and for all. For me, the first question is, “if there is no tactile feedback, then why do the buttons have to be where they’ve been placed?”.

Using this, let’s take it a stage further.“Do we need the virtual joystick at all?”. I mean, most games where this sort of control is needed are simple joystick up, down, left, right movements and then one maybe two buttons.

To get around this I’m going to define an interface which sit over the canvas and act as the UI layer. Here’s the premise…

  • There’s no joystick, where you put your thumb/finger down is your centre (origin).
  • Drag left,and it’ll fire a left event.
  • Drag right and it’ll fire a right event.
  • The same applies for up and down.
  • Lift your finger of the device and everything gets cleared.
  • Put your finger back down and this now becomes your origin.

This means there’s no concern over where the centre of the joystick is… It’s wherever your finger/thumb hit the screen. Allow me to elaborate…

First, create your canvas as normal, but put a wrapper div around this to contain it all.

Canvas

Canvas

Code

<div id="canvasWrapper">
 <canvas id="background" width="600" height="400">
 </canvas>
 </div>

CSS

#canvasWrapper{
    position: relative;
}
#background {
    width: 600px;
    height: 400px;
}

Then add a second div of the dimensions required for your movement UI. If the player can use the whole canvas then make this the same size and using CSS, overlay it on top of the canvas.

Movement Layer

Movement Layer

Code

<div id="canvasWrapper">
     <canvas id="background" width="600" height="400">
     </canvas>
     <div id="overlay">
     </div>
</div>

CSS

#canvasWrapper{
    position: relative;
}
#background {
    width: 600px;
    height: 400px;
}
#overlay{
    width: 600px;
    height: 400px;
    position: absolute;
    top: 0px;
    user-select: none;
}

Making sure you have a reference to jQuery, in your script code, add the following…

function relMouseCoords(event){
    var totalOffsetX = 0;
    var totalOffsetY = 0;
    var canvasX = 0;
    var canvasY = 0;
    var currentElement = this;

    do{
        totalOffsetX += currentElement.offsetLeft - currentElement.scrollLeft;
        totalOffsetY += currentElement.offsetTop - currentElement.scrollTop;
    }
    while(currentElement = currentElement.offsetParent)

    canvasX = event.pageX - totalOffsetX;
    canvasY = event.pageY - totalOffsetY;

    return {x:canvasX, y:canvasY}
}

HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;

var startX = 0;
var startY = 0;
var originalStartX = -1;
var originalStartY = -1;

var threshold = 24; 
var mousedown = false;

var bg = document.getElementById("background").getContext("2d");
var overlay = jQuery("#overlay");

overlay.mousedown( function(){

    startY = event.offsetY;
    startX = event.offsetX;

    if (originalStartX == -1){
        originalStartX = startX;
        originalStartY = startY;
    }

    mousedown = true;
});

overlay.mousemove( function(event){
    if (mousedown){

    var diffX = event.offsetX-startX;
    var diffY = event.offsetY-startY;

    var direction = "";

    if (diffY < -threshold){
        direction += "up";
    } else if (diffY>threshold){
        direction += "down";
    }

    if (diffX < -threshold){
        direction += "left";
    } else if (diffX>threshold){
        direction += "right";
    }
});

overlay.mouseup(function(){
    mousedown = false;
    originalStartX = -1;
    originalStartY = -1;
});

overlay.mouseout(function(){
    mousedown = false;
    originalStartX = -1;
    originalStartY = -1;
});

Mobile Users

If you are planning on implementing anything for the mobile, then you’ll need a small change to handle touch event instead of mouse events.
I’ve used jQuery Mobiles virtual handlers to ensure I don’t need to worry about which events to handle. Making sure you have a reference to jQuery Mobile, change the code to the following…

function UIMouseDown(){
    startY = event.offsetY;
    startX = event.offsetX;

    if (originalStartX == -1){
        originalStartX = startX;
        originalStartY = startY;
    }

    mousedown = true;
}
function UIMouseMove(){
    if (mousedown){

    var diffX = event.offsetX-startX;
    var diffY = event.offsetY-startY;

    var direction = "";

    if (diffY < -threshold){
        direction += "up";
    } else if (diffY>threshold){
        direction += "down";
    }

    if (diffX < -threshold){
        direction += "left";
    } else if (diffX>threshold){
        direction += "right";
    }
}
function UIMouseUp(){
    mousedown = false;
    originalStartX = -1;
    originalStartY = -1;
}
function UIMouseOut(){
    mousedown = false;
    originalStartX = -1;
    originalStartY = -1;
}

overlay.on("vmousedown", UIMouseDown);
overlay.on("vmousemove", UIMouseMove);
overlay.on("vmouseup", UIMouseUp);
overlay.on("vmouseout", UIMouseOut);
Finger goes down at point A

Finger goes down at point A

 

Dragged to point B - Right Event Fired

Dragged to point B – Right Event Fired

What the mousedown event is doing when it fires, is checking that the current location is outside of the threshold from the origin point. In the code above it’s set to 24, so you have to drag at least 24 pixels to fire the corresponding move event. Of course, you can change this to suit.

Once you lift your thumb/finger, the origin clears leaving you free to put your finger down again to move again.

Finger goes down at point C

Finger goes down at point C

Dragged to Point C - Left Event Fired

Dragged to Point C – Left Event Fired

Right now, this is usable for just movement, UI input, but you need more than that for gaming. you’d invariably need action buttons for jumping or firing.

Button actions

Of course, allowing you to do this anywhere has to have limits. You still need to implement the action buttons. This can be achieved by simply overlaying a smaller area on top of the movement layer. Obviously make this smaller but you will essentially need one area for each action button.

CanvasUI_7

Code

<div id="canvasWrapper">
    <canvas id="background" width="600" height="400">
    </canvas>
    <div id="overlay">
        <div id="action1"></div>
    </div>
</div>

Css

#overlay #action1{
    position: absolute;
    height: 30%;
    width: 30%;
    bottom: 0px;
    right: 0px;
}

Script

var action1 = jQuery("#action1");

action1.mousedown( function(event) {
    event.preventDefault();
    event.stopPropagation();

    /* YOUR ACTION CODE GOES IN HERE */
});

Taking it further

Once you’ve implemented this sort design, you could take it a stage further by allowing the player to determine where on screen the action buttons will be.
So long as the player knows where his action area(s) is, then the remainder of the screen is free for movement.

Don’t forget, this is a concept, not a walkthrough on how to code this perfectly. There will be better ways of achieving this, but this is to get you thinking…

Of course there are games where this will not work, for example where there are lots of buttons, or menus but there’s no reason why you couldn’t incorporate this sort of UI with menus, just ensure your interface layout doesn’t touch the menus etc.

This entry was posted in Canvas, Css, Development, Game Design, HTML5 Game, Input, JavaScript, UI. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *