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