diff --git a/examples/pong-raw/pong.js b/examples/pong-raw/pong.js deleted file mode 100644 index b305cb7..0000000 --- a/examples/pong-raw/pong.js +++ /dev/null @@ -1,129 +0,0 @@ -"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/examples/pong-raw/index.html b/examples/pong/index.html similarity index 100% rename from examples/pong-raw/index.html rename to examples/pong/index.html diff --git a/examples/pong/pong.js b/examples/pong/pong.js new file mode 100644 index 0000000..80dee6a --- /dev/null +++ b/examples/pong/pong.js @@ -0,0 +1,119 @@ +"use strict"; + +window.onload = function () { + + + var KeyConsts = MomentumEngine.Consts.Input.Keys; + + + var width = 640, + height = 360, + baseSize = width / 64; + + + var pong = new MomentumEngine.Classes.Game({ + canvas: document.getElementById("game"), + width: width, + height: height, + fixRatio: true, + desiredFps: 60, + inputs: { + keyboard: true + } + }); + + + // Colors + var white = new MomentumEngine.Classes.Color(255, 255, 255), + black = new MomentumEngine.Classes.Color(0, 0, 0); + + + // All of these are instances of MomentumEngine.Entity; + var mainScene = new MomentumEngine.Classes.Rect(0, 0, width, height, black), + ball = new MomentumEngine.Classes.Rect((width / 2) - (baseSize / 2), (height / 2) - (baseSize / 2), baseSize, baseSize, white), + leftPaddle = new MomentumEngine.Classes.Rect(baseSize, baseSize, baseSize, baseSize * 7, white), + rightPaddle = new MomentumEngine.Classes.Rect(width - (baseSize * 2), baseSize, baseSize, baseSize * 7, white); + + + // Create scene graph + pong.addChildEntity(mainScene); + mainScene.addChildEntity(ball); + mainScene.addChildEntity(leftPaddle); + mainScene.addChildEntity(rightPaddle); + + + // Update and render the ball + ball.state.speed = new MomentumEngine.Classes.Vector2D(0.1, 0.05); // Current ball speed + + ball.update = function () { + + this.pos.add(this.state.speed.clone().multiply(pong.lastFrameDelta)); + + if ((this.pos.x + baseSize > width) || (this.pos.x < 0)) { + this.state.speed.x = -this.state.speed.x; + } + + if ((this.pos.y + baseSize > height) || (this.pos.y < 0)) { + this.state.speed.y = -this.state.speed.y; + } + + }; + + + // Update and render the left paddle + leftPaddle.update = function () { + + if (pong.inputs.keyboard.isPressed(KeyConsts.CHAR_Q) || pong.inputs.keyboard.isPressed(KeyConsts.UP)) { + leftPaddle.pos.y -= (0.5 * pong.lastFrameDelta); + } + + if (pong.inputs.keyboard.isPressed(KeyConsts.CHAR_A) || pong.inputs.keyboard.isPressed(KeyConsts.DOWN)) { + leftPaddle.pos.y += (0.5 * pong.lastFrameDelta); + } + + if (leftPaddle.pos.y > height - (baseSize * 8)) { + leftPaddle.pos.y = height - (baseSize * 8); + } + + if (leftPaddle.pos.y < baseSize) { + leftPaddle.pos.y = baseSize; + } + + if (leftPaddle.isColliding(ball) && ball.state.speed.x < 0) { + ball.state.speed.x = -ball.state.speed.x; + console.log(ball.state.speed.length()); + } + + }; + + + // Render the right paddle + rightPaddle.update = function () { + + if (pong.inputs.keyboard.isPressed(KeyConsts.CHAR_O)) { + rightPaddle.pos.y -= (0.5 * pong.lastFrameDelta); + } + + if (pong.inputs.keyboard.isPressed(KeyConsts.CHAR_L)) { + rightPaddle.pos.y += (0.5 * pong.lastFrameDelta); + } + + if (rightPaddle.pos.y > height - (baseSize * 8)) { + rightPaddle.pos.y = height - (baseSize * 8); + } + + if (rightPaddle.pos.y < baseSize) { + rightPaddle.pos.y = baseSize; + } + + if (rightPaddle.isColliding(ball) && ball.state.speed.x > 0) { + ball.state.speed.x = -ball.state.speed.x; + } + + }; + + + pong.start(); + + +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index ac94542..e42da1b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,14 +25,16 @@ var build = function (options, callback) { } webpack({ - entry: path.join(__dirname, "src", "index.js"), + entry: { + "es5": path.join(__dirname, "src", "es5.js") + }, bail: !options.watch, watch: options.watch, devtool: "source-map", plugins: plugins, output: { path: path.join(__dirname, "dist"), - filename: "es5.js" + filename: "[name].js" }, module: { loaders: [{ diff --git a/package.json b/package.json index 7a70f41..cc7c0b7 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": "src/index.js", + "main": "src/es6.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/src/classes/color.js b/src/classes/color.js new file mode 100644 index 0000000..784d960 --- /dev/null +++ b/src/classes/color.js @@ -0,0 +1,27 @@ +class Color { + + + constructor (r, g, b, a) { + + this.r = r || 0; + this.g = g || 0; + this.b = b || 0; + this.a = a || 1; + + } + + + toString () { + return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`; + } + + + toHex () { + return `#${((r << 16) | (g << 8) | b).toString(16)}`; + } + + +} + + +export default Color; \ No newline at end of file diff --git a/src/classes/entity.js b/src/classes/entity.js index 49f13fe..d6cf91d 100644 --- a/src/classes/entity.js +++ b/src/classes/entity.js @@ -1,17 +1,31 @@ "use strict"; +import Vector2D from "./vector2d.js"; + class Entity { - constructor () { + constructor (x, y) { + + this.pos = new Vector2D(x || 0, y || 0); + this.state = {}; this.children = []; + + this._calculatedPos = this.pos.clone(); + this._lastCalculated = 0; + this._game = null; + this._parent = null; + } createChildEntity () { let child = new Entity(); + + child._updateGame(this._game); + child._parent = this; this.children.push(child); return child; @@ -19,10 +33,13 @@ class Entity { } - addChildEntity (entity) { + addChildEntity (child) { - this.children.push(entity); - return entity; + child._updateGame(this._game); + child._parent = this; + this.children.push(child); + + return child; } @@ -32,6 +49,45 @@ class Entity { } + _recalculatePos () { + + // NK: This should be called within "render", not "update". The purpose of this function is to calculate the true position of the entity relative to all its parents. It does this recursively, calling the _recalculatePos method all the way back up the tree and continuously adding the results together. + + // Note there is a limiter, where the last calculated frame is stored, so that if the position has already been calculated for that node in this particular frame, the cached result is used rather than recalculating. + + if (this._game && this._lastCalculated < this._game.frameCounter) { + + if (this._parent) { + + let parentPos = this._parent._recalculatePos(); + + this._calculatedPos.x = this.pos.x + parentPos.x; + this._calculatedPos.y = this.pos.y + parentPos.y; + } else { + this._calculatedPos.x = this.pos.x; + this._calculatedPos.y = this.pos.y; + } + + this._lastCalculated = this._game.frameCounter; + + } + + return this._calculatedPos; + + } + + + _updateGame (game) { + + this._game = game; + + this.children.forEach((child) => { + child._updateGame(game); + }); + + } + + _updateEntity () { if ((this.update && this.update()) || (typeof this.update === "undefined")) { diff --git a/src/classes/game.js b/src/classes/game.js index 7284a39..a745402 100644 --- a/src/classes/game.js +++ b/src/classes/game.js @@ -1,7 +1,7 @@ "use strict"; import Entity from "./entity.js"; -import KeyboardInput, {keyConsts} from "./keyboardinput.js"; +import KeyboardInput from "./keyboardinput.js"; class Game extends Entity { @@ -89,15 +89,12 @@ class Game extends Entity { this.lastFrameDelta = 0; this.frameCounter = 0; - this.consts = { - keys: keyConsts - }; - this.inputs = {}; if (config.inputs.keyboard) { this.inputs.keyboard = new KeyboardInput(this); } + this._game = this; this._lastFrameTimestamp = 0; this._wantPause = true; @@ -117,7 +114,7 @@ class Game extends Entity { self._wantPause = false; - var requestFrame = (function () { + var requestFrame = (() => { return (window.requestAnimationFrame || window.webkitRequestAnimationFrame || @@ -139,9 +136,7 @@ class Game extends Entity { self.lastFrameDelta = currentTimestamp - self._lastFrameTimestamp; self._lastFrameTimestamp = currentTimestamp; - if (self.lastFrameDelta > (1000 / self.desiredFps)) { - self.lastFrameDelta = (1000 / self.desiredFps); - } + self.lastFrameDelta = Math.min(self.lastFrameDelta, 1000 / self.desiredFps); if (self._wantPause) { return; diff --git a/src/classes/keyboardinput.js b/src/classes/keyboardinput.js index 0a356cf..855382b 100644 --- a/src/classes/keyboardinput.js +++ b/src/classes/keyboardinput.js @@ -1,7 +1,7 @@ "use strict"; -const keyConsts = { +const KeyConsts = { SPACE: 32, BACKSPACE: 8, TAB: 9, @@ -137,4 +137,4 @@ class KeyboardInput { export default KeyboardInput; -export {keyConsts}; \ No newline at end of file +export {KeyConsts}; \ No newline at end of file diff --git a/src/classes/rect.js b/src/classes/rect.js new file mode 100644 index 0000000..7a8ae92 --- /dev/null +++ b/src/classes/rect.js @@ -0,0 +1,52 @@ +"use strict"; + +import Entity from "./entity.js"; +import Vector2D from "./vector2d.js"; + +import CollisionMethods from "../libs/collisionmethods.js"; + + +class Rect extends Entity { + + + constructor (x, y, width, height, color) { + + super(x, y); + + this.size = new Vector2D(width, height); + this.color = color; + + } + + + isColliding (entity) { + + if (entity instanceof Rect) { + return CollisionMethods.AABB(this, entity); + } + + } + + + render () { + + this._recalculatePos(); + + if (this._game) { + + this._game.context.fillStyle = this.color; + this._game.context.fillRect(this._calculatedPos.x, this._calculatedPos.y, this.size.x, this.size.y); + + return true; + + } else { + return false; + } + + } + + +} + + +export default Rect; \ No newline at end of file diff --git a/src/classes/vector2d.js b/src/classes/vector2d.js index 359ec6d..507e22e 100644 --- a/src/classes/vector2d.js +++ b/src/classes/vector2d.js @@ -72,7 +72,7 @@ class Vector2D { dot (val) { - return this.x * val.x + this.y * val.y; + return (this.x * val.x + this.y * val.y); } @@ -96,11 +96,21 @@ class Vector2D { } + degrees () { + return (Math.atan(this.x, this.y) * 180); + } + + toArray () { return [this.x, this.y]; } + toString () { + return `[${this.x}},${this.y}}]`; + } + + clone () { return new Vector2D(this.x, this.y); } diff --git a/src/es5.js b/src/es5.js new file mode 100644 index 0000000..3d45d64 --- /dev/null +++ b/src/es5.js @@ -0,0 +1,31 @@ +"use strict"; + +import Game from "./classes/game.js"; +import Entity from "./classes/entity.js"; +import Vector2D from "./classes/vector2d.js"; +import Rect from "./classes/rect.js"; +import Color from "./classes/color.js"; + +import {KeyConsts} from "./classes/keyboardinput.js"; + + +const Classes = { + Game: Game, + Entity: Entity, + Rect: Rect, + Vector2D: Vector2D, + Color: Color +}; + + +const Consts = { + Input: { + Keys: KeyConsts + } +}; + + +window.MomentumEngine = { + Classes: Classes, + Consts: Consts +}; \ No newline at end of file diff --git a/src/es6.js b/src/es6.js new file mode 100644 index 0000000..2963499 --- /dev/null +++ b/src/es6.js @@ -0,0 +1,31 @@ +"use strict"; + +import Game from "./classes/game.js"; +import Entity from "./classes/entity.js"; +import Vector2D from "./classes/vector2d.js"; +import Rect from "./classes/rect.js"; +import Color from "./classes/color.js"; + +import {KeyConsts} from "./classes/keyboardinput.js"; + + +const Classes = { + Game: Game, + Entity: Entity, + Rect: Rect, + Vector2D: Vector2D, + Color: Color +}; + + +const Consts = { + Input: { + Keys: KeyConsts + } +}; + + +export { + Classes, + Consts +}; \ No newline at end of file diff --git a/src/index.js b/src/index.js deleted file mode 100644 index c320e78..0000000 --- a/src/index.js +++ /dev/null @@ -1,15 +0,0 @@ -"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 diff --git a/src/libs/collisionmethods.js b/src/libs/collisionmethods.js new file mode 100644 index 0000000..e4ca9bd --- /dev/null +++ b/src/libs/collisionmethods.js @@ -0,0 +1,24 @@ +import Rect from "../classes/rect.js"; +import Vector2D from "../classes/vector2d.js"; + +class CollisionMethods { + + + static AABB (entity1, entity2) { + + if (!entity1 instanceof Rect || !entity2 instanceof Rect) { + throw new Error("AABB collisions can only be checked on these entity types: Rect"); + } + + return (entity1.pos.x < entity2.pos.x + entity2.size.x && + entity1.pos.x + entity1.size.x > entity2.pos.x && + entity1.pos.y < entity2.pos.y + entity2.size.y && + entity1.size.y + entity1.pos.y > entity2.pos.y); + + } + + +} + + +export default CollisionMethods; \ No newline at end of file