Screen Adaptation for Scenes and UI
In game development, screen adaptation is a crucial feature. No matter what device or window size the player uses, they want to have the best visual experience. This tutorial will guide you step by step on how to implement screen adaptation for game scenes and UI in the Dora SSR engine.
1. Understanding Screen Adaptation
On different devices, screen sizes and resolutions vary. Without adaptation, the game may not display properly on certain devices, or the elements may be misaligned. The goal of screen adaptation is to ensure the game content displays correctly across various screens while maintaining a good user experience.
2. Implementing Screen Adaptation for Game Scenes
Screen adaptation for game scenes primarily involves adjusting the camera's view based on the window size, ensuring that the scene's content is fully displayed on the screen.
2.1 Define Design Dimensions
First, define the design dimensions for the game. This is the reference size on which you base your design and development.
- Lua
- Teal
- TypeScript
- YueScript
local DesignSceneHeight <const> = 1024
local DesignSceneHeight <const> = 1024
const DesignSceneHeight = 1024;
const DesignSceneHeight = 1024
Here, we set the design height to 1024
. This means we design the scene with a height of 1024
. When adapting, we aim to ensure the vertical scene area is fully displayed, and any horizontal areas exceeding the screen will be cropped or centered if smaller than the screen area.
2.2 Adjust Camera Zoom
Next, adjust the camera's zoom based on the current window size.
- Lua
- Teal
- TypeScript
- YueScript
local Director <const> = require("Director")
local View <const> = require("View")
local function updateViewSize()
Director.currentCamera.zoom = View.size.height / DesignSceneHeight
end
local Director <const> = require("Director")
local type Camera2D = require("Camera2D")
local View <const> = require("View")
local function updateViewSize()
local camera = Director.currentCamera as Camera2D.Type
camera.zoom = View.size.height / DesignSceneHeight
end
import { Director, View, TypeName, tolua } from 'Dora';
const updateViewSize = () => {
const camera = tolua.cast(Director.currentCamera, TypeName.Camera2D);
if (camera) {
camera.zoom = View.size.height / DesignSceneHeight;
}
};
_ENV = Dora
updateViewSize = ->
Director.currentCamera.zoom = View.size.height / DesignSceneHeight
Explanation:
Director.currentCamera
: The current scene's camera object.zoom
: The camera's zoom property, which affects the field of view.View.size.height
: The actual height of the current window.- By calculating
View.size.height / DesignSceneHeight
, we get the ratio of the actual height to the design height and set it as the camera's zoom value.
2.3 Listen for Window Size Changes
To update the camera's zoom whenever the window size changes (e.g., when the user resizes the window or rotates the device), we need to listen for the application's size change events.
- Lua
- Teal
- TypeScript
- YueScript
updateViewSize() -- Call once during initialization
Director.entry:onAppChange(function(settingName)
if settingName == "Size" then
updateViewSize() -- Update whenever a size change occurs
end
end)
updateViewSize() -- Call once during initialization
Director.entry:onAppChange(function(settingName: string)
if settingName == "Size" then
updateViewSize() -- Update whenever a size change occurs
end
end)
updateViewSize(); // Call once during initialization
Director.entry.onAppChange(settingName => {
if (settingName === 'Size') {
updateViewSize(); // Update whenever a size change occurs
}
});
updateViewSize! -- Call once during initialization
Director.entry\onAppChange (settingName) ->
if settingName == "Size"
updateViewSize! -- Update whenever a size change occurs
Explanation:
Director.entry:onAppChange
: Registers a listener that triggers when the application's settings change.settingName
: The name of the setting that changed.- When
settingName
is"Size"
, it indicates that the window size has changed, and we callupdateViewSize()
to update the camera's zoom.
2.4 Complete Code
- Lua
- Teal
- TypeScript
- YueScript
-- Example of game scene adaptation
-- Import modules
local DrawNode <const> = require("DrawNode")
local Director <const> = require("Director")
local View <const> = require("View")
local Vec2 <const> = require("Vec2")
-- Define design dimensions
local DesignSceneHeight <const> = 1024
-- Create the scene
local node = DrawNode()
node:drawDot(Vec2.zero, DesignSceneHeight / 2)
node:addTo(Director.entry)
-- Handle window size changes
local function updateViewSize()
Director.currentCamera.zoom = View.size.height / DesignSceneHeight
end
updateViewSize()
-- Register event callback for window size changes
Director.entry:onAppChange(function(settingName)
if settingName == "Size" then
updateViewSize()
end
end)
-- Example of game scene adaptation
-- Import modules
local DrawNode <const> = require("DrawNode")
local Director <const> = require("Director")
local View <const> = require("View")
local Vec2 <const> = require("Vec2")
local type Camera2D = require("Camera2D")
-- Define design dimensions
local DesignSceneHeight <const> = 1024
-- Create the scene
local node = DrawNode()
node:drawDot(Vec2.zero, DesignSceneHeight / 2)
node:addTo(Director.entry)
-- Handle window size changes
local function updateViewSize()
local camera = Director.currentCamera as Camera2D.Type
camera.zoom = View.size.height / DesignSceneHeight
end
updateViewSize()
-- Register event callback for window size changes
Director.entry:onAppChange(function(settingName: string)
if settingName == "Size" then
updateViewSize()
end
end)
// Example of game scene adaptation
// Import modules
import { DrawNode, Director, View, Vec2, TypeName } from 'Dora';
// Define design dimensions
const DesignSceneHeight = 1024;
// Create the scene
const node = DrawNode();
node.drawDot(Vec2.zero, DesignSceneHeight / 2);
node.addTo(Director.entry);
// Handle window size changes
const updateViewSize = () => {
const camera = tolua.cast(Director.currentCamera, TypeName.Camera2D);
if (camera) {
camera.zoom = View.size.height / DesignSceneHeight;
}
};
updateViewSize();
// Register event callback for window size changes
Director.entry.onAppChange(settingName => {
if (settingName === 'Size') {
updateViewSize();
}
});
-- Example of game scene adaptation
-- Import modules
_ENV = Dora
-- Define design dimensions
const DesignSceneHeight = 1024
-- Create the scene
const node = DrawNode!
node.drawDot Vec2.zero, DesignSceneHeight / 2
node.addTo Director.entry
-- Handle window size changes
updateViewSize! -- Call once during initialization
Director.entry\onAppChange (settingName) ->
if settingName == "Size"
updateViewSize! -- Update whenever a size change occurs
3. Implementing Screen Adaptation for Game UI
Screen adaptation for game UI involves ensuring that interface elements are properly arranged on screens of different sizes. We will use the Dora SSR integrated Yoga Layout Engine, which defines element layout relationships using CSS-like Flexbox layout syntax.
3.1 Import Yoga Layout Engine
Yoga is a cross-platform layout engine that supports Flexbox-based layouts. It allows us to define layouts using familiar CSS syntax.
- Lua
- Teal
- TypeScript
- YueScript
local AlignNode <const> = require("AlignNode")
local AlignNode <const> = require("AlignNode")
import { AlignNode } from 'Dora';
import "AlignNode"
AlignNode
is the layout node type in Dora SSR that supports layout functions.
3.2 Using CSS Flex Layout
To quickly learn Flex layout, you can try Flexbox Froggy, an online game: https://flexboxfroggy.com/
First, create a root node and set its layout properties.
- Lua
- Teal
- TypeScript
- YueScript
local root = AlignNode(true)
root:css("justify-content: center; align-items: center")
root:addTo(Director.ui)
local root = AlignNode(true)
root:css("justify-content: center; align-items: center")
root:addTo(Director.ui)
const root = AlignNode(true);
root.css("justify-content: center; align-items: center");
root.addTo(Director.ui);
root = with AlignNode true
\css "justify-content: center; align-items: center"
\addTo Director.ui
Explanation:
AlignNode(true)
: Creates a layout-supporting node.true
indicates this node is the layout container for the window's root.css(...)
: Applies CSS layout styles to the node.justify-content: center
: Horizontally center the child nodes.align-items: center
: Vertically center the child nodes.
root:addTo(Director.ui)
: Adds the root node to the built-in UI layer of the engine.
Next, create a child node and set its size relative to the parent node's percentage.
- Lua
- Teal
- TypeScript
- YueScript
local centerNode = AlignNode()
centerNode:css("width: 60%; height: 60%")
centerNode:addTo(root)
local centerNode = AlignNode()
centerNode:css("width: 60%; height: 60%")
centerNode:addTo(root)
const centerNode = AlignNode();
centerNode.css("width: 60%; height: 60%");
centerNode.addTo(root);
centerNode = with AlignNode
\css "width: 60%; height: 60%"
\addTo root
Explanation:
width: 60%
: Sets the width to 60% of the parent node's width.height: 60%
: Sets the height to 60% of the parent node's height.
3.3 Adjust Elements to Fit the Layout
Add actual UI elements (e.g., sprite objects displaying images) to the layout node and adjust their properties after the layout is complete.
- Lua
- Teal
- TypeScript
- YueScript
local sprite = Sprite("Image/logo.png")
sprite:addTo(centerNode)
local sprite = Sprite("Image/logo.png")
sprite:addTo(centerNode)
const sprite = Sprite("Image/logo.png");
sprite.addTo(centerNode);
sprite = with Sprite "Image/logo.png"
\addTo centerNode
To update the sprite's position and size after the layout adjusts for screen adaptation, we need to listen for the layout completion event.
- Lua
- Teal
- TypeScript
- YueScript
centerNode:onAlignLayout(function(width, height)
-- Adjust sprite display parameters to match the adapted result
sprite.position = Vec2(width / 2, height / 2)
sprite.size = Size(width, height)
end)
centerNode:onAlignLayout(function(width: number, height: number)
-- Adjust sprite display parameters to match the adapted result
sprite.position = Vec2(width / 2, height / 2)
sprite.size = Size(width, height)
end)
centerNode.onAlignLayout((width, height) => {
// Adjust sprite display parameters to match the adapted result
sprite.position = Vec2(width / 2, height / 2);
sprite.size = Size(width, height);
});
centerNode\onAlignLayout (width, height) ->
-- Adjust sprite display parameters to match the adapted result
sprite.position = Vec2 width / 2, height / 2
sprite.size = Size width, height
Explanation:
onAlignLayout
: Callback triggered when layout calculation is completed.width
andheight
: The calculated width and height of the layout node.- In the callback, we set the sprite's
position
to the center of the node and itssize
to match the node's size, ensuring the sprite displays in line with the layout result.
3.3 Complete Code
- Lua
- Teal
- TypeScript
- TSX
- YueScript
-- Adaptive Game UI
-- Import modules
local AlignNode <const> = require("AlignNode")
local Director <const> = require("Director")
local Sprite <const> = require("Sprite")
local Vec2 <const> = require("Vec2")
local Size <const> = require("Size")
-- Create root node for screen adaptation
local root = AlignNode(true)
root:css("justify-content: center; align-items: center")
root:addTo(Director.ui)
-- Create child node for centered layout
local centerNode = AlignNode()
centerNode:css("width: 60%; height: 60%")
centerNode:addTo(root)
-- Create sprite object for display on the child node
local sprite = Sprite("Image/logo.png")
sprite:addTo(centerNode)
-- Register adaptation callback to update the display object's parameters to match the layout result
centerNode:onAlignLayout(function(width, height)
sprite.position = Vec2(width / 2, height / 2)
sprite.size = Size(width, height)
end)
-- Adaptive Game UI
-- Import modules
local AlignNode <const> = require("AlignNode")
local Director <const> = require("Director")
local Sprite <const> = require("Sprite")
local Vec2 <const> = require("Vec2")
local Size <const> = require("Size")
-- Create root node for screen adaptation
local root = AlignNode(true)
root:css("justify-content: center; align-items: center")
root:addTo(Director.ui)
-- Create child node for centered layout
local centerNode = AlignNode()
centerNode:css("width: 60%; height: 60%")
centerNode:addTo(root)
-- Create sprite object for display on the child node
local sprite = Sprite("Image/logo.png")
sprite:addTo(centerNode)
-- Register adaptation callback to update the display object's parameters to match the layout result
centerNode:onAlignLayout(function(width: number, height: number)
sprite.position = Vec2(width / 2, height / 2)
sprite.size = Size(width, height)
end)
// Adaptive Game UI
// Import modules
import { AlignNode, Director, Sprite, Vec2, Size } from 'Dora';
// Create root node for screen adaptation
const root = AlignNode(true);
root.css('justify-content: center; align-items: center');
root.addTo(Director.ui);
// Create child node for centered layout
const centerNode = AlignNode();
centerNode.css('width: 60%; height: 60%');
centerNode.addTo(root);
// Create sprite object for display on the child node
const sprite = Sprite('Image/logo.png');
sprite.addTo(centerNode);
// Register adaptation callback to update the display object's parameters to match the layout result
centerNode.onAlignLayout((width, height) => {
sprite.position = Vec2(width / 2, height / 2);
sprite.size = Size(width, height);
});
// Adaptive Game UI
// Import modules
import { React, toNode, useRef } from 'DoraX';
import { Size, Sprite, Vec2, Director } from 'Dora';
const sprite = useRef<Sprite.Type>();
// Create root node for screen adaptation
const root = toNode(
<align-node windowRoot style={{
justifyContent: 'center',
alignItems: 'center'
}}>
<align-node style={{
width: '60%',
height: '60%'
}}
onLayout={(width, height) => {
const {current} = sprite;
if (current) {
current.position = Vec2(width / 2, height / 2);
current.size = Size(width, height);
}
}}>
<sprite ref={sprite} file='Image/logo.png'/>
</align-node>
</align-node>
);
root?.addTo(Director.ui);
-- Adaptive Game UI
-- Import modules
_ENV = Dora
-- Create root node for screen adaptation
root = with AlignNode true
\css "justify-content: center; align-items: center"
\addTo Director.ui
-- Create child node for centered layout
centerNode = with AlignNode
\css "width: 60%; height: 60%"
\addTo root
-- Create sprite object for display on the child node
sprite = with Sprite "Image/logo.png"
\addTo centerNode
-- Register adaptation callback to update the display object's parameters to match the layout result
centerNode\onAlignLayout (width, height) ->
sprite.position = Vec2 width / 2, height / 2
sprite.size = Size width, height
4. Conclusion
In this tutorial, you learned how to implement screen adaptation for game scenes and UI in Dora SSR:
- Scene adaptation: By setting design dimensions and adjusting the camera's zoom, you ensure the scene content is fully displayed.
- UI adaptation: Using the Yoga layout engine and CSS Flexbox layout syntax, you define element layout relationships and update their properties after layout adjustments.
These methods help you create games that adapt to different screen sizes, providing players with a consistent and enjoyable experience.
Next steps, you can try:
- Delving into more features of the Yoga Layout Engine, such as
flex-direction
andflex-wrap
. - Adding adaptive layouts to more UI elements, such as buttons and text boxes.
- Exploring other modules in Dora SSR to enhance your game development knowledge.
I hope this tutorial was helpful and wish you success on your game development journey!