mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
433 lines
14 KiB
TypeScript
433 lines
14 KiB
TypeScript
/*
|
|
* GDevelop JS Platform
|
|
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
|
* This project is released under the MIT License.
|
|
*/
|
|
namespace gdjs {
|
|
/**
|
|
* Represents a layer of a scene, used to display objects.
|
|
*/
|
|
export class Layer extends gdjs.RuntimeLayer {
|
|
_cameraRotation: float = 0;
|
|
/** The camera zoom factor strictly greater than 0. */
|
|
_zoomFactor: float = 1;
|
|
_cameraX: float;
|
|
_cameraY: float;
|
|
_cameraZ: float = 0;
|
|
/**
|
|
* `_cameraZ` is dirty when the zoom factor is set last.
|
|
*/
|
|
_isCameraZDirty: boolean = true;
|
|
|
|
/**
|
|
* @param layerData The data used to initialize the layer
|
|
* @param instanceContainer The container in which the layer is used
|
|
*/
|
|
constructor(
|
|
layerData: LayerData,
|
|
instanceContainer: gdjs.RuntimeInstanceContainer
|
|
) {
|
|
super(layerData, instanceContainer);
|
|
|
|
if (
|
|
this._defaultCameraBehavior ===
|
|
gdjs.RuntimeLayerDefaultCameraBehavior.TOP_LEFT_ANCHORED_IF_NEVER_MOVED
|
|
) {
|
|
// If top-left must stay in the top-left corner, this means we center the camera on the current size.
|
|
this._cameraX = this._runtimeScene.getViewportOriginX();
|
|
this._cameraY = this._runtimeScene.getViewportOriginY();
|
|
} else {
|
|
// Otherwise, the default camera position is the center of the initial viewport.
|
|
this._cameraX =
|
|
(this._runtimeScene.getInitialUnrotatedViewportMinX() +
|
|
this._runtimeScene.getInitialUnrotatedViewportMaxX()) /
|
|
2;
|
|
this._cameraY =
|
|
(this._runtimeScene.getInitialUnrotatedViewportMinY() +
|
|
this._runtimeScene.getInitialUnrotatedViewportMaxY()) /
|
|
2;
|
|
}
|
|
if (this.getCameraType() === gdjs.RuntimeLayerCameraType.ORTHOGRAPHIC) {
|
|
this._cameraZ =
|
|
(this._initialCamera3DFarPlaneDistance +
|
|
this._initialCamera3DNearPlaneDistance) /
|
|
2;
|
|
}
|
|
|
|
// Let the renderer do its final set up:
|
|
this._renderer.onCreated();
|
|
}
|
|
|
|
/**
|
|
* Called by the RuntimeScene whenever the game resolution size is changed.
|
|
* Updates the layer width/height and position.
|
|
*/
|
|
override onGameResolutionResized(
|
|
oldGameResolutionOriginX: float,
|
|
oldGameResolutionOriginY: float
|
|
): void {
|
|
// Adapt position of the camera center only if the camera has never moved as:
|
|
// * When the camera follows a player/object, it will rarely be at the default position.
|
|
// (and if is, it will be moved again by the behavior/events).
|
|
// * Cameras not following a player/object are usually UIs which are intuitively
|
|
// expected not to "move" (top-left stays "fixed"), while gameplay is "centered" (center stays "fixed").
|
|
//
|
|
// Note that anchor behavior is usually a better choice for UIs.
|
|
if (
|
|
this._defaultCameraBehavior ===
|
|
gdjs.RuntimeLayerDefaultCameraBehavior
|
|
.TOP_LEFT_ANCHORED_IF_NEVER_MOVED &&
|
|
// Have a safety margin of 1 pixel to avoid rounding errors.
|
|
Math.abs(this._cameraX - oldGameResolutionOriginX) < 1 &&
|
|
Math.abs(this._cameraY - oldGameResolutionOriginY) < 1 &&
|
|
this._zoomFactor === 1
|
|
) {
|
|
this._cameraX +=
|
|
this._runtimeScene.getViewportOriginX() - oldGameResolutionOriginX;
|
|
this._cameraY +=
|
|
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
|
|
}
|
|
|
|
this._renderer.updatePosition();
|
|
this._renderer.updateResolution();
|
|
}
|
|
|
|
/**
|
|
* Change the camera center X position.
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The x position of the camera
|
|
*/
|
|
override getCameraX(cameraId?: integer): float {
|
|
this._forceDimensionUpdate();
|
|
return this._cameraX;
|
|
}
|
|
|
|
/**
|
|
* Change the camera center Y position.
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The y position of the camera
|
|
*/
|
|
override getCameraY(cameraId?: integer): float {
|
|
this._forceDimensionUpdate();
|
|
return this._cameraY;
|
|
}
|
|
|
|
/**
|
|
* Set the camera center X position.
|
|
*
|
|
* @param x The new x position
|
|
* @param cameraId The camera number. Currently ignored.
|
|
*/
|
|
override setCameraX(x: float, cameraId?: integer): void {
|
|
this._forceDimensionUpdate();
|
|
this._cameraX = x;
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Set the camera center Y position.
|
|
*
|
|
* @param y The new y position
|
|
* @param cameraId The camera number. Currently ignored.
|
|
*/
|
|
override setCameraY(y: float, cameraId?: integer): void {
|
|
this._forceDimensionUpdate();
|
|
this._cameraY = y;
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Get the camera width (which can be different than the game resolution width
|
|
* if the camera is zoomed).
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The width of the camera
|
|
*/
|
|
override getCameraWidth(cameraId?: integer): float {
|
|
return this.getWidth() / this._zoomFactor;
|
|
}
|
|
|
|
/**
|
|
* Get the camera height (which can be different than the game resolution height
|
|
* if the camera is zoomed).
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The height of the camera
|
|
*/
|
|
override getCameraHeight(cameraId?: integer): float {
|
|
return this.getHeight() / this._zoomFactor;
|
|
}
|
|
|
|
/**
|
|
* Set the zoom of a camera.
|
|
*
|
|
* @param newZoom The new zoom. Must be superior to 0. 1 is the default zoom.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
*/
|
|
override setCameraZoom(newZoom: float, cameraId?: integer): void {
|
|
if (newZoom <= 0) {
|
|
return;
|
|
}
|
|
this._zoomFactor = newZoom;
|
|
this._isCameraZDirty = true;
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Get the zoom of a camera.
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The zoom.
|
|
*/
|
|
override getCameraZoom(cameraId?: integer): float {
|
|
return this._zoomFactor;
|
|
}
|
|
|
|
/**
|
|
* Set the camera center Z position.
|
|
*
|
|
* @param z The new y position.
|
|
* @param fov The field of view.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
*/
|
|
override setCameraZ(z: float, fov: float | null, cameraId?: integer): void {
|
|
if (fov) {
|
|
const cameraFovInRadians = gdjs.toRad(fov);
|
|
|
|
// The zoom factor is capped to a not too big value to avoid infinity.
|
|
// MAX_SAFE_INTEGER is an arbitrary choice. It's big but not too big.
|
|
const zoomFactor = Math.min(
|
|
Number.MAX_SAFE_INTEGER,
|
|
(0.5 * this.getHeight()) / (z * Math.tan(0.5 * cameraFovInRadians))
|
|
);
|
|
|
|
if (zoomFactor > 0) {
|
|
this._zoomFactor = zoomFactor;
|
|
}
|
|
}
|
|
|
|
this._cameraZ = z;
|
|
this._isCameraZDirty = false;
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Get the camera center Z position.
|
|
*
|
|
* @param fov The field of view.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The z position of the camera
|
|
*/
|
|
override getCameraZ(fov: float | null, cameraId?: integer): float {
|
|
if (!this._isCameraZDirty || !fov) {
|
|
return this._cameraZ;
|
|
}
|
|
|
|
// Set the camera so that it displays the whole PixiJS plane, as if it was a 2D rendering.
|
|
// The Z position is computed by taking the half height of the displayed rendering,
|
|
// and using the angle of the triangle defined by the field of view to compute the length
|
|
// of the triangle defining the distance between the camera and the rendering plane.
|
|
const cameraZPosition =
|
|
(0.5 * this.getHeight()) /
|
|
this.getCameraZoom() /
|
|
Math.tan(0.5 * gdjs.toRad(fov));
|
|
|
|
return cameraZPosition;
|
|
}
|
|
|
|
/**
|
|
* Get the rotation of the camera, expressed in degrees.
|
|
*
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @return The rotation, in degrees.
|
|
*/
|
|
override getCameraRotation(cameraId?: integer): float {
|
|
return this._cameraRotation;
|
|
}
|
|
|
|
/**
|
|
* Set the rotation of the camera, expressed in degrees.
|
|
* The rotation is made around the camera center.
|
|
*
|
|
* @param rotation The new rotation, in degrees.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
*/
|
|
override setCameraRotation(rotation: float, cameraId?: integer): void {
|
|
this._cameraRotation = rotation;
|
|
this._renderer.updatePosition();
|
|
}
|
|
|
|
/**
|
|
* Convert a point from the canvas coordinates (for example,
|
|
* the mouse position) to the container coordinates.
|
|
*
|
|
* This method handles 3D rotations.
|
|
*
|
|
* @param x The x position, in canvas coordinates.
|
|
* @param y The y position, in canvas coordinates.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @param result The point instance that is used to return the result.
|
|
*/
|
|
override convertCoords(
|
|
x: float,
|
|
y: float,
|
|
cameraId: integer = 0,
|
|
result: FloatPoint
|
|
): FloatPoint {
|
|
// This code duplicates applyLayerInverseTransformation for performance reasons;
|
|
|
|
// The result parameter used to be optional.
|
|
let position = result || [0, 0];
|
|
|
|
if (this._renderer.isCameraRotatedIn3D()) {
|
|
return this._renderer.transformTo3DWorld(x, y, 0, cameraId, result);
|
|
}
|
|
|
|
x -= this.getRuntimeScene()._cachedGameResolutionWidth / 2;
|
|
y -= this.getRuntimeScene()._cachedGameResolutionHeight / 2;
|
|
x /= this._zoomFactor;
|
|
y /= this._zoomFactor;
|
|
|
|
// Only compute angle and cos/sin once (allow heavy optimization from JS engines).
|
|
const angleInRadians = (this._cameraRotation / 180) * Math.PI;
|
|
const tmp = x;
|
|
const cosValue = Math.cos(angleInRadians);
|
|
const sinValue = Math.sin(angleInRadians);
|
|
x = cosValue * x - sinValue * y;
|
|
y = sinValue * tmp + cosValue * y;
|
|
position[0] = x + this.getCameraX(cameraId);
|
|
position[1] = y + this.getCameraY(cameraId);
|
|
return position;
|
|
}
|
|
|
|
/**
|
|
* Return an array containing the coordinates of the point passed as parameter
|
|
* in layer local coordinates (as opposed to the parent coordinates).
|
|
*
|
|
* All transformations (scale, rotation) are supported.
|
|
*
|
|
* This method doesn't handle 3D rotations.
|
|
*
|
|
* @param x The X position of the point, in parent coordinates.
|
|
* @param y The Y position of the point, in parent coordinates.
|
|
* @param result Array that will be updated with the result
|
|
* @param result The point instance that is used to return the result.
|
|
* (x and y position of the point in layer coordinates).
|
|
*/
|
|
override applyLayerInverseTransformation(
|
|
x: float,
|
|
y: float,
|
|
cameraId: integer,
|
|
result: FloatPoint
|
|
): FloatPoint {
|
|
x -= this._runtimeScene.getViewportOriginX();
|
|
y -= this._runtimeScene.getViewportOriginY();
|
|
x /= this._zoomFactor;
|
|
y /= this._zoomFactor;
|
|
|
|
// Only compute angle and cos/sin once (allow heavy optimization from JS engines).
|
|
const angleInRadians = (this._cameraRotation / 180) * Math.PI;
|
|
const tmp = x;
|
|
const cosValue = Math.cos(angleInRadians);
|
|
const sinValue = Math.sin(angleInRadians);
|
|
x = cosValue * x - sinValue * y;
|
|
y = sinValue * tmp + cosValue * y;
|
|
result[0] = x + this.getCameraX(cameraId);
|
|
result[1] = y + this.getCameraY(cameraId);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convert a point from the container coordinates (for example,
|
|
* an object position) to the canvas coordinates.
|
|
*
|
|
* This method doesn't handle 3D rotations.
|
|
*
|
|
* @param x The x position, in container coordinates.
|
|
* @param y The y position, in container coordinates.
|
|
* @param cameraId The camera number. Currently ignored.
|
|
* @param result The point instance that is used to return the result.
|
|
*/
|
|
override convertInverseCoords(
|
|
x: float,
|
|
y: float,
|
|
cameraId: integer = 0,
|
|
result: FloatPoint
|
|
): FloatPoint {
|
|
// This code duplicates applyLayerTransformation for performance reasons;
|
|
|
|
// The result parameter used to be optional.
|
|
let position = result || [0, 0];
|
|
x -= this.getCameraX(cameraId);
|
|
y -= this.getCameraY(cameraId);
|
|
|
|
// Only compute angle and cos/sin once (allow heavy optimization from JS engines).
|
|
const angleInRadians = (this._cameraRotation / 180) * Math.PI;
|
|
const tmp = x;
|
|
const cosValue = Math.cos(-angleInRadians);
|
|
const sinValue = Math.sin(-angleInRadians);
|
|
x = cosValue * x - sinValue * y;
|
|
y = sinValue * tmp + cosValue * y;
|
|
x *= this._zoomFactor;
|
|
y *= this._zoomFactor;
|
|
position[0] = x + this.getRuntimeScene()._cachedGameResolutionWidth / 2;
|
|
position[1] = y + this.getRuntimeScene()._cachedGameResolutionHeight / 2;
|
|
|
|
return position;
|
|
}
|
|
|
|
/**
|
|
* Return an array containing the coordinates of the point passed as parameter
|
|
* in parent coordinate coordinates (as opposed to the layer local coordinates).
|
|
*
|
|
* All transformations (scale, rotation) are supported.
|
|
*
|
|
* This method doesn't handle 3D rotations.
|
|
*
|
|
* @param x The X position of the point, in layer coordinates.
|
|
* @param y The Y position of the point, in layer coordinates.
|
|
* @param result Array that will be updated with the result
|
|
* (x and y position of the point in parent coordinates).
|
|
*/
|
|
override applyLayerTransformation(
|
|
x: float,
|
|
y: float,
|
|
cameraId: integer,
|
|
result: FloatPoint
|
|
): FloatPoint {
|
|
x -= this.getCameraX(cameraId);
|
|
y -= this.getCameraY(cameraId);
|
|
|
|
// Only compute angle and cos/sin once (allow heavy optimization from JS engines).
|
|
const angleInRadians = (this._cameraRotation / 180) * Math.PI;
|
|
const tmp = x;
|
|
const cosValue = Math.cos(-angleInRadians);
|
|
const sinValue = Math.sin(-angleInRadians);
|
|
x = cosValue * x - sinValue * y;
|
|
y = sinValue * tmp + cosValue * y;
|
|
x *= this._zoomFactor;
|
|
y *= this._zoomFactor;
|
|
x += this._runtimeScene.getViewportOriginX();
|
|
y += this._runtimeScene.getViewportOriginY();
|
|
|
|
result[0] = x;
|
|
result[1] = y;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This ensure that the viewport dimensions are up to date.
|
|
*
|
|
* It's needed because custom objects dimensions are only updated on
|
|
* demand for efficiency reasons.
|
|
*/
|
|
private _forceDimensionUpdate(): void {
|
|
// This will update dimensions.
|
|
this._runtimeScene.getViewportWidth();
|
|
}
|
|
}
|
|
}
|