Game = {
// This defines our grid's size and the size of each of its tiles
map_grid: {
width: 24,
height: 16,
tile: {
width: 16,
height: 16
}
},
// The total width of the game screen. Since our grid takes up the entire screen
// this is just the width of a tile times the width of the grid
width: function() {
return this.map_grid.width * this.map_grid.tile.width;
},
// The total height of the game screen. Since our grid takes up the entire screen
// this is just the height of a tile times the height of the grid
height: function() {
return this.map_grid.height * this.map_grid.tile.height;
},
// Initialize and start our game
start: function() {
// Start crafty and set a background color so that we can see it's working
Crafty.init(Game.width(), Game.height());
Crafty.background('rgb(87, 109, 20)');
// Simply start the "Loading" scene to get things going
Crafty.scene('Loading');
}
}
// The Grid component allows an element to be located
// on a grid of tiles
Crafty.c('Grid', {
init: function() {
this.attr({
w: Game.map_grid.tile.width,
h: Game.map_grid.tile.height
})
},
// Locate this entity at the given position on the grid
at: function(x, y) {
if (x === undefined && y === undefined) {
return { x: this.x/Game.map_grid.tile.width, y: this.y/Game.map_grid.tile.height }
} else {
this.attr({ x: x * Game.map_grid.tile.width, y: y * Game.map_grid.tile.height });
return this;
}
}
});
// An "Actor" is an entity that is drawn in 2D on canvas
// via our logical coordinate grid
Crafty.c('Actor', {
init: function() {
this.requires('2D, Canvas, Grid');
},
});
// A Tree is just an Actor with a certain color
Crafty.c('Tree', {
init: function() {
this.requires('Actor, Solid, spr_tree');
},
});
// A Bush is just an Actor with a certain color
Crafty.c('Bush', {
init: function() {
this.requires('Actor, Solid, spr_bush');
},
});
// This is the player-controlled character
Crafty.c('PlayerCharacter', {
init: function() {
this.requires('Actor, Fourway, Collision, spr_player, SpriteAnimation')
.fourway(4)
.stopOnSolids()
.onHit('Village', this.visitVillage)
// These next lines define our four animations
// each call to .animate specifies:
// - the name of the animation
// - the x and y coordinates within the sprite
// map at which the animation set begins
// - the number of animation frames *in addition to* the first one
.animate('PlayerMovingUp', 0, 0, 2)
.animate('PlayerMovingRight', 0, 1, 2)
.animate('PlayerMovingDown', 0, 2, 2)
.animate('PlayerMovingLeft', 0, 3, 2);
// Watch for a change of direction and switch animations accordingly
var animation_speed = 8;
this.bind('NewDirection', function(data) {
if (data.x > 0) {
this.animate('PlayerMovingRight', animation_speed, -1);
} else if (data.x < 0) {
this.animate('PlayerMovingLeft', animation_speed, -1);
} else if (data.y > 0) {
this.animate('PlayerMovingDown', animation_speed, -1);
} else if (data.y < 0) {
this.animate('PlayerMovingUp', animation_speed, -1);
} else {
this.stop();
}
});
},
// Registers a stop-movement function to be called when
// this entity hits an entity with the "Solid" component
stopOnSolids: function() {
this.onHit('Solid', this.stopMovement);
return this;
},
// Stops the movement
stopMovement: function() {
this._speed = 0;
if (this._movement) {
this.x -= this._movement.x;
this.y -= this._movement.y;
}
},
// Respond to this player visiting a village
visitVillage: function(data) {
villlage = data[0].obj;
villlage.visit();
}
});
// A village is a tile on the grid that the PC must visit in order to win the game
Crafty.c('Village', {
init: function() {
this.requires('Actor, spr_village');
},
// Process a visitation with this village
visit: function() {
this.destroy();
Crafty.audio.play('knock');
Crafty.trigger('VillageVisited', this);
}
});
// Game scene
// -------------
// Runs the core gameplay loop
Crafty.scene('Game', function() {
// A 2D array to keep track of all occupied tiles
this.occupied = new Array(Game.map_grid.width);
for (var i = 0; i < Game.map_grid.width; i++) {
this.occupied[i] = new Array(Game.map_grid.height);
for (var y = 0; y < Game.map_grid.height; y++) {
this.occupied[i][y] = false;
}
}
// Player character, placed at 5, 5 on our grid
this.player = Crafty.e('PlayerCharacter').at(5, 5);
this.occupied[this.player.at().x][this.player.at().y] = true;
// Place a tree at every edge square on our grid of 16x16 tiles
for (var x = 0; x < Game.map_grid.width; x++) {
for (var y = 0; y < Game.map_grid.height; y++) {
var at_edge = x == 0 || x == Game.map_grid.width - 1 || y == 0 || y == Game.map_grid.height - 1;
if (at_edge) {
// Place a tree entity at the current tile
Crafty.e('Tree').at(x, y);
this.occupied[x][y] = true;
} else if (Math.random() < 0.06 && !this.occupied[x][y]) {
// Place a bush entity at the current tile
Crafty.e('Bush').at(x, y);
this.occupied[x][y] = true;
}
}
}
// Generate up to five villages on the map in random locations
var max_villages = 5;
for (var x = 0; x < Game.map_grid.width; x++) {
for (var y = 0; y < Game.map_grid.height; y++) {
if (Math.random() < 0.02) {
if (Crafty('Village').length < max_villages && !this.occupied[x][y]) {
Crafty.e('Village').at(x, y);
}
}
}
}
// Show the victory screen once all villages are visisted
this.show_victory = this.bind('VillageVisited', function() {
if (!Crafty('Village').length) {
Crafty.scene('Victory');
}
});
}, function() {
// Remove our event binding from above so that we don't
// end up having multiple redundant event watchers after
// multiple restarts of the game
this.unbind('VillageVisited', this.show_victory);
});
// Victory scene
// -------------
// Tells the player when they've won and lets them start a new game
Crafty.scene('Victory', function() {
// Display some text in celebration of the victory
Crafty.e('2D, DOM, Text')
.attr({ x: 0, y: 0 })
.text('Victory!');
// Watch for the player to press a key, then restart the game
// when a key is pressed
this.restart_game = this.bind('KeyDown', function() {
Crafty.scene('Game');
});
}, function() {
// Remove our event binding from above so that we don't
// end up having multiple redundant event watchers after
// multiple restarts of the game
this.unbind('KeyDown', this.restart_game);
});
// Loading scene
// -------------
// Handles the loading of binary assets such as images and audio files
Crafty.scene('Loading', function(){
// Load our sprite map image
Crafty.load(['http://desolate-caverns-4829.herokuapp.com/assets/16x16_forest_1.gif', 'http://desolate-caverns-4829.herokuapp.com/assets/hunter.png', 'http://desolate-caverns-4829.herokuapp.com/assets/door_knock_3x.mp3', 'http://desolate-caverns-4829.herokuapp.com/assets/door_knock_3x.ogg', 'http://desolate-caverns-4829.herokuapp.com/assets/door_knock_3x.aac'], function(){
// Once the images are loaded...
// Define the individual sprites in the image
// Each one (spr_tree, etc.) becomes a component
// These components' names are prefixed with "spr_"
// to remind us that they simply cause the entity
// to be drawn with a certain sprite
Crafty.sprite(16, 'http://desolate-caverns-4829.herokuapp.com/assets/16x16_forest_1.gif', {
spr_tree: [0, 0],
spr_bush: [1, 0],
spr_village: [0, 1]
});
// Define the PC's sprite to be the first sprite in the third row of the
// animation sprite map
Crafty.sprite(16, 'http://desolate-caverns-4829.herokuapp.com/assets/hunter.png', {
spr_player: [0, 2],
}, 0, 2);
// Define our sounds for later use
//Crafty.audio.add({
// knock: ['http://desolate-caverns-4829.herokuapp.com/assets/door_knock_3x.mp3']
//});
// Draw some text for the player to see in case the file
// takes a noticeable amount of time to load
Crafty.e('2D, DOM, Text')
.attr({ x: 0, y: Game.height()/2 - 24, w: Game.width() })
.text('Loading...');
// Now that our sprites are ready to draw, start the game
Crafty.scene('Game');
})
});
Game.start();
<!DOCTYPE html>
<html>
<head>
<style type="text/css" media="screen">
body { background: black; }
</style>
</head>
<body>
</body>
</html>
External resources loaded into this fiddle: