Browse Source

NiceGame update cycle can be stopped

(and wip on tweens and easing functions)
develop
Rob Colbert 2 years ago
parent
commit
e6851281ae
  1. 13
      LICENSE
  2. 27
      README.md
  3. 4
      lib/nice-audio.js
  4. 538
      lib/nice-easing.js
  5. 97
      lib/nice-game.js
  6. 3
      lib/nice-sprite.js
  7. 50
      lib/nice-tween.js

13
LICENSE

@ -0,0 +1,13 @@
Copyright 2022 Rob Colbert @[email protected]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

27
README.md

@ -1,6 +1,8 @@
# DTP Nice Game SDK
# Nice Game SDK
The Nice Game SDK is a lightweight HTML5 gaming engine that renders to <canvas> and supports the Web Audio API, media streaming, and an abstract input manager to help make the creation of mini-games, casual games, and playable memes as quick and simple as possible.
The Nice Game SDK is a lightweight HTML5 gaming engine that renders to &lt;canvas&gt; and supports the Web Audio API, media streaming, and an abstract input manager to help make the creation of mini-games, casual games, and playable memes as quick and simple as possible.
It doesn't care about your WebAssembly or [insert hipster deviation from JavaScript here]. It's a JavaScript engine. It's only a JavaScript engine. It's not even a super tricky JavaScript engine. It's the boilerplate nonsense you don't want to have to build from scratch in a JavaScript app or game with a frame rate that targets a &lt;canvas&gt;.
## Installing
@ -12,8 +14,29 @@ app.use('/dtp-nice-game', express.static(path.join(__dirname, 'node_modules', 'd
import { NiceGame } from '/dtp-nice-game/nice-game.js';
```
## Working on Nice Game SDK Itself
If you want to fork or contribute, the easy way is to `yarn link` the project into the game you're using to host library development. Set up your fork of `dtp-nice-game`, and run `yarn link` in the project root directory. This informs Yarn that a named project is available for use in other projects on the local machine.
In your game/app directory, you can run `yarn link dtp-nice-game`. This will provide the module from your local project instead of from NPM or however you originally installed the module. This command does not alter package.json, it simply alters `node_modules` to use a symlink to your local project directory instead of being a directory with the module's contents.
For more information about `yarn link`, you can read [the documentation](https://classic.yarnpkg.com/en/docs/cli/link).
## NiceGame
`NiceGame` is the class from which you will derive your app or game's main class, implement an update and render method, initialize things how you want them, and then start the engine. From that point, you are called once per frame to update your game logic and to render your game's presentation. These are discreet phases of the update loop that discourage interacting with visual output during the update(s) of game objects. This is by design.
`NiceGame` uses `requestAnimationFrame` and measures the time elapsed between calls using [window.performance.now](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now), which has millisecond granularity but is also a real number and specifies fractions of them - so - yeah. It's a "high resolution" timer. And, the library uses it to compute the time elapsed between calls to update.
```js
const now = performance.now(); // this frame's moment
const elapsedMs = Math.max(0, now - this.lastUpdateTime);
this.lastUpdateTime = now;
this.onGameUpdate(elapsedMs, now);
```
## NiceInput
## NiceAudio

4
lib/nice-audio.js

@ -147,9 +147,9 @@ export default class NiceAudio {
delete this.musicSource;
}
get musicVolume ( ) { return this.musicGain.value; }
get musicVolume ( ) { return this.musicGain.gain.value; }
set musicVolume (volume) {
this.musicGain.value = volume;
this.musicGain.gain.value = volume;
}
get haveMusicStream ( ) {

538
lib/nice-easing.js

@ -0,0 +1,538 @@
// lib/nice-easing.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
/*
* NiceEasing is derived from:
* https://github.com/kaelzhang/easing-functions
* https://raw.githubusercontent.com/kaelzhang/easing-functions/master/index.js
* License: [unclear, MIT, acknowledges Phaser.io]
*
* Which is derived from:
* https://github.com/photonstorm/phaser
* License: MIT (C) 2014 Phaser.io
*/
'use strict';
/**
* A collection of easing methods defining ease-in ease-out curves.
*
* @class Easing
*/
class NiceEasing { }
/**
* Linear easing.
*
* @class Easing.Linear
*/
class Linear {
/**
* Ease-in.
*
* @method Easing.Linear#In
* @param {number} k - The value to be tweened.
* @returns {number} Simply: k
*/
static None (k) {
return k;
}
}
/**
* Quadratic easing.
*
* @class Easing.Quadratic
*/
class Quadratic {
/**
* Ease-in.
*
* @method Easing.Quadratic#In
* @param {number} k - The value to be tweened.
* @returns {number} k^2.
*/
static In (k) {
return k * k;
}
/**
* Ease-out.
*
* @method Easing.Quadratic#Out
* @param {number} k - The value to be tweened.
* @returns {number} k* (2-k).
*/
static Out (k) {
return k * (2 - k);
}
/**
* Ease-in/out.
*
* @method Easing.Quadratic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k;
}
return -0.5 * (--k * (k - 2) - 1);
}
}
/**
* Cubic easing.
*
* @class Easing.Cubic
*/
class Cubic {
/**
* Cubic ease-in.
*
* @method Easing.Cubic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return k * k * k;
}
/**
* Cubic ease-out.
*
* @method Easing.Cubic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return --k * k * k + 1;
}
/**
* Cubic ease-in/out.
*
* @method Easing.Cubic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k;
}
return 0.5 * ((k -= 2) * k * k + 2);
}
}
/**
* Quartic easing.
*
* @class Easing.Quartic
*/
class Quartic {
/**
* Quartic ease-in.
*
* @method Easing.Quartic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return k * k * k * k;
}
/**
* Quartic ease-out.
*
* @method Easing.Quartic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return 1 - (--k * k * k * k);
}
/**
* Quartic ease-in/out.
*
* @method Easing.Quartic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k;
}
return -0.5 * ((k -= 2) * k * k * k - 2);
}
}
/**
* Quintic easing.
*
* @class Easing.Quintic
*/
class Quintic {
/**
* Quintic ease-in.
*
* @method Easing.Quintic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return k * k * k * k * k;
}
/**
* Quintic ease-out.
*
* @method Easing.Quintic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return --k * k * k * k * k + 1;
}
/**
* Quintic ease-in/out.
*
* @method Easing.Quintic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if ((k *= 2) < 1) {
return 0.5 * k * k * k * k * k;
}
return 0.5 * ((k -= 2) * k * k * k * k + 2);
}
}
/**
* Sinusoidal easing.
*
* @class Easing.Sinusoidal
*/
class Sinusoidal {
/**
* Sinusoidal ease-in.
*
* @method Easing.Sinusoidal#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return 1 - Math.cos(k * Math.PI / 2);
}
/**
* Sinusoidal ease-out.
*
* @method Easing.Sinusoidal#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return Math.sin(k * Math.PI / 2);
}
/**
* Sinusoidal ease-in/out.
*
* @method Easing.Sinusoidal#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
return 0.5 * (1 - Math.cos(Math.PI * k));
}
}
/**
* Exponential easing.
*
* @class Easing.Exponential
*/
class Exponential {
/**
* Exponential ease-in.
*
* @method Easing.Exponential#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return k === 0 ? 0 : Math.pow(1024, k - 1);
}
/**
* Exponential ease-out.
*
* @method Easing.Exponential#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k);
}
/**
* Exponential ease-in/out.
*
* @method Easing.Exponential#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if (k === 0) { return 0; }
if (k === 1) { return 1; }
if ((k *= 2) < 1) {
return 0.5 * Math.pow(1024, k - 1);
}
return 0.5 * (-Math.pow(2, - 10 * (k - 1)) + 2);
}
}
/**
* Circular easing.
*
* @class Easing.Circular
*/
class Circular {
/**
* Circular ease-in.
*
* @method Easing.Circular#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return 1 - Math.sqrt(1 - k * k);
}
/**
* Circular ease-out.
*
* @method Easing.Circular#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
return Math.sqrt(1 - (--k * k));
}
/**
* Circular ease-in/out.
*
* @method Easing.Circular#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if ((k *= 2) < 1) {
return - 0.5 * (Math.sqrt(1 - k * k) - 1);
}
return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
}
}
/**
* Elastic easing.
*
* @class Easing.Elastic
*/
class Elastic {
/**
* Elastic ease-in.
*
* @method Easing.Elastic#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
var s, a = 0.1, p = 0.4;
if (k === 0) { return 0; }
if (k === 1) { return 1; }
if (!a || a < 1) {
a = 1; s = p / 4;
} else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
return -(a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
}
/**
* Elastic ease-out.
*
* @method Easing.Elastic#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
var s, a = 0.1, p = 0.4;
if (k === 0) { return 0; }
if (k === 1) { return 1; }
if (!a || a < 1) {
a = 1; s = p / 4;
} else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
return (a * Math.pow(2, - 10 * k) * Math.sin((k - s) * (2 * Math.PI) / p) + 1);
}
/**
* Elastic ease-in/out.
*
* @method Easing.Elastic#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
var s, a = 0.1, p = 0.4;
if (k === 0) { return 0; }
if (k === 1) { return 1; }
if (!a || a < 1) {
a = 1; s = p / 4;
}
else {
s = p * Math.asin(1 / a) / (2 * Math.PI);
}
if ((k *= 2) < 1) {
return -0.5 * (a * Math.pow(2, 10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p));
}
return a * Math.pow(2, -10 * (k -= 1)) * Math.sin((k - s) * (2 * Math.PI) / p) * 0.5 + 1;
}
}
/**
* Back easing.
*
* @class Easing.Back
*/
class Back {
/**
* Back ease-in.
*
* @method Easing.Back#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
var s = 1.70158;
return k * k * ((s + 1) * k - s);
}
/**
* Back ease-out.
*
* @method Easing.Back#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
var s = 1.70158;
return --k * k * ((s + 1) * k + s) + 1;
}
/**
* Back ease-in/out.
*
* @method Easing.Back#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
var s = 1.70158 * 1.525;
if ((k *= 2) < 1) {
return 0.5 * (k * k * ((s + 1) * k - s));
}
return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
}
}
/**
* Bounce easing.
*
* @class Easing.Bounce
*/
class Bounce {
/**
* Bounce ease-in.
*
* @method Easing.Bounce#In
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static In (k) {
return 1 - NiceEasing.Bounce.Out(1 - k);
}
/**
* Bounce ease-out.
*
* @method Easing.Bounce#Out
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static Out (k) {
if (k < (1 / 2.75)) {
return 7.5625 * k * k;
} else if (k < (2 / 2.75)) {
return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
} else if (k < (2.5 / 2.75)) {
return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
} else {
return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
}
}
/**
* Bounce ease-in/out.
*
* @method Easing.Bounce#InOut
* @param {number} k - The value to be tweened.
* @returns {number} The tweened value.
*/
static InOut (k) {
if (k < 0.5) {
return NiceEasing.Bounce.In(k * 2) * 0.5;
}
return NiceEasing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5;
}
}
/*
* Assign "child" classes to NiceEasing
*/
NiceEasing.Linear = Linear;
NiceEasing.Quadratic = Quadratic;
NiceEasing.Cubic = Cubic;
NiceEasing.Quartic = Quartic;
NiceEasing.Quintic = Quintic;
NiceEasing.Sinusoidal = Sinusoidal;
NiceEasing.Exponential = Exponential;
NiceEasing.Circular = Circular;
NiceEasing.Elastic = Elastic;
NiceEasing.Back = Back;
NiceEasing.Bounce = Bounce;
/*
* Export NiceEasing
*/
export default NiceEasing;

97
lib/nice-game.js

@ -17,6 +17,8 @@ import {
NiceInputKey,
NiceInput,
} from './nice-input.js';
import NiceTween from './nice-tween.js';
import NiceEasing from './nice-easing.js';
class NiceGame {
@ -26,7 +28,12 @@ class NiceGame {
this.log = new NiceLog(this.componentName);
this.log.enable(true);
this.keepGoing = false;
this.sprites = { };
this.tweens = [ ];
this.rafCallback = this.onUpdateEngine.bind(this);
}
async startGameEngine (onGameUpdate, onGameRender) {
@ -47,19 +54,103 @@ class NiceGame {
this.gameDisplayCanvas.height = 540;
this.gameDisplayCtx = this.gameDisplayCanvas.getContext('2d');
}
this.rafCallback = this.onUpdateEngine.bind(this);
startUpdateLoop ( ) {
this.lastUpdate = performance.now();
this.keepGoing = true;
window.requestAnimationFrame(this.rafCallback);
}
stopUpdateLoop ( ) {
this.keepGoing = false;
}
/**
* Called by `window.requestAnimationFrame` when it's time to update the game.
* Will call the registered update and render callbacks for the game, then
* request another animation frame.
*/
onUpdateEngine ( ) {
if (!this.keepGoing) { return; }
const now = performance.now();
const elapsed = now - this.lastUpdate;
this.lastUpdate = now;
this.updateTweens(elapsed, now);
if (this.onGameUpdate && (typeof this.onGameUpdate === 'function')) {
this.onGameUpdate( );
this.onGameUpdate(elapsed, now);
}
if (this.onGameRender && (typeof this.onGameRender === 'function')) {
this.onGameRender(this.gameDisplayCtx);
}
window.requestAnimationFrame(this.rafCallback);
if (this.keepGoing) {
window.requestAnimationFrame(this.rafCallback);
}
}
/**
* Create a tween effecting the `target` `from` the starting values `to` the
* ending values with the specified `easing` function.
*
* To stop a running NiceTween, use it's `stop` method, or remove it with the
* `removeTween` method of NiceGame.
*
* @param {Object} target The target object to be manipulated by the tween.
* @param {*} from The starting values for properties to be controlled by the
* tween.
* @param {*} to The ending values for properties to be controlled by the
* tween.
* @param {NiceEasing} easing The easing function to apply to the tween.
* @returns The NiceTween instance, which can be immediately started or
* started whenever. The tween won't run until started, and may not stop if it
* is a loop or infinite chain.
*/
createTween (target, from, to, easing = NiceEasing.Linear.None) {
const tween = new NiceTween(target, from, to, easing);
this.tweens.push(tween);
return tween;
}
/**
* Internal method called during every update to update all registered
* NiceTween instances as a service. It is not necessary to call this method,
* and doing so will double-update all registered NiceTween instances.
*
* IN a real programming language, this method would be "private" but I'm not
* doing the stupid shit JavaScript wants to "pretend" it's private. Don't
* call the method, that's not how this works.
*
* @param {Number} elapsed Milliseconds elapsed since last call
* @param {Number} now Current global timestamp
*/
updateTweens (elapsed, now) {
const expiredTweens = [ ];
for (const tween of this.tweens) {
if (!tween.update(elapsed, now)) {
expiredTweens.push(tween);
}
}
for (const tween of expiredTweens) {
this.removeTween(tween);
}
}
/**
* Remove a NiceTween instance from the processing queue. Can be called by
* developers to effectively cancel the tween.
* @param {NiceTween} tween The tween to be removed from the processing queue.
*/
removeTween (tween) {
const idx = this.tweens.indexOf(tween);
if (idx === -1) {
return;
}
this.tweens.splice(idx, 1);
}
}

3
lib/nice-sprite.js

@ -5,6 +5,7 @@
'use strict';
import NiceImage from './nice-image.js';
import NiceVector2d from './nice-vector-2d.js';
export default class NiceSprite {
@ -36,7 +37,7 @@ export default class NiceSprite {
constructor (game, position = { x: 0, y: 0 }) {
this.game = game;
this.position = { x: position.x, y: position.y };
this.position = new NiceVector2d(position.x, position.y);
this.scale = { x: 1.0, y: 1.0 };
this.rotation = 0.0;
}

50
lib/nice-tween.js

@ -0,0 +1,50 @@
// lib/nice-tween.js
// Copyright (C) 2022 Rob Colbert @[email protected]
// License: Apache-2.0
'use strict';
import NiceEasing from './nice-easing.js';
/**
* Animate a list of properties on a target object over time using a
* mathematical easing function.
*/
export default class NiceTween {
/**
* NiceTween will animate a liste of properties on a target object over time.
* @param {Object} target The object being influenced by the tween.
* @param {*} from An object describing the starting values of affected
* properties.
* @param {*} to An object describing the ending values of affected
* properties.
* @param {*} easing The easing function to apply to the tween
*/
constructor (
target,
from,
to,
easing = NiceEasing.Linear.None,
) {
this.target = target;
this.from = from;
this.to = to;
this.easing = easing;
this.state = 'idle';
this.start = null;
this.elapsed = 0;
this.duration = 0;
}
start (duration) {
this.start = performance.now();
this.duration = duration;
this.state = 'running';
}
update (elapsed) {
}
}
Loading…
Cancel
Save