Skip to content

Tutorial: Simple side scroller

✌ Makis Tracend edited this page Jan 24, 2014 · 1 revision

Construct.js is still in early development and there’s room for improvement. The API is not solidified but on the upside new features are constantly added. With that in mind, we can evaluate Construct.js by creating a simple side-scroller game.

Lets start with a basic HTML5 template:

<!doctype html>
<html>
<head>
	<title>Construct.js: Side-scroller</title>

	<style>

	</style>
</head>
<body>

<!-- game container -->
<div class="main"><!-- --></div>

<!-- scripts -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.8/require.min.js"></script>
<!-- main lib -->
<script type="text/javascript" src="//rawgithub.com/makesites/construct/0.3.0/build/construct-min.js"></script>

</body>
</html>

We’ve already created a “styles” tag that we’ll soon use. In the body section there’s the main container that will host our game.

At the bottom you see two scripts included, one is require.js and the other is the main lib. Construct.js can act as a dependency loader and comes with a require.js config that will be used if needed.

Before we even initialize Construct.js we can start working on basic styling and structure. Lets add some styles now:

<style>
	*{ margin: 0; padding: 0; }

	body, html{ margin: 0; padding: 0; width: 100%; height: 100%; }

	.main {
		width: 100%;
		height: 100%;
		background: #000;
	}
</style>

Obviously this resembles classic web development. Basically we’re making the container take the dimensions of the browser window, while setting the background to black.

We’ll continue by adding some markup. The general concept is that markup should take care of the basic structure. Construct.js prefers using Handlebars.js for markup rendering - although that can be customized. So before we create any logic, let’s outline our game scene with a handlebars template:

<script id="game" type="text/x-handlebars-template">
	<scene>
		<camera class="perspective" data-fov="30" data-far="4000"></camera>
		<player></player>
		<enemies></enemies>
		<rocks></rocks>
	</scene>
</script>

As shown above, we want to create a scene which will contain a camera (so we can output something to the screen) and three elements, our player object and two groups, one for enemy NPCs and one for obstacles. As per the traditional markup, it is not going in depth in the properties of each element, merely describing type and relevant context in the the hierarchy. Tags of this sort are created arbitrarily - you are free to set your own definitions and apply meaning to any custom tag.

Now that we have some styles and markup available lets initialize the logic. This is how we run Construct.js:

<script type="text/javascript">
var app;
var options = {};

construct(options, function( backbone ){

	app = backbone;

});
</script>

What’s returned from the callback is a Backbone router, according to Backbone APP(3) conventions. Our options are currently empty but we can pass things like additional dependencies through them. You can find more initialization options(9) at the wiki. The app is the main global object containing all our game code.

Although we don’t see much change on screen there’s already a lot that’s happening in the background. In fact at this stage, we can consider this a “Hello World” example. If you open the console on your browser you can see the message: “Construct.js is running...” - this means that the default router was executed and all dependencies were initialized properly.

To see things on screen, we’ll have to define templates for each individual element and attach some logic to them. Firstly we create some more handlebars templates:

<script id="player" type="text/x-handlebars-template">
	<cylinder class="player" data-radius-top="0" data-radius-bottom="1"></cylinder>
</script>

<script id="rocks" type="text/x-handlebars-template">
	<cube class="rock"></cube>
</script>

<script id="enemies" type="text/x-handlebars-template">
	<cylinder class="enemy" data-radius-top="0" data-radius-bottom="1"></cylinder>
</script>

These are fairly easy to read and practically create simple primitive shapes for every type of object we need. The data- attributes we use here define the shape of the primitive and will be used in the generation of the geometric shape(s). You may have also noticed some class names that we’ll use for additional styles. We then create an object for every template, much like the convention of views in Backbone.js:

construct.configure(function(){
	// Objects

	var Player = APP.Meshes.Player.extend({
		options: {
			html: $("#player").html()
		}
	});

	var Rock = APP.Meshes.Dynamic.extend({
		options: {
			html: $("#rocks").html()
		}
	});

	var Enemy = APP.Meshes.NPC.extend({
		options: {
			html: $("#enemies").html()
		}
	});
});

Note that this logic is enclosed in a construct.configure method - that way we guarantee that the objects we’re using are available. The list of available objects is essentially a series of extensions of basic Backbone.js constructs and you may rely on the basic set that comes with the lib, which cover most cases, or create your own. Lookup the detailed list of core objects at the wiki.

The constructs by themselves are merely blueprints - we need a binding structure that will instantiate all the objects in our scene. This is also where we will launch the 3D environment:

	APP.Main = APP.Views.Main3D.extend({
		options: {
			html: $("#game").html()
		},

		postRender: function(){

			this.objects.set({
				player: new Player({
					el: $(this.el).find("player")
				})
			});

			this.layers.set({
				rocks: new Rocks( new APP.Collection( new Array(20) ), {
					el: $(this.el).find("rocks")
				}),
				enemies: new Enemies( new APP.Collection( new Array(10) ), {
					el: $(this.el).find("enemies")
				})
			});

		}
	});

