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.
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/
Written by Andrej Mihajlov
Related protips
1 Response
Great explanation. I like such dynamic things. I am interested in this and will try to repeat it, thank you.