Compare commits
52 Commits
feature/pa
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
d05736773b | ||
|
b5da68a56e | ||
|
38744c399c | ||
|
a903bd6aa3 | ||
|
d8b1411a3e | ||
|
bf1f590d20 | ||
|
099317232e | ||
|
ec56fe7dea | ||
|
f283c4fdc4 | ||
|
00f7a64b59 | ||
|
fa8ec9125e | ||
|
d10e9b4aef | ||
|
a743ec206b | ||
|
1136aec709 | ||
|
bada9f03a3 | ||
|
26929f37d8 | ||
|
a7cce2157c | ||
|
483ea7cc54 | ||
|
68e205216f | ||
|
89c3595f94 | ||
|
2560e7656c | ||
|
11c6cc9cb8 | ||
|
5336f0d4bf | ||
|
3406c28a80 | ||
|
3cd618e66c | ||
|
8c85ec17af | ||
|
dbff95c496 | ||
|
2a032ca2e2 | ||
|
1e23ef1e62 | ||
|
91c282b915 | ||
|
1da427f168 | ||
|
84a3c92ac1 | ||
|
73ee61cb00 | ||
|
6936f97933 | ||
|
d0eec1412e | ||
|
d409b2f232 | ||
|
49cb0efe72 | ||
|
7e8c4cdf12 | ||
|
db9e310e77 | ||
|
b098281a02 | ||
|
73a517edc8 | ||
|
aa281e57b4 | ||
|
13508b7fd9 | ||
|
246453a25e | ||
|
8735e06619 | ||
|
7dbeda0dc9 | ||
|
e2ad337f75 | ||
|
f9d0dba07a | ||
|
5a76bc6773 | ||
|
9d0f3db412 | ||
|
c8777e6556 | ||
|
14f7f3e30e |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
.idea/
|
||||
.DS_Store
|
||||
dist/
|
||||
node_modules/
|
||||
dist/
|
||||
docs/
|
||||
examples/*/dist/
|
||||
browse.VC.db
|
||||
|
103
examples/fire/fire.js
Normal file
103
examples/fire/fire.js
Normal file
@ -0,0 +1,103 @@
|
||||
"use strict";
|
||||
|
||||
import MomentumEngine from "../../src/es6";
|
||||
|
||||
let KeyConsts = MomentumEngine.Consts.Input.Keys;
|
||||
|
||||
let black = new MomentumEngine.Classes.Color(0, 0, 0),
|
||||
fireParticleWidth = 150,
|
||||
fireParticleHeight = 150;
|
||||
|
||||
|
||||
let startColour = new MomentumEngine.Classes.Color(250, 218, 68, 1),
|
||||
startColourRandom = new MomentumEngine.Classes.Color(62, 60, 60, 0),
|
||||
finishColour = new MomentumEngine.Classes.Color(245, 35, 0, 0),
|
||||
finishColourRandom = new MomentumEngine.Classes.Color(60, 60, 60, 0);
|
||||
|
||||
|
||||
class RandomColor extends MomentumEngine.Classes.Color {
|
||||
|
||||
constructor (initialColor, deltaColor) {
|
||||
|
||||
let r = initialColor.r + (deltaColor.r * RandomColor._rand()),
|
||||
g = initialColor.g + (deltaColor.g * RandomColor._rand()),
|
||||
b = initialColor.b + (deltaColor.b * RandomColor._rand()),
|
||||
a = initialColor.a + (deltaColor.a * RandomColor._rand());
|
||||
|
||||
super(~~r, ~~g, ~~b, ~~a);
|
||||
|
||||
}
|
||||
|
||||
static _rand () {
|
||||
return (Math.random() * 2 - 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class FireParticle extends MomentumEngine.Classes.Entity {
|
||||
|
||||
constructor (x, y) {
|
||||
super(x, y);
|
||||
this.timeToLive = 10000;
|
||||
this.startColor = new RandomColor(startColour, startColourRandom);
|
||||
this.finishColor = new RandomColor(finishColour, startColour);
|
||||
this.deltaColor = startColour.clone().subtract(finishColour);
|
||||
}
|
||||
|
||||
update (delta) {
|
||||
this.startColor.a = this.startColor.a - (delta * 0.0001);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
var gradient = this._game.context.createRadialGradient(
|
||||
this.relativeLeft + fireParticleWidth / 2,
|
||||
this.relativeTop + fireParticleHeight / 2,
|
||||
fireParticleWidth / 10,
|
||||
this.relativeLeft + fireParticleWidth / 2,
|
||||
this.relativeTop + fireParticleHeight / 2,
|
||||
fireParticleWidth / 2
|
||||
);
|
||||
|
||||
gradient.addColorStop(0, this.startColor.toString());
|
||||
gradient.addColorStop(1, "rgba(0, 0, 0, 0)");
|
||||
this._game.context.fillStyle = gradient;
|
||||
this._game.context.fillRect(this.relativeLeft, this.relativeTop, fireParticleWidth, fireParticleHeight);
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
let width = 640,
|
||||
height = 360,
|
||||
baseSize = width / 64;
|
||||
|
||||
let fireDemo = new MomentumEngine.Classes.Game({
|
||||
canvas: document.getElementById("canvas"),
|
||||
width: width,
|
||||
height: height,
|
||||
fixRatio: true,
|
||||
desiredFps: 60,
|
||||
inputs: {
|
||||
keyboard: true
|
||||
}
|
||||
});
|
||||
|
||||
let mainScene = new MomentumEngine.Classes.Rect(0, 0, width, height, black);
|
||||
fireDemo.addChildEntity(mainScene);
|
||||
|
||||
let fireEmitter = new MomentumEngine.Classes.Emitter(width / 2 - (fireParticleWidth / 2), height / 2 - (fireParticleHeight / 2), 250, new MomentumEngine.Classes.Vector2D(0.02, 0.02), FireParticle);
|
||||
mainScene.addChildEntity(fireEmitter);
|
||||
|
||||
fireEmitter.setParticleParent(mainScene);
|
||||
fireEmitter.emitting = true;
|
||||
|
||||
fireDemo.start();
|
||||
|
||||
};
|
10
examples/fire/index.html
Normal file
10
examples/fire/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Fire - MomentumEngine</title>
|
||||
<script type="application/javascript" src="./dist/fire.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
10
examples/particles/index.html
Normal file
10
examples/particles/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Particles - MomentumEngine</title>
|
||||
<script type="application/javascript" src="./dist/particles.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
90
examples/particles/particles.js
Normal file
90
examples/particles/particles.js
Normal file
@ -0,0 +1,90 @@
|
||||
"use strict";
|
||||
|
||||
import MomentumEngine from "../../src/es6";
|
||||
|
||||
let KeyConsts = MomentumEngine.Consts.Input.Keys;
|
||||
|
||||
|
||||
class BlueParticle extends MomentumEngine.Classes.Rect {
|
||||
|
||||
constructor (x, y) {
|
||||
super(x, y, 1, 1, new MomentumEngine.Classes.Color(0, 255, 0));
|
||||
this.timeToLive = 25500;
|
||||
}
|
||||
|
||||
update (delta) {
|
||||
this.color.a = this.color.a - (delta * 0.00004);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
let width = 640,
|
||||
height = 360,
|
||||
baseSize = width / 64;
|
||||
|
||||
let particleDemo = new MomentumEngine.Classes.Game({
|
||||
canvas: document.getElementById("canvas"),
|
||||
width: width,
|
||||
height: height,
|
||||
fixRatio: true,
|
||||
desiredFps: 60,
|
||||
inputs: {
|
||||
keyboard: true
|
||||
}
|
||||
});
|
||||
|
||||
let black = new MomentumEngine.Classes.Color(0, 0, 0),
|
||||
red = new MomentumEngine.Classes.Color(255, 0, 0),
|
||||
blue = new MomentumEngine.Classes.Color(0, 0, 255);
|
||||
|
||||
let mainScene = new MomentumEngine.Classes.Rect(0, 0, width, height, black);
|
||||
particleDemo.addChildEntity(mainScene);
|
||||
|
||||
let emitterRect = new MomentumEngine.Classes.Rect(width / 8 - baseSize, height / 2 - baseSize, baseSize * 2, baseSize * 2, red),
|
||||
emitter = new MomentumEngine.Classes.Emitter(baseSize, baseSize, 4, new MomentumEngine.Classes.Vector2D(0, 0.05), BlueParticle);
|
||||
|
||||
let bottomFieldRect = new MomentumEngine.Classes.Rect(width - (baseSize * 33), height - (baseSize * 11), baseSize * 2, baseSize * 2, blue),
|
||||
bottomField = new MomentumEngine.Classes.Field(baseSize, baseSize, 0.1);
|
||||
|
||||
let topFieldRect = new MomentumEngine.Classes.Rect(width - (baseSize * 33), baseSize * 9, baseSize * 2, baseSize * 2, blue),
|
||||
topField = new MomentumEngine.Classes.Field(baseSize, baseSize, 0.1);
|
||||
|
||||
mainScene.addChildEntity(emitterRect);
|
||||
emitterRect.addChildEntity(emitter);
|
||||
|
||||
mainScene.addChildEntity(bottomFieldRect);
|
||||
bottomFieldRect.addChildEntity(bottomField);
|
||||
mainScene.addChildEntity(topFieldRect);
|
||||
topFieldRect.addChildEntity(topField);
|
||||
|
||||
emitterRect.update = function (delta) {
|
||||
|
||||
if (particleDemo.inputs.keyboard.isPressed(KeyConsts.UP)) {
|
||||
emitterRect.pos.y -= (0.2 * delta);
|
||||
}
|
||||
|
||||
if (particleDemo.inputs.keyboard.isPressed(KeyConsts.DOWN)) {
|
||||
emitterRect.pos.y += (0.2 * delta);
|
||||
}
|
||||
|
||||
if (particleDemo.inputs.keyboard.isPressed(KeyConsts.LEFT)) {
|
||||
emitterRect.pos.x -= (0.2 * delta);
|
||||
}
|
||||
|
||||
if (particleDemo.inputs.keyboard.isPressed(KeyConsts.RIGHT)) {
|
||||
emitterRect.pos.x += (0.2 * delta);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
emitter.setParticleParent(mainScene);
|
||||
emitter.particleFields.push(bottomField, topField);
|
||||
emitter.spread = Math.PI / 8;
|
||||
emitter.emitting = true;
|
||||
|
||||
particleDemo.start();
|
||||
|
||||
};
|
@ -2,10 +2,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Pong - MomentumEngine</title>
|
||||
<script type="application/javascript" src="../../dist/es5.js"></script>
|
||||
<script type="application/javascript" src="./pong.js"></script>
|
||||
<script type="application/javascript" src="./dist/pong.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="game"></canvas>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
@ -1,119 +1,332 @@
|
||||
"use strict";
|
||||
|
||||
import MomentumEngine from "../../src/es6";
|
||||
|
||||
|
||||
let KeyConsts = MomentumEngine.Consts.Input.Keys;
|
||||
|
||||
let width = 640,
|
||||
height = 360,
|
||||
baseSize = width / 64;
|
||||
|
||||
let white = new MomentumEngine.Classes.Color(255, 255, 255),
|
||||
black = new MomentumEngine.Classes.Color(0, 0, 0),
|
||||
red = new MomentumEngine.Classes.Color(255, 0, 0);
|
||||
|
||||
let font = new MomentumEngine.Classes.Font("Arial", 32, white, red);
|
||||
|
||||
|
||||
class Ball extends MomentumEngine.Classes.Rect {
|
||||
|
||||
|
||||
constructor (startingLeft, startingTop) {
|
||||
|
||||
super(startingLeft, startingTop, baseSize, baseSize, white);
|
||||
|
||||
this.startingLeft = startingLeft;
|
||||
this.startingTop = startingTop;
|
||||
this.speed = new MomentumEngine.Classes.Vector2D(0.1, 0.05); // Starting ball speed
|
||||
|
||||
}
|
||||
|
||||
|
||||
update (delta) {
|
||||
|
||||
this.pos.add(this.speed.clone().multiply(delta));
|
||||
|
||||
if (this.left + baseSize > width && this.speed.x > 0) {
|
||||
|
||||
this.left = this.startingLeft;
|
||||
this.top = this.startingTop;
|
||||
this.game.leftScoreboard.increment();
|
||||
|
||||
} else if (this.left < 0 && this.speed.x < 0) {
|
||||
|
||||
this.left = this.startingLeft;
|
||||
this.top = this.startingTop;
|
||||
this.game.rightScoreboard.increment();
|
||||
|
||||
}
|
||||
|
||||
if ((this.top + baseSize > height && this.speed.y > 0) || (this.top < 0 && this.speed.y < 0)) {
|
||||
this.speed.y = -this.speed.y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Paddle extends MomentumEngine.Classes.Rect {
|
||||
|
||||
|
||||
constructor (posLeft, keys) {
|
||||
|
||||
super(posLeft, baseSize, baseSize, baseSize * 7, white);
|
||||
|
||||
this.keyUp = keys.up;
|
||||
this.keyDown = keys.down;
|
||||
this.scoreboard = null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
update (delta) {
|
||||
|
||||
if (this.keyUp(this, delta)) {
|
||||
this.top -= (0.5 * delta);
|
||||
} else if (this.keyDown(this, delta)) {
|
||||
this.top += (0.5 * delta);
|
||||
}
|
||||
|
||||
if (this.top > height - (baseSize * 8)) {
|
||||
this.top = height - (baseSize * 8);
|
||||
} else if (this.top < baseSize) {
|
||||
this.top = baseSize;
|
||||
}
|
||||
|
||||
this.balls.forEach((ball) => {
|
||||
if (this.isCollidingWith(ball)) {
|
||||
ball.speed.x = -ball.speed.x;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Scoreboard extends MomentumEngine.Classes.Text {
|
||||
|
||||
|
||||
constructor (posLeft) {
|
||||
super(posLeft, 35, font);
|
||||
this.score = 0;
|
||||
this.text = "Score: 0";
|
||||
}
|
||||
|
||||
|
||||
increment () {
|
||||
this.score++;
|
||||
this.text = `Score: ${this.score}`;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Pong extends MomentumEngine.Classes.Game {
|
||||
|
||||
|
||||
constructor (canvas, width, height) {
|
||||
|
||||
super({
|
||||
canvas: canvas,
|
||||
width: width,
|
||||
height: height,
|
||||
fixRatio: true,
|
||||
desiredFps: 60,
|
||||
//fixFrameRate: true,
|
||||
fullscreen: {
|
||||
nativeResolution: true,
|
||||
maintainAspectRatio: true
|
||||
},
|
||||
inputs: {
|
||||
keyboard: true,
|
||||
gamepad: true
|
||||
}
|
||||
});
|
||||
|
||||
let background = new MomentumEngine.Classes.Rect(0, 0, width, height, black);
|
||||
this.addChildEntity(background);
|
||||
|
||||
this.balls = [];
|
||||
this.paddles = [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
setLeftScoreboard (scoreboard) {
|
||||
this.leftScoreboard = scoreboard;
|
||||
this.addChildEntity(scoreboard);
|
||||
}
|
||||
|
||||
setRightScoreboard (scoreboard) {
|
||||
this.rightScoreboard = scoreboard;
|
||||
this.addChildEntity(scoreboard);
|
||||
}
|
||||
|
||||
|
||||
addBall (ball) {
|
||||
ball.game = this;
|
||||
this.balls.push(ball);
|
||||
this.addChildEntity(ball);
|
||||
}
|
||||
|
||||
|
||||
addPaddle (paddle) {
|
||||
this.paddles.push(paddle);
|
||||
this.addChildEntity(paddle);
|
||||
paddle.balls = this.balls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
var leftPaddleUpCondition = function (paddle, delta) {
|
||||
|
||||
let gamepadInput = paddle._game.inputs.gamepad;
|
||||
|
||||
let gamepadConnected = (gamepadInput.numGamepads >= 1),
|
||||
axisMoved = false;
|
||||
|
||||
if (gamepadConnected) {
|
||||
|
||||
let upDownAxes = gamepadInput.getGamepadById(0).getAxis(1);
|
||||
|
||||
if (upDownAxes < -0.1) {
|
||||
this.top += (0.5 * delta) * upDownAxes;
|
||||
axisMoved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!axisMoved) {
|
||||
if (paddle._game.inputs.keyboard.isPressed(KeyConsts.CHAR_Q)) {
|
||||
this.top -= 0.5 * delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var leftPaddleDownCondition = function (paddle, delta) {
|
||||
|
||||
let gamepadInput = paddle._game.inputs.gamepad;
|
||||
|
||||
let gamepadConnected = (gamepadInput.numGamepads >= 1),
|
||||
axisMoved = false;
|
||||
|
||||
if (gamepadConnected) {
|
||||
|
||||
let upDownAxes = gamepadInput.getGamepadById(0).getAxis(1);
|
||||
|
||||
if (upDownAxes > 0.1) {
|
||||
this.top += (0.5 * delta) * upDownAxes;
|
||||
axisMoved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!axisMoved) {
|
||||
if (paddle._game.inputs.keyboard.isPressed(KeyConsts.CHAR_A)) {
|
||||
this.top += 0.5 * delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var rightPaddleUpCondition = function (paddle, delta) {
|
||||
|
||||
let gamepadInput = paddle._game.inputs.gamepad;
|
||||
|
||||
let gamepadConnected = (gamepadInput.numGamepads >= 1 && gamepadInput.getGamepadById(0).numAxis >= 6),
|
||||
axisMoved = false;
|
||||
|
||||
if (gamepadConnected) {
|
||||
|
||||
let upDownAxes = gamepadInput.getGamepadById(0).getAxis(4);
|
||||
|
||||
if (upDownAxes < -0.1) {
|
||||
this.top += (0.5 * delta) * upDownAxes;
|
||||
axisMoved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!axisMoved) {
|
||||
if (paddle._game.inputs.keyboard.isPressed(KeyConsts.CHAR_O)) {
|
||||
this.top -= 0.5 * delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
var rightPaddleDownCondition = function (paddle, delta) {
|
||||
|
||||
let gamepadInput = paddle._game.inputs.gamepad;
|
||||
|
||||
let gamepadConnected = (gamepadInput.numGamepads >= 1),
|
||||
axisMoved = false;
|
||||
|
||||
if (gamepadConnected) {
|
||||
|
||||
let upDownAxes = gamepadInput.getGamepadById(0).getAxis(4);
|
||||
|
||||
if (upDownAxes > 0.1) {
|
||||
this.top += (0.5 * delta) * upDownAxes;
|
||||
axisMoved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!axisMoved) {
|
||||
if (paddle._game.inputs.keyboard.isPressed(KeyConsts.CHAR_L)) {
|
||||
this.top += 0.5 * delta;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
var pong = new Pong(document.getElementById("canvas"), width, height);
|
||||
|
||||
var KeyConsts = MomentumEngine.Consts.Input.Keys;
|
||||
var ball = new Ball((width / 2) - (baseSize / 2), (height / 2) - (baseSize / 2));
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
var leftPaddle = new Paddle(baseSize, {
|
||||
up: leftPaddleUpCondition,
|
||||
down: leftPaddleDownCondition
|
||||
});
|
||||
|
||||
var rightPaddle = new Paddle(width - (baseSize * 2), {
|
||||
up: rightPaddleUpCondition,
|
||||
down: rightPaddleDownCondition
|
||||
});
|
||||
|
||||
// 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);
|
||||
var leftScoreboard = new Scoreboard(baseSize),
|
||||
rightScoreboard = new Scoreboard(width - baseSize);
|
||||
|
||||
rightScoreboard.textAlign = "right"; // Right align the text of the right scoreboard
|
||||
|
||||
// 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.addBall(ball);
|
||||
pong.addPaddle(leftPaddle);
|
||||
pong.addPaddle(rightPaddle);
|
||||
|
||||
pong.setLeftScoreboard(leftScoreboard);
|
||||
pong.setRightScoreboard(rightScoreboard);
|
||||
|
||||
pong.start();
|
||||
|
||||
|
||||
document.addEventListener("keydown", function(e) {
|
||||
if (e.keyCode == 13) {
|
||||
pong.toggleFullScreen();
|
||||
}
|
||||
}, false);
|
||||
|
||||
|
||||
};
|
10
examples/snowflakes/index.html
Normal file
10
examples/snowflakes/index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Snowflakes - MomentumEngine</title>
|
||||
<script type="application/javascript" src="./dist/snowflakes.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="canvas"></canvas>
|
||||
</body>
|
||||
</html>
|
BIN
examples/snowflakes/snowflake.png
Normal file
BIN
examples/snowflakes/snowflake.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 194 KiB |
70
examples/snowflakes/snowflakes.js
Normal file
70
examples/snowflakes/snowflakes.js
Normal file
@ -0,0 +1,70 @@
|
||||
"use strict";
|
||||
|
||||
import MomentumEngine from "../../src/es6";
|
||||
|
||||
let KeyConsts = MomentumEngine.Consts.Input.Keys;
|
||||
|
||||
|
||||
window.onload = function () {
|
||||
|
||||
|
||||
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 (delta) {
|
||||
|
||||
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.top = this.top + (delta * 0.06);
|
||||
};
|
||||
|
||||
mainScene.addChildEntity(newSnowflake);
|
||||
|
||||
mainScene.children.forEach(function (oldSnowflake) {
|
||||
|
||||
if (oldSnowflake.top > height) {
|
||||
// Clean up snowflakes that are no longer visible
|
||||
mainScene.detachChildEntity(oldSnowflake);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
snowflakes.start();
|
||||
|
||||
|
||||
};
|
97
gulpfile.js
97
gulpfile.js
@ -3,7 +3,33 @@
|
||||
var gulp = require("gulp"),
|
||||
path = require("path"),
|
||||
gutil = require("gulp-util"),
|
||||
webpack = require("webpack");
|
||||
webpack = require("webpack"),
|
||||
jsdoc = require("gulp-jsdoc3");
|
||||
|
||||
|
||||
let minify = true,
|
||||
watch = false,
|
||||
examples = [
|
||||
"fire",
|
||||
"particles",
|
||||
"pong",
|
||||
"snowflakes"
|
||||
];
|
||||
|
||||
|
||||
process.argv.forEach((arg) => {
|
||||
|
||||
if (arg == "--dev" || arg == "-d") {
|
||||
minify = false;
|
||||
gutil.log("[Momentum Engine] dev flag passed, enabled");
|
||||
}
|
||||
|
||||
if (arg == "--watch" || arg == "-w") {
|
||||
watch = true;
|
||||
gutil.log("[Momentum Engine] watch flag passed, enabled");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
var build = function (options, callback) {
|
||||
@ -25,15 +51,13 @@ var build = function (options, callback) {
|
||||
}
|
||||
|
||||
webpack({
|
||||
entry: {
|
||||
"es5": path.join(__dirname, "src", "es5.js")
|
||||
},
|
||||
entry: options.entry,
|
||||
bail: !options.watch,
|
||||
watch: options.watch,
|
||||
devtool: "source-map",
|
||||
plugins: plugins,
|
||||
output: {
|
||||
path: path.join(__dirname, "dist"),
|
||||
path: options.path,
|
||||
filename: "[name].js"
|
||||
},
|
||||
module: {
|
||||
@ -41,7 +65,8 @@ var build = function (options, callback) {
|
||||
loader: "babel-loader",
|
||||
test: /\.js$/,
|
||||
include: [
|
||||
path.join(__dirname, "src")
|
||||
path.join(__dirname, "src"),
|
||||
path.join(__dirname, "examples")
|
||||
],
|
||||
query: {
|
||||
plugins: ["transform-runtime"],
|
||||
@ -76,25 +101,51 @@ var build = function (options, callback) {
|
||||
};
|
||||
|
||||
|
||||
gulp.task("build-dev", (callback) => {
|
||||
build({
|
||||
watch: false,
|
||||
minify: false
|
||||
}, callback);
|
||||
});
|
||||
examples.forEach((example) => {
|
||||
|
||||
let entry = {};
|
||||
entry[example] = path.join(__dirname, "examples", example, `${example}.js`);
|
||||
|
||||
gulp.task("build", (callback) => {
|
||||
build({
|
||||
watch: false,
|
||||
minify: true
|
||||
}, callback);
|
||||
});
|
||||
gulp.task(`${example}-example`, (callback) => {
|
||||
|
||||
|
||||
gulp.task("watch", () => {
|
||||
build({
|
||||
watch: true,
|
||||
minify: false
|
||||
build({
|
||||
entry: entry,
|
||||
path: path.join(__dirname, "examples", example, "dist"),
|
||||
watch: watch,
|
||||
minify: minify
|
||||
}, callback);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
gulp.task("examples", examples.map((example) => { return `${example}-example`; }));
|
||||
|
||||
|
||||
gulp.task("engine", (callback) => {
|
||||
build({
|
||||
entry: {
|
||||
"es5": path.join(__dirname, "src", "es5.js")
|
||||
},
|
||||
path: path.join(__dirname, "dist"),
|
||||
watch: watch,
|
||||
minify: minify
|
||||
}, callback);
|
||||
});
|
||||
|
||||
|
||||
gulp.task("docs", (callback) => {
|
||||
gulp.src([
|
||||
"src/classes/*.js"
|
||||
], {
|
||||
read: false
|
||||
}).pipe(jsdoc({
|
||||
opts: {
|
||||
destination: "docs"
|
||||
}
|
||||
}, callback));
|
||||
})
|
||||
|
||||
|
||||
gulp.task("build", ["engine", "docs", "examples"]);
|
||||
gulp.task("default", ["build"]);
|
||||
|
35
package.json
35
package.json
@ -1,26 +1,31 @@
|
||||
{
|
||||
"name": "momentumengine",
|
||||
"version": "0.0.1",
|
||||
"version": "0.10.0",
|
||||
"description": "An ES6 game and animation engine.",
|
||||
"main": "src/es6.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nathankunicki/momentumengine.git"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "gulp --silent",
|
||||
"build": "gulp",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Nathan Kunicki <me@nathankunicki.com>",
|
||||
"license": "ISC",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^4.2.3"
|
||||
"node": "^7.6.0"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"babel-core": "6.23.1",
|
||||
"babel-loader": "6.3.2",
|
||||
"babel-plugin-transform-runtime": "6.23.0",
|
||||
"babel-preset-es2015": "6.22.0",
|
||||
"babel-preset-stage-0": "6.22.0",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-jsdoc3": "1.0.1",
|
||||
"gulp-util": "3.0.8",
|
||||
"webpack": "2.2.1"
|
||||
}
|
||||
}
|
||||
|
87
src/classes/audio.js
Normal file
87
src/classes/audio.js
Normal file
@ -0,0 +1,87 @@
|
||||
"use strict";
|
||||
|
||||
class AudioTrack {
|
||||
|
||||
|
||||
constructor (src) {
|
||||
|
||||
this._loaded = false; // Default is true, set it to false until the audio has loaded
|
||||
this._error = false; // If the audio fails to load, this will contain the reason
|
||||
this._loop = false;
|
||||
|
||||
this._audioObj = new Audio();
|
||||
|
||||
this._audioObj.addEventListener("loadeddata", () => {
|
||||
this._loaded = true;
|
||||
this._error = false;
|
||||
|
||||
this._audioObj.addEventListener("ended", () => {
|
||||
if (this._loop) {
|
||||
this._audioObj.currentTime = 0;
|
||||
this._audioObj.play();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this._audioObj.addEventListener("error", (err) => {
|
||||
this._loaded = false;
|
||||
this._error = err;
|
||||
});
|
||||
|
||||
this._audioObj.src = src;
|
||||
|
||||
}
|
||||
|
||||
|
||||
get loop () {
|
||||
return this._loop;
|
||||
}
|
||||
|
||||
|
||||
set loop (shouldLoop) {
|
||||
return this._loop = shouldLoop;
|
||||
}
|
||||
|
||||
|
||||
play () {
|
||||
if (this._loaded) {
|
||||
return this._audioObj.play();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pause () {
|
||||
if (this._loaded) {
|
||||
return this._audioObj.pause();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
seek (seconds) {
|
||||
if (this._loaded) {
|
||||
return this._audioObj.currentTime = seconds;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isLoaded () {
|
||||
return this._loaded;
|
||||
}
|
||||
|
||||
|
||||
isError () {
|
||||
return this._error;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default AudioTrack;
|
@ -1,26 +1,128 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* Class representing a color
|
||||
*/
|
||||
class Color {
|
||||
|
||||
|
||||
constructor (r, g, b, a) {
|
||||
/**
|
||||
* Create a color
|
||||
* @param {number} red - Red value (0-255)
|
||||
* @param {number} green - Green value (0-255)
|
||||
* @param {number} blue - Blue value (0-255)
|
||||
* @param {number} alpha - Alpha value (0-1)
|
||||
*/
|
||||
constructor (red = 0, green = 0, blue = 0, alpha = 1) {
|
||||
|
||||
this.r = r || 0;
|
||||
this.g = g || 0;
|
||||
this.b = b || 0;
|
||||
this.a = a || 1;
|
||||
this.red = red;
|
||||
this.green = green;
|
||||
this.blue = blue;
|
||||
this.alpha = alpha;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the rgba (rgba()) string representation of the color
|
||||
* @returns {string}
|
||||
*/
|
||||
toString () {
|
||||
return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
|
||||
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the hex (#) representation of the color
|
||||
* @returns {string}
|
||||
*/
|
||||
toHex () {
|
||||
return `#${((r << 16) | (g << 8) | b).toString(16)}`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clones the color and returns a new color
|
||||
* @returns {Color}
|
||||
*/
|
||||
clone () {
|
||||
return new Color(this.red, this.green, this.blue, this.alpha);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a color to this color
|
||||
* @param {Color} color - Color to add
|
||||
* @returns {Color}
|
||||
*/
|
||||
add (color) {
|
||||
|
||||
if (color instanceof Color) {
|
||||
this.red += color.r; this.green += color.g; this.blue += color.b; this.alpha += color.a;
|
||||
} else {
|
||||
this.red += color; this.green += color; this.blue += color; this.alpha += color;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Subtract a color from this color
|
||||
* @param {Color} color - Color to subtract
|
||||
* @returns {Color}
|
||||
*/
|
||||
subtract (color) {
|
||||
|
||||
if (color instanceof Color) {
|
||||
this.red -= color.r; this.green -= color.g; this.blue -= color.b; this.alpha -= color.a;
|
||||
} else {
|
||||
this.red -= color; this.green -= color; this.blue -= color; this.alpha -= color;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Multiply this color with another color
|
||||
* @param {Color} color - Color to multiply with
|
||||
* @returns {Color}
|
||||
*/
|
||||
multiply (color) {
|
||||
|
||||
if (color instanceof Color) {
|
||||
this.red *= color.r; this.green *= color.g; this.blue *= color.b; this.alpha *= color.a;
|
||||
} else {
|
||||
this.red *= color; this.green *= color; this.blue *= color; this.alpha *= color;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Divide this color with another color
|
||||
* @param {Color} color - Color to divide by
|
||||
* @returns {Color}
|
||||
*/
|
||||
divide (color) {
|
||||
|
||||
if (color instanceof Color) {
|
||||
this.red /= color.r; this.green /= color.g; this.blue /= color.b; this.alpha /= color.a;
|
||||
} else {
|
||||
this.red /= color; this.green /= color; this.blue /= color; this.alpha /= color;
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
95
src/classes/emitter.js
Normal file
95
src/classes/emitter.js
Normal file
@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
import Utils from "../libs/utils";
|
||||
|
||||
|
||||
class Emitter extends Entity {
|
||||
|
||||
|
||||
constructor (x, y, rate, velocity, particle) {
|
||||
|
||||
super(x, y);
|
||||
|
||||
this.particleVelocity = velocity;
|
||||
this.particleClass = particle;
|
||||
this.particleFields = [];
|
||||
|
||||
this.rate = rate;
|
||||
this.emitting = false;
|
||||
this.spread = Math.PI;
|
||||
this._lastEmitTime = this._creationTime;
|
||||
this._wasEmitting = false;
|
||||
|
||||
this._particles = [];
|
||||
|
||||
}
|
||||
|
||||
|
||||
setParticleParent (entity) {
|
||||
this._particleParent = entity;
|
||||
}
|
||||
|
||||
|
||||
_emit () {
|
||||
|
||||
let ParticleClass = this.particleClass,
|
||||
parent = this._particleParent || this._parent;
|
||||
|
||||
let angle = this.particleVelocity.angle() + this.spread - (Math.random() * this.spread * 2),
|
||||
magnitude = this.particleVelocity.length(),
|
||||
velocity = Vector2D.fromAngle(angle, magnitude);
|
||||
|
||||
// NK: This might cause a bug where child renders have an incorrect position because preprocess should normally be called after the update function but before the render, here it is before update. We'll see.
|
||||
let particle = new ParticleClass(this.relativeLeft, this.relativeTop);
|
||||
particle.velocity = velocity;
|
||||
Utils.mergeIntoArray(particle.fields, this.particleFields);
|
||||
|
||||
//this._particles.push(particle);
|
||||
parent.addChildEntity(particle);
|
||||
|
||||
}
|
||||
|
||||
|
||||
_triggerEmissions () {
|
||||
|
||||
if (this.emitting) {
|
||||
|
||||
let currentTime = Date.now();
|
||||
|
||||
if (!this._wasEmitting) {
|
||||
this._wasEmitting = true;
|
||||
this._lastEmitTime = currentTime;
|
||||
}
|
||||
|
||||
let emitDelta = currentTime - this._lastEmitTime;
|
||||
if (emitDelta > this.rate) {
|
||||
|
||||
let emissions = ~~(emitDelta / this.rate);
|
||||
|
||||
this._lastEmitTime = currentTime + (emitDelta - (this.rate * emissions));
|
||||
|
||||
for (let i = 0; i < emissions; i++) {
|
||||
this._emit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
this._wasEmitting = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
update () {
|
||||
this._triggerEmissions();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Emitter;
|
@ -2,24 +2,162 @@
|
||||
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
|
||||
/**
|
||||
* Class representing an entity in a scene
|
||||
*/
|
||||
class Entity {
|
||||
|
||||
|
||||
constructor (x, y) {
|
||||
/**
|
||||
* Create an entity
|
||||
* @param {number} x - x (Left) position of the entity
|
||||
* @param {number} y - y (Top) position of the entity
|
||||
*/
|
||||
constructor (x = 0, y = 0) {
|
||||
|
||||
this.pos = new Vector2D(x || 0, y || 0);
|
||||
this.pos = new Vector2D(x, y);
|
||||
this.velocity = new Vector2D(0, 0);
|
||||
this.acceleration = new Vector2D(0, 0);
|
||||
this.size = new Vector2D(0, 0);
|
||||
this.rotation = 0;
|
||||
this.display = true;
|
||||
|
||||
this.fields = [];
|
||||
|
||||
this.state = {};
|
||||
this.children = [];
|
||||
|
||||
this._calculatedPos = this.pos.clone();
|
||||
this._lastCalculated = 0;
|
||||
this._relativePos = this.pos.clone();
|
||||
this._lastRelativePosCalculated = 0;
|
||||
this._lastRelativeSizeCalculated = 0;
|
||||
this._game = null;
|
||||
this._parent = null;
|
||||
|
||||
this._creationTime = +(new Date());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* x (Left) position of the entity
|
||||
*/
|
||||
get left () {
|
||||
return this.pos.x;
|
||||
}
|
||||
|
||||
set left (val) {
|
||||
let res = (this.pos.x = val);
|
||||
if (this._parent) {
|
||||
this._relativePos.x = this.pos.x + this._parent.relativeLeft;
|
||||
} else {
|
||||
this._relativePos.x = this.pos.x;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* y (Top) position of the entity
|
||||
*/
|
||||
get top () {
|
||||
return this.pos.y;
|
||||
}
|
||||
|
||||
set top (val) {
|
||||
let res = (this.pos.y = val);
|
||||
if (this._parent) {
|
||||
this._relativePos.y = this.pos.y + this._parent.relativeTop;
|
||||
} else {
|
||||
this._relativePos.y = this.pos.y;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute x (Left) position relative to the entities parent tree
|
||||
* @returns {number} x - x (Left) position relative to the entities parent tree
|
||||
*/
|
||||
get relativeLeft () {
|
||||
return this._calculateRelativePos().x;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the absolute y (Top) position relative to the entities parent tree
|
||||
* @returns {number} y - y (Top) position relative to the entities parent tree
|
||||
*/
|
||||
get relativeTop () {
|
||||
return this._calculateRelativePos().y;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Width of the entity
|
||||
*/
|
||||
get width () {
|
||||
return this.size.x;
|
||||
}
|
||||
|
||||
|
||||
set width (width) {
|
||||
return this.size.x = width;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Height of the entity
|
||||
*/
|
||||
get height () {
|
||||
return this.size.y;
|
||||
}
|
||||
|
||||
|
||||
set height (height) {
|
||||
return this.size.y = height;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the velocity of the entity
|
||||
* @param {Number} x - x (Left) velocity
|
||||
* @param {Number} y - y (Top) velocity
|
||||
*/
|
||||
setVelocity (x, y) {
|
||||
|
||||
if (x instanceof Vector2D) {
|
||||
this.velocity = x;
|
||||
} else {
|
||||
this.velocity.x = x;
|
||||
this.velocity.y = y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the acceleration of the entity
|
||||
* @param x {Number} x - x (Left) acceleration
|
||||
* @param y {Number} y - y (Top) acceleration
|
||||
*/
|
||||
setAcceleration (x, y) {
|
||||
|
||||
if (x instanceof Vector2D) {
|
||||
this.acceleration = x;
|
||||
} else {
|
||||
this.acceleration.x = x;
|
||||
this.acceleration.y = y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new child entity.
|
||||
* Note: This creates an instance of Entity, the base class. Under most circumstances you should use addChildEntity with an entity you have created.
|
||||
* @returns {Entity}
|
||||
*/
|
||||
createChildEntity () {
|
||||
|
||||
let child = new Entity();
|
||||
@ -33,6 +171,11 @@ class Entity {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add an entity as a child
|
||||
* @param {Entity} child - The child entity
|
||||
* @returns {Entity}
|
||||
*/
|
||||
addChildEntity (child) {
|
||||
|
||||
child._updateGame(this._game);
|
||||
@ -44,35 +187,52 @@ class Entity {
|
||||
}
|
||||
|
||||
|
||||
detachChildEntity (entity) {
|
||||
// Not implemented
|
||||
/**
|
||||
* Removes entity from children
|
||||
* @param {Entity} child - The child entity
|
||||
* @returns {boolean} Indicates successful removal
|
||||
*/
|
||||
detachChildEntity (child) {
|
||||
|
||||
for (let i = 0; i < this.children.length; i++) {
|
||||
if (this.children[i] == child) {
|
||||
|
||||
this.children.splice(i, 1);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
_recalculatePos () {
|
||||
_calculateRelativePos () {
|
||||
|
||||
// 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.
|
||||
// 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 _calculateRelativePos 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) {
|
||||
// When rendering, the draw calls should use this._relativePos rather than this.pos in order for the position to be correct.
|
||||
|
||||
if (this._game && this._lastRelativePosCalculated < this._game.frameCounter) {
|
||||
|
||||
if (this._parent) {
|
||||
|
||||
let parentPos = this._parent._recalculatePos();
|
||||
this._relativePos.x = this.pos.x + this._parent.relativeLeft;
|
||||
this._relativePos.y = this.pos.y + this._parent.relativeTop;
|
||||
|
||||
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._relativePos.x = this.pos.x;
|
||||
this._relativePos.y = this.pos.y;
|
||||
}
|
||||
|
||||
this._lastCalculated = this._game.frameCounter;
|
||||
this._lastRelativePosCalculated = this._game.frameCounter;
|
||||
|
||||
}
|
||||
|
||||
return this._calculatedPos;
|
||||
return this._relativePos;
|
||||
|
||||
}
|
||||
|
||||
@ -88,12 +248,109 @@ class Entity {
|
||||
}
|
||||
|
||||
|
||||
_updateEntity () {
|
||||
_scaleForLeft (val) {
|
||||
|
||||
if ((this.update && this.update()) || (typeof this.update === "undefined")) {
|
||||
let game = this._game;
|
||||
|
||||
if (!game.isFullScreen) {
|
||||
return val;
|
||||
} else {
|
||||
return (game._fullScreenXPos + (val * game._fullScreenXScaling));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_scaleForWidth (val) {
|
||||
|
||||
let game = this._game;
|
||||
|
||||
if (!game.isFullScreen) {
|
||||
return val;
|
||||
} else {
|
||||
return (val * game._fullScreenXScaling);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_scaleForTop (val) {
|
||||
|
||||
let game = this._game;
|
||||
|
||||
if (!game.isFullScreen) {
|
||||
return val;
|
||||
} else {
|
||||
return (game._fullScreenYPos + (val * game._fullScreenYScaling));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_scaleForHeight (val) {
|
||||
|
||||
let game = this._game;
|
||||
|
||||
if (!game.isFullScreen) {
|
||||
return val;
|
||||
} else {
|
||||
return (val * game._fullScreenYScaling);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_calculateFields (delta) {
|
||||
|
||||
let acceleration = new Vector2D(0, 0);
|
||||
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
|
||||
let field = this.fields[i];
|
||||
|
||||
// NK: These call _relativePos, I don't like using this outside of the render method...
|
||||
let vector = new Vector2D(
|
||||
field.relativeLeft - this.relativeLeft,
|
||||
field.relativeTop - this.relativeTop
|
||||
);
|
||||
|
||||
let force = field.mass / Math.pow(vector.dot(vector), 1.5);
|
||||
|
||||
acceleration.add(vector.multiply(force).multiply(delta));
|
||||
|
||||
}
|
||||
|
||||
return this.acceleration.clone().add(acceleration);
|
||||
|
||||
}
|
||||
|
||||
|
||||
_updateEntity (delta) {
|
||||
|
||||
if (this.timeToLive) {
|
||||
if (Date.now() - this._creationTime > this.timeToLive) {
|
||||
this._parent.detachChildEntity(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate new position based on velocity and acceleration if there's one set
|
||||
if (this.velocity && (this.velocity.x !== 0 || this.velocity.y !== 0)) {
|
||||
|
||||
this.velocity.add(this._calculateFields(delta));
|
||||
this.pos.x += (this.velocity.x * delta);
|
||||
this.pos.y += (this.velocity.y * delta);
|
||||
|
||||
}
|
||||
|
||||
// If there's an update method, call it
|
||||
let updated = this.update && this.update(delta);
|
||||
|
||||
if (updated || (typeof updated == "undefined") || (typeof this.update === "undefined")) {
|
||||
|
||||
this.children.forEach((child) => {
|
||||
child._updateEntity();
|
||||
child._updateEntity(delta);
|
||||
});
|
||||
|
||||
}
|
||||
@ -103,7 +360,13 @@ class Entity {
|
||||
|
||||
_renderEntity () {
|
||||
|
||||
if ((this.render && this.render()) || (typeof this.render === "undefined")) {
|
||||
let rendered = this.display && this.render && this.render();
|
||||
|
||||
if (rendered) {
|
||||
this._game._lastFrameTotalRenders++;
|
||||
}
|
||||
|
||||
if (rendered || (typeof rendered == "undefined") || (typeof this.render === "undefined")) {
|
||||
|
||||
this.children.forEach((child) => {
|
||||
child._renderEntity();
|
||||
|
21
src/classes/field.js
Normal file
21
src/classes/field.js
Normal file
@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
|
||||
class Field extends Entity {
|
||||
|
||||
|
||||
constructor (x, y, mass) {
|
||||
|
||||
super(x, y);
|
||||
this.mass = mass;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Field;
|
22
src/classes/font.js
Normal file
22
src/classes/font.js
Normal file
@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
|
||||
import Color from "./color.js";
|
||||
|
||||
|
||||
class Font {
|
||||
|
||||
|
||||
constructor(family, size, fill = null, stroke = null) {
|
||||
|
||||
this.family = family;
|
||||
this.size = `${size}px`;
|
||||
this.fill = fill;
|
||||
this.stroke = stroke;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Font;
|
@ -1,7 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
import KeyboardInput from "./keyboardinput.js";
|
||||
import GamepadInput from "./gamepadinput.js";
|
||||
|
||||
class Game extends Entity {
|
||||
|
||||
@ -10,6 +13,7 @@ class Game extends Entity {
|
||||
|
||||
super(); // Call entity constructor
|
||||
config = config || {};
|
||||
config.fullscreen = config.fullscreen || {};
|
||||
config.inputs = config.inputs || {};
|
||||
|
||||
|
||||
@ -17,24 +21,28 @@ 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");
|
||||
}
|
||||
|
||||
this.scale = 1;
|
||||
|
||||
// Optional params
|
||||
this.desiredFps = config.desiredFps || 60;
|
||||
this.fixFrameRate = !!config.fixFrameRate;
|
||||
|
||||
this.fullScreenNativeResolution = !!config.fullscreen.nativeResolution;
|
||||
|
||||
if (config.fixRatio) {
|
||||
|
||||
@ -57,19 +65,45 @@ class Game extends Entity {
|
||||
}
|
||||
|
||||
this.scale = deviceRatio / backingStoreRatio;
|
||||
this._deviceRatio = deviceRatio;
|
||||
|
||||
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";
|
||||
this.canvas.style.width = `${this.width}px`;
|
||||
this.canvas.style.height = `${this.height}px`;
|
||||
|
||||
|
||||
// Calculate fullscreen settings
|
||||
|
||||
if (config.fullscreen.nativeResolution) {
|
||||
this._fullScreenXScaling = screen.width / this.width;
|
||||
this._fullScreenYScaling = screen.height / this.height;
|
||||
} else {
|
||||
this._fullScreenXScaling = 1;
|
||||
this._fullScreenYScaling = 1;
|
||||
}
|
||||
|
||||
this._fullScreenXPos = 0;
|
||||
this._fullScreenYPos = 0;
|
||||
|
||||
if (config.fullscreen.maintainAspectRatio) {
|
||||
if (this._fullScreenXScaling > this._fullScreenYScaling) {
|
||||
this._fullScreenXScaling = this._fullScreenYScaling;
|
||||
this._fullScreenXPos = (screen.width - (this.width * this._fullScreenXScaling)) / 2;
|
||||
} else {
|
||||
this._fullScreenYScaling = this._fullScreenXScaling;
|
||||
this._fullScreenYPos = (screen.height - (this.height * this._fullScreenYScaling)) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Call getContext last for Ejecta only.
|
||||
if (typeof ejecta !== "undefined") {
|
||||
this.context = this.canvas.getContext("2d");
|
||||
}
|
||||
|
||||
this.context.scale(deviceRatio, deviceRatio);
|
||||
this.context.scale(this._deviceRatio, this._deviceRatio);
|
||||
|
||||
} else {
|
||||
|
||||
@ -86,17 +120,108 @@ class Game extends Entity {
|
||||
|
||||
|
||||
// Initialize defaults
|
||||
this.lastFrameDelta = 0;
|
||||
this.frameCounter = 0;
|
||||
|
||||
|
||||
// Initialize input methods
|
||||
this.inputs = {};
|
||||
|
||||
if (config.inputs.keyboard) {
|
||||
this.inputs.keyboard = new KeyboardInput(this);
|
||||
}
|
||||
|
||||
if (config.inputs.gamepad) {
|
||||
this.inputs.gamepad = new GamepadInput(this);
|
||||
}
|
||||
|
||||
this._game = this;
|
||||
this._lastFrameTimestamp = 0;
|
||||
this._lastFrameTotalRenders = 0;
|
||||
this._wantPause = true;
|
||||
this._fullScreenLastFrame = false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
setCamera (x, y) {
|
||||
|
||||
if (x instanceof Vector2D) {
|
||||
|
||||
let pos = x.clone();
|
||||
pos.x = -pos.x;
|
||||
pos.y = -pos.y;
|
||||
|
||||
this.pos = pos;
|
||||
|
||||
} else {
|
||||
|
||||
this.pos.x = -x;
|
||||
this.pos.y = -y;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
step (delta) {
|
||||
|
||||
this.frameCounter++;
|
||||
|
||||
this._preStep();
|
||||
this._updateEntity(delta);
|
||||
this._renderEntity();
|
||||
this._updateInputs(); // NK: This happens at the end for reasons
|
||||
|
||||
}
|
||||
|
||||
|
||||
_updateInputs () {
|
||||
|
||||
for (let input in this.inputs) {
|
||||
if (this.inputs[input].update) {
|
||||
this.inputs[input].update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_preStep () {
|
||||
|
||||
if (this.isFullScreen) {
|
||||
|
||||
if (this._fullScreenLastFrame == false) {
|
||||
|
||||
this.canvas.style.width = `${screen.width}px`;
|
||||
this.canvas.style.height = `${screen.height}px`;
|
||||
|
||||
if (this.fullScreenNativeResolution) {
|
||||
this.canvas.width = screen.width * this.scale;
|
||||
this.canvas.height = screen.height * this.scale;
|
||||
this.context.scale(this._deviceRatio, this._deviceRatio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._fullScreenLastFrame = true;
|
||||
|
||||
} else {
|
||||
|
||||
if (this._fullScreenLastFrame == true) {
|
||||
|
||||
this.canvas.style.width = `${this.width}px`;
|
||||
this.canvas.style.height = `${this.height}px`;
|
||||
|
||||
this.canvas.width = this.width * this.scale;
|
||||
this.canvas.height = this.height * this.scale;
|
||||
|
||||
this.context.scale(this._deviceRatio, this._deviceRatio);
|
||||
|
||||
}
|
||||
|
||||
this._fullScreenLastFrame = false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -108,13 +233,13 @@ class Game extends Entity {
|
||||
if (self._wantPause) {
|
||||
self._wantPause = false;
|
||||
} else {
|
||||
console.log("MomentumEngine.Game.start called, game instance is already started");
|
||||
console.log("MomentumEngine.Classes.Game.start called, game instance is already started");
|
||||
return false; // Game is already running
|
||||
}
|
||||
|
||||
self._wantPause = false;
|
||||
|
||||
var requestFrame = (() => {
|
||||
let requestFrame = (() => {
|
||||
|
||||
return (window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
@ -126,27 +251,26 @@ class Game extends Entity {
|
||||
})();
|
||||
|
||||
self._lastFrameTimestamp = +(new Date());
|
||||
self.startTime = self._lastFrameTimestamp;
|
||||
|
||||
var loop = function () {
|
||||
|
||||
self.frameCounter++;
|
||||
|
||||
let currentTimestamp = +(new Date());
|
||||
|
||||
self.lastFrameDelta = currentTimestamp - self._lastFrameTimestamp;
|
||||
self._lastFrameTimestamp = currentTimestamp;
|
||||
|
||||
self.lastFrameDelta = Math.min(self.lastFrameDelta, 1000 / self.desiredFps);
|
||||
|
||||
if (self._wantPause) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._updateEntity.bind(self);
|
||||
self._updateEntity();
|
||||
let currentTimestamp = +(new Date()),
|
||||
delta = currentTimestamp - self._lastFrameTimestamp;
|
||||
|
||||
self._renderEntity.bind(self);
|
||||
self._renderEntity();
|
||||
if (self.fixFrameRate) {
|
||||
delta = 1000 / self.desiredFps;
|
||||
}
|
||||
|
||||
//delta = Math.min(delta, 1000 / self.desiredFps);
|
||||
self._lastFrameTimestamp = currentTimestamp;
|
||||
self._lastFrameTotalRenders = 0;
|
||||
|
||||
self.step(delta);
|
||||
|
||||
requestFrame(loop);
|
||||
|
||||
@ -164,12 +288,40 @@ class Game extends Entity {
|
||||
this._wantPause = true;
|
||||
return true;
|
||||
} else {
|
||||
console.log("MomentumEngine.Game.pause called, game instance is already paused");
|
||||
console.log("MomentumEngine.Classes.Game.pause called, game instance is already paused");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
toggleFullScreen () {
|
||||
|
||||
if (!document.mozFullScreen && !document.webkitFullScreen) {
|
||||
|
||||
if (this.canvas.mozRequestFullScreen) {
|
||||
this.canvas.mozRequestFullScreen();
|
||||
} else {
|
||||
this.canvas.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else {
|
||||
document.webkitCancelFullScreen();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
get isFullScreen () {
|
||||
return document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
155
src/classes/gamepadinput.js
Normal file
155
src/classes/gamepadinput.js
Normal file
@ -0,0 +1,155 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
class Gamepad {
|
||||
|
||||
constructor (gamepadObj) {
|
||||
this._gamepadObj = gamepadObj;
|
||||
}
|
||||
|
||||
get numButtons () {
|
||||
return this._gamepadObj.buttons.length;
|
||||
}
|
||||
|
||||
get numAxis () {
|
||||
return this._gamepadObj.axes.length;
|
||||
}
|
||||
|
||||
isPressed (buttonId) {
|
||||
|
||||
if (this._gamepadObj.buttons[buttonId]) {
|
||||
return !!this._gamepadObj.buttons[buttonId].pressed;
|
||||
} else {
|
||||
throw new Error(`Button ${buttonId} not found on gamepad`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getAxis (axisId) {
|
||||
|
||||
if (typeof this._gamepadObj.axes[axisId] !== "undefined") {
|
||||
return this._gamepadObj.axes[axisId];
|
||||
} else {
|
||||
throw new Error(`Axis ${axisId} not found on gamepad`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class GamepadInput {
|
||||
|
||||
|
||||
constructor () {
|
||||
|
||||
var self = this;
|
||||
|
||||
self._gamepadState = {};
|
||||
self.gamepadIds = [];
|
||||
|
||||
if ('ongamepadconnected' in window) {
|
||||
|
||||
window.addEventListener("gamepadconnected", (event) => {
|
||||
self._gamepadState[event.gamepad.index] = new Gamepad(event.gamepad);
|
||||
self.gamepadIds.push(event.gamepad.index);
|
||||
console.log(`Gamepad ${event.gamepad.index} connected`);
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", (event) => {
|
||||
delete self._gamepadState[event.gamepad.index];
|
||||
self.gamepadIds.splice(self.gamepadIds.indexOf(event.gamepad.index));
|
||||
console.log(`Gamepad ${event.gamepad.index} disconnected`);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
update () {
|
||||
|
||||
if (!("ongamepadconnected" in window)) {
|
||||
|
||||
let gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
|
||||
|
||||
// If there are more gamepads registered than we know about, make ourselves aware of the new ones
|
||||
if (gamepads.length != this.gamepadIds.length) {
|
||||
|
||||
for (let i = 0; i < gamepads.length; i++) {
|
||||
|
||||
let gamepad = gamepads[i];
|
||||
|
||||
if (gamepad) {
|
||||
|
||||
if (this.gamepadIds.indexOf(gamepad.index) < 0) {
|
||||
this._gamepadState[gamepad.index] = new Gamepad(gamepad);
|
||||
this.gamepadIds.push(gamepad.index);
|
||||
|
||||
console.log(`Gamepad ${gamepad.index} connected`);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If there is still a mismatch, then we assume some gamepads have been disconnected, so we need to remove them
|
||||
if (gamepads.length != this.gamepadIds.length) {
|
||||
|
||||
for (let i = 0; i < this.gamepadIds.length; i++) {
|
||||
|
||||
let found = false;
|
||||
|
||||
for (let j = 0; j < gamepads.length; j++) {
|
||||
|
||||
let gamepad = gamepads[i];
|
||||
|
||||
if (gamepad && gamepad.index == this.gamepadIds[i]) {
|
||||
found = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
|
||||
console.log(`Gamepad ${this.gamepadIds[i]} disconnected`);
|
||||
|
||||
delete this._gamepadState[this.gamepadIds[i]];
|
||||
this.gamepadIds.splice(this.gamepadIds.indexOf(this.gamepadIds[i]));
|
||||
|
||||
i--;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
get numGamepads () {
|
||||
return this.gamepadIds.length;
|
||||
}
|
||||
|
||||
|
||||
getGamepadById (gamepadId) {
|
||||
|
||||
if (this._gamepadState[gamepadId]) {
|
||||
return this._gamepadState[gamepadId];
|
||||
} else {
|
||||
throw new Error(`Gamepad ${buttonId} is not connected`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default GamepadInput;
|
52
src/classes/imageloader.js
Normal file
52
src/classes/imageloader.js
Normal file
@ -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;
|
@ -99,6 +99,10 @@ const KeyConsts = {
|
||||
};
|
||||
|
||||
|
||||
let wasReleased = {},
|
||||
wasPressed = {};
|
||||
|
||||
|
||||
class KeyboardInput {
|
||||
|
||||
|
||||
@ -119,16 +123,52 @@ class KeyboardInput {
|
||||
|
||||
|
||||
isPressed (keyCode) {
|
||||
return !!this._keyState[keyCode];
|
||||
console.log("[MomentumEngine] WARNING: MomentumEngine.Classes.KeyboardInput.isPressed is deprecated. Use isDown instead.")
|
||||
return !!this._keyState[keyCode];
|
||||
}
|
||||
|
||||
|
||||
isDown (keyCode) {
|
||||
|
||||
return !!this._keyState[keyCode];
|
||||
}
|
||||
|
||||
|
||||
wasPressed (keyCode) {
|
||||
|
||||
let pressed = !!wasPressed[keyCode];
|
||||
|
||||
if (pressed) {
|
||||
wasPressed[keyCode] = false;
|
||||
}
|
||||
|
||||
return pressed;
|
||||
|
||||
}
|
||||
|
||||
|
||||
wasReleased (keyCode) {
|
||||
|
||||
let pressed = !!wasReleased[keyCode];
|
||||
|
||||
if (pressed) {
|
||||
wasReleased[keyCode] = false;
|
||||
}
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
|
||||
_keyDownHandler (event) {
|
||||
wasReleased[event.keyCode] = false;
|
||||
wasPressed[event.keyCode] = true;
|
||||
this._keyState[event.keyCode] = true;
|
||||
}
|
||||
|
||||
|
||||
_keyUpHandler (event) {
|
||||
wasReleased[event.keyCode] = true;
|
||||
wasPressed[event.keyCode] = false;
|
||||
this._keyState[event.keyCode] = false;
|
||||
}
|
||||
|
||||
|
57
src/classes/path.js
Normal file
57
src/classes/path.js
Normal file
@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
import CollisionMethods from "../libs/collisionmethods.js";
|
||||
|
||||
|
||||
class Path extends Entity {
|
||||
|
||||
|
||||
constructor (x, y, width, height, color) {
|
||||
|
||||
super(x, y);
|
||||
|
||||
if (width instanceof Array) {
|
||||
color = height;
|
||||
this.coords = width;
|
||||
} else {
|
||||
this.coords = [new Vector2D(width, height)];
|
||||
}
|
||||
|
||||
this.color = color;
|
||||
|
||||
}
|
||||
|
||||
|
||||
render () {
|
||||
|
||||
if (this._game) {
|
||||
|
||||
let ctx = this._game.context;
|
||||
ctx.strokeStyle = this.color.toString();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(this._scaleForLeft(this.relativeLeft), this._scaleForTop(this.relativeTop));
|
||||
|
||||
for (let coord in this.coords) {
|
||||
ctx.lineTo(this._scaleForWidth(this.relativeLeft + this.coords[coord].x), this._scaleForHeight(this.relativeTop + this.coords[coord].y));
|
||||
}
|
||||
|
||||
//ctx.closePath();
|
||||
ctx.stroke();
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Path;
|
@ -1,25 +1,42 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
import CollisionMethods from "../libs/collisionmethods.js";
|
||||
|
||||
|
||||
/**
|
||||
* Class representing a rectangle in a scene
|
||||
* @extends Entity
|
||||
*/
|
||||
class Rect extends Entity {
|
||||
|
||||
|
||||
/**
|
||||
* Create a rectangle
|
||||
* @param {number} x - x (Left) position of the rectangle
|
||||
* @param {number} y - y (Top) position of the rectangle
|
||||
* @param {number} width - Width of the rectangle
|
||||
* @param {number} height - Height of the rectangle
|
||||
* @param {Color} color - Color of the rectangle
|
||||
*/
|
||||
constructor (x, y, width, height, color) {
|
||||
|
||||
super(x, y);
|
||||
|
||||
this.size = new Vector2D(width, height);
|
||||
this.size.x = width || 0;
|
||||
this.size.y = height || 0;
|
||||
this.color = color;
|
||||
|
||||
}
|
||||
|
||||
|
||||
isColliding (entity) {
|
||||
/**
|
||||
* Detects if the rectangle is colliding with another entity
|
||||
* @param {Entity} entity - Entity to check against
|
||||
* @returns {boolean} Indicates whether the entities are colliding
|
||||
*/
|
||||
isCollidingWith (entity) {
|
||||
|
||||
if (entity instanceof Rect) {
|
||||
return CollisionMethods.AABB(this, entity);
|
||||
@ -30,12 +47,33 @@ class Rect extends 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);
|
||||
let left = this._scaleForLeft(this.relativeLeft),
|
||||
top = this._scaleForTop(this.relativeTop),
|
||||
width = this._scaleForWidth(this.width),
|
||||
height = this._scaleForHeight(this.height),
|
||||
ctx = this._game.context;
|
||||
|
||||
let xRot = left + (width / 2),
|
||||
yRot = top + (height / 2);
|
||||
|
||||
if (this.rotation > 0) {
|
||||
// Rotate the canvas based on the central point of this entity
|
||||
ctx.translate(xRot, yRot);
|
||||
ctx.rotate(this.rotation * Math.PI / 180);
|
||||
ctx.translate(-xRot, -yRot);
|
||||
}
|
||||
|
||||
ctx.fillStyle = this.color.toString();
|
||||
ctx.fillRect(left, top, width, height);
|
||||
|
||||
if (this.rotation > 0) {
|
||||
// Rotate back after drawing
|
||||
ctx.translate(xRot, yRot);
|
||||
ctx.rotate(-(this.rotation * Math.PI / 180));
|
||||
ctx.translate(-xRot, -yRot);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
|
79
src/classes/sprite.js
Normal file
79
src/classes/sprite.js
Normal file
@ -0,0 +1,79 @@
|
||||
"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.x = width || 0;
|
||||
this.size.y = 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 () {
|
||||
return (this._image.isLoaded() && !this._image.isError());
|
||||
}
|
||||
|
||||
|
||||
render () {
|
||||
|
||||
if (this.isReady() && this._game) {
|
||||
|
||||
let imageObj = this._image.getImageObj();
|
||||
|
||||
let 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._scaleForLeft(this.relativeLeft),
|
||||
this._scaleForTop(this.relativeTop),
|
||||
this._scaleForWidth(this.width || subWidth),
|
||||
this._scaleForHeight(this.height || subHeight)
|
||||
);
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Sprite;
|
113
src/classes/text.js
Normal file
113
src/classes/text.js
Normal file
@ -0,0 +1,113 @@
|
||||
"use strict";
|
||||
|
||||
import Entity from "./entity.js";
|
||||
import Vector2D from "./vector2d.js";
|
||||
|
||||
|
||||
class Text extends Entity {
|
||||
|
||||
|
||||
constructor (x, y, font, text) {
|
||||
|
||||
super(x, y);
|
||||
|
||||
this.font = font;
|
||||
this.text = text;
|
||||
|
||||
this.textAlign = "start";
|
||||
this.textBaseline = "alphabetic";
|
||||
this.direction = "inherit";
|
||||
this.letterSpacing = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
isCollidingWith (entity) {
|
||||
|
||||
if (entity instanceof Rect) {
|
||||
return CollisionMethods.AABB(this, entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
_renderText (text, x, y, letterSpacing, renderFunc) {
|
||||
|
||||
// Code modified from original by David Hong (sozonov): https://jsfiddle.net/sozonov/mg1jkz3q/
|
||||
|
||||
if (!text || typeof text !== "string" || text.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (letterSpacing == 0) {
|
||||
renderFunc(text, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
let characters = text.split(""),
|
||||
index = 0,
|
||||
current,
|
||||
currentPosition = x,
|
||||
align = 1;
|
||||
|
||||
if (this.textAlign === "right") {
|
||||
characters = characters.reverse();
|
||||
align = -1;
|
||||
} else if (this.textAlign === "center") {
|
||||
|
||||
let totalWidth = 0;
|
||||
|
||||
for (let i = 0; i < characters.length; i++) {
|
||||
// NK: We want to cache the results of measureText instead of recalculating every character every frame
|
||||
totalWidth += (this._game.context.measureText(characters[i]).width + letterSpacing);
|
||||
}
|
||||
|
||||
currentPosition = x - (totalWidth / 2);
|
||||
|
||||
}
|
||||
|
||||
while (index < text.length) {
|
||||
|
||||
current = characters[index++];
|
||||
renderFunc(current, currentPosition, y);
|
||||
// NK: We want to cache the results of measureText instead of recalculating every character every frame
|
||||
currentPosition += (align * (this._game.context.measureText(current).width + letterSpacing));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
render () {
|
||||
|
||||
if (this._game) {
|
||||
|
||||
this._game.context.font = `${this.font.size} ${this.font.family}`;
|
||||
|
||||
this._game.context.textAlign = this.textAlign;
|
||||
this._game.context.textBaseline = this.textBaseline;
|
||||
this._game.context.direction = this.direction;
|
||||
|
||||
if (this.font.fill) {
|
||||
this._game.context.fillStyle = this.font.fill;
|
||||
this._renderText(this.text, this._scaleForLeft(this.relativeLeft), this._scaleForTop(this.relativeTop), this.letterSpacing, this._game.context.fillText.bind(this._game.context));
|
||||
}
|
||||
|
||||
if (this.font.stroke) {
|
||||
this._game.context.strokeStyle = this.font.stroke;
|
||||
this._renderText(this.text, this._scaleForLeft(this.relativeLeft), this._scaleForTop(this.relativeTop), this.letterSpacing, this._game.context.strokeText.bind(this._game.context));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Text;
|
@ -101,13 +101,18 @@ class Vector2D {
|
||||
}
|
||||
|
||||
|
||||
angle () {
|
||||
return Math.atan2(this.x, this.y);
|
||||
}
|
||||
|
||||
|
||||
toArray () {
|
||||
return [this.x, this.y];
|
||||
}
|
||||
|
||||
|
||||
toString () {
|
||||
return `[${this.x}},${this.y}}]`;
|
||||
return `[${this.x},${this.y}]`;
|
||||
}
|
||||
|
||||
|
||||
@ -116,6 +121,11 @@ class Vector2D {
|
||||
}
|
||||
|
||||
|
||||
static fromAngle (angle, length) {
|
||||
return new Vector2D(length * Math.cos(angle), length * Math.sin(angle));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
26
src/es5.js
26
src/es5.js
@ -1,20 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
import Game from "./classes/game.js";
|
||||
import Emitter from "./classes/emitter.js";
|
||||
import Field from "./classes/field.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 Path from "./classes/path.js";
|
||||
import Color from "./classes/color.js";
|
||||
import Text from "./classes/text.js";
|
||||
import Font from "./classes/font.js";
|
||||
import AudioTrack from "./classes/audio.js";
|
||||
import ImageLoader from "./classes/imageloader.js";
|
||||
|
||||
import {KeyConsts} from "./classes/keyboardinput.js";
|
||||
|
||||
|
||||
const Classes = {
|
||||
Game: Game,
|
||||
Entity: Entity,
|
||||
Rect: Rect,
|
||||
Vector2D: Vector2D,
|
||||
Color: Color
|
||||
Game,
|
||||
Emitter,
|
||||
Field,
|
||||
Entity,
|
||||
Sprite,
|
||||
Rect,
|
||||
Path,
|
||||
Vector2D,
|
||||
Color,
|
||||
Text,
|
||||
Font,
|
||||
AudioTrack,
|
||||
ImageLoader
|
||||
};
|
||||
|
||||
|
||||
|
28
src/es6.js
28
src/es6.js
@ -1,20 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
import Game from "./classes/game.js";
|
||||
import Emitter from "./classes/emitter.js";
|
||||
import Field from "./classes/field.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 Path from "./classes/path.js";
|
||||
import Color from "./classes/color.js";
|
||||
import Text from "./classes/text.js";
|
||||
import Font from "./classes/font.js";
|
||||
import AudioTrack from "./classes/audio.js";
|
||||
import ImageLoader from "./classes/imageloader.js";
|
||||
|
||||
import {KeyConsts} from "./classes/keyboardinput.js";
|
||||
|
||||
|
||||
const Classes = {
|
||||
Game: Game,
|
||||
Entity: Entity,
|
||||
Rect: Rect,
|
||||
Vector2D: Vector2D,
|
||||
Color: Color
|
||||
Game,
|
||||
Emitter,
|
||||
Field,
|
||||
Entity,
|
||||
Sprite,
|
||||
Rect,
|
||||
Path,
|
||||
Vector2D,
|
||||
Color,
|
||||
Text,
|
||||
Font,
|
||||
AudioTrack,
|
||||
ImageLoader
|
||||
};
|
||||
|
||||
|
||||
@ -25,7 +41,7 @@ const Consts = {
|
||||
};
|
||||
|
||||
|
||||
export {
|
||||
export default {
|
||||
Classes,
|
||||
Consts
|
||||
};
|
@ -1,5 +1,4 @@
|
||||
import Rect from "../classes/rect.js";
|
||||
import Vector2D from "../classes/vector2d.js";
|
||||
|
||||
class CollisionMethods {
|
||||
|
||||
@ -10,10 +9,12 @@ class CollisionMethods {
|
||||
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);
|
||||
let colliding = (entity1.left < entity2.left + entity2.width &&
|
||||
entity1.left + entity1.width > entity2.left &&
|
||||
entity1.top < entity2.top + entity2.height &&
|
||||
entity1.height + entity1.top > entity2.top);
|
||||
|
||||
return colliding;
|
||||
|
||||
}
|
||||
|
||||
|
20
src/libs/utils.js
Normal file
20
src/libs/utils.js
Normal file
@ -0,0 +1,20 @@
|
||||
class Utils {
|
||||
|
||||
|
||||
static mergeIntoArray (dest, source) {
|
||||
|
||||
source.forEach((item) => {
|
||||
if (dest.indexOf(item) < 0) {
|
||||
dest.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
return dest;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default Utils;
|
Loading…
x
Reference in New Issue
Block a user