We have named our main class APP.Main because it will save us some boilerplate code - the default router will look for the existence of this class and load it automatically. Note that this code belongs in the same construct.configure as before. The Main3D instance will load Three.js through jQuery Three and save a reference to the 3D environment as a class variable, under this.$3d - this object will be broadcasting events back and forth between our “structural” JavaScript and the Three.js environment.

Also worth noting is that we have two containers to organize our objects with, one for unique objects in this.objects and one for groups of objects in this.layers . We do this so we can apply group-specific logic without much interference from other objects, pretty much how layers are used in a traditional graphics application.

Now, there are objects displayed on screen but they are black and they are one on top of each other. We can fix this with some more styling:

	camera {
		-webkit-transform: translate3d( 0, 0, 100px);
	}

	.player {
		-webkit-transform: translate3d( -40px, 0, 0) rotate3d(0, 0, 1, -90deg);
		color: #00f;
	}

	.enemy {
		-webkit-transform: translate3d( 40px, 0, 0) rotate3d(0, 0, 1, 90deg);
		color: #f00;
	}

	.rock {
		color: #999;
	}

The camera is moved to some distance, and the player and enemies are rotated to face each other and positioned in opposing sides on screen. We also used some bright colors for texturing the objects so they are clearly visible.

Everything’s OK so far but it all seems static. In fact, there’s a constant loop running at 60fps that each object can tap into by using their update method. Even without using that though we can set a default speed for each object as part of its options. Just by updating the rock’s options with:

		speed: { x: -0.01 }

we immediately see movement. We do the same for the enemies… Moving the objects is a start but we need them to appear in random locations and regenerate as they move off the screen. We address the first by assigning some properties on the preRender method of each object and the latter by monitoring the population of the layer when the refresh event is triggered.

Ultimately, the updated code for the objects and layers is as follows:

	// Objects

	var Rock = APP.Meshes.Dynamic.extend({
		options: {
			html: $("#rocks").html(),
			speed: { x: -0.01 }
		},

		preRender: function(){
			// set a random position
			var x = Math.random()* 160;
			var y = 30 - Math.random()* 60;
			// set a random size/rotation
			var s = 1 + Math.random()*3;
			var r = Math.random()*360;
			this.data.set({
				position: [x,y,0],
				scale: [s,s,s],
				rotation: [r,r,r]
			});
		},

		update: function( e ){
			// remove when the object is no longer visible
			if( this.object.position.x < -100){
				//
				this.remove();
			}
		}

	});

	var Enemy = APP.Meshes.NPC.extend({
		options: {
			html: $("#enemies").html(),
			speed: { x: -0.1 }
		},

		preRender: function(){
			// set a random position
			var x = 80 + Math.random()* 200;
			var y = 30 - Math.random()* 60;
			this.data.set({
				position: [x,y,0]
			});
		},

		update: function( e ){
			// remove when the object is no longer visible
			if( this.object.position.x < -100){
				//
				this.remove();
			}
		}
	});

	// Layers

	var Rocks = APP.Layer.extend({
		model: Rock,
		refresh: function(){
			// preserve population
			if( this.objects.length < 20){
				this.add();
			}
		}

	});

	var Enemies = APP.Layer.extend({
		model: Enemy,
		refresh: function(){
			// preserve population
			if( this.objects.length < 10){
				this.add();
			}
		}
	});

Congratulations! You’ve built a screensaver :D There’s no real game without user interaction so lets try to move our player object around using the arrow keys. Input is handled as an extension of Construct.js - there’s a whole extensions directory to add more features as needed.

<!-- extenstions -->
<script type="text/javascript" src="//rawgithub.com/constructjs/input/0.2.0/build/construct.input.js"></script>

<script type="text/javascript">
construct.input(["keys"]);
</script>

Now that we’ve got the additional dependencies loaded, we’re ready to add the necessary event triggers to our Player object:

keys : {
	'left right up down': 'onMove'
},

onMove: function(event, key) {
	event.preventDefault();
	var position = this.object.position;
	switch( key ) {
		case "left":
			position.x--;
		break;
		case "right":
			position.x++;
		break;
		case "up":
			position.y++;
		break;
		case "down":
			position.y--;
		break;

	}
	// check if the position has changed first
	this.object.position.set(position.x, position.y, position.z);
}

We can now move around our little blue player using the arrows. Yay!

We’ve already covered a lot of ground but there’s so much more we can do. To avoid making this article too lengthy we can pause here and continue in a follow up article. In the next part, we’ll add more game-like features like shooting, collision detection and some simple animations.

Hope you enjoyed the tutorial so far.

View the complete source code

Play a working demo

Credits

Written by Makis Tracend ( @tracend )

All content copyrighted - CC BY-NC-ND

Clone this wiki locally