Responsiveness in Age of New Worlds is not the same problem as responsiveness on a normal website.
A 4X game is dense by nature. It has a map, units, cities, resources, action buttons, panels, notifications, fog of war, overlays, turn state, and sometimes multiplayer information. The challenge is not only to “fit the UI on the screen”. The challenge is to keep the map playable while still giving the player enough information to make decisions.
That means I do not treat responsiveness as a final CSS-like pass. I treat it as part of the presentation architecture.
The Map Is the Primary Surface
The game screen is built as a full-screen stack: Flame renders the world, Flutter renders the interface above it.
Stack(
children: [
CappedGameWidget(game: _renderer),
GameHud(...),
],
);
This is the core responsive decision.
The map always gets the full viewport. I do not put the game world inside a decorative panel or a fixed-size frame. The player should be able to pan, zoom, inspect tiles, and read the board on every supported device.
Flutter UI then sits above the map as overlays: HUD, menus, action deck, resource strip, notifications, modals, and temporary banners.
flowchart TB
Screen["Device Viewport"]
Flame["Full-Screen Flame Map"]
Flutter["Flutter HUD Overlays"]
Commands["Game Commands"]
Screen --> Flame
Screen --> Flutter
Flutter --> Commands
Flame --> CommandsThe renderer owns pixels on the board. Flutter owns the interface. Neither one owns the rules.
Responsiveness Is About Decisions, Not Only Sizes
I use explicit layout metrics for the HUD instead of scattering random breakpoint checks everywhere.
A simplified version looks like this:
class HudLayoutMetrics {
static const double tinyPhoneWidth = 360;
static const double landscapePhoneHeight = 520;
static const double tabletShortestSide = 600;
static const double tabletLongestSide = 1200;
factory HudLayoutMetrics.fromSize({
required Size size,
required bool canShowGlobalActions,
required bool showTopResources,
}) {
final tinyPhone = size.width < tinyPhoneWidth;
final landscapePhone =
size.height < landscapePhoneHeight && size.width > size.height;
final tablet =
size.shortestSide >= tabletShortestSide &&
size.longestSide < tabletLongestSide;
final portraitPhone = size.width < 520 && size.height >= size.width;
final useSideSelectionDetail = tablet && !landscapePhone;
final useStackedTopResources =
showTopResources && size.width < 900 && size.height >= size.width;
return HudLayoutMetrics(
tinyPhone: tinyPhone,
landscapePhone: landscapePhone,
tablet: tablet,
portraitPhone: portraitPhone,
useSideSelectionDetail: useSideSelectionDetail,
useStackedTopResources: useStackedTopResources,
// ...
);
}
}
The important part is that the output is semantic.
I do not only ask: “How wide is the screen?”
I ask: “What kind of layout should this screen use?”
- Is this a tiny phone?
- Is this a landscape phone?
- Is this a tablet?
- Is this a portrait phone?
- Should resources stack?
- Should selection details move to the side?
- How much bottom space does the action deck need?
That keeps the rest of the HUD from knowing too much about raw screen dimensions.
Phones Need Compression
On phones, the main problem is vertical space.
The map still needs room, so the UI has to compress. Panels need smaller padding. Top resources may stack. Bottom action areas need predictable height. Text needs maxLines and overflow handling. Some information has to move into sheets or modals instead of staying permanently visible.
For example, the HUD computes larger bottom padding on portrait phones because the action deck needs space:
final panelBottomPadding = portraitPhone
? HudActionDeck.expandedBottomPadding
: 112.0;
This is the kind of detail that matters in a strategy game. If a panel ignores the bottom command area, it can cover the exact place where the player needs to act.
Tablets Can Show More Structure
Tablets are not just “large phones”.
On a tablet, I can keep more context visible at once. Selection details can move to the side. Panels can breathe a little more. The player can inspect the map while keeping supporting information open.
That is why HudLayoutMetrics has decisions like:
final useSideSelectionDetail = tablet && !landscapePhone;
A side panel is useful when the screen has enough room. It is frustrating when it steals space from a short landscape layout.
Responsiveness here is not about making every device show the same UI. It is about preserving the same game flow with different arrangements.
Desktop Has Different Expectations
Desktop gives me more space, but it also raises expectations.
The player expects hover states, tooltips, keyboard input, stable panels, and dense information. A desktop UI can show more at once, but it should not become visually loud just because there is room.
This is where Flutter is useful. I can use the same presentation components across devices, but let layout constraints decide how much of each component is visible.
Menus and setup screens use LayoutBuilder to choose compact or wider layouts:
LayoutBuilder(
builder: (context, constraints) {
final compact = constraints.maxWidth < 700;
final panelWidth = compact
? constraints.maxWidth
: constraints.maxWidth.clamp(340.0, 390.0).toDouble();
return SizedBox(width: panelWidth, child: _MenuPanel(...));
},
);
The same idea appears in loading screens, lobby screens, new game setup, map picker screens, and editor UI.
Safe Areas Matter
The game uses SafeArea around important controls.
That sounds mundane, but it matters for a game that runs on many devices. A menu button, top resource strip, or editor toolbar should not disappear under a notch, system gesture area, or window inset.
SafeArea(
child: Align(
alignment: Alignment.topLeft,
child: _HudMenuButton(...),
),
);
The map can live under the full screen. Critical UI cannot.
Adaptive Modals and Bottom Sheets
Modal UI also changes with screen size.
GameModalScaffold has adaptive sizing:
GameModalSize _resolvedSize(Size screenSize) {
if (size != GameModalSize.adaptive) return size;
if (shape == GameModalShape.bottomSheet || screenSize.width < 640) {
return GameModalSize.compact;
}
return GameModalSize.regular;
}
On smaller screens, the modal can behave more like a compact sheet. On larger screens, it can become a centered dialog with a controlled max width.
double _maxWidth(GameModalSize size, double screenWidth) {
return switch (size) {
GameModalSize.compact => screenWidth,
GameModalSize.regular => 680,
GameModalSize.wide => 980,
GameModalSize.adaptive => 680,
};
}
This keeps dialogs readable without letting them stretch across a desktop monitor.
A city production dialog, technology tree, confirmation prompt, or editor dialog should feel like the same UI system, but it should not use the same physical shape on every device.
The HUD Uses Fixed Rhythms
For the in-game HUD, I prefer stable dimensions over fully fluid layout.
A strategy HUD has repeated controls: action buttons, end turn button, resource counters, panels, chips, and icons. If every component grows and shrinks freely, the layout becomes hard to scan.
So the HUD has tokens like:
abstract final class GameHudTheme {
static const double buttonHeightCompact = 48;
static const double iconTileSizeCompact = 56.0;
static const double actionButtonWidthCompact = 50;
static const double endTurnButtonWidthNormal = 136;
}
This gives the UI a predictable rhythm.
Responsive design does not mean everything is liquid. In a game UI, some elements should have stable sizes so the player can build muscle memory.
Performance Is Part of Responsiveness
Responsiveness is also about how the game feels, not only how it lays out.
The Flame game is embedded through CappedGameWidget, which controls stepping and keeps the engine paused between capped updates.
class CappedGameWidget<T extends Game> extends StatefulWidget {
static const int defaultMaxFps = 60;
final T game;
final int maxFps;
}
Internally, it steps the engine at a configured frame cap and clamps large frame deltas:
static const _maxFrameDelta = Duration(milliseconds: 100);
Duration _clampDelta(Duration elapsed) {
if (elapsed <= Duration.zero) return _frameInterval;
if (elapsed > _maxFrameDelta) return _maxFrameDelta;
return elapsed;
}
This helps keep the game predictable across devices with different performance profiles.
A low-end phone, a tablet, and a desktop machine should not turn the simulation into three different experiences just because their frame timing differs.
Accessibility Settings Also Affect Layout
Some UI also respects platform preferences like reduced animations:
if (MediaQuery.maybeOf(context)?.disableAnimations ?? false) return;
Animations make the game feel better, but they should not be required for understanding the interface. If the system asks for fewer animations, the UI should still communicate state clearly.
This is part of responsiveness too. Devices are not only different by size. They are different by input method, performance, settings, and user expectations.
What I Test
I do not try to screenshot-test every possible device size. That would be brittle.
Instead, I test the pieces that make responsiveness reliable:
- shared modal scaffolds render their header, content, and actions,
- capped game stepping respects the configured frame rate,
- HUD and game widgets can build under test constraints,
- architecture tests keep presentation widgets small enough to remain maintainable,
- text-heavy widgets use constraints like
maxLinesand overflow where needed.
The goal is to catch broken assumptions early.
If a modal cannot scroll, a button has no compact mode, or a panel assumes infinite width, it should become visible during development instead of only after trying the game on a small screen.
The Main Tradeoff
The hardest part is deciding what not to show.
A 4X game always has more information than the screen can comfortably hold. On desktop, I can show more. On phone, I need to prioritize.
That means the responsive UI is also a design filter.
The most important information stays close to the map. Secondary information moves into panels, modals, sheets, or expandable details. Temporary feedback appears as notifications or banners. Deep inspection can wait until the player asks for it.
Responsiveness is not only technical. It is editorial.
The Lesson
For Age of New Worlds, responsive design is not a single breakpoint table.
It is a set of architectural choices:
- Flame renders the full-screen map.
- Flutter overlays the HUD and app UI.
- Layout metrics turn raw sizes into semantic decisions.
- Phones compress and move detail into sheets.
- Tablets can show more side context.
- Desktop keeps dense controls stable.
- Modals adapt between compact and regular layouts.
- Safe areas protect important controls.
- Frame pacing helps the game feel consistent.
- Tests protect the reusable pieces.
The goal is not to make every device look identical.
The goal is to make every device feel playable.
Leave a Reply