From 7ff89a21b93f50d67b51ecd4e73d94f6768743ff Mon Sep 17 00:00:00 2001 From: Nathan Kunicki Date: Mon, 21 Dec 2015 01:18:04 +0000 Subject: [PATCH] Entities, keyboard input, vector2d class, pong-raw example start --- .gitignore | 1 + examples/pong-raw/index.html | 11 +++ examples/pong-raw/pong.js | 129 +++++++++++++++++++++++++ gulpfile.js | 12 ++- package.json | 11 ++- src/classes/entity.js | 64 +++++++++++++ src/classes/game.js | 181 +++++++++++++++++++++++++++++++++++ src/classes/keyboardinput.js | 140 +++++++++++++++++++++++++++ src/classes/vector2d.js | 112 ++++++++++++++++++++++ src/index.js | 16 +++- 10 files changed, 669 insertions(+), 8 deletions(-) create mode 100644 examples/pong-raw/index.html create mode 100644 examples/pong-raw/pong.js create mode 100644 src/classes/entity.js create mode 100644 src/classes/game.js create mode 100644 src/classes/keyboardinput.js create mode 100644 src/classes/vector2d.js 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