497 lines
15 KiB
JavaScript
497 lines
15 KiB
JavaScript
/**
|
|
* The renderer for a gdjs.RuntimeGame using Pixi.js.
|
|
* @class RuntimeGamePixiRenderer
|
|
* @memberof gdjs
|
|
* @param {gdjs.RuntimeGame} game The game that is being rendered
|
|
* @param {boolean} forceFullscreen If fullscreen should be always activated
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer = function(game, forceFullscreen) {
|
|
this._game = game;
|
|
|
|
this._isFullPage = true; //Used to track if the canvas is displayed on the full page.
|
|
this._isFullscreen = false; //Used to track if the window is displayed as fullscreen (see setFullscreen method).
|
|
this._forceFullscreen = forceFullscreen; //If set to true, the canvas will always be displayed as fullscreen, even if _isFullscreen == false.
|
|
|
|
/** @type {PIXI.SystemRenderer} */
|
|
this._pixiRenderer = null;
|
|
this._canvasWidth = 0; // Current width of the canvas (might be scaled down/up compared to renderer)
|
|
this._canvasHeight = 0; // Current height of the canvas (might be scaled down/up compared to renderer)
|
|
this._keepRatio = true;
|
|
this._marginLeft = this._marginTop = this._marginRight = this._marginBottom = 0;
|
|
};
|
|
|
|
gdjs.RuntimeGameRenderer = gdjs.RuntimeGamePixiRenderer; //Register the class to let the engine use it.
|
|
|
|
/**
|
|
* Create a standard canvas inside canvasArea.
|
|
*
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.createStandardCanvas = function(
|
|
parentElement
|
|
) {
|
|
//This prevents flickering on some mobile devices
|
|
PIXI.glCore.VertexArrayObject.FORCE_NATIVE = true;
|
|
|
|
//Create the renderer and setup the rendering area
|
|
//"preserveDrawingBuffer: true" is needed to avoid flickering and background issues on some mobile phones (see #585 #572 #566 #463)
|
|
this._pixiRenderer = PIXI.autoDetectRenderer(
|
|
this._game.getGameResolutionWidth(),
|
|
this._game.getGameResolutionHeight(),
|
|
{
|
|
preserveDrawingBuffer: true,
|
|
antialias: false,
|
|
}
|
|
);
|
|
parentElement.appendChild(this._pixiRenderer.view); // add the renderer view element to the DOM
|
|
this._pixiRenderer.view.style['position'] = 'absolute';
|
|
this._pixiRenderer.view.tabindex = '1'; //Ensure that the canvas has the focus.
|
|
this._resizeCanvas();
|
|
|
|
// Handle scale mode
|
|
if (this._game.getScaleMode() === 'nearest') {
|
|
this._pixiRenderer.view.style['image-rendering'] = '-moz-crisp-edges';
|
|
this._pixiRenderer.view.style['image-rendering'] =
|
|
'-webkit-optimize-contrast';
|
|
this._pixiRenderer.view.style['image-rendering'] = '-webkit-crisp-edges';
|
|
this._pixiRenderer.view.style['image-rendering'] = 'pixelated';
|
|
}
|
|
|
|
//Handle resize
|
|
var that = this;
|
|
window.addEventListener('resize', function() {
|
|
that._game.onWindowInnerSizeChanged();
|
|
that._resizeCanvas();
|
|
that._game._notifySceneForResize = true;
|
|
});
|
|
|
|
return this._pixiRenderer;
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.getWindowInnerWidth = function() {
|
|
return typeof window !== 'undefined' ? window.innerWidth : 800;
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.getWindowInnerHeight = function() {
|
|
return typeof window !== 'undefined' ? window.innerHeight : 800;
|
|
};
|
|
|
|
/**
|
|
* Update the game renderer size according to the "game resolution".
|
|
* Called when game resolution changes.
|
|
*
|
|
* Note that if the canvas is fullscreen, it won't be resized, but when going back to
|
|
* non fullscreen mode, the requested size will be used.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.updateRendererSize = function() {
|
|
this._resizeCanvas();
|
|
};
|
|
|
|
/**
|
|
* Resize the renderer (the "game resolution") and the canvas (which can be larger
|
|
* or smaller to fill the page, with optional margins).
|
|
*
|
|
* @private
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype._resizeCanvas = function() {
|
|
// Set the Pixi renderer size to the game size.
|
|
// There is no "smart" resizing to be done here: the rendering of the game
|
|
// should be done with the size set on the game.
|
|
if (
|
|
this._pixiRenderer.width !== this._game.getGameResolutionWidth() ||
|
|
this._pixiRenderer.height !== this._game.getGameResolutionHeight()
|
|
) {
|
|
this._pixiRenderer.resize(
|
|
this._game.getGameResolutionWidth(),
|
|
this._game.getGameResolutionHeight()
|
|
);
|
|
}
|
|
|
|
// Set the canvas size.
|
|
// Resizing is done according to the settings. This is a "CSS" resize
|
|
// only, so won't create visual artifacts during the rendering.
|
|
var isFullPage =
|
|
this._forceFullscreen || this._isFullPage || this._isFullscreen;
|
|
var canvasWidth = this._game.getGameResolutionWidth();
|
|
var canvasHeight = this._game.getGameResolutionHeight();
|
|
var maxWidth = window.innerWidth - this._marginLeft - this._marginRight;
|
|
var maxHeight = window.innerHeight - this._marginTop - this._marginBottom;
|
|
if (maxWidth < 0) maxWidth = 0;
|
|
if (maxHeight < 0) maxHeight = 0;
|
|
|
|
if (isFullPage && !this._keepRatio) {
|
|
canvasWidth = maxWidth;
|
|
canvasHeight = maxHeight;
|
|
} else if (
|
|
(isFullPage && this._keepRatio) ||
|
|
canvasWidth > maxWidth ||
|
|
canvasHeight > maxHeight
|
|
) {
|
|
var factor = maxWidth / canvasWidth;
|
|
if (canvasHeight * factor > maxHeight) factor = maxHeight / canvasHeight;
|
|
|
|
canvasWidth *= factor;
|
|
canvasHeight *= factor;
|
|
}
|
|
|
|
this._pixiRenderer.view.style['top'] =
|
|
this._marginTop + (maxHeight - canvasHeight) / 2 + 'px';
|
|
this._pixiRenderer.view.style['left'] =
|
|
this._marginLeft + (maxWidth - canvasWidth) / 2 + 'px';
|
|
this._pixiRenderer.view.style.width = canvasWidth + 'px';
|
|
this._pixiRenderer.view.style.height = canvasHeight + 'px';
|
|
|
|
// Store the canvas size for fast access to it.
|
|
this._canvasWidth = canvasWidth;
|
|
this._canvasHeight = canvasHeight;
|
|
};
|
|
|
|
/**
|
|
* Set if the aspect ratio must be kept when the game canvas is resized to fill
|
|
* the page.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.keepAspectRatio = function(enable) {
|
|
if (this._keepRatio === enable) return;
|
|
|
|
this._keepRatio = enable;
|
|
this._resizeCanvas();
|
|
this._game._notifySceneForResize = true;
|
|
};
|
|
|
|
/**
|
|
* Change the margin that must be preserved around the game canvas.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.setMargins = function(
|
|
top,
|
|
right,
|
|
bottom,
|
|
left
|
|
) {
|
|
if (
|
|
this._marginTop === top &&
|
|
this._marginRight === right &&
|
|
this._marginBottom === bottom &&
|
|
this._marginLeft === left
|
|
)
|
|
return;
|
|
|
|
this._marginTop = top;
|
|
this._marginRight = right;
|
|
this._marginBottom = bottom;
|
|
this._marginLeft = left;
|
|
this._resizeCanvas();
|
|
this._game._notifySceneForResize = true;
|
|
};
|
|
|
|
/**
|
|
* Update the window size, if possible.
|
|
* @param {number} width The new width, in pixels.
|
|
* @param {number} height The new height, in pixels.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.setWindowSize = function(width, height) {
|
|
var electron = this.getElectron();
|
|
if (electron) {
|
|
// Use Electron BrowserWindow API
|
|
var browserWindow = electron.remote.getCurrentWindow();
|
|
if (browserWindow) {
|
|
browserWindow.setContentSize(width, height);
|
|
}
|
|
} else {
|
|
console.warn("Window size can't be changed on this platform.");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Center the window on screen.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.centerWindow = function() {
|
|
var electron = this.getElectron();
|
|
if (electron) {
|
|
// Use Electron BrowserWindow API
|
|
var browserWindow = electron.remote.getCurrentWindow();
|
|
if (browserWindow) {
|
|
browserWindow.center();
|
|
}
|
|
} else {
|
|
// Not supported
|
|
}
|
|
};
|
|
|
|
/**
|
|
* De/activate fullscreen for the game.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.setFullScreen = function(enable) {
|
|
if (this._forceFullscreen) return;
|
|
|
|
if (this._isFullscreen !== enable) {
|
|
this._isFullscreen = !!enable;
|
|
|
|
var electron = this.getElectron();
|
|
if (electron) {
|
|
// Use Electron BrowserWindow API
|
|
var browserWindow = electron.remote.getCurrentWindow();
|
|
if (browserWindow) {
|
|
browserWindow.setFullScreen(this._isFullscreen);
|
|
}
|
|
} else {
|
|
// Use HTML5 Fullscreen API
|
|
//TODO: Do this on a user gesture, otherwise most browsers won't activate fullscreen
|
|
if (this._isFullscreen) {
|
|
if (document.documentElement.requestFullScreen) {
|
|
document.documentElement.requestFullScreen();
|
|
} else if (document.documentElement.mozRequestFullScreen) {
|
|
document.documentElement.mozRequestFullScreen();
|
|
} else if (document.documentElement.webkitRequestFullScreen) {
|
|
document.documentElement.webkitRequestFullScreen();
|
|
}
|
|
} else {
|
|
if (document.cancelFullScreen) {
|
|
document.cancelFullScreen();
|
|
} else if (document.mozCancelFullScreen) {
|
|
document.mozCancelFullScreen();
|
|
} else if (document.webkitCancelFullScreen) {
|
|
document.webkitCancelFullScreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
this._resizeCanvas();
|
|
this._notifySceneForResize = true;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Add the standard events handler.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.bindStandardEvents = function(
|
|
manager,
|
|
window,
|
|
document
|
|
) {
|
|
var renderer = this._pixiRenderer;
|
|
var canvas = renderer.view;
|
|
|
|
//Translate an event (mouse or touch) made on the canvas on the page
|
|
//to game coordinates.
|
|
var that = this;
|
|
function getEventPosition(e) {
|
|
var pos = [0, 0];
|
|
if (e.pageX) {
|
|
pos[0] = e.pageX - canvas.offsetLeft;
|
|
pos[1] = e.pageY - canvas.offsetTop;
|
|
} else {
|
|
pos[0] =
|
|
e.clientX +
|
|
document.body.scrollLeft +
|
|
document.documentElement.scrollLeft -
|
|
canvas.offsetLeft;
|
|
pos[1] =
|
|
e.clientY +
|
|
document.body.scrollTop +
|
|
document.documentElement.scrollTop -
|
|
canvas.offsetTop;
|
|
}
|
|
|
|
//Handle the fact that the game is stretched to fill the canvas.
|
|
pos[0] *= that._game.getGameResolutionWidth() / (that._canvasWidth || 1);
|
|
pos[1] *= that._game.getGameResolutionHeight() / (that._canvasHeight || 1);
|
|
|
|
return pos;
|
|
}
|
|
|
|
//Some browsers lacks definition of some variables used to do calculations
|
|
//in getEventPosition. They are defined to 0 as they are useless.
|
|
(function ensureOffsetsExistence() {
|
|
if (isNaN(canvas.offsetLeft)) {
|
|
canvas.offsetLeft = 0;
|
|
canvas.offsetTop = 0;
|
|
}
|
|
if (isNaN(document.body.scrollLeft)) {
|
|
document.body.scrollLeft = 0;
|
|
document.body.scrollTop = 0;
|
|
}
|
|
if (
|
|
document.documentElement === undefined ||
|
|
document.documentElement === null
|
|
) {
|
|
document.documentElement = {};
|
|
}
|
|
if (isNaN(document.documentElement.scrollLeft)) {
|
|
document.documentElement.scrollLeft = 0;
|
|
document.documentElement.scrollTop = 0;
|
|
}
|
|
if (isNaN(canvas.offsetLeft)) {
|
|
canvas.offsetLeft = 0;
|
|
canvas.offsetTop = 0;
|
|
}
|
|
})();
|
|
|
|
//Keyboard
|
|
document.onkeydown = function(e) {
|
|
manager.onKeyPressed(e.keyCode);
|
|
};
|
|
document.onkeyup = function(e) {
|
|
manager.onKeyReleased(e.keyCode);
|
|
};
|
|
//Mouse
|
|
renderer.view.onmousemove = function(e) {
|
|
var pos = getEventPosition(e);
|
|
manager.onMouseMove(pos[0], pos[1]);
|
|
};
|
|
renderer.view.onmousedown = function(e) {
|
|
manager.onMouseButtonPressed(
|
|
e.button === 2
|
|
? gdjs.InputManager.MOUSE_RIGHT_BUTTON
|
|
: e.button === 1
|
|
? gdjs.InputManager.MOUSE_MIDDLE_BUTTON
|
|
: gdjs.InputManager.MOUSE_LEFT_BUTTON
|
|
);
|
|
if (window.focus !== undefined) window.focus();
|
|
return false;
|
|
};
|
|
renderer.view.onmouseup = function(e) {
|
|
manager.onMouseButtonReleased(
|
|
e.button === 2
|
|
? gdjs.InputManager.MOUSE_RIGHT_BUTTON
|
|
: e.button === 1
|
|
? gdjs.InputManager.MOUSE_MIDDLE_BUTTON
|
|
: gdjs.InputManager.MOUSE_LEFT_BUTTON
|
|
);
|
|
return false;
|
|
};
|
|
window.addEventListener(
|
|
'click',
|
|
function(e) {
|
|
if (window.focus !== undefined) window.focus();
|
|
e.preventDefault();
|
|
return false;
|
|
},
|
|
false
|
|
);
|
|
renderer.view.oncontextmenu = function(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
return false;
|
|
};
|
|
renderer.view.onmousewheel = function(event) {
|
|
manager.onMouseWheel(event.wheelDelta);
|
|
};
|
|
//Touches
|
|
//Also simulate mouse events when receiving touch events
|
|
window.addEventListener('touchmove', function(e) {
|
|
e.preventDefault();
|
|
if (e.changedTouches) {
|
|
for (var i = 0; i < e.changedTouches.length; ++i) {
|
|
var pos = getEventPosition(e.changedTouches[i]);
|
|
manager.onTouchMove(e.changedTouches[i].identifier, pos[0], pos[1]);
|
|
}
|
|
}
|
|
});
|
|
window.addEventListener('touchstart', function(e) {
|
|
e.preventDefault();
|
|
if (e.changedTouches) {
|
|
for (var i = 0; i < e.changedTouches.length; ++i) {
|
|
var pos = getEventPosition(e.changedTouches[i]);
|
|
manager.onTouchStart(e.changedTouches[i].identifier, pos[0], pos[1]);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
window.addEventListener('touchend', function(e) {
|
|
e.preventDefault();
|
|
if (e.changedTouches) {
|
|
for (var i = 0; i < e.changedTouches.length; ++i) {
|
|
var pos = getEventPosition(e.changedTouches[i]);
|
|
manager.onTouchEnd(e.changedTouches[i].identifier);
|
|
}
|
|
}
|
|
return false;
|
|
});
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.prototype.setWindowTitle = function(title) {
|
|
if (typeof document !== 'undefined') document.title = title;
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.prototype.getWindowTitle = function() {
|
|
return typeof document !== 'undefined' ? document.title : '';
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.prototype.startGameLoop = function(fn) {
|
|
requestAnimationFrame(gameLoop);
|
|
|
|
var oldTime = 0;
|
|
function gameLoop(time) {
|
|
var dt = oldTime ? time - oldTime : 0;
|
|
oldTime = time;
|
|
|
|
if (fn(dt)) requestAnimationFrame(gameLoop);
|
|
}
|
|
};
|
|
|
|
gdjs.RuntimeGamePixiRenderer.prototype.getPIXIRenderer = function() {
|
|
return this._pixiRenderer;
|
|
};
|
|
|
|
/**
|
|
* Open the given URL in the system browser (or a new tab)
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.openURL = function(url) {
|
|
// Try to detect the environment to use the most adapted
|
|
// way of opening an URL.
|
|
if (typeof Cocoon !== 'undefined' && Cocoon.App && Cocoon.App.openURL) {
|
|
Cocoon.App.openURL(url);
|
|
} else if (typeof window !== 'undefined') {
|
|
var target = window.cordova ? '_system' : '_blank';
|
|
window.open(url, target);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Close the game, if applicable
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.stopGame = function() {
|
|
// Try to detect the environment to use the most adapted
|
|
// way of closing the app
|
|
var electron = this.getElectron();
|
|
if (electron) {
|
|
var browserWindow = electron.remote.getCurrentWindow();
|
|
if (browserWindow) {
|
|
browserWindow.close();
|
|
}
|
|
} else if (
|
|
typeof navigator !== 'undefined' &&
|
|
navigator.app &&
|
|
navigator.app.exitApp
|
|
) {
|
|
navigator.app.exitApp();
|
|
}
|
|
|
|
// HTML5 games on mobile/browsers don't have a way to close their window/page.
|
|
};
|
|
|
|
/**
|
|
* Get the canvas DOM element.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.getCanvas = function() {
|
|
return this._pixiRenderer.view;
|
|
};
|
|
|
|
/**
|
|
* Check if the device supports WebGL.
|
|
* @returns {boolean} true if WebGL is supported
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.isWebGLSupported = function() {
|
|
return this._pixiRenderer.type === PIXI.RENDERER_TYPE.WEBGL;
|
|
};
|
|
|
|
/**
|
|
* Get the electron module, if running as a electron renderer process.
|
|
*/
|
|
gdjs.RuntimeGamePixiRenderer.prototype.getElectron = function() {
|
|
if (typeof require !== 'undefined') {
|
|
return require('electron');
|
|
}
|
|
|
|
return null;
|
|
};
|