Tile outlines on floors, wall tops and wall corners (toggle with G)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
220d9543cc
commit
a101f6ddc4
4 files changed
+75
-6
No files matched your search
@@ -16,6 +16,7 @@ Eigenständiges Dungeon-Management-Spiel (Genre: Dungeon-Aufbau & Verteidigung),
|
|||||||
- Texturen: **prozedurale Pixelart** (Kachel-Atlas)
|
- Texturen: **prozedurale Pixelart** (Kachel-Atlas)
|
||||||
- Figuren: **Pixel-Sprites als Billboards** (immer zur Kamera gedreht)
|
- Figuren: **Pixel-Sprites als Billboards** (immer zur Kamera gedreht)
|
||||||
- Licht: **Dunkel + Fackeln** – Grunddunkelheit, eroberte Bereiche und Lichtquellen (Fackeln, Kern, Portal, Tor) hell, flackerndes Licht
|
- Licht: **Dunkel + Fackeln** – Grunddunkelheit, eroberte Bereiche und Lichtquellen (Fackeln, Kern, Portal, Tor) hell, flackerndes Licht
|
||||||
|
- **Kachel-Outlines**: dezente dunkle Linien um jedes Feld (Böden, Wandoberseiten, senkrechte Wandkanten), folgen der unregelmäßigen Oberfläche; abschaltbar (Taste G, später Grafik-Einstellungen)
|
||||||
- UI bleibt 2D-Ebene darüber, Minikarte
|
- UI bleibt 2D-Ebene darüber, Minikarte
|
||||||
|
|
||||||
## Steuerung
|
## Steuerung
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ async fn main() {
|
|||||||
if (is_key_pressed(KeyCode::P) || is_key_pressed(KeyCode::Space)) && game.state == 0 {
|
if (is_key_pressed(KeyCode::P) || is_key_pressed(KeyCode::Space)) && game.state == 0 {
|
||||||
game.paused = !game.paused;
|
game.paused = !game.paused;
|
||||||
}
|
}
|
||||||
|
if is_key_pressed(KeyCode::G) {
|
||||||
|
r3d.outlines = !r3d.outlines;
|
||||||
|
}
|
||||||
if is_key_pressed(KeyCode::M) {
|
if is_key_pressed(KeyCode::M) {
|
||||||
sounds.muted = !sounds.muted;
|
sounds.muted = !sounds.muted;
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-5
@@ -319,6 +319,25 @@ impl MB {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Thin strip lying on a surface (normal n) from p to q, used for tile outlines
|
||||||
|
fn strip(&mut self, p: Vec3, q: Vec3, n: Vec3, w: f32, c: [u8; 4]) {
|
||||||
|
let side = (q - p).cross(n).normalize_or_zero() * (w * 0.5);
|
||||||
|
let o = n * 0.006;
|
||||||
|
self.quad([p - side + o, q - side + o, q + side + o, p + side + o], [Vec2::ZERO; 4], [c; 4], [n; 4]);
|
||||||
|
}
|
||||||
|
// Polyline along displaced points
|
||||||
|
fn polyline(&mut self, pts: &[Vec3], n: Vec3, w: f32, c: [u8; 4]) {
|
||||||
|
for k in 0..pts.len().saturating_sub(1) {
|
||||||
|
self.strip(pts[k], pts[k + 1], n, w, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn into_plain_mesh(self) -> Option<Mesh> {
|
||||||
|
if self.v.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Mesh { vertices: self.v, indices: self.i, texture: None })
|
||||||
|
}
|
||||||
|
}
|
||||||
fn into_mesh(self, tex: &Texture2D) -> Option<Mesh> {
|
fn into_mesh(self, tex: &Texture2D) -> Option<Mesh> {
|
||||||
if self.v.is_empty() {
|
if self.v.is_empty() {
|
||||||
None
|
None
|
||||||
@@ -379,9 +398,23 @@ fn solid_at(map: &Map, x: i32, z: i32) -> bool {
|
|||||||
map.get(x, z).map_or(true, |t| t.solid())
|
map.get(x, z).map_or(true, |t| t.solid())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas: &Texture2D) -> (Option<Mesh>, Option<Mesh>) {
|
const OUTLINE_W: f32 = 0.035;
|
||||||
|
const OUTLINE_C: [u8; 4] = [8, 5, 6, 150];
|
||||||
|
|
||||||
|
// Displaced points along a straight edge (same displacement as the wall grids -> lines follow the surface)
|
||||||
|
fn edge_pts(a: Vec3, b: Vec3, n: usize) -> Vec<Vec3> {
|
||||||
|
(0..=n)
|
||||||
|
.map(|k| {
|
||||||
|
let p = a.lerp(b, k as f32 / n as f32);
|
||||||
|
p + displace(p)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas: &Texture2D) -> (Option<Mesh>, Option<Mesh>, Option<Mesh>) {
|
||||||
let mut mb = MB::new();
|
let mut mb = MB::new();
|
||||||
let mut ov = MB::new();
|
let mut ov = MB::new();
|
||||||
|
let mut ln = MB::new();
|
||||||
let ld = LIGHT_DIR.normalize();
|
let ld = LIGHT_DIR.normalize();
|
||||||
for z in cz * CHUNK..(cz + 1) * CHUNK {
|
for z in cz * CHUNK..(cz + 1) * CHUNK {
|
||||||
for x in cx * CHUNK..(cx + 1) * CHUNK {
|
for x in cx * CHUNK..(cx + 1) * CHUNK {
|
||||||
@@ -406,6 +439,13 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
|||||||
c,
|
c,
|
||||||
[Vec3::Y; 4],
|
[Vec3::Y; 4],
|
||||||
);
|
);
|
||||||
|
// outlines: north and west edge, only between two open tiles (each edge once)
|
||||||
|
if !solid_at(map, x, z - 1) {
|
||||||
|
ln.strip(vec3(xf, 0.0, zf), vec3(xf + 1.0, 0.0, zf), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
}
|
||||||
|
if !solid_at(map, x - 1, z) {
|
||||||
|
ln.strip(vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
}
|
||||||
// room decor
|
// room decor
|
||||||
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
|
let dc = col(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), 1.0);
|
||||||
match t.kind {
|
match t.kind {
|
||||||
@@ -455,6 +495,19 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
|||||||
(ua, ub),
|
(ua, ub),
|
||||||
|_p, nrm| Vec3::splat(top_amb * (0.75 + 0.25 * nrm.dot(ld).max(0.0))),
|
|_p, nrm| Vec3::splat(top_amb * (0.75 + 0.25 * nrm.dot(ld).max(0.0))),
|
||||||
);
|
);
|
||||||
|
// outlines on the wall top: north/west always, south/east where no wall continues
|
||||||
|
let tl = vec3(xf, WALL_H, zf);
|
||||||
|
let tr = vec3(xf + 1.0, WALL_H, zf);
|
||||||
|
let br = vec3(xf + 1.0, WALL_H, zf + 1.0);
|
||||||
|
let bl = vec3(xf, WALL_H, zf + 1.0);
|
||||||
|
ln.polyline(&edge_pts(tl, tr, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
ln.polyline(&edge_pts(tl, bl, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
if !solid_at(map, x, z + 1) {
|
||||||
|
ln.polyline(&edge_pts(bl, br, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
}
|
||||||
|
if !solid_at(map, x + 1, z) {
|
||||||
|
ln.polyline(&edge_pts(tr, br, n), Vec3::Y, OUTLINE_W, OUTLINE_C);
|
||||||
|
}
|
||||||
if t.marked {
|
if t.marked {
|
||||||
let (a, b) = cell_uv(T_MARK);
|
let (a, b) = cell_uv(T_MARK);
|
||||||
let y = WALL_H + 0.12;
|
let y = WALL_H + 0.12;
|
||||||
@@ -478,6 +531,10 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
|||||||
_ => (vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0)), // west
|
_ => (vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0)), // west
|
||||||
};
|
};
|
||||||
let out = vec3(dx as f32, 0.0, dz as f32);
|
let out = vec3(dx as f32, 0.0, dz as f32);
|
||||||
|
// vertical outlines at both ends of the exposed face (tile borders and corners)
|
||||||
|
for end in [a, b] {
|
||||||
|
ln.polyline(&edge_pts(end, end + vec3(0.0, WALL_H, 0.0), n), out, OUTLINE_W, OUTLINE_C);
|
||||||
|
}
|
||||||
build_grid(
|
build_grid(
|
||||||
&mut mb,
|
&mut mb,
|
||||||
|u, v| a + (b - a) * u + vec3(0.0, WALL_H * (1.0 - v), 0.0),
|
|u, v| a + (b - a) * u + vec3(0.0, WALL_H * (1.0 - v), 0.0),
|
||||||
@@ -492,7 +549,7 @@ fn build_chunk(map: &Map, amb: &[f32], cx: i32, cz: i32, gold_fill: f32, atlas:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(mb.into_mesh(atlas), ov.into_mesh(atlas))
|
(mb.into_mesh(atlas), ov.into_mesh(atlas), ln.into_plain_mesh())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subdivided, displaced face. pos(u, v) gives the undisplaced point (u, v in 0..1).
|
// Subdivided, displaced face. pos(u, v) gives the undisplaced point (u, v in 0..1).
|
||||||
@@ -542,6 +599,7 @@ struct Chunk {
|
|||||||
sig: u64,
|
sig: u64,
|
||||||
mesh: Option<Mesh>,
|
mesh: Option<Mesh>,
|
||||||
overlay: Option<Mesh>,
|
overlay: Option<Mesh>,
|
||||||
|
lines: Option<Mesh>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Overlay {
|
pub struct Overlay {
|
||||||
@@ -556,6 +614,7 @@ pub struct R3d {
|
|||||||
lit: Material,
|
lit: Material,
|
||||||
glow: Material,
|
glow: Material,
|
||||||
chunks: Vec<Chunk>,
|
chunks: Vec<Chunk>,
|
||||||
|
pub outlines: bool, // tile outlines on/off
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rgba(u: u32) -> Color {
|
fn rgba(u: u32) -> Color {
|
||||||
@@ -585,8 +644,8 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32, fill_bucket: u8) -> u64 {
|
|||||||
|
|
||||||
impl R3d {
|
impl R3d {
|
||||||
pub fn new() -> R3d {
|
pub fn new() -> R3d {
|
||||||
let chunks = (0..NCH * NCH).map(|_| Chunk { sig: 0, mesh: None, overlay: None }).collect();
|
let chunks = (0..NCH * NCH).map(|_| Chunk { sig: 0, mesh: None, overlay: None, lines: None }).collect();
|
||||||
R3d { lit: lit_material(), glow: glow_material(), chunks }
|
R3d { lit: lit_material(), glow: glow_material(), chunks, outlines: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_chunks(&mut self, g: &Game, gfx: &Gfx) {
|
fn update_chunks(&mut self, g: &Game, gfx: &Gfx) {
|
||||||
@@ -599,9 +658,10 @@ impl R3d {
|
|||||||
let ch = &mut self.chunks[(cz * NCH + cx) as usize];
|
let ch = &mut self.chunks[(cz * NCH + cx) as usize];
|
||||||
if ch.sig != sig {
|
if ch.sig != sig {
|
||||||
let a = amb.get_or_insert_with(|| ambient_field(&g.map));
|
let a = amb.get_or_insert_with(|| ambient_field(&g.map));
|
||||||
let (m, o) = build_chunk(&g.map, a, cx, cz, bucket as f32 / 6.0, &gfx.atlas);
|
let (m, o, l) = build_chunk(&g.map, a, cx, cz, bucket as f32 / 6.0, &gfx.atlas);
|
||||||
ch.mesh = m;
|
ch.mesh = m;
|
||||||
ch.overlay = o;
|
ch.overlay = o;
|
||||||
|
ch.lines = l;
|
||||||
ch.sig = sig;
|
ch.sig = sig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -725,6 +785,11 @@ impl R3d {
|
|||||||
// ---- unlit glow / overlays
|
// ---- unlit glow / overlays
|
||||||
gl_use_material(&self.glow);
|
gl_use_material(&self.glow);
|
||||||
for c in &self.chunks {
|
for c in &self.chunks {
|
||||||
|
if self.outlines {
|
||||||
|
if let Some(l) = &c.lines {
|
||||||
|
draw_mesh(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(o) = &c.overlay {
|
if let Some(o) = &c.overlay {
|
||||||
draw_mesh(o);
|
draw_mesh(o);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -424,7 +424,7 @@ impl Ui {
|
|||||||
("Mausrad Zoomen", TXT),
|
("Mausrad Zoomen", TXT),
|
||||||
("1-9, 0, X, V Werkzeug wählen", TXT),
|
("1-9, 0, X, V Werkzeug wählen", TXT),
|
||||||
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
|
("P / Leertaste Pause M Ton an/aus +/- Lautstärke", TXT),
|
||||||
("H Diese Hilfe", TXT),
|
("G Kachel-Umrisse an/aus H Diese Hilfe", TXT),
|
||||||
("", TXT),
|
("", TXT),
|
||||||
("1. Grabe zum Portal und lass es von Grubenwichten erobern.", TXT),
|
("1. Grabe zum Portal und lass es von Grubenwichten erobern.", TXT),
|
||||||
("2. Baue Quartier, Pilzgarten und Schatzkammer.", TXT),
|
("2. Baue Quartier, Pilzgarten und Schatzkammer.", TXT),
|
||||||
|
|||||||
Reference in new issue
Block a user