RapidGameFramework
Reusable Godot managers for data-driven small games
Loading...
Searching...
No Matches
top_down_controller_2d.gd
Go to the documentation of this file.
1extends RefCounted
2
3
6
7const DEFAULT_CONFIG := {
8 "speed": 160.0,
9 "radius": 10.0,
10 "tile_size": 40.0
11}
12
13
14
15func create_state(position := Vector2.ZERO, config := {}) -> Dictionary:
16 var merged = _merged_config(config)
17 return {
18 "position": position,
19 "velocity": Vector2.ZERO,
20 "radius": float(merged.get("radius", DEFAULT_CONFIG["radius"])),
21 "grid": world_to_grid(position, merged)
22 }
23
24
25
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:
31 move = Vector2.ZERO
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)
44 return next_state
45
46
47
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:
52 return next_state
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
59 return next_state
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"]))
63 return next_state
64
65
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)))
70
71
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)
76
77
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)))
83 var cells := []
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))
87 return cells
88
89
90func trace_grid_line(origin:Vector2i, direction:Vector2i, max_range:int, bounds:Rect2, blocked_cells:Array, config := {}) -> Array:
91 var cells := []
92 var current = origin
93 for _index in range(max(0, max_range)):
94 current += direction
95 if _grid_cell_blocked(current, bounds, blocked_cells, config):
96 break
97 cells.append(current)
98 return cells
99
100
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):
110 return current
111 current = candidate
112 return current
113
114
115func _collides(position:Vector2, radius:float, obstacles:Array) -> bool:
116 for obstacle in obstacles:
117 var rect := Rect2()
118 if obstacle is Rect2:
119 rect = obstacle
120 elif obstacle is Dictionary and obstacle.has("rect"):
121 rect = obstacle.get("rect")
122 else:
123 continue
124 if rect.grow(radius).has_point(position):
125 return true
126 return false
127
128
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):
132 return true
133 for blocked in blocked_cells:
134 if blocked is Vector2i and blocked == cell:
135 return true
136 if blocked is Vector2 and Vector2i(int(blocked.x), int(blocked.y)) == cell:
137 return true
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:
141 return true
142 return false
143
144
145func _clamp_to_bounds(position:Vector2, radius:float, bounds:Rect2) -> Vector2:
146 if bounds.size == Vector2.ZERO:
147 return position
148 return Vector2(
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)
151 )
152
153
154func _merged_config(config:Dictionary) -> Dictionary:
155 var merged = DEFAULT_CONFIG.duplicate(true)
156 for key in config.keys():
157 merged[key] = config[key]
158 return merged
159
160