feat: Enhance rendering with pushwall and enemy color support in ASCII, Sixel, and Software renderers

Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
2026-03-23 11:15:17 +01:00
parent 1165e0bc44
commit 0c74abcb7e
4 changed files with 102 additions and 12 deletions
@@ -6,6 +6,7 @@ import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart'; import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
import 'package:wolf_3d_dart/wolf_3d_menu.dart'; import 'package:wolf_3d_dart/wolf_3d_menu.dart';
import 'cli_renderer_backend.dart'; import 'cli_renderer_backend.dart';
@@ -416,8 +417,10 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
final int floorColor = ColorPalette.vga32Bit[8]; final int floorColor = ColorPalette.vga32Bit[8];
final int wallColor = ColorPalette.vga32Bit[7]; final int wallColor = ColorPalette.vga32Bit[7];
final int doorColor = ColorPalette.vga32Bit[14]; final int doorColor = ColorPalette.vga32Bit[14];
final int pushwallColor = ColorPalette.vga32Bit[6];
final int enemyColor = ColorPalette.vga32Bit[12];
final int playerColor = ColorPalette.vga32Bit[10]; final int playerColor = ColorPalette.vga32Bit[10];
final int facingColor = ColorPalette.vga32Bit[12]; final int facingColor = ColorPalette.vga32Bit[15];
if (_usesTerminalLayout) { if (_usesTerminalLayout) {
_fillTerminalRect( _fillTerminalRect(
@@ -460,9 +463,14 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
final int tileId = engine.currentLevel[y][x]; final int tileId = engine.currentLevel[y][x];
final int color = tileId == 0 final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
'$x,$y',
);
final int color = isPushwall
? pushwallColor
: (tileId == 0
? floorColor ? floorColor
: (tileId >= 90 ? doorColor : wallColor); : (tileId >= 90 ? doorColor : wallColor));
_fillMapRect( _fillMapRect(
mapStartX + (x * tileSize), mapStartX + (x * tileSize),
mapStartY + (y * tileSize), mapStartY + (y * tileSize),
@@ -473,6 +481,23 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
} }
} }
final int enemyRadius = math.max(0, tileSize ~/ 3);
final int enemySize = (enemyRadius * 2) + 1;
for (final entity in engine.entities) {
if (entity is! Enemy || entity.state == EntityState.dead) {
continue;
}
final int enemyX = (mapStartX + (entity.x * tileSize)).floor();
final int enemyY = (mapStartY + (entity.y * tileSize)).floor();
_fillMapRect(
enemyX - enemyRadius,
enemyY - enemyRadius,
enemySize,
enemySize,
enemyColor,
);
}
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor(); final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor(); final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
final int markerRadius = math.max(1, tileSize ~/ 2); final int markerRadius = math.max(1, tileSize ~/ 2);
@@ -11,6 +11,7 @@ import 'package:wolf_3d_dart/src/menu/menu_manager.dart';
import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart'; import 'package:wolf_3d_dart/src/rendering/fizzle_fade.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
import 'package:wolf_3d_dart/wolf_3d_menu.dart'; import 'package:wolf_3d_dart/wolf_3d_menu.dart';
import 'cli_renderer_backend.dart'; import 'cli_renderer_backend.dart';
@@ -370,8 +371,10 @@ class SixelRenderer extends CliRendererBackend<String> {
const int floorColor = 8; const int floorColor = 8;
const int wallColor = 7; const int wallColor = 7;
const int doorColor = 14; const int doorColor = 14;
const int pushwallColor = 6;
const int enemyColor = 12;
const int playerColor = 10; const int playerColor = 10;
const int facingColor = 12; const int facingColor = 15;
for (int i = 0; i < _screen.length; i++) { for (int i = 0; i < _screen.length; i++) {
_screen[i] = mapBgColor; _screen[i] = mapBgColor;
@@ -392,9 +395,14 @@ class SixelRenderer extends CliRendererBackend<String> {
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
final int tileId = engine.currentLevel[y][x]; final int tileId = engine.currentLevel[y][x];
final int color = tileId == 0 final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
'$x,$y',
);
final int color = isPushwall
? pushwallColor
: (tileId == 0
? floorColor ? floorColor
: (tileId >= 90 ? doorColor : wallColor); : (tileId >= 90 ? doorColor : wallColor));
_fillMapRect( _fillMapRect(
mapStartX + (x * tileSize), mapStartX + (x * tileSize),
mapStartY + (y * tileSize), mapStartY + (y * tileSize),
@@ -405,6 +413,23 @@ class SixelRenderer extends CliRendererBackend<String> {
} }
} }
final int enemyRadius = math.max(0, tileSize ~/ 3);
final int enemySize = (enemyRadius * 2) + 1;
for (final entity in engine.entities) {
if (entity is! Enemy || entity.state == EntityState.dead) {
continue;
}
final int enemyX = (mapStartX + (entity.x * tileSize)).floor();
final int enemyY = (mapStartY + (entity.y * tileSize)).floor();
_fillMapRect(
enemyX - enemyRadius,
enemyY - enemyRadius,
enemySize,
enemySize,
enemyColor,
);
}
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor(); final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor(); final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
final int markerRadius = math.max(1, tileSize ~/ 2); final int markerRadius = math.max(1, tileSize ~/ 2);
@@ -7,6 +7,7 @@ import 'package:wolf_3d_dart/src/rendering/menu_font.dart';
import 'package:wolf_3d_dart/src/rendering/renderer_backend.dart'; import 'package:wolf_3d_dart/src/rendering/renderer_backend.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
import 'package:wolf_3d_dart/wolf_3d_menu.dart'; import 'package:wolf_3d_dart/wolf_3d_menu.dart';
/// Pixel-accurate software renderer that writes directly into [FrameBuffer]. /// Pixel-accurate software renderer that writes directly into [FrameBuffer].
@@ -150,8 +151,10 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
final int floorColor = ColorPalette.vga32Bit[8]; final int floorColor = ColorPalette.vga32Bit[8];
final int wallColor = ColorPalette.vga32Bit[7]; final int wallColor = ColorPalette.vga32Bit[7];
final int doorColor = ColorPalette.vga32Bit[14]; final int doorColor = ColorPalette.vga32Bit[14];
final int pushwallColor = ColorPalette.vga32Bit[6];
final int enemyColor = ColorPalette.vga32Bit[12];
final int playerColor = ColorPalette.vga32Bit[10]; final int playerColor = ColorPalette.vga32Bit[10];
final int facingColor = ColorPalette.vga32Bit[12]; final int facingColor = ColorPalette.vga32Bit[15];
_fillMenuPanel(0, 0, width, height, mapBgColor); _fillMenuPanel(0, 0, width, height, mapBgColor);
@@ -170,9 +173,14 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (int x = 0; x < 64; x++) {
final int tileId = engine.currentLevel[y][x]; final int tileId = engine.currentLevel[y][x];
final int color = tileId == 0 final bool isPushwall = engine.pushwallManager.pushwalls.containsKey(
'$x,$y',
);
final int color = isPushwall
? pushwallColor
: (tileId == 0
? floorColor ? floorColor
: (tileId >= 90 ? doorColor : wallColor); : (tileId >= 90 ? doorColor : wallColor));
_fillMenuPanel( _fillMenuPanel(
mapStartX + (x * tileSize), mapStartX + (x * tileSize),
mapStartY + (y * tileSize), mapStartY + (y * tileSize),
@@ -183,6 +191,23 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
} }
} }
final int enemyRadius = math.max(0, tileSize ~/ 3);
final int enemySize = (enemyRadius * 2) + 1;
for (final entity in engine.entities) {
if (entity is! Enemy || entity.state == EntityState.dead) {
continue;
}
final int enemyX = (mapStartX + (entity.x * tileSize)).floor();
final int enemyY = (mapStartY + (entity.y * tileSize)).floor();
_fillMenuPanel(
enemyX - enemyRadius,
enemyY - enemyRadius,
enemySize,
enemySize,
enemyColor,
);
}
final int playerX = (mapStartX + (engine.player.x * tileSize)).floor(); final int playerX = (mapStartX + (engine.player.x * tileSize)).floor();
final int playerY = (mapStartY + (engine.player.y * tileSize)).floor(); final int playerY = (mapStartY + (engine.player.y * tileSize)).floor();
final int markerRadius = math.max(1, tileSize ~/ 2); final int markerRadius = math.max(1, tileSize ~/ 2);
@@ -1,6 +1,7 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'package:wolf_3d_dart/src/entities/entities/enemies/guard.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart'; import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart'; import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_input.dart'; import 'package:wolf_3d_dart/wolf_3d_input.dart';
@@ -11,6 +12,16 @@ void main() {
test('software renderer draws fullscreen map overlay when enabled', () { test('software renderer draws fullscreen map overlay when enabled', () {
final engine = _buildEngine(); final engine = _buildEngine();
engine.init(); engine.init();
expect(engine.pushwallManager.pushwalls.containsKey('5,5'), isTrue);
engine.entities.add(
Guard(
x: 8.5,
y: 8.5,
angle: 0,
mapId: MapObject.guardStart,
difficulty: Difficulty.medium,
),
);
final renderer = SoftwareRenderer(); final renderer = SoftwareRenderer();
final frameNormal = renderer.render(engine); final frameNormal = renderer.render(engine);
@@ -28,6 +39,8 @@ void main() {
} }
expect(changedPixels, greaterThan(mapPixels.length ~/ 5)); expect(changedPixels, greaterThan(mapPixels.length ~/ 5));
expect(mapPixels.contains(ColorPalette.vga32Bit[6]), isTrue);
expect(mapPixels.contains(ColorPalette.vga32Bit[12]), isTrue);
expect(mapPixels.contains(ColorPalette.vga32Bit[10]), isTrue); expect(mapPixels.contains(ColorPalette.vga32Bit[10]), isTrue);
}); });
}); });
@@ -57,7 +70,9 @@ WolfEngine _buildEngine() {
_fillBoundaries(wallGrid, 2); _fillBoundaries(wallGrid, 2);
wallGrid[2][4] = 1; wallGrid[2][4] = 1;
wallGrid[2][5] = 90; wallGrid[2][5] = 90;
wallGrid[5][5] = 1;
objectGrid[2][2] = MapObject.playerEast; objectGrid[2][2] = MapObject.playerEast;
objectGrid[5][5] = MapObject.pushwallTrigger;
return WolfEngine( return WolfEngine(
data: WolfensteinData( data: WolfensteinData(