dy_dla
Last Updated: February 25, 2016
·
2.081K
· pronebird
704044

Scale your KineticJS game dynamically

Building games or interactive scenes with KineticJS means you target almost any possible resolution, besides it can be dynamically changed.

dynamic stage

Unlike other frameworks like Phaser, KineticJS does not offer much to proportionally scale stage. However, it has all we need to achieve this.

Here is my approach to the problem.

1. Define a fixed coordinate space.

I choose middle-ground 800x600 resolution for stage.

I position and size all shapes, images and groups in my stage accordingly.

Designers and illustrators prepare all graphical assets according to that resolution.

In my case a little bit of upscale wouldn't hurt much so I allow upscale but limit it.

// Fixed stage size
var SCENE_BASE_WIDTH = 800
var SCENE_BASE_HEIGHT = 600

// Max upscale
var SCENE_MAX_WIDTH = 1024
var SCENE_MAX_HEIGHT = 768

Your stage coordinate space will always stay fixed at SCENE_BASE_WIDTH x SCENE_BASE_HEIGHT.

Upscale or downscale will only happen visually.

2. Resize stage

Keep your stage as large as possible, fill the whole container. Later on, we will center content inside.

// Get kinetic stage container div
var container = stage.container();

// Get container size
var containerSize = {
    width: container.clientWidth,
    height: container.clientHeight
};

// Odd size can cause blurry picture due to subpixel rendering
if(containerSize.width % 2 !== 0) containerSize.width--;

if(containerSize.height % 2 !== 0) containerSize.height--;

// Resize stage
stage.size(containerSize);

3. Scale stage

Calculate stage scale to size it proportionally and fit container.

var scaleX = Math.min(containerSize.width, SCENE_MAX_WIDTH) / SCENE_BASE_WIDTH;

var scaleY = Math.min(containerSize.height, SCENE_MAX_HEIGHT) / SCENE_BASE_HEIGHT;

var minRatio = Math.min(scaleX, scaleY);
var scale = { x: minRatio, y: minRatio };

stage.scale(scale);

4. Center stage

Stage scales properly now, but it sticks to the top left corner. I would like to center it on screen.

Surprisingly, changing stage position offsets layers within. Since we work with scaled stage, we have to apply scale to get right values back.

var stagePos = {
    x: (containerSize.width - SCENE_BASE_WIDTH * minRatio) * 0.5,
    y: (containerSize.height - SCENE_BASE_HEIGHT * minRatio) * 0.5
};

stage.position(stagePos);

5. Redraw stage

Don't forget to redraw stage after you modify it.

I suggest using batchDraw to throttle repaints caused by window.onresize.

stage.batchDraw();

6. Caveats

When you work with absolute coordinates, you have to convert coordinates from fixed space to absolute and vise-versa.

You can stumble upon absolute coordinates in KineticJS when you implement dragBoundFunc, use cursor position in mouse events, or use setAbsolutePosition, getAbsolutePosition.

I use two handy functions to convert between coordinate spaces:

// convert fixed coordinates to absolute
function toAbsolute (stage, pt) {
    return {
        x: stage.x() + pt.x * stage.scaleX(),
        y: stage.y() + pt.y * stage.scaleY()
    };
}

// convert absolute coordinates to fixed
function fromAbsolute (stage, pt) {
    var newPos = { x: 0, y: 0 };
    var scaleX = stage.scaleX();
    var scaleY = stage.scaleY();

    if(scaleX !== 0) {
        newPos.x = (pt.x - stage.x()) / scaleX;
    }

    if(scaleY !== 0) {
        newPos.y = (pt.y - stage.y()) / scaleY;
    }

    return newPos;
}

Demo

You can poke around jsfiddle: http://jsfiddle.net/pronebird/pthm3m9j/

Say Thanks
Respond