shaku
Version:
A simple and effective JavaScript game development framework that knows its place!
198 lines (161 loc) • 6.09 kB
HTML
<html>
<head>
<meta charset="UTF-8">
<title>Shaku</title>
<meta name="description" content="Shaku - a simple and easy-to-use javascript library for videogame programming.">
<meta name="author" content="Ronen Ness">
<link href="css/style.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
<div style="padding:0.5em" class="noselect">
<h1 class="demo-title" style="margin-left: 0;">Shaku: Snake</h1>
<p>This demo shows a simple snake game made with Shaku.<br />Use <b>Arrows</b> or <b>WASD</b> to move.</p>
<!-- include shaku -->
<script src="js/demos.js"></script>
<script src="js/shaku.js"></script>
<!-- demo code -->
<script id="main-code">async function runGame()
{
// init shaku
await Shaku.init();
// add shaku's canvas to document
document.body.appendChild(Shaku.gfx.canvas);
// create shapes batch
let shapesBatch = new Shaku.gfx.ShapesBatch();
// create text sprite batch
let textSpriteBatch = new Shaku.gfx.TextSpriteBatch();
textSpriteBatch.outlineWeight = 0.75;
// single tile size in the snake game
let tileSize = 50;
let gridSize = new Shaku.utils.Vector2(25, 15);
// we don't move snake every frame; only in this intervals
const snakeMovementInterval = 0.1;
let timeForSnakeMovement;
// snake direction
let direction;
let lastMovedDirection;
// set canvas size to match grid
let screenX = tileSize * gridSize.x;
let screenY = tileSize * gridSize.y;
Shaku.gfx.setResolution(screenX, screenY, true);
// load font texture
let fontTexture = await Shaku.assets.loadFontTexture('assets/DejaVuSansMono.ttf', {fontName: 'DejaVuSansMono'});
// head position and snake body
let head;
let snake;
// food position
let food;
// did we get game over?
let gameOver = false;
let gameOverTextGroup = Shaku.gfx.buildText(fontTexture, "Game Over!\nPress 'Space' to restart.", 64, Shaku.utils.Color.white, Shaku.gfx.TextAlignments.Center);
gameOverTextGroup.position.set(screenX / 2, screenY / 2 - 32);
// generate food position
function generateFood()
{
food = new Shaku.utils.Vector2(Math.floor(Math.random() * gridSize.x), Math.floor(Math.random() * gridSize.y));
}
// draw a tile
function drawTile(index, color)
{
shapesBatch.drawRectangle(new Shaku.utils.Rectangle(index.x * tileSize, index.y * tileSize, tileSize, tileSize), color);
}
// convert directions to values to movement vectors
const directionsToVector = {up: {x:0, y:-1}, down: {x:0, y:1}, left: {x:-1, y:0}, right: {x:1, y:0}};
// reset game state
function resetGame()
{
head = gridSize.div(2).floor();
snake = [head];
direction = 'right';
lastMovedDirection = direction;
timeForSnakeMovement = snakeMovementInterval * 2;
gameOver = false;
generateFood();
}
resetGame();
// do a single main loop step
function step()
{
// start new frame and clear screen
Shaku.startFrame();
Shaku.gfx.clear(Shaku.utils.Color.cornflowerblue);
shapesBatch.begin();
// draw snake
for (let i = 1; i < snake.length; ++i) {
drawTile(snake[i], Shaku.utils.Color.darkred);
}
drawTile(snake[0], Shaku.utils.Color.red);
// draw food
if (food) {
drawTile(food, Shaku.utils.Color.lime);
}
// if game over, stop here
if (gameOver) {
if (Shaku.input.released('space')) {
resetGame();
}
shapesBatch.end();
textSpriteBatch.begin();
textSpriteBatch.drawText(gameOverTextGroup);
textSpriteBatch.end();
Shaku.endFrame();
Shaku.requestAnimationFrame(step);
return;
}
// set snake direction
if ((Shaku.input.down('left') || Shaku.input.down('a')) && lastMovedDirection !== 'right') { direction = 'left'; }
if ((Shaku.input.down('right') || Shaku.input.down('d')) && lastMovedDirection !== 'left') { direction = 'right'; }
if ((Shaku.input.down('up') || Shaku.input.down('w')) && lastMovedDirection !== 'down') { direction = 'up'; }
if ((Shaku.input.down('down') || Shaku.input.down('s')) && lastMovedDirection !== 'up') { direction = 'down'; }
// check if its time to make a snake step
timeForSnakeMovement -= Shaku.gameTime.delta;
if (timeForSnakeMovement <= 0) {
timeForSnakeMovement = snakeMovementInterval;
// store last part position
let last = snake[snake.length - 1].clone();
let lastHead = head.clone();
// move head
head.x += directionsToVector[direction].x;
head.y += directionsToVector[direction].y;
// store last moved direction
lastMovedDirection = snake.length > 1 ? direction : null;
// update tail
for (let i = snake.length - 1; i >= 1; --i) {
snake[i] = (i === 1) ? lastHead : snake[i - 1].clone();
}
// pick up food
if (head.equals(food)) {
generateFood();
snake.push(last.clone());
}
// check if game over due to out of level
if (head.x < 0 || head.y < 0 || head.x >= gridSize.x || head.y >= gridSize.y) {
gameOver = true;
}
// check if game over due to stepping on tail
for (let i = 1; i < snake.length && !gameOver; ++i) {
if (head.equals(snake[i])) {
gameOver = true;
}
}
}
// draw shapes batch
shapesBatch.end();
// draw score
let scoreTextGroup = Shaku.gfx.buildText(fontTexture, "Score: " + snake.length, 26, Shaku.utils.Color.white, Shaku.gfx.TextAlignments.Left);
scoreTextGroup.position.set(5, 18);
textSpriteBatch.begin();
textSpriteBatch.drawText(scoreTextGroup);
textSpriteBatch.end();
// end frame and request next frame
Shaku.endFrame();
Shaku.requestAnimationFrame(step);
}
// start main loop
step();
}
runGame();</script>
</div>
</body>
</html>