Fixed HUD and menu position and scaling in hardware renderer
Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -94,10 +94,15 @@ class _GameScreenState extends State<GameScreen> {
|
||||
Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (node, event) {
|
||||
if (event is KeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.tab) {
|
||||
setState(_cycleRendererMode);
|
||||
return KeyEventResult.handled;
|
||||
if (event is KeyDownEvent) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.tab) {
|
||||
setState(_cycleRendererMode);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.backquote) {
|
||||
setState(_toggleFpsCounter);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
@@ -118,7 +123,7 @@ class _GameScreenState extends State<GameScreen> {
|
||||
top: 16,
|
||||
right: 16,
|
||||
child: Text(
|
||||
'TAB: ${_modeLabel(_rendererMode)}',
|
||||
'TAB: ${_modeLabel(_rendererMode)} `: FPS ${_engine.showFpsCounter ? 'On' : 'Off'}',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
),
|
||||
@@ -172,6 +177,10 @@ class _GameScreenState extends State<GameScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
void _toggleFpsCounter() {
|
||||
_engine.showFpsCounter = !_engine.showFpsCounter;
|
||||
}
|
||||
|
||||
String _modeLabel(_RendererMode mode) {
|
||||
switch (mode) {
|
||||
case _RendererMode.software:
|
||||
|
||||
@@ -80,6 +80,9 @@ class WolfEngine {
|
||||
/// Current smoothed FPS, suitable for lightweight on-screen diagnostics.
|
||||
double get fps => _smoothedFps;
|
||||
|
||||
/// Whether renderers should draw the FPS counter overlay.
|
||||
bool showFpsCounter = true;
|
||||
|
||||
/// The episode index where the game session begins.
|
||||
final int? startingEpisode;
|
||||
|
||||
|
||||
@@ -79,7 +79,9 @@ abstract class RendererBackend<T>
|
||||
|
||||
if (engine.difficulty == null) {
|
||||
drawMenu(engine);
|
||||
drawFpsOverlay(engine);
|
||||
if (engine.showFpsCounter) {
|
||||
drawFpsOverlay(engine);
|
||||
}
|
||||
return finalizeFrame();
|
||||
}
|
||||
|
||||
@@ -89,7 +91,9 @@ abstract class RendererBackend<T>
|
||||
// 3. Draw 2D overlays.
|
||||
drawWeapon(engine);
|
||||
drawHud(engine);
|
||||
drawFpsOverlay(engine);
|
||||
if (engine.showFpsCounter) {
|
||||
drawFpsOverlay(engine);
|
||||
}
|
||||
|
||||
// 4. Finalize and return the frame data (Buffer or String/List).
|
||||
return finalizeFrame();
|
||||
|
||||
@@ -203,9 +203,9 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
const int panelY = 58;
|
||||
const int panelW = 264;
|
||||
const int panelH = 104;
|
||||
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
||||
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
||||
|
||||
_drawMenuTextCentered('SELECT GAME', 38, headingColor, scale: 2);
|
||||
_drawCanonicalMenuTextCentered('SELECT GAME', 38, headingColor, scale: 2);
|
||||
|
||||
final cursor = art.mappedPic(
|
||||
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
||||
@@ -222,7 +222,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
if (isSelected && cursor != null) {
|
||||
_blitVgaImage(cursor, panelX + 10, y - 2);
|
||||
}
|
||||
_drawMenuText(
|
||||
_drawCanonicalMenuText(
|
||||
_gameTitle(engine.availableGames[i].version),
|
||||
textX,
|
||||
y,
|
||||
@@ -243,9 +243,14 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
const int panelY = 20;
|
||||
const int panelW = 296;
|
||||
const int panelH = 158;
|
||||
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
||||
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
||||
|
||||
_drawMenuTextCentered('WHICH EPISODE TO PLAY?', 6, headingColor, scale: 2);
|
||||
_drawCanonicalMenuTextCentered(
|
||||
'WHICH EPISODE TO PLAY?',
|
||||
6,
|
||||
headingColor,
|
||||
scale: 2,
|
||||
);
|
||||
|
||||
final cursor = art.mappedPic(
|
||||
engine.menuManager.isCursorAltFrame(engine.timeAliveMs) ? 9 : 8,
|
||||
@@ -271,7 +276,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
|
||||
final parts = engine.data.episodes[i].name.split('\n');
|
||||
if (parts.isNotEmpty) {
|
||||
_drawMenuText(
|
||||
_drawCanonicalMenuText(
|
||||
parts.first,
|
||||
textX,
|
||||
y + 1,
|
||||
@@ -279,7 +284,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
);
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
_drawMenuText(
|
||||
_drawCanonicalMenuText(
|
||||
parts.sublist(1).join(' '),
|
||||
textX,
|
||||
y + 12,
|
||||
@@ -292,8 +297,8 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
void _drawCenteredMenuFooter(WolfClassicMenuArt art) {
|
||||
final bottom = art.mappedPic(15);
|
||||
if (bottom != null) {
|
||||
final int x = ((width - bottom.width) ~/ 2).clamp(0, width - 1);
|
||||
final int y = (height - bottom.height - 8).clamp(0, height - 1);
|
||||
final int x = ((320 - bottom.width) ~/ 2).clamp(0, 319);
|
||||
final int y = (200 - bottom.height - 8).clamp(0, 199);
|
||||
_blitVgaImage(bottom, x, y);
|
||||
return;
|
||||
}
|
||||
@@ -316,9 +321,9 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
textWidth += WolfMenuFont.measureTextWidth(text, 1);
|
||||
}
|
||||
|
||||
final int panelWidth = (textWidth + 12).clamp(1, width);
|
||||
final int panelX = ((width - panelWidth) ~/ 2).clamp(0, width - 1);
|
||||
_fillMenuPanel(
|
||||
final int panelWidth = (textWidth + 12).clamp(1, 320);
|
||||
final int panelX = ((320 - panelWidth) ~/ 2).clamp(0, 319);
|
||||
_fillCanonicalRect(
|
||||
panelX,
|
||||
_menuFooterY,
|
||||
panelWidth,
|
||||
@@ -329,7 +334,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
int cursorX = panelX + 6;
|
||||
const int textY = _menuFooterY + 2;
|
||||
for (final (text, color) in segments) {
|
||||
_drawMenuText(text, cursorX, textY, color, scale: 1);
|
||||
_drawCanonicalMenuText(text, cursorX, textY, color, scale: 1);
|
||||
cursorX += WolfMenuFont.measureTextWidth(text, 1);
|
||||
}
|
||||
}
|
||||
@@ -348,14 +353,19 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
const int panelY = 70;
|
||||
const int panelW = 264;
|
||||
const int panelH = 82;
|
||||
_fillMenuPanel(panelX, panelY, panelW, panelH, panelColor);
|
||||
_fillCanonicalRect(panelX, panelY, panelW, panelH, panelColor);
|
||||
|
||||
_drawMenuTextCentered(Difficulty.menuText, 48, headingColor, scale: 2);
|
||||
_drawCanonicalMenuTextCentered(
|
||||
Difficulty.menuText,
|
||||
48,
|
||||
headingColor,
|
||||
scale: 2,
|
||||
);
|
||||
|
||||
final bottom = art.mappedPic(15);
|
||||
if (bottom != null) {
|
||||
final x = (width - bottom.width) ~/ 2;
|
||||
final y = height - bottom.height - 8;
|
||||
final int x = ((320 - bottom.width) ~/ 2).clamp(0, 319);
|
||||
final int y = (200 - bottom.height - 8).clamp(0, 199);
|
||||
_blitVgaImage(bottom, x, y);
|
||||
}
|
||||
|
||||
@@ -380,7 +390,7 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
_blitVgaImage(cursor, panelX + 10, y - 2);
|
||||
}
|
||||
|
||||
_drawMenuText(
|
||||
_drawCanonicalMenuText(
|
||||
Difficulty.values[i].title,
|
||||
textX,
|
||||
y,
|
||||
@@ -453,6 +463,74 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
return (0xFF000000) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
|
||||
double get _uiScaleX => width / 320.0;
|
||||
|
||||
double get _uiScaleY => height / 200.0;
|
||||
|
||||
void _fillCanonicalRect(
|
||||
int startX320,
|
||||
int startY200,
|
||||
int width320,
|
||||
int height200,
|
||||
int color,
|
||||
) {
|
||||
final int startX = (startX320 * _uiScaleX).floor();
|
||||
final int endX = ((startX320 + width320) * _uiScaleX).ceil();
|
||||
final int startY = (startY200 * _uiScaleY).floor();
|
||||
final int endY = ((startY200 + height200) * _uiScaleY).ceil();
|
||||
|
||||
for (int y = startY; y < endY; y++) {
|
||||
if (y < 0 || y >= height) continue;
|
||||
final int rowStart = y * width;
|
||||
for (int x = startX; x < endX; x++) {
|
||||
if (x >= 0 && x < width) {
|
||||
_buffer.pixels[rowStart + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _drawCanonicalMenuText(
|
||||
String text,
|
||||
int startX320,
|
||||
int startY200,
|
||||
int color, {
|
||||
int scale = 1,
|
||||
}) {
|
||||
int x320 = startX320;
|
||||
for (final rune in text.runes) {
|
||||
final String char = String.fromCharCode(rune).toUpperCase();
|
||||
final List<String> pattern = WolfMenuFont.glyphFor(char);
|
||||
|
||||
for (int row = 0; row < pattern.length; row++) {
|
||||
final String bits = pattern[row];
|
||||
for (int col = 0; col < bits.length; col++) {
|
||||
if (bits[col] != '1') continue;
|
||||
_fillCanonicalRect(
|
||||
x320 + (col * scale),
|
||||
startY200 + (row * scale),
|
||||
scale,
|
||||
scale,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
x320 += WolfMenuFont.glyphAdvance(char, scale);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawCanonicalMenuTextCentered(
|
||||
String text,
|
||||
int y200,
|
||||
int color, {
|
||||
int scale = 1,
|
||||
}) {
|
||||
final int textWidth = WolfMenuFont.measureTextWidth(text, scale);
|
||||
final int x320 = ((320 - textWidth) ~/ 2).clamp(0, 319);
|
||||
_drawCanonicalMenuText(text, x320, y200, color, scale: scale);
|
||||
}
|
||||
|
||||
/// Draws bitmap menu text directly into the framebuffer.
|
||||
void _drawMenuText(
|
||||
String text,
|
||||
@@ -486,18 +564,6 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws bitmap menu text centered in the current framebuffer width.
|
||||
void _drawMenuTextCentered(
|
||||
String text,
|
||||
int y,
|
||||
int color, {
|
||||
int scale = 1,
|
||||
}) {
|
||||
final int textWidth = WolfMenuFont.measureTextWidth(text, scale);
|
||||
final int x = ((width - textWidth) ~/ 2).clamp(0, width - 1);
|
||||
_drawMenuText(text, x, y, color, scale: scale);
|
||||
}
|
||||
|
||||
@override
|
||||
FrameBuffer finalizeFrame() {
|
||||
// If the player took damage, overlay a red tint across the 3D view
|
||||
@@ -513,17 +579,23 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
|
||||
|
||||
/// Maps planar VGA image data directly to 32-bit framebuffer pixels.
|
||||
///
|
||||
/// This renderer assumes a 1:1 mapping with the canonical 320x200 layout.
|
||||
/// UI coordinates are expressed in canonical 320x200 space and scaled to the
|
||||
/// current framebuffer so higher-resolution render targets preserve layout.
|
||||
void _blitVgaImage(VgaImage image, int startX, int startY) {
|
||||
for (int dy = 0; dy < image.height; dy++) {
|
||||
for (int dx = 0; dx < image.width; dx++) {
|
||||
int drawX = startX + dx;
|
||||
int drawY = startY + dy;
|
||||
final int destStartX = (startX * _uiScaleX).floor();
|
||||
final int destStartY = (startY * _uiScaleY).floor();
|
||||
final int destWidth = math.max(1, (image.width * _uiScaleX).ceil());
|
||||
final int destHeight = math.max(1, (image.height * _uiScaleY).ceil());
|
||||
|
||||
for (int dy = 0; dy < destHeight; dy++) {
|
||||
for (int dx = 0; dx < destWidth; dx++) {
|
||||
final int drawX = destStartX + dx;
|
||||
final int drawY = destStartY + dy;
|
||||
|
||||
if (drawX >= 0 && drawX < width && drawY >= 0 && drawY < height) {
|
||||
int srcX = dx.clamp(0, image.width - 1);
|
||||
int srcY = dy.clamp(0, image.height - 1);
|
||||
int colorByte = image.decodePixel(srcX, srcY);
|
||||
final int srcX = (dx / _uiScaleX).toInt().clamp(0, image.width - 1);
|
||||
final int srcY = (dy / _uiScaleY).toInt().clamp(0, image.height - 1);
|
||||
final int colorByte = image.decodePixel(srcX, srcY);
|
||||
if (colorByte != 255) {
|
||||
_buffer.pixels[drawY * width + drawX] =
|
||||
ColorPalette.vga32Bit[colorByte];
|
||||
|
||||
@@ -26,8 +26,8 @@ class WolfGlslRenderer extends BaseWolfRenderer {
|
||||
}
|
||||
|
||||
class _WolfGlslRendererState extends BaseWolfRendererState<WolfGlslRenderer> {
|
||||
static const int _renderWidth = 320;
|
||||
static const int _renderHeight = 200;
|
||||
static const int _renderWidth = 640;
|
||||
static const int _renderHeight = 400;
|
||||
|
||||
final SoftwareRenderer _renderer = SoftwareRenderer();
|
||||
|
||||
|
||||
@@ -34,10 +34,5 @@ void main() {
|
||||
vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25;
|
||||
vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45);
|
||||
|
||||
vec2 centered = uv - 0.5;
|
||||
float vignette = 1.0 - dot(centered, centered) * 0.35;
|
||||
vignette = clamp(vignette, 0.75, 1.0);
|
||||
|
||||
vec3 color = aaColor * vignette;
|
||||
fragColor = vec4(color, centerSample.a);
|
||||
fragColor = vec4(aaColor, centerSample.a);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user