Recently I’ve seen an amazing demo of Assassin’s Creed Pirates inside browser: Assassin’s Creed. I was very impressed by this demo and looked a bit into the details.
So, this demo was written with a help of babylon.js engine. A lot of information about this framework can be found in this blog.
This engine is a very easy-to-use JavaScript library. All you need – is to download it and start to create amazing things. Let me show you a demo of Snake:
You can download a full version using this link.
Keys:
- W – up;
- A – left;
- S – down;
- D – right.
Hope you’ll enjoy it and will dig a little bit into the WebGL to create your own demo.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
body {margin:0;} | |
body, canvas {width:100%; height:100%;overflow:hidden;} | |
div {position:absolute; width:100%; height: 100%; z-index:100;} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<script data-main="javascript/index" src="javascript/libraries/require.js" type="text/javascript"></script> | |
<link href="css/main.css" type="text/css" rel="Stylesheet" /> | |
</head> | |
<body> | |
<div style="display:none;"></div> | |
<canvas id="renderCanvas"></canvas> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
requirejs.config({ | |
baseUrl: 'javascript' | |
}); | |
define('index', ['ui/main'], function() { | |
}); | |
define('game', [], function() { | |
var fieldSize = 15, | |
defaultInterval = 350, | |
intervalStep = 30, | |
intervalMinimum = 50, | |
snake = null, | |
applePosition = null, | |
events = { | |
'gameOver': [], | |
'move': [], | |
'increase': [], | |
'createApple': [] | |
}, | |
currentDirection = null, | |
intervalId = null, | |
currentInterval = null, | |
fireEvent = function(eventName, params) { | |
var i, handlers; | |
if (!events.hasOwnProperty(eventName)) { | |
throw 'Unsupported event'; | |
} | |
handlers = events[eventName]; | |
for (var i = 0; i < handlers.length; ++i) { | |
handlers[i].apply(this, params); | |
} | |
}, | |
increaseSpeed = function() { | |
if (snake.length % 10 === 0 && currentInterval > intervalMinimum) { | |
clearInterval(intervalId); | |
currentInterval -= intervalStep; | |
intervalId = setInterval(move, currentInterval); | |
} | |
}, | |
move = function() { | |
var headPosition = snake[0], | |
tailPosition = snake[snake.length – 1], | |
newPosition = {x: headPosition.x + currentDirection.x, y: headPosition.y + currentDirection.y}; | |
if (!isCorrectPosition(newPosition)) { | |
clearInterval(intervalId); | |
fireEvent('gameOver', [snake.length]); | |
return; | |
} | |
snake.unshift(newPosition); | |
if (newPosition.x === applePosition.x && newPosition.y === applePosition.y) { | |
createApple(); | |
fireEvent('increase', [newPosition]); | |
increaseSpeed(); | |
} else { | |
snake.pop(); | |
fireEvent('move', [newPosition]); | |
} | |
}, | |
getRandom = function() { | |
return Math.floor((Math.random()*fieldSize)); | |
}, | |
createApple = function() { | |
var correctPlace = false; | |
while (!correctPlace) { | |
applePosition = {x: getRandom(), y: getRandom()}; | |
correctPlace = true; | |
for (var i = 0; i < snake.length; ++i) { | |
if (snake[i].x === applePosition.x && snake[i].y === applePosition.y) { | |
correctPlace = false; | |
break; | |
} | |
} | |
} | |
fireEvent('createApple', [applePosition]); | |
}, | |
isCorrectPosition = function(position) { | |
var isCorrect = true; | |
if (position.x < 0 || position.x >= fieldSize || position.y < 0 || position.y >= fieldSize ) { | |
return false; | |
} | |
for (var i = 0; i < snake.length – 2; ++i) { | |
if (position.x === snake[i].x && position.y === snake[i].y) { | |
isCorrect = false; | |
break; | |
} | |
} | |
return isCorrect; | |
}, | |
isCorrectDirection = function(direction) { | |
if (currentDirection.x === –1*direction.x && currentDirection.y === –1*direction.y) { | |
return false; | |
} | |
return true; | |
}, | |
startGame = function() { | |
var defaultState = {x: 0, y: 0}; | |
snake = [defaultState]; | |
clearInterval(intervalId); | |
currentInterval = defaultInterval; | |
intervalId = setInterval(move, currentInterval); | |
createApple(); | |
}; | |
return { | |
directions: { | |
up: {x: 0, y: 1}, | |
down: {x: 0, y: –1}, | |
left: {x: –1, y: 0}, | |
right: {x: 1, y: 0} | |
}, | |
addEventListener: function(eventName, callback) { | |
if (events.hasOwnProperty(eventName)) { | |
events[eventName].push(callback); | |
} else { | |
throw 'Unsupported event'; | |
} | |
}, | |
changeDirection: function(direction) { | |
if (currentDirection === null) { | |
currentDirection = direction; | |
startGame(); | |
} else if (isCorrectDirection(direction)){ | |
currentDirection = direction; | |
} | |
}, | |
getFieldSize: function() { | |
return fieldSize; | |
} | |
}; | |
}); | |
define('engine', ['libraries/babylon'], function() { | |
if (!BABYLON.Engine.isSupported()) { | |
return; | |
} | |
var engine = {}; | |
engine.canvas = document.getElementById('renderCanvas'); | |
engine.engine = new BABYLON.Engine(engine.canvas, true); | |
engine.scene = new BABYLON.Scene(engine.engine); | |
engine.camera = new BABYLON.ArcRotateCamera("MainCamera", – 7*Math.PI/16, Math.PI/4, 50, BABYLON.Vector3.Zero(), engine.scene); | |
engine.light = new BABYLON.PointLight("Omni", new BABYLON.Vector3(0, 100, 0), engine.scene); | |
renderLoop = function () { | |
//engine.beginFrame(); | |
engine.scene.render(); | |
//engine.endFrame(); | |
BABYLON.Tools.QueueNewFrame(renderLoop); | |
}; | |
BABYLON.Tools.QueueNewFrame(renderLoop); | |
engine.camera.attachControl(engine.canvas); | |
return engine; | |
}); | |
define('ui/appleElement', ['ui/baseObject', 'engine', 'libraries/babylon'], function(BaseObject, engine) { | |
var appleElement = function(position) { | |
this._createElement(); | |
this.moveTo(position); | |
}; | |
appleElement.prototype = new BaseObject(); | |
appleElement.prototype._texture = 'images/appleTexture.jpg'; | |
appleElement.prototype._createElement = function() { | |
this._object = BABYLON.Mesh.CreateSphere("Sphere", 16, this._size, engine.scene); | |
var material = new BABYLON.StandardMaterial("default", engine.scene); | |
material.diffuseTexture = new BABYLON.Texture(this._texture, engine.scene); | |
this._object.material = material; | |
}; | |
return appleElement; | |
}); | |
define('ui/baseObject', ['game'], function(game) { | |
var constructor = function() {}; | |
constructor.prototype = { | |
_size: 2, | |
_fieldSize: game.getFieldSize(), | |
_object: null, | |
_convertPositionToCoordinate: function(position) { | |
var middlePosition = (this._fieldSize – 1)/2, | |
coordinate = { | |
x: (position.x – middlePosition)*2, | |
y: 0, | |
z: (position.y – middlePosition)*2 | |
}; | |
return coordinate; | |
}, | |
moveTo: function(position) { | |
var coordinates = this._convertPositionToCoordinate(position); | |
this._object.position.x = coordinates.x; | |
this._object.position.y = coordinates.y; | |
this._object.position.z = coordinates.z; | |
} | |
}; | |
return constructor; | |
}); | |
define('ui/main', ['engine', 'game', 'ui/snakeElement', 'ui/appleElement', 'ui/planeField'], function(engine, game, SnakeElement, AppleElement) { | |
var snake = [new SnakeElement({x: 0, y: 0})], | |
apple = null; | |
game.addEventListener('move', function(position) { | |
var tail = snake.pop(); | |
tail.moveTo(position); | |
snake.unshift(tail); | |
}); | |
game.addEventListener('increase', function(position) { | |
snake.unshift(new SnakeElement(position)); | |
}); | |
game.addEventListener('createApple', function(position) { | |
if (apple === null) { | |
apple = new AppleElement(position); | |
} else { | |
apple.moveTo(position); | |
} | |
}); | |
game.addEventListener('gameOver', function(length) { | |
alert('Epic fail on length: ' + length); | |
}); | |
window.addEventListener('keydown', function(event) { | |
switch(event.keyCode) { | |
case 87: //W | |
game.changeDirection(game.directions.up); | |
break; | |
case 65: //A | |
game.changeDirection(game.directions.left); | |
break; | |
case 83: //S | |
game.changeDirection(game.directions.down); | |
break; | |
case 68: //D | |
game.changeDirection(game.directions.right); | |
break; | |
} | |
}); | |
return null; | |
}); | |
define('ui/planeField', ['engine', 'game', 'libraries/babylon'], function(engine, game) { | |
var diameter = 2, | |
planeMaterial = new BABYLON.StandardMaterial("default", engine.scene), | |
plane = BABYLON.Mesh.CreatePlane('GameField', game.getFieldSize() * diameter, engine.scene); | |
plane.rotation.x = Math.PI/2; | |
plane.position.y -= diameter/2; | |
planeMaterial.diffuseTexture = new BABYLON.Texture("images/grassTexture.jpg", engine.scene); | |
planeMaterial.diffuseTexture.uScale = planeMaterial.diffuseTexture.vScale = game.getFieldSize(); | |
plane.material = planeMaterial; | |
return null; | |
}); | |
define('ui/snakeElement', ['ui/baseObject', 'engine', 'libraries/babylon'], function(BaseObject, engine) { | |
var snakeElement = function(position) { | |
this._createElement(); | |
this.moveTo(position); | |
}; | |
snakeElement.prototype = new BaseObject(); | |
snakeElement.prototype._texture = 'images/snakeTexture.jpg'; | |
snakeElement.prototype._createElement = function() { | |
this._object = BABYLON.Mesh.CreateSphere("Sphere", 16, this._size, engine.scene); | |
var material = new BABYLON.StandardMaterial("default", engine.scene); | |
material.diffuseTexture = new BABYLON.Texture(this._texture, engine.scene); | |
this._object.material = material; | |
}; | |
return snakeElement; | |
}); |