From e607bf667e07399234ff919aec37e2227c89940d Mon Sep 17 00:00:00 2001 From: Florent Cailhol Date: Sun, 18 Aug 2013 21:06:49 +0200 Subject: [PATCH] Add engine and build system --- .gitignore | 2 + Gruntfile.js | 72 +++++++++++++++++++++++++ package.json | 14 +++++ src/engine.js | 8 +++ src/engine/entities.js | 118 +++++++++++++++++++++++++++++++++++++++++ src/engine/input.js | 63 ++++++++++++++++++++++ src/engine/systems.js | 46 ++++++++++++++++ src/game.js | 8 +++ src/game/components.js | 17 ++++++ src/game/main.js | 15 ++++++ src/game/systems.js | 26 +++++++++ src/index.dev.html | 23 ++++++++ src/index.html | 15 ++++++ 13 files changed, 427 insertions(+) create mode 100644 .gitignore create mode 100644 Gruntfile.js create mode 100644 package.json create mode 100644 src/engine.js create mode 100644 src/engine/entities.js create mode 100644 src/engine/input.js create mode 100644 src/engine/systems.js create mode 100644 src/game.js create mode 100644 src/game/components.js create mode 100644 src/game/main.js create mode 100644 src/game/systems.js create mode 100644 src/index.dev.html create mode 100644 src/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1eae0cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..949d836 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,72 @@ +module.exports = function(grunt) { + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + clean: { + build: ['dist/'] + }, + jshint: { + files: ['Gruntfile.js', 'src/**/*.js'] + }, + watch: { + files: ['<%= jshint.files %>'], + tasks: ['dev'] + }, + compress: { + dist: { + options: { + archive: 'dist/pixelwars.zip', + mode: 'zip' + }, + files: [ + {expand: true, cwd: 'dist/', src: ['*.min.js']}, + {expand: true, cwd: 'dist/', src: ['index.html']} + ] + } + }, + concat: { + dist: { + files: { + 'dist/engine.js': ['src/engine.js', 'src/engine/*.js'], + 'dist/game.js': ['src/game.js', 'src/game/*.js'] + } + } + }, + uglify: { + dist: { + options: { + hoist_funs: false, + }, + files: { + 'dist/engine.min.js': ['dist/engine.js'], + 'dist/game.min.js': ['dist/game.js'] + } + } + }, + htmlmin: { + dist: { + options: { + collapseWhitespace: true, + removeComments: true + }, + files: { + 'dist/index.html': 'src/index.html' + } + } + } + }); + + grunt.loadNpmTasks('grunt-contrib-clean'); + grunt.loadNpmTasks('grunt-contrib-compress'); + grunt.loadNpmTasks('grunt-contrib-concat'); + grunt.loadNpmTasks('grunt-contrib-htmlmin'); + grunt.loadNpmTasks('grunt-contrib-jshint'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-watch'); + + grunt.registerTask('dev', ['jshint']); + grunt.registerTask('package', ['jshint', 'clean', 'concat', 'uglify', 'htmlmin', 'compress']); + + grunt.registerTask('default', ['package']); + +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..40c99ef --- /dev/null +++ b/package.json @@ -0,0 +1,14 @@ +{ + "name": "pixelwars", + "version": "0.1.0", + "devDependencies": { + "grunt": "~0.4", + "grunt-contrib-clean": "~0.5", + "grunt-contrib-compress": "~0.5", + "grunt-contrib-concat": "~0.3", + "grunt-contrib-htmlmin": "~0.1", + "grunt-contrib-jshint": "~0.6", + "grunt-contrib-uglify": "~0.2", + "grunt-contrib-watch": "~0.5" + } +} diff --git a/src/engine.js b/src/engine.js new file mode 100644 index 0000000..d1f4a65 --- /dev/null +++ b/src/engine.js @@ -0,0 +1,8 @@ +(function(global) { + 'use strict'; + + global.engine = { + debug: false + }; + +})(window); diff --git a/src/engine/entities.js b/src/engine/entities.js new file mode 100644 index 0000000..f3239e8 --- /dev/null +++ b/src/engine/entities.js @@ -0,0 +1,118 @@ +(function (engine) { + 'use strict'; + + var currentId = 0; + var entities = {}; + var entitiesToComponents = {}; + var componentsToEntities = {}; + + /** + * Computes the intersection of two arrays. + * + * @param {array} The array with master values to check + * @param {array} An array to compare values against + * @return {array} + */ + var intersect = function(a, b) { + var results = []; + + var i = 0; + var n = a.length; + + for (; i < n; i++) { + if (b.indexOf(a[i]) >= 0) { + results.push(a[i]); + } + } + + return results; + }; + + /** + * Manage entities. + */ + var EntityManager = { + /** + * Create a new entity. + * + * @param {String} tag + * @return {int} entity + */ + create: function(name) { + var id = currentId++; + var tag = '$' + (name || id); + + entities[id] = tag; + entitiesToComponents[id] = {}; + + return id; + }, + /** + * Kill the specified entity. + * + * @param {int} entity + */ + kill: function(entity) { + var components = Object.keys(entitiesToComponents[entity]); + var component; + for (component in components) { + EntityManager.remove(entity, component); + } + + delete entities[id]; + }, + /** + * Add a component to the specified entity. + * + * @param {int} entity + * @param {Object} component + * @param {String} component name + */ + add: function(entity, component, name) { + entitiesToComponents[entity][name] = component; + + if (!componentsToEntities[name]) { + componentsToEntities[name] = []; + } + + componentsToEntities[name][entity] = true; + }, + /** + * Remove a component from the specified entity. + * + * @param {int} entity + * @param {String} component name + */ + remove: function(entity, name) { + delete entitiesToComponents[entity][name]; + delete componentsToEntities[name][entity]; + }, + /** + * Get a component from the specified entity. + * + * @param {int} entity + * @param {String} component name + * @return {Object} component + */ + get: function(entity, name) { + return entitiesToComponents[entity][name]; + }, + /** + * Get entities with specified components. + * + * @return {int[]} entities + */ + filter: function() { + var entities = Object.keys(componentsToEntities[arguments[0]] || {}); + + for (var i = 0; i < arguments.length; i++) { + entities = intersect(entities, Object.keys(componentsToEntities[arguments[i]] || {})); + } + + return entities; + } + }; + + engine.EntityManager = EntityManager; + +})(engine); diff --git a/src/engine/input.js b/src/engine/input.js new file mode 100644 index 0000000..3c4764a --- /dev/null +++ b/src/engine/input.js @@ -0,0 +1,63 @@ +(function (engine, document) { + 'use strict'; + + var capture = []; + var active = {}; + + /** + * Manage keyboard events. + */ + var Input = { + /** + * Specify keys to capture. + * Capturing a key stops the default behavior. + * + * @param {int[]} keys + */ + capture: function(keys) { + capture = keys; + }, + /** + * Get the state of the specified key. + * + * @param {int} code + * @return {Boolean} + */ + key: function(code) { + return !!active[code]; + } + }; + + /** + * Bind a listener on `document`. + * + * @param {String} event name + * @param {Function} listener + */ + var bind = function(name, listener) { + document.addEventListener(name, listener, false); + }; + + // Listen `keypress` events + bind('keypress', function(e) { + var key = e.keyCode; + if (capture.indexOf(key) >= 0) { + e.preventDefault(); + } + + active[key] = true; + }); + + // Listen `keyup` events + bind('keyup', function(e) { + var key = e.keyCode; + if (capture.indexOf(key) >= 0) { + e.preventDefault(); + } + + active[key] = false; + }); + + engine.Input = Input; + +})(engine, document); diff --git a/src/engine/systems.js b/src/engine/systems.js new file mode 100644 index 0000000..475ffa5 --- /dev/null +++ b/src/engine/systems.js @@ -0,0 +1,46 @@ +(function(engine, window) { + 'use strict'; + + var systems = []; + var requestID = -1; + + var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame; + var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; + + var SystemManager = { + register: function(system) { + systems.push(system); + }, + start: function() { + if (requestID === -1) { + var previousTime = +new Date(); + var update = function(timestamp) { + var tmp = previousTime; + previousTime = +new Date(); + SystemManager.update((previousTime - tmp) / 1000); + requestID = requestAnimationFrame(update); + }; + + requestID = requestAnimationFrame(update); + } + }, + stop: function() { + if (requestID !== -1) { + cancelAnimationFrame(requestID); + requestID = -1; + } + }, + update: function(elapsed) { + var i = 0; + var n = systems.length; + + for (; i < n; i++) { + systems[i].update(elapsed); + } + } + }; + + engine.SystemManager = SystemManager; + +})(engine, window); diff --git a/src/game.js b/src/game.js new file mode 100644 index 0000000..f5e4ae2 --- /dev/null +++ b/src/game.js @@ -0,0 +1,8 @@ +(function(global) { + 'use strict'; + + global.game = { + debug: false + }; + +})(window); diff --git a/src/game/components.js b/src/game/components.js new file mode 100644 index 0000000..b8fd912 --- /dev/null +++ b/src/game/components.js @@ -0,0 +1,17 @@ +(function (game) { + 'use strict'; + + function Position(x, y) { + this.x = x; + this.y = y; + } + + function Motion(dx, dy) { + this.dx = dx; + this.dy = dy; + } + + game.Position = Position; + game.Motion = Motion; + +})(game); diff --git a/src/game/main.js b/src/game/main.js new file mode 100644 index 0000000..ca02374 --- /dev/null +++ b/src/game/main.js @@ -0,0 +1,15 @@ +(function (game, engine) { + 'use strict'; + + var SystemManager = engine.SystemManager; + + game.Pixelwars = { + init: function(canvas) { + SystemManager.register(new game.MovementSystem()); + }, + run: function() { + SystemManager.start(); + } + }; + +})(game, engine); diff --git a/src/game/systems.js b/src/game/systems.js new file mode 100644 index 0000000..06f0ec0 --- /dev/null +++ b/src/game/systems.js @@ -0,0 +1,26 @@ +(function (game, engine) { + 'use strict'; + + var EntityManager = engine.EntityManager; + var Position = game.Position; + var Motion = game.Motion; + + function MovementSystem() { + this.update = function(elapsed) { + var entities = EntityManager.filter(Position.name, Motion.name); + var i = 0; + var n = entities.length; + + for (; i < n; i++) { + var position = EntityManager.get(entities[i], Position.name); + var motion = EntityManager.get(entities[i], Motion.name); + + position.x = elapsed * motion.dx; + position.y = elapsed * motion.dy; + } + }; + } + + game.MovementSystem = MovementSystem; + +})(game, engine); diff --git a/src/index.dev.html b/src/index.dev.html new file mode 100644 index 0000000..2d9bd2a --- /dev/null +++ b/src/index.dev.html @@ -0,0 +1,23 @@ + + + + Pixel Wars + + + + + + + + + + + + + + + + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..6e2e5b9 --- /dev/null +++ b/src/index.html @@ -0,0 +1,15 @@ + + + + Pixel Wars + + + + + + + +