Layout
Dill pixel provides two main ways to handle layouts in your game: FlexContainer
and UICanvas
. These classes offer different approaches to positioning and organizing UI elements.
FlexContainer
The FlexContainer provides a flexible box layout model similar to CSS Flexbox. It’s ideal for creating dynamic layouts that need to adapt to different screen sizes or content.
Basic Usage
// Create a flex containerconst flexContainer = this.add.flexContainer({ gap: 10, flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-start',});// Add items to the containerflexContainer.add.text({ text: 'Item 1', style: textStyle });flexContainer.add.text({ text: 'Item 2', style: textStyle });flexContainer.add.text({ text: 'Item 3', style: textStyle });
Configuration Options
The FlexContainer supports several layout properties:
flexDirection
: Controls the direction of items (‘row’ | ‘column’)flexWrap
: Determines if items should wrap (‘wrap’ | ‘nowrap’)alignItems
: Aligns items on the cross axis (‘center’ | ‘flex-start’ | ‘flex-end’)justifyContent
: Aligns items on the main axis (‘center’ | ‘flex-start’ | ‘flex-end’ | ‘space-between’ | ‘space-around’ | ‘space-evenly’)gap
: Space between items (number)
const container = this.add.flexContainer({ width: 800, height: 200, gap: 20, flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'space-between',});
Nested Containers
FlexContainers can be nested to create more complex layouts:
const mainContainer = this.add.flexContainer({ flexDirection: 'column', gap: 20,});const topRow = mainContainer.add.flexContainer({ flexDirection: 'row', gap: 10, alignItems: 'center',});topRow.add.text({ text: 'Row 1 Item 1' });topRow.add.text({ text: 'Row 1 Item 2' });const bottomRow = mainContainer.add.flexContainer({ flexDirection: 'row', gap: 10, alignItems: 'center',});bottomRow.add.text({ text: 'Row 2 Item 1' });bottomRow.add.text({ text: 'Row 2 Item 2' });
UICanvas
The UICanvas provides edge-based alignment for UI elements, making it perfect for HUD elements, menus, and other screen-anchored UI components.
Basic Usage
// Create a UI canvasconst ui = this.add.uiCanvas({ useAppSize: true, // Bind to application size debug: true, // Show alignment guides});// Add elements with alignmentui.addElement(this.make.text({ text: 'Top Left' }), { align: 'top left' });ui.addElement(this.make.text({ text: 'Bottom Right' }), { align: 'bottom right' });
Alignment Options
UICanvas supports various edge alignments:
- Cardinal directions: ‘top’, ‘bottom’, ‘left’, ‘right’
- Corners: ‘top left’, ‘top right’, ‘bottom left’, ‘bottom right’
- Center positions: ‘top center’, ‘bottom center’, ‘left center’, ‘right center’
- Absolute center: ‘center’
Adding Padding
Elements can be positioned with padding from their aligned edges:
ui.addElement(this.make.text({ text: 'Padded Corner' }), { align: 'top right', padding: { top: 20, right: 20 },});
Combining with FlexContainer
UICanvas and FlexContainer can be used together for complex layouts:
const ui = this.add.uiCanvas({ useAppSize: true });// Create a flex container for a bottom toolbarconst toolbar = this.make.flexContainer({ gap: 20, alignItems: 'center', height: 48,});toolbar.add.text({ text: 'Button 1' });toolbar.add.text({ text: 'Button 2' });toolbar.add.text({ text: 'Button 3' });// Add the flex container to the UI canvasui.addElement(toolbar, { align: 'bottom' });
Handling Anchored Elements
FlexContainer
and UICanvas
use a sophisticated system to handle elements with anchors or pivot points. When a child is added to a FlexContainer
or UICanvas
, it’s automatically wrapped in an inner container to ensure consistent positioning:
const container = this.add.flexContainer({ flexDirection: 'row', gap: 10,});
// This sprite has an anchor point of 0.5 (centered)const sprite = this.add.sprite({ texture: 'myTexture', anchor: 0.5,});
container.add.existing(sprite); // Will be wrapped automatically
How It Works
When a child is added to a FlexContainer, the following process occurs:
- The child is wrapped in an inner container
- The inner container’s bounds are calculated
- The container’s pivot is adjusted to ensure consistent positioning:
protected handleChildAdded(child: any) { // Create inner container const container = this.add.container(); container.add.existing(child);
// Calculate and adjust for bounds const bounds = container.getLocalBounds(); if (bounds.x < 0) { container.pivot.x = bounds.x; } if (bounds.y < 0) { container.pivot.y = bounds.y; }
// Track the relationship between child and container this._childMap.set(child, container);}
Implications
- Accessing Children: When you need to access a child element, be aware it’s wrapped in a container
- Positioning: All positioning is handled relative to the top-left corner of the inner container
- Layout Updates: Changes to the child’s anchor or pivot will be automatically accommodated by the container system
Example with Mixed Elements
const flexContainer = this.add.flexContainer({ flexDirection: 'row', gap: 20, alignItems: 'center',});
// Centered spriteconst centeredSprite = this.add.sprite({ texture: 'icon', anchor: 0.5,});
// Text with default anchor (0,0)const text = this.add.text({ text: 'Label',});
// Both will be centered espite different anchorsflexContainer.add.existing(centeredSprite);flexContainer.add.existing(text);
Best Practices
-
Choose the Right Tool:
- Use FlexContainer for dynamic content that needs to flow and adapt
- Use UICanvas for screen-anchored UI elements like HUDs and menus
-
Performance:
- Minimize nested containers to reduce layout calculations
- Use
size
constraints when possible to help optimize layout calculations
-
Responsive Design:
- Use
useAppSize: true
with UICanvas for responsive layouts - Combine FlexContainer’s
flexWrap
with appropriate sizing for adaptive layouts
- Use