A platform game (more or less) in angularjs

If you read the introduction to angularjs, it’s stated clearly that angularjs can be not the best choice to create games.
I can imagine the reason easily: the performances can be very problematic, mostly if the game requires a complex DOM.

Moreover a lot of good frameworks exist to create amazing games in html5 (have you ever heard about impactjs?).

Nevertheless I have found very fascinating the idea to manage via directive a level design instead of a classic multidimensional array as I did in the pawn and tiles experiment.

I wanted to try something more complex than the memory game that I have created few months ago, I wanted a game with a typical game-loop.

In this case it was for me a double challenge because I had to try to do something I had never done with angular and in a field (video game) where I am an enthusiastic newbie.

I wanted to be able to write something like

<start></start>
<score></score>
<screen width="10" height="10">
	<player x="0" y="5" speed="4" jump="2"></player>
	<platform id="p1" width="1" height="1" x="2" y="0"></platform>
	<platform id="p2" width="1" height="1" x="3" y="1"></platform>
	<platform id="p3" width="1" height="1" x="0" y="1"></platform>
	<platform id="p4" width="1" height="1" x="4" y="3"></platform>
	<platform id="p5" width="1" height="1" x="6" y="3"></platform>
	<money id="m1" x="2" y="1"></money>
	<money id="m2" x="3" y="2"></money>
	<money id="m3" x="4" y="2"></money>
	<floor></floor>
</screen>

All the dimensions and coordinates had to be relative and depending on the resolution of the screen to draw my elements correctly.
So for instance the player had to be able to jump up two platforms with a height of 1 or up to a platform with a height of 2.
The attribute width and height of the <screen> had to be used to divide the screen in a grid and scale correctly all the objects of the level.

angular.module('Platform.directives')
	.directive('screen', ['screenService', function (screenService) {
		return {
			restrict: 'E',
			replace: true,
			priority: 1,
			transclude: true,
			scope: {
				width: '=',
				height: '='
			},
			controller: function ($scope) {
				this.getTileDimension = function () {
					return $scope.tileDimension;
				}
			},
			templateUrl: '/AppJs/Platform/views/directives/screenDirective.html',
			link: {
				pre: function (scope) {
					screenService.setGridDimension(scope.width, scope.height);
					scope.tileDimension = screenService.getTileDimension();
				}
			}
		};
	}]);

The screenDirective has a dependency to a screenService that I have used to calculate the grid and the tiles in pixel.
I have used the pre-link to pass the values to the service and the directive has a priority 1, so I was sure that it would be loaded as first.
In the controller is exposed a method that the other directives will use to calculate their dimension and position.

To calculate correctly dimension and position the other directives require the screenDirective.

The role of the other directives for me had to be pretty simple, to create the correct markup and at the same time to store the object in a collection of the existing objects in the specific level.
To deal with this I created a service named levelObjects.

So giving a look to the platform directive we have

angular.module('Platform.directives')
	.directive('platform', ['levelObjects'
		, function (levelObjects) {
			return {
				restrict: 'E',
				require:'^screen',
				replace: true,
				scope: {
					x: '=',
					y: '=',
					width: '=',
					height: '='
				},
				templateUrl: '/AppJs/Platform/views/directives/platformDirective.html',
				link: function ($scope, element, attrs, screenCtrl) {
					console.log(screenCtrl);
					$scope.screenWidth = $scope.width * screenCtrl.getTileDimension().width;
					$scope.screenHeight = $scope.height * screenCtrl.getTileDimension().height;
					$scope.screenBottom = $scope.y * screenCtrl.getTileDimension().height + 20;
					$scope.screenLeft = $scope.x * screenCtrl.getTileDimension().width;

					levelObjects.addPlatform(element, {
						x: $scope.x,
						y: $scope.y,
						width: $scope.width,
						height: $scope.height,
						screenWidth: $scope.screenWidth,
						screenHeight: $scope.screenHeight,
						screenBottom: $scope.screenBottom,
						screenLeft: $scope.screenLeft
					});
				}
			};
		}]);

The levelObjects service is very simple and has three dependencies on three factories: playerFactory, platformFactory, moneyFactory.
The goal of these factories is to create a more complex model where the dom element is just one of its properties.

angular.module('Platform')
	.factory('levelObjects', ['platformFactory', 'playerFactory', 'moneyFactory'
		, function (platformFactory, ballFactory, moneyFactory) {

		return new function () {
			var t = this;
			t._objects = [];
			t._player = null;
			t._floor = null;
			t.addPlayer = function(el, config) {
				t._player = ballFactory.create(el, config);
			};
			t.addPlatform = function(el, config) {
				t._objects.push(platformFactory.create(el, config));
			};
			t.addMoney = function (el, config) {
				t._objects.push(moneyFactory.create(el, config));
			};
			t.setFloor = function(el) {
				t._floor = el;
			};
		};
	}]);

I have created the classic “press start” screen.
The start button is the bootstrap of the game.

To do the “start screen” I have simply created a <start> directive

angular.module('Platform.directives')
	.directive('start', ['screenService', 'physicService', 'collideService', 'levelObjects'
		, function (screenService, physicService, collideService, levelObjects) {
		return {
			restrict: 'E',
			replace: true,
			scope: {
			},
			templateUrl: '/AppJs/Platform/views/directives/startDirective.html',
			link: function ($scope) {
				$scope.isStarted = false;
				$scope.start = function () {
					collideService.init(levelObjects);
					screenService.run();
					physicService.activate();
					
					$scope.isStarted = true;
				};

			}
		};
	}]);

Once pressed “start” I could initialize safely my services: collideService, screenService, physicService.

platform-start

I have to admit that the look and feel is not astonishing, but as first try is enough.

platform-screen

The collide service had to understand about “money” and “platforms” in order to block the movement or to broadcast the event to update the score.
Honestly I think that my physics services are far far away from a very reliable algorithm, but part of the fun was to not use external libraries to manage the physics.

I can use this angular test as excuse to learn a bit more about this fascinating field.
If you are interested in the code, here there is the source.

In the next weeks I’ll try to improve the physics (collisions, gravity, etc etc.) and to introduce monsters and more details as possible (hopefully!)

p.s. Marco promised me to give me a few hints on the subject!

solution-platform

Advertisements

One thought on “A platform game (more or less) in angularjs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s