Skip to content

Instantly share code, notes, and snippets.

@sugendran
Created April 5, 2014 09:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sugendran/9989745 to your computer and use it in GitHub Desktop.
Save sugendran/9989745 to your computer and use it in GitHub Desktop.
/*
* Yes this CSS is ugly, but it's a demo
* about canvas and not styling html+css.
*/
html, body {
background: black;
color: white;
font-family: monospace;
margin: 0;
padding: 0;
}
h1 strike {
color: #CCC;
}
#game-canvas {
background: white;
}
dl.legend dt, dl.legend dd {
display: inline;
margin-left: 0px;
}
dl.legend dt img {
background: white;
width: 20px;
margin-left: 40px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, user-scalable=no" name="viewport">
<title>JS Bin</title>
<script src="http://code.createjs.com/createjs-2013.12.12.min.js"></script>
</head>
<body onload="window.skigratis('game-canvas')">
<!-- <h1>Ski <strike>Free</strike> Gratis</h1> -->
<canvas id="game-canvas"></canvas>
<!-- <p> Use your mouse or finger to steer. All icons are Japanese characters.</p>
<dl class="legend">
<dt><img src="https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/person.png" /></dt>
<dd>Person</dd>
<dt><img src="https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/tree.png" /></dt>
<dd>Wood/Tree</dd>
<dt><img src="https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/rock.png" /></dt>
<dd>Rock</dd>
</dl>
-->
<!-- https://github.com/sugendran/ski-gratis -->
</body>
</html>
/*
* Ski Gratis
* =====
* A game that looks very much like ski free
*
* This game was written to explore using easel.js
* At some point I will turn this into a blog post
* and perhaps even try and give some sort of talk.
*
* -- Sugendran
*/
// should probably encapsulate and play nice
(function(window, document, createjs) {
var IMG_PERSON = "https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/person.png";
var IMG_TREE = "https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/tree.png";
var IMG_ROCK = "https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/rock.png";
var IMG_CRASH = "https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/crash.png";
var IMG_BEAR = "https://raw.githubusercontent.com/sugendran/ski-gratis/master/images/bear.png";
// list of rows of things to draw
// screen is broken into 64x64 grid
// creating this as multiple worlds so that
// in theory we can have many ski free games
// all at the same time!
function newWorld(stage, stageWidth, stageHeight) {
// "Constants"
var TILE_EDGE = 64;
var TILE_EDGE_MINUS = -TILE_EDGE;
var HALF_TILE_EDGE = TILE_EDGE * 0.5;
var DIFFICULTY = 28;
// "privates" (Haha... I made you say privates in your head)
var _gameGrid = [];
var _maxCols = Math.floor(stageWidth / TILE_EDGE) + 16;
var _maxRows = Math.floor(stageHeight / TILE_EDGE) + 8;
var _offsetX = 0;
var _offsetY = 0;
var _direction = 0;
var _crashed = false;
var _score = 0;
var _offsetYPerTick = TILE_EDGE / 1000;
var _offsetXPerTick = 2 * TILE_EDGE / 1000;
var _offsetYSpeedUp = 0;
var _bearUnleashed = false;
// no z-indexes anywhere... WTF.
// gonna setup a "container" for each layer
var bgContainer = new createjs.Container();
var skierContainer = new createjs.Container();
stage.addChild(bgContainer);
stage.addChild(skierContainer);
// displaying the frames per second
// seems like a popular thing to do
var fpsDisplay = new createjs.Text();
fpsDisplay.textAlign = "right";
fpsDisplay.x = stageWidth - 10;
fpsDisplay.y = stageHeight - 10;
skierContainer.addChild(fpsDisplay);
// draw the score in the top right
var scoreDisplay = new createjs.Text();
scoreDisplay.textAlign = "right";
scoreDisplay.x = stageWidth - 10;
scoreDisplay.y = 24;
scoreDisplay.font = "bold 24px monospace";
skierContainer.addChild(scoreDisplay);
// now add our skier
var skier = new createjs.Bitmap(IMG_PERSON);
skier.regX = HALF_TILE_EDGE;
skier.regY = TILE_EDGE;
skier.x = (stageWidth * 0.5);
skier.y = (stageHeight * 0.5);
skierContainer.addChild(skier);
// also add a crash/stumble icon, but keep it hidden
var crash = new createjs.Bitmap(IMG_CRASH);
crash.regX = HALF_TILE_EDGE;
crash.regY = TILE_EDGE;
crash.x = (stageWidth * 0.5);
crash.y = (stageHeight * 0.5);
crash.visible = false;
crash.alpha = 0;
skierContainer.addChild(crash);
// load the sprite sheet
var bearSpriteSheet = new createjs.SpriteSheet({
images: [IMG_BEAR],
// each frame is a 64x64 square
frames: {width: 64, height: 64, count: 4, regX: 32, regY: 64},
// only one animation in the list and it uses frames zero to three
animations: { run: [0, 3, true, 10] }
});
// make a bitmap object for sticking onto the screen
var bear = new createjs.Sprite(bearSpriteSheet);
// start that running animation up
bear.gotoAndPlay("run");
bear.x = -TILE_EDGE;
bear.y = -TILE_EDGE;
skierContainer.addChild(bear);
// build up the grid
// the grid is a map of the world
// that wraps around as we move about it
while(_gameGrid.length < _maxRows) {
var newRow = [];
for(var i=0, ii=_maxCols; i<ii; i++) {
var r = Math.floor(Math.random() * DIFFICULTY);
if(r == 1) {
var tree = new createjs.Bitmap(IMG_TREE);
bgContainer.addChild(tree);
newRow.push(tree);
} else if(r == 2) {
var rock = new createjs.Bitmap(IMG_ROCK);
bgContainer.addChildAt(rock);
newRow.push(rock);
} else {
newRow.push(0);
}
}
_gameGrid.push(newRow);
}
// functions for messing with arrays in the grid
function shiftPush(arr) {
arr.push(arr.shift());
}
function popSplice(arr) {
arr.splice(0, 0, arr.pop());
}
// move rows/cols around if we have moved to far
// also update the x,y co-ords of everything in the grid
function updateGrid() {
if(_offsetY < TILE_EDGE_MINUS) {
// take a row of cells from the top and move them to the bottom
shiftPush(_gameGrid);
_offsetY += TILE_EDGE;
}
if(_offsetX < TILE_EDGE_MINUS) {
// gotta take one from the left and put it on the right
_gameGrid.forEach(shiftPush);
_offsetX += TILE_EDGE;
} else if (_offsetX > TILE_EDGE) {
// gotta take one from the right and put it on the left
_gameGrid.forEach(popSplice);
_offsetX -= TILE_EDGE;
}
// updated all locations
var y = _gameGrid.length + _offsetY;
for(var i=0; i<_maxRows; i++) {
var row = _gameGrid[i];
var x = _offsetX;
for(var j=0; j<_maxCols; j++) {
row[j].x = x;
row[j].y = y;
x += TILE_EDGE;
}
y += TILE_EDGE;
}
}
// first run to get some locations happening before we draw
updateGrid();
// we should speed up as time goes by and then reset it when we crash!
setInterval(function(){
_offsetYSpeedUp = Math.min(8, _offsetXPerTick + (_offsetYPerTick * 0.5));
},1000);
function between(val, min, max) {
return val < max && val > min;
}
// this function will do a very simple collision check
// it just checks to see if the anchor point of the person
// collides with ANY object below it.
function checkForCollision() {
if(_crashed) {
return;
}
var hit = false;
for(var i=0; !hit && i<_maxRows; i++) {
var row = _gameGrid[i];
for(var j=0; !hit && j<_maxCols; j++) {
if (row[j].getBounds) {
var bounds = row[j].getBounds();
hit = between(skier.x, row[j].x, row[j].x+bounds.width) &&
between(skier.y, row[j].y, row[j].y+bounds.height);
}
}
}
var obj = bgContainer.getObjectUnderPoint(skier.x, skier.y);
if(hit) {
// we probably didn't need the tweening here but the switch
// between the sprites was very jarring so I stuck it in.
// plus you get to see tween.js work
_crashed = true;
createjs.Tween.get(skier).to({alpha: 0}, 200).set({visible: false});
createjs.Tween.get(crash).set({visible: true}).to({alpha: 1}, 200);
_offsetYSpeedUp = 0;
_score -= 50;
setTimeout(function(){
createjs.Tween.get(crash).to({alpha: 0}, 200).set({visible: false});
createjs.Tween.get(skier).set({visible: true}).to({alpha: 1}, 200);
_crashed = false;
}, 2300);
}
}
function killPlayer() {
createjs.Tween.get(bear).to({x: skier.x, y: skier.y}, 3000, createjs.Ease.elasticOut).call(function(){
crash.alpha = 1;
skier.alpha = 0;
createjs.Ticker.setPaused(true);
});
}
// we want to move 1 tile every 1 second
// so we need to work out how many pixels
// are moved per tick
// The tick function gets called with the elapsed time
// since the last time it was called
this.tick = function(evt) {
var elapsed = evt.delta;
_offsetY -= (_offsetYPerTick + _offsetYSpeedUp) * elapsed;
_offsetX -= _direction * _offsetXPerTick * elapsed;
updateGrid();
checkForCollision();
if(!_crashed) {
_score += (elapsed / 100);
}
if(_score > 1000 && !_bearUnleashed) {
// unleash the bear and end the game
killPlayer();
}
fpsDisplay.text = "fps: " + ~~createjs.Ticker.getMeasuredFPS();
scoreDisplay.text = Math.floor(_score) + " points";
stage.update(evt);
};
// just going to skew the object along the Y-axis
// so that it looks liek they're changin direction
// really should load up a different sprite for this... meh.
function updatePersonOrientation() {
skier.skewY = -0.3 * TILE_EDGE * _direction;
}
// the following bits of code deal with mouse/touch events
var mouseActive = false;
stage.addEventListener('stagemouseup', function () {
mouseActive = false;
});
stage.addEventListener('stagemouseup', function () {
mouseActive = true;
});
stage.addEventListener('stagemousemove', function(mouseEvent) {
if(!mouseActive || _crashed) {
return;
}
var mx = Math.round(mouseEvent.stageX / TILE_EDGE);
var px = Math.round(skier.x / TILE_EDGE);
var delta = mx - px;
if(delta < 0 && _direction > -1) {
_direction = -1;
} else if(delta > 0 && _direction < 1) {
_direction = 1;
} else if (delta === 0 && _direction !== 0) {
_direction = 0;
}
updatePersonOrientation();
});
// added this so that I can hook in keyboard events
this.setDirection = function(d) {
if(_crashed) {
return;
}
_direction = d;
updatePersonOrientation();
};
return this;
}
// loads a self contained game in the given element
function loadSkiGratis(elementId) {
// setup the canvas
canvasElement = document.getElementById(elementId);
var width = canvasElement.width = innerWidth;
var height = canvasElement.height = innerHeight;
var stage = new createjs.Stage(canvasElement);
// create our world object
var world = newWorld(stage, width, height);
// tell the Ticker to call world.tick such that
// it renders at 60 FPS
createjs.Ticker.addEventListener("tick", world.tick);
createjs.Ticker.useRAF = true;
createjs.Ticker.setFPS(60);
// allow touches to work
createjs.Touch.enable(stage);
// global listener - not cool for embedding, but nice for debugging
window.addEventListener("keypress", function(e) {
if(e.charCode == 97) {
//a
world.setDirection(-1);
} else if(e.charCode == 115) {
// s
world.setDirection(0);
} else if (e.charCode == 100) {
// d
world.setDirection(1);
}
});
}
window.skigratis = loadSkiGratis;
})(window, document, createjs);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment