pixelbox
Version:
A framework to fast-prototype pixel-based games
502 lines (415 loc) • 17.2 kB
JavaScript
/**
* Pixelbox Bootstrap
* @author Cedric Stoquer
*
* @license
* This file is part of the Pixelbox SDK.
*
* Copyright (C) 2016-2020 Cedric Stoquer
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
var pixelbox = require('.');
var projectData = __PROJECT_DATA__;
// projectData.settings.buildTime = __BUILD_TIME__;
// projectData.settings.version = __GAME_VERSION__;
// projectData.settings.pixelboxVersion = __PIXELBOX_VERSION__;
var settings = pixelbox.settings = projectData.settings;
var assetLoader = require('./assetLoader');
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// built-in modules
pixelbox.inherits = require('./inherits');
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// electron
if (__BUILD_TYPE__ === 'electron') {
var remote = require('electron').remote;
pixelbox.setFullScreen = function(value) {
remote.getCurrentWindow().setFullScreen(value);
}
} else {
pixelbox.setFullScreen = function () {};
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// TINA
if (__USE_TINA__) {
var TINA = require('tina');
pixelbox.TINA = TINA;
// setup TINA with a ticker
var ticker = new TINA.Ticker().useAsDefault();
TINA.add(ticker);
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// Polyfill to ensure only one instance of AudioContext
(function(){
var audioContextInstance = null;
var AudioContext = window.AudioContext || window.webkitAudioContext;
function resumeContext() {
audioContextInstance.resume();
document.body.removeEventListener('mousedown', resumeContext);
document.body.removeEventListener('touchstart', resumeContext);
}
window.AudioContext = window.webkitAudioContext = function () {
if (audioContextInstance) return audioContextInstance;
audioContextInstance = new AudioContext();
document.body.addEventListener('mousedown', resumeContext);
document.body.addEventListener('touchstart', resumeContext);
return audioContextInstance;
}
})();
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// Audio Manager
if (__USE_AUDIO__ || __USE_BLEEPER__) {
var AudioManager = require('audio-manager');
var audioManager = pixelbox.audioManager = new AudioManager(['sfx']);
audioManager.settings.audioPath = 'audio/';
audioManager.settings.defaultFade = 0.3;
audioManager.init();
audioManager.setVolume('sfx', 1.0);
pixelbox.sfx = function (soundId, volume, panoramic, pitch) {
audioManager.playSound('sfx', soundId, volume, panoramic, pitch);
};
pixelbox.music = function (soundId, volume, loopStart, loopEnd) {
if (!soundId) {
audioManager.stopLoopSound('sfx');
return;
}
audioManager.playLoopSound('sfx', soundId, volume, 0, 0, loopStart, loopEnd);
};
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// controls
var button = pixelbox.btn = {};
var bpress = pixelbox.btnp = {};
var brelease = pixelbox.btnr = {};
var keyMap = {};
var buttonNames = [];
var buttonLength = 0;
(function setupKeyMap() {
var controls = settings.controls || { up: 38, down: 40, left: 37, right: 39, A: 32, B: 88 };
for (var key in controls) {
var keyCode = controls[key];
keyMap[keyCode] = key;
button[key] = false;
bpress[key] = false;
brelease[key] = false;
buttonNames.push(key);
}
buttonLength = buttonNames.length;
})();
if (__KEYBOARD__) {
function resetControlTriggers() {
for (var i = 0; i < buttonLength; i++) {
bpress[buttonNames[i]] = false;
brelease[buttonNames[i]] = false;
}
}
function keyChange(keyCode, isPressed) {
var key = keyMap[keyCode];
if (!key) return;
if ( isPressed && !button[key]) bpress[key] = true;
if (!isPressed && button[key]) brelease[key] = true;
button[key] = isPressed;
}
window.addEventListener('keydown', function onKeyPressed(e) {
e.preventDefault();
if (e.repeat) return;
keyChange(e.keyCode, true);
});
window.addEventListener('keyup', function onKeyRelease(e) {
e.preventDefault();
keyChange(e.keyCode, false);
});
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// utility functions
pixelbox.chr$ = function (chr) {
return String.fromCharCode(chr);
};
pixelbox.clamp = function (value, min, max) {
return Math.max(min, Math.min(max, value));
};
pixelbox.random = function (n, max) {
if (max === undefined) return ~~(n * Math.random());
return ~~(n + Math.random() * (max - n));
};
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// pixelbox Core
var webGL;
if (__USE_CORE__) {
var Texture = require('./Texture');
var TileMap = require('./TileMap');
if (__USE_WEBGL__) {
webGL = require('./webGL');
require('./Texture/webGL');
require('./TileMap/webGL');
pixelbox.webGL = webGL;
if (__MINI_TEXT__) require('./Texture/textWebGL');
} else {
require('./Texture/canvas2D');
if (__MINI_TEXT__) require('./Texture/textCanvas2D');
}
var TILE_WIDTH = settings.tileSize.width;
var TILE_HEIGHT = settings.tileSize.height;
Texture.setTileSize(TILE_WIDTH, TILE_HEIGHT);
TileMap.setTileSize(TILE_WIDTH, TILE_HEIGHT);
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// Texture
pixelbox.texture = function (img) {
var texture = new Texture(img.width, img.height);
texture.clear().draw(img, 0, 0);
return texture;
}
/** change default tilesheet used.
* @param {Image | Texture | Map} img - tilesheet to use as default.
* It can be any renderable thing in Pixelbox
*/
pixelbox.tilesheet = function(img) {
return Texture.prototype.setGlobalTilesheet(img);
};
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// screen
function createScreen() {
var params = settings.screen;
var width = params.width;
var height = params.height;
var pixelSize = params.pixelSize;
var texture = pixelbox.$screen = new Texture(width, height);
if (__USE_WEBGL__) texture.canvas = webGL.context.canvas;
var canvas = texture.canvas;
var style = canvas.style;
document.body.appendChild(canvas);
if (params.fullscreen) {
if (params.keepAspectRatio) {
function resizeScreen() {
var iw = window.innerWidth;
var ih = window.innerHeight;
var sw = width * pixelSize.width;
var sh = height * pixelSize.height;
var ratio = Math.min(iw / sw, ih / sh);
var fw = sw * ratio;
var fh = sh * ratio;
// get current canvas (allow $screen redefinition, e.g. webGL)
var style = pixelbox.$screen.canvas.style;
style.width = fw + 'px';
style.height = fh + 'px';
style.left = (iw - fw) / 2 + 'px';
style.top = (ih - fh) / 2 + 'px';
}
addEventListener('resize', resizeScreen);
resizeScreen();
} else {
style.width = style.height = '100%';
}
} else {
style.width = width * pixelSize.width + 'px';
style.height = height * pixelSize.height + 'px';
}
return texture;
}
var screen = createScreen();
pixelbox.cls = function () { return screen.cls(); };
pixelbox.sprite = function (s, x, y, h, v, r) { return screen.sprite(s, x, y, h, v, r); };
pixelbox.draw = function (i, x, y, h, v, r) { return screen.draw(i, x, y, h, v, r); };
pixelbox.rect = function (x, y, w, h) { return screen.rect(x, y, w, h); };
pixelbox.rectf = function (x, y, w, h) { return screen.rectf(x, y, w, h); };
pixelbox.camera = function (x, y) { return screen.setCamera(x, y); };
pixelbox.pen = function (paletteIndex) { return screen.pen(paletteIndex); };
pixelbox.paper = function (paletteIndex) { return screen.paper(paletteIndex); };
if (__MINI_TEXT__) {
pixelbox.locate = function (i, j) { return screen.locate(i, j); };
pixelbox.print = function (text, x, y) { return screen.print(text, x, y); };
pixelbox.println = function (text) { return screen.println(text); };
pixelbox.setCharset = function (img) { return screen.setCharset(img); };
}
pixelbox.getMap = TileMap.getMap;
pixelbox.rectfill = pixelbox.rectf; // legacy
if (__NO_CONTEXT_MENU__) {
// disable browser's context menu
screen.canvas.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; });
}
} else {
if (__NO_CONTEXT_MENU__) {
document.body.addEventListener('contextmenu', function (e) { e.preventDefault(); return false; });
}
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// main
var FRAME_INTERVAL = 1 / 60;
var requestAnimationFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function nextFrame(callback) { window.setTimeout(callback, FRAME_INTERVAL); };
window.frameId = 0;
function onAssetsLoaded(error, assets) {
if (__USE_CORE__) screen.paper(0).pen(1).cls();
if (error) {
console.error(error);
print(error);
return;
}
pixelbox.assets = assets;
// expose pixelbox on global scope
for (var key in pixelbox) {
window[key] = pixelbox[key];
}
if (__USE_BLEEPER__) {
var bleeper = pixelbox.bleeper = require('./bleeper');
window.bleeper = bleeper;
// NOTA: `assets.bleeper` program is overwritten with a sound map
if (assets.bleeper) bleeper.loadProgram(assets.bleeper);
}
if (__USE_TRACKER__) {
var patatracker = pixelbox.patatracker = require('./pataTracker');
window.patatracker = patatracker;
if (assets.patatracker) patatracker.loadData(assets.patatracker);
}
if (__HAS_ATLAS__) {
require('./spritesheet').unpackSpritesheets();
}
if (__USE_CORE__) {
// extract palette
function getPalette() {
var Color = require('./Color');
var palette = settings.palette;
if (palette.file) {
// TODO: allow file to be json data
var paths = palette.file.split('/');
var img = assets;
while (img && paths.length) img = img[paths.shift()];
if (!img) return; // TODO: default palette
var w = img.width;
var h = img.height;
var ox = img.x || 0;
var oy = img.y || 0;
if (img._isSprite) img = img.img;
var createCanvas = require('./domUtils/createCanvas');
var ctx = createCanvas(w, h).getContext('2d');
ctx.drawImage(img, ox, oy, w, h, 0, 0, w, h);
var pixels = ctx.getImageData(0, 0, w, h).data;
var colors = [];
for (var i = 0; i < pixels.length; i += 4) {
if (pixels[i + 3] <= 1) continue;
colors.push(new Color().fromPixel(pixels, i));
}
screen.setPalette(colors);
} else {
var colorConfig;
if (palette.colors) {
colorConfig = palette.colors
} else {
// TODO: to be deprecated. assuming legacy format, an array of css colors strings
colorConfig = palette;
}
var colors = [];
for (var i = 0; i < colorConfig.length; i++) {
colors.push(new Color().fromString(colorConfig[i]));
}
screen.setPalette(colors);
}
}
getPalette();
// set default tilesheet
if (assets.tilesheet) pixelbox.tilesheet(assets.tilesheet);
// setup all maps
TileMap.loadBank(assets.maps);
// setup nine slice
if (assets.slicesConfig) {
var initNineSlices = require('./NineSlice/init');
initNineSlices(assets.slicesConfig);
}
}
if (__GAMEPAD__) {
var gamepad = require('./gamepad');
var gamepadSettings = settings.gamepad || {};
if (gamepadSettings.analogToDpad) {
gamepad.mapAnalogStickToDpad(gamepadSettings.deadZone);
}
}
// `main.js` from `node_modules/pixelbox/boostrap`
var main = require('../../src/main.js');
if (!main.update) {
if (__USE_WEBGL__) {
// still need an update loop to commit webGL
function updateWebGL() {
webGL.commit();
requestAnimationFrame(updateWebGL);
}
updateWebGL();
}
return;
}
if (__USE_WEBGL__) webGL.batcher.flush();
function update() {
window.frameId += 1;
if (__USE_TINA__) TINA.update(); // update all tweeners
if (__GAMEPAD__) gamepad.update();
main.update(); // call main update function
if (__KEYBOARD__) resetControlTriggers(); // reset button pressed and release
// TODO: pointer/cursor/mouse/touch reset
if (__USE_WEBGL__) webGL.commit();
requestAnimationFrame(update);
}
if (__KEYBOARD__) resetControlTriggers();
update();
}
//▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
// loader progress bar
if (__CUSTOM_LOADER__) {
var customLoader = require('../../src/' + __CUSTOM_LOADER__);
function nopAsync(cb) { return cb(); }
var customStart = customLoader.onStart || nopAsync;
var customEnd = customLoader.onEnd || nopAsync;
var customProgress = customLoader.onProgress || function (load, current, count, percent) {};
customStart(function () {
assetLoader.preloadStaticAssets(
projectData,
function (error, assets) {
customEnd(function () {
onAssetsLoaded(error, assets);
}, error);
},
customProgress
);
});
} else if (__USE_CORE__) {
var CENTER = ~~(settings.screen.width / 2);
var HALF_WIDTH = ~~(settings.screen.width / 4);
var MIDDLE = ~~(settings.screen.height / 2);
function showProgress(load, current, count, percent) {
screen
.rect(CENTER - HALF_WIDTH - 2, MIDDLE - 4, HALF_WIDTH * 2 + 4, 8)
.rectf(CENTER - HALF_WIDTH, MIDDLE - 2, ~~(percent * HALF_WIDTH * 2), 4);
if (__USE_WEBGL__) webGL.commit();
}
var DEFAULT_LOADER_COLORS = ['#000', '#FFF'];
var loaderColors = (settings.loader && settings.loader.colors) || settings.loaderColors || DEFAULT_LOADER_COLORS;
if (!Array.isArray(loaderColors)) loaderColors = DEFAULT_LOADER_COLORS;
var Color = require('./Color');
screen.setPalette([
new Color().fromString(loaderColors[0]),
new Color().fromString(loaderColors[1])
]);
screen.paper(0).cls().paper(1).pen(1).rect(CENTER - HALF_WIDTH - 2, MIDDLE - 4, HALF_WIDTH * 2 + 4, 8); // loading bar border
if (__USE_WEBGL__) webGL.commit();
assetLoader.preloadStaticAssets(projectData, onAssetsLoaded, showProgress);
} else {
assetLoader.preloadStaticAssets(projectData, onAssetsLoaded);
}