7const DEFAULT_CONFIG := {
15func create_state(position := Vector2.ZERO, config := {}) -> Dictionary:
16 var merged = _merged_config(config)
19 "velocity": Vector2.ZERO,
20 "radius": float(merged.get(
"radius", DEFAULT_CONFIG[
"radius"])),
21 "grid": world_to_grid(position, merged)
26func step(state:Dictionary, input:Dictionary, delta:float, bounds:Rect2, obstacles:Array, config := {}) -> Dictionary:
27 var merged = _merged_config(config)
28 var next_state = state.duplicate(true)
29 var move = input.get(
"move", Vector2.ZERO)
30 if not move
is Vector2:
32 if move.length() > 1.0:
33 move = move.normalized()
34 var speed = float(merged.get(
"speed", DEFAULT_CONFIG[
"speed"]))
35 var radius = float(next_state.get(
"radius", merged.get(
"radius", DEFAULT_CONFIG[
"radius"])))
36 var velocity = move * speed
37 var position = next_state.get(
"position", Vector2.ZERO)
38 position = _move_axis(position, Vector2(velocity.x * delta, 0), radius, bounds, obstacles)
39 position = _move_axis(position, Vector2(0, velocity.y * delta), radius, bounds, obstacles)
40 next_state[
"position"] = position
41 next_state[
"velocity"] = velocity
42 next_state[
"radius"] = radius
43 next_state[
"grid"] = world_to_grid(position, merged)
48func step_grid(state:Dictionary, direction:Vector2i, bounds:Rect2, blocked_cells:Array, config := {}) -> Dictionary:
49 var merged = _merged_config(config)
50 var next_state = state.duplicate(true)
51 if direction == Vector2i.ZERO:
53 var current_cell = next_state.get(
"grid", world_to_grid(next_state.get(
"position", Vector2.ZERO), merged))
54 if not current_cell
is Vector2i:
55 current_cell = Vector2i(int(current_cell.x), int(current_cell.y))
56 var target_cell = current_cell + direction
57 if _grid_cell_blocked(target_cell, bounds, blocked_cells, merged):
58 next_state[
"velocity"] = Vector2.ZERO
60 next_state[
"grid"] = target_cell
61 next_state[
"position"] = grid_to_world(target_cell, merged)
62 next_state[
"velocity"] = Vector2(direction) * float(merged.get(
"tile_size", DEFAULT_CONFIG[
"tile_size"]))
66func world_to_grid(position:Vector2, config := {}) -> Vector2i:
67 var merged = _merged_config(config)
68 var tile_size = max(1.0, float(merged.get(
"tile_size", DEFAULT_CONFIG[
"tile_size"])))
69 return Vector2i(int(floor(position.x / tile_size)), int(floor(position.y / tile_size)))
72func grid_to_world(cell:Vector2i, config := {}) -> Vector2:
73 var merged = _merged_config(config)
74 var tile_size = max(1.0, float(merged.get(
"tile_size", DEFAULT_CONFIG[
"tile_size"])))
75 return Vector2(float(cell.x) * tile_size + tile_size * 0.5, float(cell.y) * tile_size + tile_size * 0.5)
78func rect_to_cells(rect:Rect2, config := {}) -> Array:
79 var merged = _merged_config(config)
80 var tile_size = max(1.0, float(merged.get(
"tile_size", DEFAULT_CONFIG[
"tile_size"])))
81 var min_cell = Vector2i(int(floor(rect.position.x / tile_size)), int(floor(rect.position.y / tile_size)))
82 var max_cell = Vector2i(int(floor((rect.position.x + max(0.0, rect.size.x - 0.01)) / tile_size)), int(floor((rect.position.y + max(0.0, rect.size.y - 0.01)) / tile_size)))
84 for y
in range(min_cell.y, max_cell.y + 1):
85 for x
in range(min_cell.x, max_cell.x + 1):
86 cells.append(Vector2i(x, y))
90func trace_grid_line(origin:Vector2i, direction:Vector2i, max_range:int, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
93 for _index
in range(max(0, max_range)):
95 if _grid_cell_blocked(current, bounds, blocked_cells, config):
101func _move_axis(position:Vector2, offset:Vector2, radius:float, bounds:Rect2, obstacles:Array) -> Vector2:
102 if offset == Vector2.ZERO:
103 return _clamp_to_bounds(position, radius, bounds)
104 var current = _clamp_to_bounds(position, radius, bounds)
105 var steps = max(1, int(ceil(offset.length() / max(1.0, radius * 0.5))))
106 var step_offset = offset / float(steps)
107 for _index
in range(steps):
108 var candidate = _clamp_to_bounds(current + step_offset, radius, bounds)
109 if _collides(candidate, radius, obstacles):
115func _collides(position:Vector2, radius:float, obstacles:Array) -> bool:
116 for obstacle
in obstacles:
118 if obstacle
is Rect2:
120 elif obstacle
is Dictionary
and obstacle.has(
"rect"):
121 rect = obstacle.get(
"rect")
124 if rect.grow(radius).has_point(position):
129func _grid_cell_blocked(cell:Vector2i, bounds:Rect2, blocked_cells:Array, config:Dictionary) -> bool:
130 var world_position = grid_to_world(cell, config)
131 if not bounds.has_point(world_position):
133 for blocked
in blocked_cells:
134 if blocked
is Vector2i
and blocked == cell:
136 if blocked
is Vector2
and Vector2i(int(blocked.x), int(blocked.y)) == cell:
138 if blocked
is Dictionary
and blocked.has(
"cell"):
139 var blocked_cell = blocked.get(
"cell")
140 if blocked_cell
is Vector2i
and blocked_cell == cell:
145func _clamp_to_bounds(position:Vector2, radius:float, bounds:Rect2) -> Vector2:
146 if bounds.size == Vector2.ZERO:
149 clamp(position.x, bounds.position.x + radius, bounds.position.x + bounds.size.x - radius),
150 clamp(position.y, bounds.position.y + radius, bounds.position.y + bounds.size.y - radius)
154func _merged_config(config:Dictionary) -> Dictionary:
155 var merged = DEFAULT_CONFIG.duplicate(true)
156 for key
in config.keys():
157 merged[key] = config[key]