diff --git a/examples/pong/index.html b/examples/pong/index.html index 2ad831c..b0456ca 100644 --- a/examples/pong/index.html +++ b/examples/pong/index.html @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/examples/pong/pong.js b/examples/pong/pong.js index 6b94175..5cf0750 100644 --- a/examples/pong/pong.js +++ b/examples/pong/pong.js @@ -12,7 +12,7 @@ window.onload = function () { var pong = new MomentumEngine.Classes.Game({ - canvas: document.getElementById("game"), + canvas: document.getElementById("canvas"), width: width, height: height, fixRatio: true, @@ -28,7 +28,7 @@ window.onload = function () { black = new MomentumEngine.Classes.Color(0, 0, 0); - // All of these are instances of MomentumEngine.Entity; + // All of these are instances of MomentumEngine.Classes.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), @@ -81,7 +81,6 @@ window.onload = function () { if (leftPaddle.isCollidingWith(ball) && ball.state.speed.x < 0) { ball.state.speed.x = -ball.state.speed.x; - console.log(ball.state.speed.length()); } }; @@ -111,8 +110,6 @@ window.onload = function () { } }; - - pong.start(); diff --git a/examples/snowflakes/index.html b/examples/snowflakes/index.html new file mode 100644 index 0000000..e23ac9d --- /dev/null +++ b/examples/snowflakes/index.html @@ -0,0 +1,11 @@ + + + + Snowflakes - MomentumEngine + + + + + + + \ No newline at end of file diff --git a/examples/snowflakes/pong.js b/examples/snowflakes/pong.js new file mode 100644 index 0000000..c9f27b5 --- /dev/null +++ b/examples/snowflakes/pong.js @@ -0,0 +1,116 @@ +"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("canvas"), + 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 the ball position + 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.state.speed.x > 0) || (this.pos.x < 0 && this.state.speed.x < 0)) { + this.state.speed.x = -this.state.speed.x; + } + + if ((this.pos.y + baseSize > height && this.state.speed.y > 0) || (this.pos.y < 0 && this.state.speed.y < 0)) { + this.state.speed.y = -this.state.speed.y; + } + + }; + + + // Update the left paddle according to keyboard input + 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.isCollidingWith(ball) && ball.state.speed.x < 0) { + ball.state.speed.x = -ball.state.speed.x; + } + + }; + + + // Update the right paddle according to keyboard input + 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.isCollidingWith(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/examples/snowflakes/snowflake.png b/examples/snowflakes/snowflake.png new file mode 100644 index 0000000..08b36ce Binary files /dev/null and b/examples/snowflakes/snowflake.png differ diff --git a/examples/snowflakes/snowflakes.js b/examples/snowflakes/snowflakes.js new file mode 100644 index 0000000..f93b0ca --- /dev/null +++ b/examples/snowflakes/snowflakes.js @@ -0,0 +1,67 @@ +"use strict"; + +window.onload = function () { + + + var KeyConsts = MomentumEngine.Consts.Input.Keys; + + + var width = 640, + height = 360; + + + var snowflakes = new MomentumEngine.Classes.Game({ + canvas: document.getElementById("canvas"), + width: width, + height: height, + fixRatio: true, + desiredFps: 60 + }); + + + // Colors + var blue = new MomentumEngine.Classes.Color(204, 255, 255); + + + // All of these are instances of MomentumEngine.Classes.Entity + var mainScene = new MomentumEngine.Classes.Rect(0, 0, width, height, blue); + + + // Load images + var snowflakeImg = new MomentumEngine.Classes.ImageLoader("./snowflake.png"); + + + // Create scene graph + snowflakes.addChildEntity(mainScene); + + mainScene.update = function () { + + if ((snowflakes.frameCounter % 120) == 0) { // Every two seconds or so, add a new snowflake + + var startPos = (Math.random() * width) - 50; + + var newSnowflake = new MomentumEngine.Classes.Sprite(startPos, -100, 100, 100, snowflakeImg); + + newSnowflake.update = function () { + this.pos.y = this.pos.y + (snowflakes.lastFrameDelta * 0.06); + }; + + mainScene.addChildEntity(newSnowflake); + + mainScene.children.forEach((oldSnowflake) => { + + if (oldSnowflake.pos.y > height) { + mainScene.detachChildEntity(oldSnowflake); + } + + }); + + } + + }; + + + snowflakes.start(); + + +}; \ No newline at end of file diff --git a/src/classes/color.js b/src/classes/color.js index 784d960..59ecebd 100644 --- a/src/classes/color.js +++ b/src/classes/color.js @@ -1,3 +1,5 @@ +"use strict"; + class Color { diff --git a/src/classes/entity.js b/src/classes/entity.js index 97fd892..e572646 100644 --- a/src/classes/entity.js +++ b/src/classes/entity.js @@ -9,6 +9,7 @@ class Entity { this.pos = new Vector2D(x || 0, y || 0); + this._ready = true; this.state = {}; this.children = []; @@ -44,22 +45,40 @@ class Entity { } - detachChildEntity (entity) { - // Not implemented + detachChildEntity (child) { + + for (var i = 0; i < this.children.length; i++) { + if (this.children[i] == child) { + + this.children.splice(i, 1); + return true; + + } + } + + return false; + } - _recalculatePos () { + isReady () { + return this._ready; + } - // 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. + + _preprocess () { + + // NK: 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 _preprocess 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. + // When rendering, the draw calls should use this._calculatedPos rather than this.pos in order for the position to be correct. + if (this._game && this._lastCalculated < this._game.frameCounter) { if (this._parent) { - let parentPos = this._parent._recalculatePos(); + let parentPos = this._parent._preprocess(); this._calculatedPos.x = this.pos.x + parentPos.x; this._calculatedPos.y = this.pos.y + parentPos.y; @@ -91,9 +110,9 @@ class Entity { _updateEntity () { - this._recalculatePos(); + var updated = this.update && this.update(); - if ((this.update && this.update()) || (typeof this.update === "undefined")) { + if (this.isReady() && (updated || (typeof updated == "undefined") || (typeof this.update === "undefined"))) { this.children.forEach((child) => { child._updateEntity(); @@ -106,7 +125,11 @@ class Entity { _renderEntity () { - if ((this.render && this.render()) || (typeof this.render === "undefined")) { + this._preprocess(); + + var rendered = this.render && this.render(); + + if (this.isReady() && (rendered || (typeof rendered == "undefined") || (typeof this.render === "undefined"))) { this.children.forEach((child) => { child._renderEntity(); diff --git a/src/classes/game.js b/src/classes/game.js index a745402..addf7f6 100644 --- a/src/classes/game.js +++ b/src/classes/game.js @@ -17,19 +17,19 @@ class Game extends Entity { if (config.canvas) { this.canvas = config.canvas; } else { - throw new Error("MomentumEngine.Game must be constructed with a canvas"); + throw new Error("MomentumEngine.Classes.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"); + throw new Error("MomentumEngine.Classes.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"); + throw new Error("MomentumEngine.Classes.Game must be constructed with canvas height"); } @@ -126,6 +126,7 @@ class Game extends Entity { })(); self._lastFrameTimestamp = +(new Date()); + self.startTime = self._lastFrameTimestamp; var loop = function () { diff --git a/src/classes/imageloader.js b/src/classes/imageloader.js new file mode 100644 index 0000000..1d214a0 --- /dev/null +++ b/src/classes/imageloader.js @@ -0,0 +1,52 @@ +"use strict"; + +class ImageLoader { + + + constructor (src) { + + this._loaded = false; // Default is true, set it to false until the image has loaded + this._error = false; // If the image fails to load, this will contain the reason + + this._imageObj = new Image(); + + this._imageObj.addEventListener("load", () => { + this._loaded = true; + this._error = false; + }); + + this._imageObj.addEventListener("_error", (err) => { + this._loaded = false; + this._error = err; + }); + + this._imageObj.src = src; + + } + + + getImageObj () { + + if (this._loaded) { + return this._imageObj; + } else { + return false; + } + + } + + + isLoaded () { + return this._loaded; + } + + + isError () { + return this._error; + } + + +} + + +export default ImageLoader; \ No newline at end of file diff --git a/src/classes/sprite.js b/src/classes/sprite.js new file mode 100644 index 0000000..376f0ee --- /dev/null +++ b/src/classes/sprite.js @@ -0,0 +1,78 @@ +"use strict"; + +import Entity from "./entity.js"; +import Vector2D from "./vector2d.js"; +import ImageLoader from "./imageloader.js"; + +import CollisionMethods from "../libs/collisionmethods.js"; + + +class Sprite extends Entity { + + + constructor (x, y, width, height, image) { + + if (!image instanceof ImageLoader) { + throw new Error("MomentumEngine.Classes.Sprite must be instantiated with an ImageLoader instance"); + } + + super(x, y); + + this.size = new Vector2D(width || 0, height || 0); + + this._image = image; + this._imagePos = new Vector2D(0, 0); + this._imageSize = new Vector2D(0, 0); + + } + + + setImageCoords (x, y, width, height) { + + this._imagePos.x = x; + this._imagePos.y = y; + this._imageSize.x = width || 0; + this._imageSize.y = height || 0; + + } + + + isReady () { // Overrides super isReady + return (this._image.isLoaded() && !this._image.isError()); + } + + + render () { + + if (this.isReady()) { + + var imageObj = this._image.getImageObj(); + + var subWidth = imageObj.width - this._imagePos.x, + subHeight = imageObj.height - this._imagePos.y; + + this._game.context.drawImage( + imageObj, + this._imagePos.x, + this._imagePos.y, + this._imageSize.x || subWidth, + this._imageSize.y || subHeight, + this._calculatedPos.x, + this._calculatedPos.y, + this.size.x || subWidth, + this.size.y || subHeight + ); + + return true; + + } else { + return false; + } + + } + + +} + + +export default Sprite; \ No newline at end of file diff --git a/src/es5.js b/src/es5.js index 3d45d64..d81b8ec 100644 --- a/src/es5.js +++ b/src/es5.js @@ -3,8 +3,10 @@ import Game from "./classes/game.js"; import Entity from "./classes/entity.js"; import Vector2D from "./classes/vector2d.js"; +import Sprite from "./classes/sprite.js"; import Rect from "./classes/rect.js"; import Color from "./classes/color.js"; +import ImageLoader from "./classes/imageloader.js"; import {KeyConsts} from "./classes/keyboardinput.js"; @@ -12,9 +14,11 @@ import {KeyConsts} from "./classes/keyboardinput.js"; const Classes = { Game: Game, Entity: Entity, + Sprite: Sprite, Rect: Rect, Vector2D: Vector2D, - Color: Color + Color: Color, + ImageLoader: ImageLoader }; diff --git a/src/es6.js b/src/es6.js index 2963499..60008dd 100644 --- a/src/es6.js +++ b/src/es6.js @@ -3,8 +3,10 @@ import Game from "./classes/game.js"; import Entity from "./classes/entity.js"; import Vector2D from "./classes/vector2d.js"; +import Sprite from "./classes/sprite.js"; import Rect from "./classes/rect.js"; import Color from "./classes/color.js"; +import ImageLoader from "./classes/imageloader.js"; import {KeyConsts} from "./classes/keyboardinput.js"; @@ -12,9 +14,11 @@ import {KeyConsts} from "./classes/keyboardinput.js"; const Classes = { Game: Game, Entity: Entity, + Sprite: Sprite, Rect: Rect, Vector2D: Vector2D, - Color: Color + Color: Color, + ImageLoader: ImageLoader };