Fog lit by rays: shadows cut through tiles, and walls stay visible
ci / verify (push) Successful in 52s
ci / verify (push) Successful in 52s
The fog asked one question per tile -- can I see its centre? -- and drew or skipped the whole tile on the answer. Every shadow was a staircase, tiles snapped in and out as you walked, and a wall beside you vanished whenever its centre happened to sit behind its neighbour. Visibility casts rays from the eye instead: a fixed sweep, plus two aimed either side of every wall corner, which is what makes each shadow edge a straight line rather than a smear between rays. The view now draws terrain and actors first and then darkness over whatever the rays did not reach, so a shadow runs straight through a tile and an enemy rounding a corner appears a slice at a time. Bullets are a child node and are still drawn over the dark; telegraphs are drawn after it, as they must be. "A bit less strict" is three specific things. A wall face you can see is lit 26 units in, measured perpendicular to the face; shadows end in a short fade rather than a line; and sight fades over its last 64 units rather than stopping at a circle. Still hard fog -- nothing remembered. The first version measured that depth along the ray, and a screenshot caught what no test had: a wall you look along got a sliver, so the wall beside you in a tunnel looked missing -- the old fog's own complaint. Perpendicular depth fixes it, and light stops the instant a ray steps back into the open, so it never rounds a corner, never reaches the far side of a one-tile wall, and never leaves a stepped wall through its other face. Two tiles touching only at a corner are closed. About half a millisecond a frame. Measured against the old fog in the same scene: hub 68 fps against 62, dungeon 240 against 240. The hub's own low rate predates this. MapGrid gains a revision counter so the corner list is rebuilt only when the map changes or the eye crosses a tile. screenshot.tscn now captures a dungeon, a tunnel and the tunnel with F1's overlay, which draws where each ray stopped -- and makes SHOT_DIR rather than failing every capture when it does not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83c3b920fb
commit
855669a665
12 files changed
+871
-45
No files matched your search
+17
-2
@@ -54,6 +54,11 @@ var tiles := PackedByteArray()
|
||||
## existing coordinate (spawn points, portal, boss placement) meaningful and
|
||||
## avoids an all-positive coordinate space where "0" is a corner.
|
||||
var origin := Vector2.ZERO
|
||||
## Bumped by every write, so something that derives data from the tiles can tell
|
||||
## whether it is stale without comparing them. The client's fog extracts wall
|
||||
## corners from the map and would otherwise have to redo it every frame, or miss
|
||||
## a chunk arriving.
|
||||
var revision: int = 0
|
||||
|
||||
|
||||
func _init(w: int = 1, h: int = 1, fill: Kind = Kind.WALL) -> void:
|
||||
@@ -65,6 +70,7 @@ func resize(w: int, h: int, fill: Kind = Kind.WALL) -> void:
|
||||
height = maxi(h, 1)
|
||||
tiles.resize(width * height)
|
||||
tiles.fill(fill)
|
||||
revision += 1
|
||||
|
||||
|
||||
func in_bounds(tx: int, ty: int) -> bool:
|
||||
@@ -82,6 +88,7 @@ func at(tx: int, ty: int) -> Kind:
|
||||
func set_tile(tx: int, ty: int, kind: Kind) -> void:
|
||||
if in_bounds(tx, ty):
|
||||
tiles[ty * width + tx] = kind
|
||||
revision += 1
|
||||
|
||||
|
||||
func fill_rect(rect: Rect2i, kind: Kind) -> void:
|
||||
@@ -111,6 +118,7 @@ func to_tile(world: Vector2) -> Vector2i:
|
||||
## Centre the map on the world origin.
|
||||
func centre_on_origin() -> void:
|
||||
origin = -world_size() * 0.5
|
||||
revision += 1
|
||||
|
||||
|
||||
## World-space rectangle the map occupies.
|
||||
@@ -175,8 +183,14 @@ func slide_circle(pos: Vector2, delta: Vector2, radius: float) -> Vector2:
|
||||
return out
|
||||
|
||||
|
||||
## Bresenham-style sight test between two world points. Used for fog on the
|
||||
## client and for aggro on the server, so it has to agree on both.
|
||||
## Bresenham-style sight test between two world points, from tile to tile.
|
||||
## Used for aggro on the server.
|
||||
##
|
||||
## It used to drive the client's fog as well, which is why the two once had to
|
||||
## agree. The fog is exact geometry now (see Visibility), so this is the
|
||||
## server's question alone -- and it is coarser than what the player sees: its
|
||||
## diagonal steps pass between two tiles that touch only at a corner, which
|
||||
## the fog treats as closed.
|
||||
##
|
||||
## [param include_end] is false here: the endpoints themselves never block, so
|
||||
## standing in a doorway, or shooting at something embedded in a wall, still
|
||||
@@ -303,3 +317,4 @@ func apply_chunk(chunk_id: int, data: PackedByteArray) -> void:
|
||||
for tx in range(r.position.x, r.end.x):
|
||||
tiles[ty * width + tx] = data[i]
|
||||
i += 1
|
||||
revision += 1
|
||||
Reference in new issue
Block a user