7. Complex Scene Composition Stays in Game Components
Some scenes need more than centering and scaling. The item scene has an album, title, progress bar, and buttons.
ItemSceneLayoutComponent describes the composition:
private applyLayout(): void {
const orientation = this.node.screen.getOrientation();
const screen = this.node.screen.getVirtualSize();
this.applyButtonScale(orientation);
this.positionAlbumStage(screen.width, screen.height, orientation);
this.sizeTitleCover(screen.width);
this.sizeAlbumProgress();
this.positionAlbumProgress();
this.positionTitle();
this.positionPlayButton(screen.height);
this.positionAlbumsButton(screen.height);
}
The album size uses different axes:
const targetScale = orientation === 'port'
? (screenWidth * this.config.portImageWidthScreenPercent) / frameWidth
: (screenHeight * this.config.landImageHeightScreenPercent) / frameHeight;
albumStage.setScale(targetScale, targetScale);
albumStage.setPosition(Math.round(screenWidth / 2), Math.round(screenHeight / 2));
In portrait, width matters more. In landscape, height matters more.
The layout JSON exposes this as config:
{
"landImageHeightScreenPercent": 0.6,
"portImageWidthScreenPercent": 0.6,
"landPlayButtonScale": 1,
"portPlayButtonScale": 1
}
This is adaptive layout, not just responsive scaling.
8. Catalog Grid: Different Columns per Orientation
The items scene changes the album catalog grid:
- landscape: 5 columns;
- portrait: 2 columns.
Config:
{
"itemsAlbumsGrid": {
"type": "ItemsAlbumsGridComponent",
"enabled": true,
"data": {
"landColumns": 5,
"portColumns": 2,
"horizontalPaddingPx": 48,
"bottomPaddingPx": 48,
"columnGapPx": 28,
"rowGapPx": 28,
"maxScale": 1
}
}
}
Code:
private resolveColumns(): number {
return this.node.screen.getOrientation() === 'land'
? this.config.landColumns
: this.config.portColumns;
}
After that, the grid measures cards, computes the total grid size, and fits the result into the available area:
const fitScale = ScreenFitLayoutResolver.resolveScale({
contentWidth: gridWidth <= 0 ? 1 : gridWidth,
contentHeight: gridHeight <= 0 ? 1 : gridHeight,
availableWidth,
availableHeight,
minScale: 0,
maxScale: this.config.maxScale,
});
This is a practical pattern:
- Choose the UX structure for the current orientation.
- Measure the actual content.
- Fit the result into the screen.