Takt und Bewegung der Figur

TickClock rechnet Bildzeit in Takte um und holt nach einem Ruckler
höchstens fünf Takte auf. GridMotion bewegt einen Körper über das
Kachelraster und lässt ihn an Wänden entlanggleiten. Die Figur läuft
fünf Felder pro Sekunde, außerhalb des Gleichschritts.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Steven Wroblewski
2026-10-01 11:11:52 +02:00
parent bd0f588714
commit 2607df8a97
12 changed files with 220 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
class_name Avatar
extends Node2D
## The local player's figure. It moves outside the lockstep simulation; only
## its pickaxe hits become commands.
## Walking speed: five tiles per second.
const SPEED := 5.0 * GridMotion.TILE_SIZE
const RADIUS := 5.0
## Longest frame that moves the figure in one go. Longer frames are cut, so
## that the figure cannot pass through a wall after a stall.
const MAX_STEP_SECONDS := 0.1
## Callable(x: int, y: int) -> bool that says whether a tile can be walked on.
var is_walkable: Callable
func _process(delta: float) -> void:
walk(Input.get_vector("move_left", "move_right", "move_up", "move_down"), delta)
## Moves the figure in `direction` for `delta` seconds, sliding along walls.
func walk(direction: Vector2, delta: float) -> void:
if direction == Vector2.ZERO or not is_walkable.is_valid():
return
var motion := direction * SPEED * minf(delta, MAX_STEP_SECONDS)
position = GridMotion.move(position, motion, RADIUS, is_walkable)
func _draw() -> void:
draw_circle(Vector2.ZERO, RADIUS + 1.0, Color(0.15, 0.1, 0.05))
draw_circle(Vector2.ZERO, RADIUS, Color(0.95, 0.85, 0.6))
+1
View File
@@ -0,0 +1 @@
uid://bpltng2n5f61g
+44
View File
@@ -0,0 +1,44 @@
class_name GridMotion
extends RefCounted
## Movement of a body over the tile grid. Figures move outside the lockstep
## simulation, so this code may use floating point.
## Edge length of a tile, in pixels.
const TILE_SIZE := 16.0
## The tile that contains `point`.
static func tile_of(point: Vector2) -> Vector2i:
return Vector2i(floori(point.x / TILE_SIZE), floori(point.y / TILE_SIZE))
## The centre of `tile`, in pixels.
static func tile_center(tile: Vector2i) -> Vector2:
return (Vector2(tile) + Vector2(0.5, 0.5)) * TILE_SIZE
## True if a square body of half width `radius` around `center` touches
## only walkable tiles. `is_walkable` is a Callable(x: int, y: int) -> bool.
static func fits(center: Vector2, radius: float, is_walkable: Callable) -> bool:
var first := tile_of(center - Vector2(radius, radius))
# The far edge lies just inside its tile, so a body that ends exactly on
# a tile border does not count the next tile.
var last := tile_of(center + Vector2(radius, radius) - Vector2(0.001, 0.001))
for y in range(first.y, last.y + 1):
for x in range(first.x, last.x + 1):
if not is_walkable.call(x, y):
return false
return true
## Moves a body by `motion`, one axis after the other. An axis that would
## end in a wall is skipped, so a body pushed into a wall slides along it.
static func move(center: Vector2, motion: Vector2, radius: float, is_walkable: Callable) -> Vector2:
var result := center
var along_x := result + Vector2(motion.x, 0.0)
if fits(along_x, radius, is_walkable):
result = along_x
var along_y := result + Vector2(0.0, motion.y)
if fits(along_y, radius, is_walkable):
result = along_y
return result
+1
View File
@@ -0,0 +1 @@
uid://dn0m8pxkigqig
+29
View File
@@ -0,0 +1,29 @@
class_name TickClock
extends RefCounted
## Turns frame time into a whole number of simulation ticks.
## Most ticks one frame may run. After a long stall the simulation catches
## up only this far and drops the rest, instead of freezing to catch up.
const MAX_TICKS_PER_FRAME := 5
var seconds_per_tick: float
var paused := false
var _accumulated := 0.0
func _init(ticks_per_second: int) -> void:
seconds_per_tick = 1.0 / ticks_per_second
## Returns how many ticks to run for a frame that took `delta` seconds.
func advance(delta: float) -> int:
if paused:
return 0
_accumulated += delta
var ticks := int(_accumulated / seconds_per_tick)
if ticks > MAX_TICKS_PER_FRAME:
_accumulated = 0.0
return MAX_TICKS_PER_FRAME
_accumulated -= ticks * seconds_per_tick
return ticks
+1
View File
@@ -0,0 +1 @@
uid://bmrv5nfb0wu6r