diff --git a/.gitignore b/.gitignore
index d6c44a9..8683cf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.idea/
+.DS_Store
dist/
node_modules/
\ No newline at end of file
diff --git a/examples/pong-raw/index.html b/examples/pong-raw/index.html
new file mode 100644
index 0000000..2ad831c
--- /dev/null
+++ b/examples/pong-raw/index.html
@@ -0,0 +1,11 @@
+
+
+
+ Pong - MomentumEngine
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/pong-raw/pong.js b/examples/pong-raw/pong.js
new file mode 100644
index 0000000..b305cb7
--- /dev/null
+++ b/examples/pong-raw/pong.js
@@ -0,0 +1,129 @@
+"use strict";
+
+window.onload = function () {
+
+ var pong = new MomentumEngine.Game({
+ canvas: document.getElementById("game"),
+ width: 640,
+ height: 360,
+ fixRatio: true,
+ desiredFps: 60,
+ inputs: {
+ keyboard: true
+ }
+ });
+
+
+ var multiply = pong.width / 640;
+
+
+ // All of these are instances of MomentumEngine.Entity;
+ var mainScene = pong.createChildEntity(),
+ ball = mainScene.createChildEntity(),
+ leftPaddle = mainScene.createChildEntity(),
+ rightPaddle = mainScene.createChildEntity();
+
+
+ // Wipe the screen
+ pong.render = function () {
+ pong.context.fillStyle = "rgba(0, 0, 0, 1)";
+ pong.context.fillRect(0, 0, pong.width, pong.height);
+ return true;
+ };
+
+
+ // Update and render the ball
+ ball.state.pos = new MomentumEngine.Vector2D(pong.width / 2, pong.height / 2); // Current ball position;
+ ball.state.width = multiply * 10;
+ ball.state.height = multiply * 10;
+ ball.state.vector = new MomentumEngine.Vector2D(0.1, 0.05); // Current ball vector
+
+ ball.update = function () {
+
+ this.state.pos.add(this.state.vector.clone().multiply(pong.lastFrameDelta));
+
+ if ((this.state.pos.x + 5 > pong.width) || (this.state.pos.x - 5 < 0)) {
+ //this.state.vector.multiply(1.1);
+ this.state.vector.x = -this.state.vector.x;
+ }
+
+ if ((this.state.pos.y + 5 > pong.height) || (this.state.pos.y - 5 < 0)) {
+ //this.state.vector.multiply(1.1);
+ this.state.vector.y = -this.state.vector.y;
+ }
+
+ };
+
+ ball.render = function () {
+ pong.context.fillStyle = "rgba(255, 255, 255, 1)";
+ pong.context.fillRect(ball.state.pos.x - (ball.state.width / 2), ball.state.pos.y - (ball.state.height / 2), ball.state.width, ball.state.height);
+ };
+
+
+ // Update and render the left paddle
+ leftPaddle.state.left = multiply * 10;
+ leftPaddle.state.top = multiply * 10;
+ leftPaddle.state.width = multiply * 10;
+ leftPaddle.state.height = multiply * 70;
+
+ leftPaddle.update = function () {
+
+ if (pong.inputs.keyboard.isPressed(pong.consts.keys.CHAR_Q) || pong.inputs.keyboard.isPressed(pong.consts.keys.UP)) {
+ leftPaddle.state.top -= (0.5 * pong.lastFrameDelta);
+ }
+
+ if (pong.inputs.keyboard.isPressed(pong.consts.keys.CHAR_A) || pong.inputs.keyboard.isPressed(pong.consts.keys.DOWN)) {
+ leftPaddle.state.top += (0.5 * pong.lastFrameDelta);
+ }
+
+ if (leftPaddle.state.top > pong.height - (multiply * 80)) {
+ leftPaddle.state.top = pong.height - (multiply * 80);
+ }
+
+ if (leftPaddle.state.top < (multiply * 10)) {
+ leftPaddle.state.top = (multiply * 10);
+ }
+
+ };
+
+ leftPaddle.render = function () {
+ pong.context.fillStyle = "rgba(255, 255, 255, 1)";
+ pong.context.fillRect(leftPaddle.state.left, leftPaddle.state.top, leftPaddle.state.width, leftPaddle.state.height);
+ };
+
+
+ // Render the right paddle
+ rightPaddle.state.left = pong.width - (multiply * 10 * 2);
+ rightPaddle.state.top = multiply * 10;
+ rightPaddle.state.width = multiply * 10;
+ rightPaddle.state.height = multiply * 70;
+
+ rightPaddle.update = function () {
+
+ if (pong.inputs.keyboard.isPressed(pong.consts.keys.CHAR_O)) {
+ rightPaddle.state.top -= (0.5 * pong.lastFrameDelta);
+ }
+
+ if (pong.inputs.keyboard.isPressed(pong.consts.keys.CHAR_L)) {
+ rightPaddle.state.top += (0.5 * pong.lastFrameDelta);
+ }
+
+ if (rightPaddle.state.top > pong.height - (multiply * 80)) {
+ rightPaddle.state.top = pong.height - (multiply * 80);
+ }
+
+ if (rightPaddle.state.top < (multiply * 10)) {
+ rightPaddle.state.top = (multiply * 10);
+ }
+
+ };
+
+ rightPaddle.render = function () {
+ pong.context.fillStyle = "rgba(255, 255, 255, 1)";
+ pong.context.fillRect(rightPaddle.state.left, rightPaddle.state.top, rightPaddle.state.width, rightPaddle.state.height);
+ };
+
+
+ pong.start();
+
+};
\ No newline at end of file
diff --git a/gulpfile.js b/gulpfile.js
index 0bf16e8..ac94542 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -8,7 +8,7 @@ var gulp = require("gulp"),
var build = function (options, callback) {
- var plugins = [];
+ let plugins = [];
if (options.minify) {
plugins = [
@@ -32,7 +32,7 @@ var build = function (options, callback) {
plugins: plugins,
output: {
path: path.join(__dirname, "dist"),
- filename: "index.js"
+ filename: "es5.js"
},
module: {
loaders: [{
@@ -40,14 +40,18 @@ var build = function (options, callback) {
test: /\.js$/,
include: [
path.join(__dirname, "src")
- ]
+ ],
+ query: {
+ plugins: ["transform-runtime"],
+ presets: ["es2015", "stage-0"]
+ }
}]
}
}, (error, stats) => {
if (error) {
- var pluginError = new gutil.PluginError("webpack", error);
+ let pluginError = new gutil.PluginError("webpack", error);
if (callback) {
callback(pluginError);
diff --git a/package.json b/package.json
index 6f9cb2c..7a70f41 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "momentumengine",
"version": "0.0.1",
"description": "An ES6 game and animation engine.",
- "main": "dist/index.js",
+ "main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@@ -14,8 +14,13 @@
"devDependencies": {
"babel-core": "6.3.17",
"babel-loader": "6.2.0",
+ "babel-plugin-transform-runtime": "6.3.13",
+ "babel-preset-es2015": "6.3.13",
+ "babel-preset-stage-0": "6.3.13",
+ "babel-runtime": "6.3.19",
"gulp": "3.9.0",
"gulp-util": "3.0.7",
"webpack": "1.12.9"
- }
-}
\ No newline at end of file
+ },
+ "dependencies": {}
+}
diff --git a/src/classes/entity.js b/src/classes/entity.js
new file mode 100644
index 0000000..49f13fe
--- /dev/null
+++ b/src/classes/entity.js
@@ -0,0 +1,64 @@
+"use strict";
+
+class Entity {
+
+
+ constructor () {
+ this.state = {};
+ this.children = [];
+ }
+
+
+ createChildEntity () {
+
+ let child = new Entity();
+ this.children.push(child);
+
+ return child;
+
+ }
+
+
+ addChildEntity (entity) {
+
+ this.children.push(entity);
+ return entity;
+
+ }
+
+
+ detachChildEntity (entity) {
+ // Not implemented
+ }
+
+
+ _updateEntity () {
+
+ if ((this.update && this.update()) || (typeof this.update === "undefined")) {
+
+ this.children.forEach((child) => {
+ child._updateEntity();
+ });
+
+ }
+
+ }
+
+
+ _renderEntity () {
+
+ if ((this.render && this.render()) || (typeof this.render === "undefined")) {
+
+ this.children.forEach((child) => {
+ child._renderEntity();
+ });
+
+ }
+
+ }
+
+
+}
+
+
+export default Entity;
\ No newline at end of file
diff --git a/src/classes/game.js b/src/classes/game.js
new file mode 100644
index 0000000..7284a39
--- /dev/null
+++ b/src/classes/game.js
@@ -0,0 +1,181 @@
+"use strict";
+
+import Entity from "./entity.js";
+import KeyboardInput, {keyConsts} from "./keyboardinput.js";
+
+class Game extends Entity {
+
+
+ constructor (config) {
+
+ super(); // Call entity constructor
+ config = config || {};
+ config.inputs = config.inputs || {};
+
+
+ // Required params
+ if (config.canvas) {
+ this.canvas = config.canvas;
+ } else {
+ throw new Error("MomentumEngine.Game must be constructed with a canvas");
+ }
+
+ if (config.width) {
+ this.width = config.width;
+ } else {
+ throw new Error("MomentumEngine.Game must be constructed with canvas width");
+ }
+
+ if (config.height) {
+ this.height = config.height;
+ } else {
+ throw new Error("MomentumEngine.Game must be constructed with canvas height");
+ }
+
+
+ // Optional params
+ this.desiredFps = config.desiredFps || 60;
+
+ if (config.fixRatio) {
+
+ let deviceRatio = window.devicePixelRatio,
+ backingStoreRatio = 0;
+
+ // Unfortunately Ejecta requires calling getContext last, so we cannot get the backingStorePixelRatio. So for Ejecta's case, we set it to 1, and call getContext later.
+ if (typeof ejecta !== "undefined") {
+ backingStoreRatio = 1;
+ } else {
+
+ this.context = this.canvas.getContext("2d");
+
+ backingStoreRatio = this.context.webkitBackingStorePixelRatio ||
+ this.context.mozBackingStorePixelRatio ||
+ this.context.msBackingStorePixelRatio ||
+ this.context.oBackingStorePixelRatio ||
+ this.context.backingStorePixelRatio || 1;
+
+ }
+
+ this.scale = deviceRatio / backingStoreRatio;
+
+ this.canvas.width = this.width * this.scale;
+ this.canvas.height = this.height * this.scale;
+
+ this.canvas.style.width = this.width + "px";
+ this.canvas.style.height = this.height + "px";
+
+ // Call getContext last for Ejecta only.
+ if (typeof ejecta !== "undefined") {
+ this.context = this.canvas.getContext("2d");
+ }
+
+ this.context.scale(deviceRatio, deviceRatio);
+
+ } else {
+
+ this.canvas.width = this.width;
+ this.canvas.height = this.height;
+
+ this.context = this.canvas.getContext("2d");
+
+ }
+
+ if (typeof this.context.imageSmoothingEnabled !== "undefined") {
+ this.context.imageSmoothingEnabled = config.imageSmoothing || false;
+ }
+
+
+ // Initialize defaults
+ this.lastFrameDelta = 0;
+ this.frameCounter = 0;
+
+ this.consts = {
+ keys: keyConsts
+ };
+
+ this.inputs = {};
+ if (config.inputs.keyboard) {
+ this.inputs.keyboard = new KeyboardInput(this);
+ }
+
+ this._lastFrameTimestamp = 0;
+ this._wantPause = true;
+
+ }
+
+
+ start () {
+
+ var self = this; // NK: Hate doing this...better way plz?
+
+ if (self._wantPause) {
+ self._wantPause = false;
+ } else {
+ console.log("MomentumEngine.Game.start called, game instance is already started");
+ return false; // Game is already running
+ }
+
+ self._wantPause = false;
+
+ var requestFrame = (function () {
+
+ return (window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ function (callback) {
+ window.setTimeout(callback, 1000 / self.desiredFps);
+ });
+
+ })();
+
+ self._lastFrameTimestamp = +(new Date());
+
+ var loop = function () {
+
+ self.frameCounter++;
+
+ let currentTimestamp = +(new Date());
+
+ self.lastFrameDelta = currentTimestamp - self._lastFrameTimestamp;
+ self._lastFrameTimestamp = currentTimestamp;
+
+ if (self.lastFrameDelta > (1000 / self.desiredFps)) {
+ self.lastFrameDelta = (1000 / self.desiredFps);
+ }
+
+ if (self._wantPause) {
+ return;
+ }
+
+ self._updateEntity.bind(self);
+ self._updateEntity();
+
+ self._renderEntity.bind(self);
+ self._renderEntity();
+
+ requestFrame(loop);
+
+ };
+
+ loop();
+ return true;
+
+ }
+
+
+ pause () {
+
+ if (!this._wantPause) {
+ this._wantPause = true;
+ return true;
+ } else {
+ console.log("MomentumEngine.Game.pause called, game instance is already paused");
+ return false;
+ }
+ }
+
+
+}
+
+
+export default Game;
\ No newline at end of file
diff --git a/src/classes/keyboardinput.js b/src/classes/keyboardinput.js
new file mode 100644
index 0000000..0a356cf
--- /dev/null
+++ b/src/classes/keyboardinput.js
@@ -0,0 +1,140 @@
+"use strict";
+
+
+const keyConsts = {
+ SPACE: 32,
+ BACKSPACE: 8,
+ TAB: 9,
+ ENTER: 13,
+ SHIFT: 16,
+ CTRL: 17,
+ ALT: 18,
+ PAUSE: 19,
+ CAPS_LOCK: 20,
+ ESCAPE: 27,
+ PAGE_UP: 33,
+ PAGE_DOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40,
+ INSERT: 45,
+ DELETE: 46,
+ NUM_0: 48,
+ NUM_1: 49,
+ NUM_2: 50,
+ NUM_3: 51,
+ NUM_4: 52,
+ NUM_5: 53,
+ NUM_6: 54,
+ NUM_7: 55,
+ NUM_8: 56,
+ NUM_9: 57,
+ CHAR_A: 65,
+ CHAR_B: 66,
+ CHAR_C: 67,
+ CHAR_D: 68,
+ CHAR_E: 69,
+ CHAR_F: 70,
+ CHAR_G: 71,
+ CHAR_H: 72,
+ CHAR_I: 73,
+ CHAR_J: 74,
+ CHAR_K: 75,
+ CHAR_L: 76,
+ CHAR_M: 77,
+ CHAR_N: 78,
+ CHAR_O: 79,
+ CHAR_P: 80,
+ CHAR_Q: 81,
+ CHAR_R: 82,
+ CHAR_S: 83,
+ CHAR_T: 84,
+ CHAR_U: 85,
+ CHAR_V: 86,
+ CHAR_W: 87,
+ CHAR_X: 88,
+ CHAR_Y: 89,
+ CHAR_Z: 90,
+ NUM_PAD_0: 96,
+ NUM_PAD_1: 97,
+ NUM_PAD_2: 98,
+ NUM_PAD_3: 99,
+ NUM_PAD_4: 100,
+ NUM_PAD_5: 101,
+ NUM_PAD_6: 102,
+ NUM_PAD_7: 103,
+ NUM_PAD_8: 104,
+ NUM_PAD_9: 105,
+ MULTIPLY: 106,
+ ADD: 107,
+ SUBTRACT: 109,
+ DECIMAL: 110,
+ DIVIDE: 111,
+ F1: 112,
+ F2: 113,
+ F3: 114,
+ F4: 115,
+ F5: 116,
+ F6: 117,
+ F7: 118,
+ F8: 119,
+ F9: 120,
+ F10: 121,
+ F11: 122,
+ F12: 123,
+ SEMICOLON: 186,
+ EQUALS: 187,
+ COMMA: 188,
+ DASH: 189,
+ PERIOD: 190,
+ FORWARD_SLASH: 191,
+ GRAVE: 192,
+ OPEN_BRACKET: 219,
+ BACK_SLASH: 220,
+ CLOSE_BRACKET: 221,
+ SINGLE_QUOTE: 222
+};
+
+
+class KeyboardInput {
+
+
+ constructor () {
+
+ var self = this;
+ self._keyState = {};
+
+ window.addEventListener("keydown", (event) => {
+ self._keyDownHandler(event);
+ }, false);
+
+ window.addEventListener("keyup", (event) => {
+ self._keyUpHandler(event);
+ }, false);
+
+ }
+
+
+ isPressed (keyCode) {
+ return !!this._keyState[keyCode];
+ }
+
+
+ _keyDownHandler (event) {
+ this._keyState[event.keyCode] = true;
+ }
+
+
+ _keyUpHandler (event) {
+ this._keyState[event.keyCode] = false;
+ }
+
+
+}
+
+
+export default KeyboardInput;
+export {keyConsts};
\ No newline at end of file
diff --git a/src/classes/vector2d.js b/src/classes/vector2d.js
new file mode 100644
index 0000000..359ec6d
--- /dev/null
+++ b/src/classes/vector2d.js
@@ -0,0 +1,112 @@
+"use strict";
+
+class Vector2D {
+
+
+ constructor (x, y) {
+ this.x = x || 0; this.y = y || 0;
+ }
+
+
+ invert () {
+ this.x = -this.x; this.y = -this.y;
+ return this;
+ }
+
+
+ add (val) {
+
+ if (val instanceof Vector2D) {
+ this.x += val.x; this.y += val.y;
+ } else {
+ this.x += val; this.y += val;
+ }
+
+ return this;
+
+ }
+
+
+ subtract (val) {
+
+ if (val instanceof Vector2D) {
+ this.x -= val.x; this.y -= val.y;
+ } else {
+ this.x -= val; this.y -= val;
+ }
+
+ return this;
+
+ }
+
+
+ multiply (val) {
+
+ if (val instanceof Vector2D) {
+ this.x *= val.x; this.y *= val.y;
+ } else {
+ this.x *= val; this.y *= val;
+ }
+
+ return this;
+
+ }
+
+
+ divide (val) {
+
+ if (val instanceof Vector2D) {
+ this.x /= val.x; this.y /= val.y;
+ } else {
+ this.x /= val; this.y /= val;
+ }
+
+ return this;
+
+ }
+
+
+ equals (val) {
+ return (this.x == val.x && this.y == val.y);
+ }
+
+
+ dot (val) {
+ return this.x * val.x + this.y * val.y;
+ }
+
+
+ length () {
+ return Math.sqrt(this.dot(this));
+ }
+
+
+ unit () {
+ return this.divide(this.length());
+ }
+
+
+ min () {
+ return Math.min(this.x, this.y);
+ }
+
+
+ max () {
+ return Math.max(this.x, this.y);
+ }
+
+
+ toArray () {
+ return [this.x, this.y];
+ }
+
+
+ clone () {
+ return new Vector2D(this.x, this.y);
+ }
+
+
+}
+
+
+export default Vector2D;
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 635625a..c320e78 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1 +1,15 @@
-console.log("Hello world!");
\ No newline at end of file
+"use strict";
+
+import Game from "./classes/game.js";
+import Entity from "./classes/entity.js";
+import Vector2D from "./classes/vector2d.js";
+
+window.MomentumEngine = {
+ Game: Game,
+ Entity: Entity,
+ Vector2D: Vector2D
+};
+
+export {Game};
+export {Entity};
+export {Vector2D};
\ No newline at end of file