avg-core-ts
Version:
Develop Adventure Game (or ADV, Visual Novel, Galgame, etc.) on your own!
310 lines (266 loc) • 7.01 kB
JavaScript
/**
* @file core
* @author MicroMatrix <yangpan.1985@bytedance.com>
* @copyright 2012-2020 bytedance
* @link git@code.byted.org:yangpan.1985/avg.git
*/
import compose from 'koa-compose';
import FontFaceObserver from 'fontfaceobserver';
import { render as renderReact } from 'react-dom';
import Container from 'classes/Container';
import { attachToSprite } from 'classes/EventManager';
import sayHello from 'utils/sayHello';
import fitWindow from 'utils/fitWindow';
import Logger from './logger';
import { init as preloaderInit, getTexture, load as loadResources } from './preloader';
import Ticker from './ticker';
const PIXI = require('pixi.js');
const isMobile = require('ismobilejs');
const logger = Logger.create('Core');
/**
* Core of AVG.js, you can start your game development from here.
*
* @class
* @memberof AVG
*/
class Core {
constructor() {
/**
* @type {Boolean}
* @private
* @readonly
*/
this._init = false;
this._tickTime = 0;
this.renderer = null;
this.stage = null;
this.canvas = null;
this.options = {};
/**
* Currently used middleware
* @member {object<string, function[]>}
* @default {}
* @private
*/
this.middlewares = {};
this.plugins = {};
this.assetsPath = null;
}
/**
* install a middleware.
*
* @param {string} name signal name
* @param {function} middleware instance of middleware
* @see AVG.core.Middleware
*/
use(name, middleware) {
let middlewares;
if (!this.middlewares[name]) {
middlewares = [];
this.middlewares[name] = middlewares;
} else {
middlewares = this.middlewares[name];
}
middlewares.push(middleware);
}
/**
* uninstall a middleware.
*
* @param {string} name signal name
* @param {function} middleware instance of middleware
* @see AVG.core.Middleware
*/
unuse(name, middleware) {
const middlewares = this.middlewares[name];
if (middlewares) {
const pos = middlewares.indexOf(middleware);
if (pos !== -1) {
middlewares.splice(pos, 1);
} else {
logger.warn(`Do not find the given middleware in ${name}.`);
}
} else {
logger.warn(`Do not find the given middleware in ${name}.`);
}
}
/**
* send a signal to core
*
* @param {string} name signal name
* @param {object} [context={}] context to process
* @param {function} next
* @return {promise}
*/
post(name, context, next) {
const middlewares = this.middlewares[name];
if (middlewares) {
return compose(middlewares)(context || {}, next);
}
return Promise.resolve();
}
/**
* install a plugin
*
* @param {any} constructor plugin class
*
* @memberOf Core
*/
installPlugin(constructor) {
new constructor(this);
}
/**
* Initial AVG.js core functions
*
* @param {number} width width of screen
* @param {number} height height of screen
* @param {object} [options]
* @param {HTMLCanvasElement} [options.view] custom canvas element
* @param {PIXI.WebGLRenderer} [options.renderer] custom renderer
* @param {string|array<string>} [options.fontFamily] load custom web-font
* @param {boolean} [options.fitWindow=false] auto scale canvas to fit window
* @param {string} [options.assetsPath='assets'] assets path
* @param {string} [options.tryWebp=false] auto replace image file extension with .webp format when webp is supported by browser
*/
async init(width, height, options = {}) {
if (this._init) {
return;
}
const _options = {
fitWindow: false,
assetsPath: '/',
tryWebp: false,
...options,
};
if (_options.fontFamily) {
const font = new FontFaceObserver(_options.fontFamily);
await font.load();
}
if (_options.renderer) {
this.renderer = _options.renderer;
} else {
/* create PIXI renderer */
if (isMobile.any) {
PIXI.settings.RESOLUTION = 1;
PIXI.settings.TARGET_FPMS = 0.03;
} else {
PIXI.settings.RESOLUTION = window.devicePixelRatio || 1;
}
this.renderer = new PIXI.WebGLRenderer(width, height, {
view: _options.view,
autoResize: true,
// resolution: 2,
roundPixels: true,
});
}
this.options = _options;
if (_options.fitWindow) {
window.addEventListener('resize', () => {
fitWindow(this.renderer, window.innerWidth, window.innerHeight);
});
fitWindow(this.renderer, window.innerWidth, window.innerHeight);
}
let assetsPath = _options.assetsPath;
if (!assetsPath.endsWith('/')) {
assetsPath += '/';
}
this.assetsPath = assetsPath;
preloaderInit(assetsPath, _options.tryWebp);
this.stage = new Container();
attachToSprite(this.stage);
this.stage._ontap = e => this.post('tap', e);
this.stage._onclick = e => this.post('click', e);
this.ticker = new Ticker();
this.ticker.add(this.tick.bind(this));
sayHello();
this._init = true;
}
getRenderer() {
if (this._init) {
return this.renderer;
}
logger.error('Renderer hasn\'t been initialed.');
return null;
}
getStage() {
if (this._init) {
return this.stage;
}
logger.error('Stage hasn\'t been initialed.');
return null;
}
getAssetsPath() {
return this.assetsPath;
}
getTexture(url) {
return getTexture(url);
}
/**
* create a logger for specific name
*
* @param {string} name
* @return {Logger} logger instance
*/
getLogger(name) {
return Logger.create(name);
}
// TODO: need more elegent code
loadAssets(list, onProgress) {
return loadResources(list, onProgress);
}
/**
* render surface to page.
*
* @param {React.Component} component
* @param {HTMLDOMElement} target
* @param {boolean} append whether append canvas element to target
* @return {Promise}
*/
async render(component, target, append = true) {
if (!this._init) {
throw Error('not initialed');
}
return new Promise(resolve => {
renderReact(component, target, resolve);
}).then(() => {
append && target.appendChild(this.renderer.view);
});
}
/**
* Get ticker of AVG.js render loop
*
* @return {PIXI.ticker.Ticker} shared ticker
*/
getTicker() {
return this.ticker;
}
/**
* @private
*/
tick(deltaTime) {
this._tickTime += deltaTime;
if (this._init && this._tickTime > 0.98) {
this.renderer.render(this.stage);
this._tickTime = 0;
window.stats && window.stats.update();
}
}
/**
* start rendering, this must be called if you want to start your game.
*/
start() {
this.ticker.start();
}
/**
* stop rendering
*/
stop() {
this.ticker.stop();
}
}
/**
* @export
* @type {Core}
*/
const core = new Core();
// Object.freeze(core);
export default core;