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/wolf_3d_data_types.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 'cli_renderer_backend.dart';
@@ -416,8 +417,10 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
final int floorColor = ColorPalette.vga32Bit[8];
final int wallColor = ColorPalette.vga32Bit[7];
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 facingColor = ColorPalette.vga32Bit[12];
final int facingColor = ColorPalette.vga32Bit[15];
if (_usesTerminalLayout) {
_fillTerminalRect(
@@ -460,9 +463,14 @@ class AsciiRenderer extends CliRendererBackend<dynamic> {
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; 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
: (tileId >= 90 ? doorColor : wallColor);
: (tileId >= 90 ? doorColor : wallColor));
_fillMapRect(
mapStartX + (x * 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 playerY = (mapStartY + (engine.player.y * tileSize)).floor();
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/wolf_3d_data_types.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 'cli_renderer_backend.dart';
@@ -370,8 +371,10 @@ class SixelRenderer extends CliRendererBackend<String> {
const int floorColor = 8;
const int wallColor = 7;
const int doorColor = 14;
const int pushwallColor = 6;
const int enemyColor = 12;
const int playerColor = 10;
const int facingColor = 12;
const int facingColor = 15;
for (int i = 0; i < _screen.length; i++) {
_screen[i] = mapBgColor;
@@ -392,9 +395,14 @@ class SixelRenderer extends CliRendererBackend<String> {
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; 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
: (tileId >= 90 ? doorColor : wallColor);
: (tileId >= 90 ? doorColor : wallColor));
_fillMapRect(
mapStartX + (x * 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 playerY = (mapStartY + (engine.player.y * tileSize)).floor();
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/wolf_3d_data_types.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';
/// 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 wallColor = ColorPalette.vga32Bit[7];
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 facingColor = ColorPalette.vga32Bit[12];
final int facingColor = ColorPalette.vga32Bit[15];
_fillMenuPanel(0, 0, width, height, mapBgColor);
@@ -170,9 +173,14 @@ class SoftwareRenderer extends RendererBackend<FrameBuffer> {
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 64; 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
: (tileId >= 90 ? doorColor : wallColor);
: (tileId >= 90 ? doorColor : wallColor));
_fillMenuPanel(
mapStartX + (x * 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 playerY = (mapStartY + (engine.player.y * tileSize)).floor();
final int markerRadius = math.max(1, tileSize ~/ 2);
@@ -1,6 +1,7 @@
import 'dart:typed_data';
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_engine.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', () {
final engine = _buildEngine();
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 frameNormal = renderer.render(engine);
@@ -28,6 +39,8 @@ void main() {
}
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);
});
});
@@ -57,7 +70,9 @@ WolfEngine _buildEngine() {
_fillBoundaries(wallGrid, 2);
wallGrid[2][4] = 1;
wallGrid[2][5] = 90;
wallGrid[5][5] = 1;
objectGrid[2][2] = MapObject.playerEast;
objectGrid[5][5] = MapObject.pushwallTrigger;
return WolfEngine(
data: WolfensteinData(