diff --git a/Core/GDCore/Project/Project.h b/Core/GDCore/Project/Project.h
index 59a300206168..6ba0ef36bca2 100644
--- a/Core/GDCore/Project/Project.h
+++ b/Core/GDCore/Project/Project.h
@@ -374,12 +374,14 @@ class GD_CORE_API Project {
void SetVerticalSyncActivatedByDefault(bool enable) { verticalSync = enable; }
/**
- * Return the scale mode used by the game (usually "linear" or "nearest").
+ * Return the scale mode used by the game (usually "linear", "magnified" or
+ * "nearest").
*/
const gd::String& GetScaleMode() const { return scaleMode; }
/**
- * Set the scale mode used by the game (usually "linear" or "nearest").
+ * Set the scale mode used by the game (usually "linear", "magnified" or
+ * "nearest").
*/
void SetScaleMode(const gd::String& scaleMode_) { scaleMode = scaleMode_; }
diff --git a/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts b/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts
index 91f2ae3faf00..c09e697396ea 100644
--- a/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts
+++ b/Extensions/TextInput/textinputruntimeobject-pixi-renderer.ts
@@ -211,8 +211,8 @@ namespace gdjs {
const isOutsideCanvas =
canvasRight < 0 ||
canvasBottom < 0 ||
- canvasLeft > runtimeGame.getGameResolutionWidth() ||
- canvasTop > runtimeGame.getGameResolutionHeight();
+ canvasLeft > runtimeGame.getRenderingResolutionWidth() ||
+ canvasTop > runtimeGame.getRenderingResolutionHeight();
if (isOutsideCanvas) {
this._form.style.display = 'none';
return;
diff --git a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts
index 0fe94a9b8085..1cf12ceb3deb 100644
--- a/Extensions/TextObject/textruntimeobject-pixi-renderer.ts
+++ b/Extensions/TextObject/textruntimeobject-pixi-renderer.ts
@@ -4,6 +4,7 @@ namespace gdjs {
_fontManager: any;
_text: PIXI.Text;
_justCreated: boolean = true;
+ _upscaleRatio: integer = 1;
constructor(
runtimeObject: gdjs.TextRuntimeObject,
@@ -47,7 +48,7 @@ namespace gdjs {
const style = this._text.style;
style.fontStyle = this._object._italic ? 'italic' : 'normal';
style.fontWeight = this._object._bold ? 'bold' : 'normal';
- style.fontSize = this._object._characterSize;
+ style.fontSize = this._object._characterSize * this._upscaleRatio;
style.fontFamily = fontName;
if (this._object._useGradient) {
style.fill = this._getGradientHex();
@@ -62,7 +63,7 @@ namespace gdjs {
// @ts-ignore
style.align = this._object._textAlign;
style.wordWrap = this._object._wrapping;
- style.wordWrapWidth = this._object._wrappingWidth;
+ style.wordWrapWidth = this._object._wrappingWidth * this._upscaleRatio;
style.breakWords = true;
style.stroke = gdjs.rgbToHexNumber(
this._object._outlineColor[0],
@@ -70,7 +71,7 @@ namespace gdjs {
this._object._outlineColor[2]
);
style.strokeThickness = this._object._isOutlineEnabled
- ? this._object._outlineThickness
+ ? this._object._outlineThickness * this._upscaleRatio
: 0;
style.dropShadow = this._object._shadow;
style.dropShadowColor = gdjs.rgbToHexNumber(
@@ -79,13 +80,16 @@ namespace gdjs {
this._object._shadowColor[2]
);
style.dropShadowAlpha = this._object._shadowOpacity / 255;
- style.dropShadowBlur = this._object._shadowBlur;
+ style.dropShadowBlur = this._object._shadowBlur * this._upscaleRatio;
style.dropShadowAngle = gdjs.toRad(this._object._shadowAngle);
- style.dropShadowDistance = this._object._shadowDistance;
+ style.dropShadowDistance =
+ this._object._shadowDistance * this._upscaleRatio;
const extraPaddingForShadow = style.dropShadow
- ? style.dropShadowDistance + style.dropShadowBlur
+ ? this._object._shadowDistance + this._object._shadowBlur
: 0;
- style.padding = Math.ceil(this._object._padding + extraPaddingForShadow);
+ style.padding =
+ Math.ceil(this._object._padding + extraPaddingForShadow) *
+ this._upscaleRatio;
// Prevent spikey outlines by adding a miter limit
style.miterLimit = 3;
@@ -117,7 +121,6 @@ namespace gdjs {
this._text.position.x = this._object.x + this._text.width / 2;
this._text.anchor.x = 0.5;
}
- this._text.position.y = this._object.y + this._text.height / 2;
const alignmentY =
this._object._verticalTextAlignment === 'bottom'
@@ -181,18 +184,30 @@ namespace gdjs {
return gradient;
}
+ /**
+ * Set the text object upscale ratio.
+ * @param upscaleRatio The new upscale ratio for the text object.
+ * @see gdjs.RuntimeGame.getZoomFactor
+ */
+ setUpscaleRatio(upscaleRatio: integer): void {
+ this._upscaleRatio = upscaleRatio;
+ this._text.scale.x = this._object.getScaleX() / this._upscaleRatio;
+ this._text.scale.y = this._object.getScaleY() / this._upscaleRatio;
+ this.updateStyle();
+ }
+
/**
* Get x-scale of the text.
*/
getScaleX(): float {
- return this._text.scale.x;
+ return this._object.getScaleX();
}
/**
* Get y-scale of the text.
*/
getScaleY(): float {
- return this._text.scale.y;
+ return this._object.getScaleY();
}
/**
@@ -200,8 +215,8 @@ namespace gdjs {
* @param newScale The new scale for the text object.
*/
setScale(newScale: float): void {
- this._text.scale.x = newScale;
- this._text.scale.y = newScale;
+ this._text.scale.x = newScale / this._upscaleRatio;
+ this._text.scale.y = newScale / this._upscaleRatio;
}
/**
@@ -209,7 +224,7 @@ namespace gdjs {
* @param newScale The new x-scale for the text object.
*/
setScaleX(newScale: float): void {
- this._text.scale.x = newScale;
+ this._text.scale.x = newScale / this._upscaleRatio;
}
/**
@@ -217,7 +232,7 @@ namespace gdjs {
* @param newScale The new y-scale for the text object.
*/
setScaleY(newScale: float): void {
- this._text.scale.y = newScale;
+ this._text.scale.y = newScale / this._upscaleRatio;
}
destroy() {
diff --git a/Extensions/TextObject/textruntimeobject.ts b/Extensions/TextObject/textruntimeobject.ts
index abd6ea72d914..e5b6437c2539 100644
--- a/Extensions/TextObject/textruntimeobject.ts
+++ b/Extensions/TextObject/textruntimeobject.ts
@@ -149,7 +149,7 @@ namespace gdjs {
this.onCreated();
}
- updateFromObjectData(
+ override updateFromObjectData(
oldObjectData: TextObjectData,
newObjectData: TextObjectData
): boolean {
@@ -214,7 +214,7 @@ namespace gdjs {
return true;
}
- getNetworkSyncData(): TextObjectNetworkSyncData {
+ override getNetworkSyncData(): TextObjectNetworkSyncData {
return {
...super.getNetworkSyncData(),
str: this._str,
@@ -242,7 +242,7 @@ namespace gdjs {
};
}
- updateFromNetworkSyncData(
+ override updateFromNetworkSyncData(
networkSyncData: TextObjectNetworkSyncData
): void {
super.updateFromNetworkSyncData(networkSyncData);
@@ -317,15 +317,15 @@ namespace gdjs {
}
}
- getRendererObject() {
+ override getRendererObject() {
return this._renderer.getRendererObject();
}
- update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
+ override update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
this._renderer.ensureUpToDate();
}
- onDestroyed(): void {
+ override onDestroyed(): void {
super.onDestroyed();
this._renderer.destroy();
}
@@ -345,6 +345,12 @@ namespace gdjs {
}
}
+ override onGameZoomFactorChanged(): void {
+ this._renderer.setUpscaleRatio(
+ this.getRuntimeScene().getGame().getZoomFactor()
+ );
+ }
+
/**
* Update the rendered object position.
*/
@@ -353,27 +359,17 @@ namespace gdjs {
this._renderer.updatePosition();
}
- /**
- * Set object position on X axis.
- */
- setX(x: float): void {
+ override setX(x: float): void {
super.setX(x);
this._updateTextPosition();
}
- /**
- * Set object position on Y axis.
- */
- setY(y: float): void {
+ override setY(y: float): void {
super.setY(y);
this._updateTextPosition();
}
- /**
- * Set the angle of the object.
- * @param angle The new angle of the object
- */
- setAngle(angle: float): void {
+ override setAngle(angle: float): void {
super.setAngle(angle);
this._renderer.updateAngle();
}
@@ -499,14 +495,14 @@ namespace gdjs {
/**
* Get width of the text.
*/
- getWidth(): float {
+ override getWidth(): float {
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
}
/**
* Get height of the text.
*/
- getHeight(): float {
+ override getHeight(): float {
return this._renderer.getHeight();
}
@@ -685,11 +681,11 @@ namespace gdjs {
}
}
- setWidth(width: float): void {
+ override setWidth(width: float): void {
this.setWrappingWidth(width);
}
- getDrawableY(): float {
+ override getDrawableY(): float {
return (
this.getY() -
(this._verticalTextAlignment === 'center'
diff --git a/GDJS/Runtime/CustomRuntimeObject.ts b/GDJS/Runtime/CustomRuntimeObject.ts
index 3c597d8ce06c..f650460d4f63 100644
--- a/GDJS/Runtime/CustomRuntimeObject.ts
+++ b/GDJS/Runtime/CustomRuntimeObject.ts
@@ -229,6 +229,10 @@ namespace gdjs {
*/
onDestroy(parent: gdjs.RuntimeInstanceContainer) {}
+ override onGameZoomFactorChanged() {
+ this._instanceContainer.onGameZoomFactorChanged();
+ }
+
override updatePreRender(parent: gdjs.RuntimeInstanceContainer): void {
this._instanceContainer._updateObjectsPreRender();
this.getRenderer().ensureUpToDate();
diff --git a/GDJS/Runtime/RuntimeInstanceContainer.ts b/GDJS/Runtime/RuntimeInstanceContainer.ts
index e4ef5eee2099..b33e06bddc04 100644
--- a/GDJS/Runtime/RuntimeInstanceContainer.ts
+++ b/GDJS/Runtime/RuntimeInstanceContainer.ts
@@ -547,6 +547,15 @@ namespace gdjs {
return this._allInstancesList;
}
+ /**
+ * Called when the game zoom factor is changed to adapt to a new resolution.
+ */
+ onGameZoomFactorChanged(): void {
+ for (const instance of this.getAdhocListOfAllInstances()) {
+ instance.onGameZoomFactorChanged();
+ }
+ }
+
/**
* Update the objects before launching the events.
*/
diff --git a/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts b/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts
index 1957181a9ff4..2df63a192a28 100644
--- a/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts
+++ b/GDJS/Runtime/pixi-renderers/DebuggerPixiRenderer.ts
@@ -37,18 +37,10 @@ namespace gdjs {
showPointsNames: boolean,
showCustomPoints: boolean
) {
- const pixiContainer = this._instanceContainer
- .getRenderer()
- .getRendererObject();
if (!this._debugDraw || !this._debugDrawContainer) {
this._debugDrawContainer = new PIXI.Container();
this._debugDraw = new PIXI.Graphics();
-
- // Add on top of all layers:
this._debugDrawContainer.addChild(this._debugDraw);
- if (pixiContainer) {
- pixiContainer.addChild(this._debugDrawContainer);
- }
}
const debugDraw = this._debugDraw;
@@ -112,7 +104,8 @@ namespace gdjs {
const polygon: float[] = [];
polygon.push.apply(
polygon,
- layer.applyLayerTransformation(
+ this.applyLayerTransformation(
+ layer,
aabb.min[0],
aabb.min[1],
0,
@@ -121,7 +114,8 @@ namespace gdjs {
);
polygon.push.apply(
polygon,
- layer.applyLayerTransformation(
+ this.applyLayerTransformation(
+ layer,
aabb.max[0],
aabb.min[1],
0,
@@ -130,7 +124,8 @@ namespace gdjs {
);
polygon.push.apply(
polygon,
- layer.applyLayerTransformation(
+ this.applyLayerTransformation(
+ layer,
aabb.max[0],
aabb.max[1],
0,
@@ -139,7 +134,8 @@ namespace gdjs {
);
polygon.push.apply(
polygon,
- layer.applyLayerTransformation(
+ this.applyLayerTransformation(
+ layer,
aabb.min[0],
aabb.max[1],
0,
@@ -185,7 +181,8 @@ namespace gdjs {
// as this is for debug draw.
const polygon: float[] = [];
hitboxes[j].vertices.forEach((point) => {
- point = layer.applyLayerTransformation(
+ point = this.applyLayerTransformation(
+ layer,
point[0],
point[1],
0,
@@ -205,7 +202,8 @@ namespace gdjs {
debugDraw.fill.alpha = 0.3;
// Draw Center point
- const centerPoint = layer.applyLayerTransformation(
+ const centerPoint = this.applyLayerTransformation(
+ layer,
object.getCenterXInScene(),
object.getCenterYInScene(),
0,
@@ -221,7 +219,8 @@ namespace gdjs {
);
// Draw position point
- const positionPoint = layer.applyLayerTransformation(
+ const positionPoint = this.applyLayerTransformation(
+ layer,
object.getX(),
object.getY(),
0,
@@ -245,7 +244,8 @@ namespace gdjs {
Math.abs(originPoint[0] - positionPoint[0]) >= 1 ||
Math.abs(originPoint[1] - positionPoint[1]) >= 1
) {
- originPoint = layer.applyLayerTransformation(
+ originPoint = this.applyLayerTransformation(
+ layer,
originPoint[0],
originPoint[1],
0,
@@ -270,7 +270,8 @@ namespace gdjs {
for (const customPointName in animationFrame.points.items) {
let customPoint = object.getPointPosition(customPointName);
- customPoint = layer.applyLayerTransformation(
+ customPoint = this.applyLayerTransformation(
+ layer,
customPoint[0],
customPoint[1],
0,
@@ -303,6 +304,27 @@ namespace gdjs {
debugDraw.endFill();
}
+ private applyLayerTransformation(
+ layer: gdjs.RuntimeLayer,
+ x: float,
+ y: float,
+ cameraId: integer,
+ result: FloatPoint
+ ): FloatPoint {
+ layer.applyLayerTransformation(x, y, cameraId, result);
+ const gamePixiContainer = this._instanceContainer
+ .getRenderer()
+ .getRendererObject();
+ if (!gamePixiContainer) {
+ return result;
+ }
+ // The scale is usually near 1 unless the 'magnified' scale mode is used.
+ // See gdjs.RuntimeGame.getZoomFactor
+ result[0] *= gamePixiContainer.scale.x;
+ result[1] *= gamePixiContainer.scale.y;
+ return result;
+ }
+
clearDebugDraw(): void {
if (this._debugDraw) {
this._debugDraw.clear();
diff --git a/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts
index bdeff384f2ee..d8709d6b2c6d 100644
--- a/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts
+++ b/GDJS/Runtime/pixi-renderers/layer-pixi-renderer.ts
@@ -262,8 +262,8 @@ namespace gdjs {
if (game.getAntialiasingMode() !== 'none') {
this._threeEffectComposer.addPass(
new THREE_ADDONS.SMAAPass(
- game.getGameResolutionWidth(),
- game.getGameResolutionHeight()
+ game.getRenderingResolutionWidth(),
+ game.getRenderingResolutionHeight()
)
);
}
@@ -304,9 +304,12 @@ namespace gdjs {
this._threePlaneTexture = texture;
this._threePlaneTexture.generateMipmaps = false;
+ const scaleMode = this._layer
+ .getRuntimeScene()
+ .getGame()
+ .getScaleMode();
const filter =
- this._layer.getRuntimeScene().getGame().getScaleMode() ===
- 'nearest'
+ scaleMode === 'nearest' || scaleMode === 'magnified'
? THREE.NearestFilter
: THREE.LinearFilter;
this._threePlaneTexture.minFilter = filter;
@@ -501,8 +504,8 @@ namespace gdjs {
if (this._threeEffectComposer) {
const game = this._layer.getRuntimeScene().getGame();
this._threeEffectComposer.setSize(
- game.getGameResolutionWidth(),
- game.getGameResolutionHeight()
+ game.getRenderingResolutionWidth(),
+ game.getRenderingResolutionHeight()
);
}
}
diff --git a/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts
index 9c736c974d2d..e1d35994a2be 100644
--- a/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts
+++ b/GDJS/Runtime/pixi-renderers/runtimegame-pixi-renderer.ts
@@ -100,16 +100,16 @@ namespace gdjs {
this._threeRenderer.useLegacyLights = true;
this._threeRenderer.autoClear = false;
this._threeRenderer.setSize(
- this._game.getGameResolutionWidth(),
- this._game.getGameResolutionHeight()
+ this._game.getRenderingResolutionWidth(),
+ this._game.getRenderingResolutionHeight()
);
// Create a PixiJS renderer that use the same GL context as Three.js
// so that both can render to the canvas and even have PixiJS rendering
// reused in Three.js (by using a RenderTexture and the same internal WebGL texture).
this._pixiRenderer = new PIXI.Renderer({
- width: this._game.getGameResolutionWidth(),
- height: this._game.getGameResolutionHeight(),
+ width: this._game.getRenderingResolutionWidth(),
+ height: this._game.getRenderingResolutionHeight(),
view: gameCanvas,
// @ts-ignore - reuse the context from Three.js.
context: this._threeRenderer.getContext(),
@@ -124,8 +124,8 @@ namespace gdjs {
// "preserveDrawingBuffer: true" is needed to avoid flickering
// and background issues on some mobile phones (see #585 #572 #566 #463).
this._pixiRenderer = PIXI.autoDetectRenderer({
- width: this._game.getGameResolutionWidth(),
- height: this._game.getGameResolutionHeight(),
+ width: this._game.getRenderingResolutionWidth(),
+ height: this._game.getRenderingResolutionHeight(),
view: gameCanvas,
preserveDrawingBuffer: true,
antialias: false,
@@ -272,20 +272,20 @@ namespace gdjs {
// 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.width !== this._game.getRenderingResolutionWidth() ||
+ this._pixiRenderer.height !== this._game.getRenderingResolutionHeight()
) {
// TODO (3D): It might be useful to resize pixi view in 3D depending on FOV value
// to enable a mode where pixi always fills the whole screen.
this._pixiRenderer.resize(
- this._game.getGameResolutionWidth(),
- this._game.getGameResolutionHeight()
+ this._game.getRenderingResolutionWidth(),
+ this._game.getRenderingResolutionHeight()
);
if (this._threeRenderer) {
this._threeRenderer.setSize(
- this._game.getGameResolutionWidth(),
- this._game.getGameResolutionHeight()
+ this._game.getRenderingResolutionWidth(),
+ this._game.getRenderingResolutionHeight()
);
}
}
@@ -295,8 +295,8 @@ namespace gdjs {
// only, so won't create visual artifacts during the rendering.
const isFullPage =
this._forceFullscreen || this._isFullPage || this._isFullscreen;
- let canvasWidth = this._game.getGameResolutionWidth();
- let canvasHeight = this._game.getGameResolutionHeight();
+ let canvasWidth = this._game.getRenderingResolutionWidth();
+ let canvasHeight = this._game.getRenderingResolutionHeight();
let maxWidth = window.innerWidth - this._marginLeft - this._marginRight;
let maxHeight = window.innerHeight - this._marginTop - this._marginBottom;
if (maxWidth < 0) {
@@ -523,10 +523,10 @@ namespace gdjs {
// Handle the fact that the game is stretched to fill the canvas.
pageCoords[0] =
(canvasCoords[0] * this._canvasWidth) /
- this._game.getGameResolutionWidth();
+ this._game.getRenderingResolutionWidth();
pageCoords[1] =
(canvasCoords[1] * this._canvasHeight) /
- this._game.getGameResolutionHeight();
+ this._game.getRenderingResolutionHeight();
return pageCoords;
}
diff --git a/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts b/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts
index 603c1bb3436e..89ced94d85d0 100644
--- a/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts
+++ b/GDJS/Runtime/pixi-renderers/runtimescene-pixi-renderer.ts
@@ -275,6 +275,12 @@ namespace gdjs {
pixiRenderer.render(this._pixiContainer, {
clear: this._runtimeScene.getClearCanvas(),
});
+ const debugContainer = this._runtimeScene
+ .getDebuggerRenderer()
+ .getRendererObject();
+ if (debugContainer) {
+ pixiRenderer.render(debugContainer, { clear: false });
+ }
this._layerRenderingMetrics.rendered2DLayersCount++;
}
diff --git a/GDJS/Runtime/runtimegame.ts b/GDJS/Runtime/runtimegame.ts
index 7bb9cceabd01..abbb3484e3c2 100644
--- a/GDJS/Runtime/runtimegame.ts
+++ b/GDJS/Runtime/runtimegame.ts
@@ -141,7 +141,7 @@ namespace gdjs {
_originalHeight: float;
_resizeMode: 'adaptWidth' | 'adaptHeight' | string;
_adaptGameResolutionAtRuntime: boolean;
- _scaleMode: 'linear' | 'nearest';
+ _scaleMode: ScaleMode;
_pixelsRounding: boolean;
_antialiasingMode: 'none' | 'MSAA';
_isAntialisingEnabledOnMobile: boolean;
@@ -158,6 +158,11 @@ namespace gdjs {
* When set to true, the scenes are notified that game resolution size changed.
*/
_notifyScenesForGameResolutionResize: boolean = false;
+ /**
+ * When set to true, the scenes are notified that game zoom factor changed.
+ */
+ _notifyScenesForGameZoomFactorChange: boolean = false;
+ _zoomFactor: number = 1;
/**
* When paused, the game won't step and will be freezed. Useful for debugging.
@@ -551,21 +556,45 @@ namespace gdjs {
}
/**
- * Get the game resolution (the size at which the game is played and rendered) width.
+ * Get the game resolution width for events.
* @returns The game resolution width, in pixels.
*/
getGameResolutionWidth(): float {
- return this._gameResolutionWidth;
+ return this._gameResolutionWidth / this._zoomFactor;
}
/**
- * Get the game resolution (the size at which the game is played and rendered) height.
+ * Get the game resolution height for events.
* @returns The game resolution height, in pixels.
*/
getGameResolutionHeight(): float {
+ return this._gameResolutionHeight / this._zoomFactor;
+ }
+
+ /**
+ * Get the game resolution width (the size at which the game is rendered).
+ * @returns The game resolution width, in pixels.
+ */
+ getRenderingResolutionWidth(): float {
+ return this._gameResolutionWidth;
+ }
+
+ /**
+ * Get the game resolution height (the size at which the game is rendered).
+ * @returns The game resolution height, in pixels.
+ */
+ getRenderingResolutionHeight(): float {
return this._gameResolutionHeight;
}
+ /**
+ * The scale is usually near 1 unless the 'magnified' scale mode is used.
+ * @returns the factor between game resolution size and rendering resolution size.
+ */
+ getZoomFactor() {
+ return this._zoomFactor;
+ }
+
/**
* Change the game resolution.
*
@@ -577,17 +606,16 @@ namespace gdjs {
this._gameResolutionWidth = width;
this._gameResolutionHeight = height;
- if (this._adaptGameResolutionAtRuntime) {
- if (
- gdjs.RuntimeGameRenderer &&
- gdjs.RuntimeGameRenderer.getWindowInnerWidth &&
- gdjs.RuntimeGameRenderer.getWindowInnerHeight
- ) {
- const windowInnerWidth =
- gdjs.RuntimeGameRenderer.getWindowInnerWidth();
- const windowInnerHeight =
- gdjs.RuntimeGameRenderer.getWindowInnerHeight();
+ if (
+ gdjs.RuntimeGameRenderer &&
+ gdjs.RuntimeGameRenderer.getWindowInnerWidth &&
+ gdjs.RuntimeGameRenderer.getWindowInnerHeight
+ ) {
+ const windowInnerWidth = gdjs.RuntimeGameRenderer.getWindowInnerWidth();
+ const windowInnerHeight =
+ gdjs.RuntimeGameRenderer.getWindowInnerHeight();
+ if (this._adaptGameResolutionAtRuntime) {
// Enlarge either the width or the eight to fill the inner window space.
if (this._resizeMode === 'adaptWidth') {
this._gameResolutionWidth =
@@ -614,6 +642,35 @@ namespace gdjs {
}
}
}
+ if (
+ this._scaleMode === 'magnified' &&
+ this._gameResolutionWidth > 0 &&
+ this._gameResolutionHeight > 0 &&
+ // Fall back on linear if magnified is used on a high resolution game.
+ this._originalWidth <= 960 &&
+ this._originalHeight <= 540
+ ) {
+ const pixelSize = Math.max(
+ 1,
+ Math.ceil(
+ this._zoomFactor *
+ Math.min(
+ windowInnerWidth / this._gameResolutionWidth,
+ windowInnerHeight / this._gameResolutionHeight
+ )
+ )
+ );
+ this._gameResolutionWidth = Math.round(
+ (this._gameResolutionWidth * pixelSize) / this._zoomFactor
+ );
+ this._gameResolutionHeight = Math.round(
+ (this._gameResolutionHeight * pixelSize) / this._zoomFactor
+ );
+ if (this._zoomFactor !== pixelSize && pixelSize >= 1) {
+ this._zoomFactor = pixelSize;
+ this._notifyScenesForGameZoomFactorChange = true;
+ }
+ }
}
// Don't alter the game resolution. The renderer
@@ -682,9 +739,9 @@ namespace gdjs {
}
/**
- * Return the scale mode of the game ("linear" or "nearest").
+ * Return the scale mode of the game ("linear", "magnified" or "nearest").
*/
- getScaleMode(): 'linear' | 'nearest' {
+ getScaleMode(): ScaleMode {
return this._scaleMode;
}
@@ -962,6 +1019,10 @@ namespace gdjs {
this._sceneStack.onGameResolutionResized();
this._notifyScenesForGameResolutionResize = false;
}
+ if (this._notifyScenesForGameZoomFactorChange) {
+ this._sceneStack.onGameZoomFactorChanged();
+ this._notifyScenesForGameZoomFactorChange = false;
+ }
// Render and step the scene.
if (this._sceneStack.step(elapsedTime)) {
diff --git a/GDJS/Runtime/runtimeobject.ts b/GDJS/Runtime/runtimeobject.ts
index 47179079b686..0434373b14d5 100644
--- a/GDJS/Runtime/runtimeobject.ts
+++ b/GDJS/Runtime/runtimeobject.ts
@@ -658,6 +658,11 @@ namespace gdjs {
*/
onSceneResumed(runtimeScene: gdjs.RuntimeScene): void {}
+ /**
+ * Called when the game zoom factor is changed to adapt to a new resolution.
+ */
+ onGameZoomFactorChanged(): void {}
+
//Rendering:
/**
* @return The internal object for a 2D rendering (PIXI.DisplayObject...)
diff --git a/GDJS/Runtime/scenestack.ts b/GDJS/Runtime/scenestack.ts
index b04bd5976187..1c3831b4fb9d 100644
--- a/GDJS/Runtime/scenestack.ts
+++ b/GDJS/Runtime/scenestack.ts
@@ -34,6 +34,15 @@ namespace gdjs {
}
}
+ /**
+ * Called when the game zoom factor is changed to adapt to a new resolution.
+ */
+ onGameZoomFactorChanged(): void {
+ for (let i = 0; i < this._stack.length; ++i) {
+ this._stack[i].onGameZoomFactorChanged();
+ }
+ }
+
step(elapsedTime: float): boolean {
this._throwIfDisposed();
if (this._isNextLayoutLoading || this._stack.length === 0) {
diff --git a/GDJS/Runtime/types/project-data.d.ts b/GDJS/Runtime/types/project-data.d.ts
index 8f4947b17911..024939051aed 100644
--- a/GDJS/Runtime/types/project-data.d.ts
+++ b/GDJS/Runtime/types/project-data.d.ts
@@ -329,13 +329,15 @@ declare interface EffectNetworkSyncData {
};
}
+declare type ScaleMode = 'linear' | 'magnified' | 'nearest';
+
declare interface ProjectPropertiesData {
adaptGameResolutionAtRuntime: boolean;
folderProject: boolean;
orientation: string;
packageName: string;
projectFile: string;
- scaleMode: 'linear' | 'nearest';
+ scaleMode: ScaleMode;
pixelsRounding: boolean;
antialiasingMode: 'none' | 'MSAA';
antialisingEnabledOnMobile: boolean;
diff --git a/newIDE/app/src/AssetStore/InstallAsset.js b/newIDE/app/src/AssetStore/InstallAsset.js
index 86bfb6e90861..5b4c674cb63d 100644
--- a/newIDE/app/src/AssetStore/InstallAsset.js
+++ b/newIDE/app/src/AssetStore/InstallAsset.js
@@ -129,7 +129,9 @@ export const installResource = (
if (newResource.getKind() === 'image') {
// $FlowExpectedError[prop-missing] - We know the resource is an ImageResource and has the setSmooth method.
newResource.setSmooth(
- project.getScaleMode() !== 'nearest' && !isPixelArt(asset)
+ project.getScaleMode() !== 'nearest' &&
+ project.getScaleMode() !== 'magnified' &&
+ !isPixelArt(asset)
);
}
diff --git a/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js b/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js
index 625fce456066..53321d6387c6 100644
--- a/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js
+++ b/newIDE/app/src/InstancesEditor/InstancesRenderer/LayerRenderer.js
@@ -655,7 +655,8 @@ export default class LayerRenderer {
threePlaneTexture.generateMipmaps = false;
const filter =
- this.project.getScaleMode() === 'nearest'
+ this.project.getScaleMode() === 'nearest' ||
+ this.project.getScaleMode() === 'magnified'
? THREE.NearestFilter
: THREE.LinearFilter;
threePlaneTexture.minFilter = filter;
diff --git a/newIDE/app/src/ProjectManager/ProjectPropertiesDialog.js b/newIDE/app/src/ProjectManager/ProjectPropertiesDialog.js
index 46a01a06b6aa..1ebd11b3bb8e 100644
--- a/newIDE/app/src/ProjectManager/ProjectPropertiesDialog.js
+++ b/newIDE/app/src/ProjectManager/ProjectPropertiesDialog.js
@@ -729,11 +729,15 @@ const ProjectPropertiesDialog = (props: Props) => {
>
+
{
notifyOfChange();
}}
/>
- {scaleMode === 'nearest' && (
+ {(scaleMode === 'nearest' || scaleMode === 'magnified') && (
{
if (newResource instanceof gd.ImageResource) {
- newResource.setSmooth(project.getScaleMode() !== 'nearest');
+ newResource.setSmooth(
+ project.getScaleMode() !== 'nearest' &&
+ project.getScaleMode() !== 'magnified'
+ );
}
};