UI in device pixels on the character grid with fixed integer font sizes, ASCII bars; 3D render scale for a pixel look

- text only at 1x/2x/4x times an integer interface factor (auto or 1-4 in the settings)
- high DPI rendering, own 2D camera in device pixels, mouse converted
- toolbar buttons 6 columns, integer icon scale, minimap integer pixels per tile
- all bars (top bar, unit/tower/gate HP) as CP437 block characters
- 3D world optionally rendered at 1/2, 1/3, 1/4 and upscaled with nearest filtering

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-03 08:42:50 +01:00
1 parent 889ca66dbf
commit 50d013bb28
8 files changed
+344 -213

No files matched your search

+13 -10
View File
@@ -36,7 +36,7 @@ fn conf() -> macroquad::conf::Conf {
window_title: "DungeonCraft".into(),
window_width: 1280,
window_height: 800,
high_dpi: false,
high_dpi: true,
..Default::default()
},
draw_call_vertex_capacity: 40000,
@@ -67,10 +67,11 @@ fn next_seed(seed: u32) -> u32 {
fn apply_settings(s: &Settings, r3d: &mut R3d) {
i18n::set_language(&s.language);
ui::set_ui_scale(s.ui_scale as f32);
ui::set_ui_factor(s.ui_factor);
r3d.outlines = s.outlines;
r3d.set_wall_detail(s.wall_subdivisions());
r3d.max_lights = s.max_lights as usize;
r3d.render_div = s.render_scale.clamp(1, 4) as u32;
audio::set_volumes(s.music_volume, s.volume, s.muted);
}
@@ -238,7 +239,8 @@ async fn main() {
loop {
let dt = get_frame_time().min(0.1);
let now = get_time();
let (mx, my) = mouse_position();
let (mx, my) = mouse_position(); // CSS pixels (3D picking)
let (ux, uy) = ui::mouse(); // device pixels (interface)
let mut settings_changed = false;
// ---------------- savegame files: file picker result or dropped onto the window
@@ -272,7 +274,7 @@ async fn main() {
let dialog = ui.show_langsel || ui.show_help;
let in_menu = menu.open();
let playing = !in_menu && !dialog;
let over_ui = ui.over_ui(mx, my);
let over_ui = ui.over_ui(ux, uy);
let picked = app.cam.pick_tile(&app.game.map, mx, my);
let hover_tile = picked.map(|(x, z, _)| (x, z));
let ground = app.cam.ground_point(mx, my);
@@ -436,7 +438,7 @@ async fn main() {
// ---------------- left button
if is_mouse_button_pressed(MouseButton::Left) {
if dialog {
match ui.click(mx, my, &app.game) {
match ui.click(ux, uy, &app.game) {
UiAction::SelectLanguage(code) => {
app.settings.language = code;
settings_changed = true;
@@ -446,7 +448,7 @@ async fn main() {
_ => {}
}
} else if in_menu {
match menu.click(mx, my, &mut app.settings) {
match menu.click(ux, uy, &mut app.settings) {
MenuAction::SettingsChanged => settings_changed = true,
MenuAction::Resume => {}
MenuAction::NewGame => {
@@ -483,7 +485,7 @@ async fn main() {
MenuAction::None | MenuAction::Consumed => {}
}
} else {
match ui.click(mx, my, &app.game) {
match ui.click(ux, uy, &app.game) {
UiAction::SelectTool(t) => {
ui.tool = t;
app.game.sfx_ui(game::Sfx::Click);
@@ -520,8 +522,8 @@ async fn main() {
}
}
if inp.minimap_drag {
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(mx, my) {
let (x, y) = ui.minimap_world(mx, my);
if is_mouse_button_down(MouseButton::Left) && ui.minimap_contains(ux, uy) {
let (x, y) = ui.minimap_world(ux, uy);
app.cam.tx = x;
app.cam.tz = y;
} else if !is_mouse_button_down(MouseButton::Left) {
@@ -591,8 +593,9 @@ async fn main() {
#[allow(clippy::too_many_arguments)]
fn draw_frame(app: &mut App, gfx: &Gfx, r3d: &mut R3d, ui: &mut Ui, menu: &mut Menu, inp: &Input, now: f64) {
let (mx, my) = mouse_position();
let (ux, uy) = ui::mouse();
let blocked = menu.open() || ui.show_help || ui.show_langsel;
let over_ui = blocked || ui.over_ui(mx, my);
let over_ui = blocked || ui.over_ui(ux, uy);
let hover_tile = app.cam.pick_tile(&app.game.map, mx, my).map(|(x, z, _)| (x, z));
let ground = app.cam.ground_point(mx, my);
clear_background(Color::from_rgba(4, 3, 5, 255));