-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathTilemapCollisionBaker.gd
173 lines (135 loc) · 5.81 KB
/
TilemapCollisionBaker.gd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
@tool
extends StaticBody2D
## This script pre-bakes collisions for square tilemaps, therefore optimizing code
## and getting rid of weird physics bugs!
##
## How it works TLDR:
## This script finds the position of every tile on the layer you've selected from the top left
## It then goes to the right until it reaches an edge, then created a rectange CollisionShape2D
## and places it in the correct position and size. It keeps doing this until it reaches the end.
## For further optimizations, it combines different rows of CollisionShapes if
## they are the same size.
## Your TileMapLayer Nodes
@export var tilemaplayers_nodepaths: Array[NodePath]
## Whether or not you want the children of this node to be deleted on run or not.
## Be careful with this!
@export var delete_children_on_run: bool = true
## A fake button to run the code. Bakes collisions and adds colliders as children to this node!
@export var run_script: bool = false : set = run_code
func run_code(_fake_bool = null):
if delete_children_on_run:
delete_children()
if tilemaplayers_nodepaths.is_empty():
print("Hey, you forgot to add tilemaplayers to bake!")
return
for tile_map_path: NodePath in tilemaplayers_nodepaths:
var tile_map: TileMapLayer = get_node(tile_map_path)
if tile_map == null:
print("Hey, there's a null TileMapLayer in your nodepaths!")
return
bake_tilemaplayer(tile_map)
func bake_tilemaplayer(tile_map: TileMapLayer):
var tile_size = tile_map.tile_set.tile_size
var tilemap_locations = tile_map.get_used_cells()
if tilemap_locations.size() == 0:
print("Hey, this tilemap layer is empty!")
return
# I use .pop_back() to go through the array, so I sort them from bottom right to top left.
tilemap_locations.sort_custom(sortVectorsByY)
var last_loc: Vector2i = Vector2i(-99999, -99999)
var size: Vector2i = Vector2i(1, 1)
var xMarginStart = 0
print("Starting first pass (Creating initial colliders)...")
var first_colliders_arr = []
## First pass: add horizontal rect colliders starting from the top left
while true:
var temp_loc = tilemap_locations.pop_back()
if temp_loc == null:
# Add the last collider and break out of loop
var newXPos = (xMarginStart + abs(last_loc.x - xMarginStart) / 2.0 + 0.5) * tile_size.x
@warning_ignore("integer_division")
var newYPos = last_loc.y * tile_size.y - (-tile_size.y / 2)
first_colliders_arr.append(createCollisionShape(Vector2i(newXPos, newYPos), size, tile_size))
print("Finished calculating first pass!")
break
if last_loc == Vector2i(-99999, -99999):
last_loc = temp_loc
xMarginStart = temp_loc.x
continue
if last_loc.y == temp_loc.y and abs(last_loc.x - temp_loc.x) == 1:
size += Vector2i(1,0)
else:
var newXPos = (xMarginStart + abs(last_loc.x - xMarginStart) / 2.0 + 0.5) * tile_size.x
@warning_ignore("integer_division")
var newYPos = last_loc.y * tile_size.y - (-tile_size.y / 2)
first_colliders_arr.append(createCollisionShape(Vector2i(newXPos, newYPos), size, tile_size))
size = Vector2i(1, 1)
xMarginStart = temp_loc.x
#print("New row placed at (%s, %s)" % [newXPos, newYPos])
last_loc = temp_loc
## Sort collider nodes for use in second pass
first_colliders_arr.sort_custom(sortNodesByX)
var last_collider_pos: Vector2 = Vector2(-99999, -99999)
var last_collider
var colliders_to_merge = 1 # Used to count how many colliders will merge
var second_colliders_arr = []
print("Starting second pass (Merging colliders)...")
## Second pass: Merge colliders that are on top of eachother and are the same size
while true:
var temp_collider = first_colliders_arr.pop_back()
if temp_collider == null:
# Add final merged collider and break
last_collider.shape.size.y = tile_size.y * colliders_to_merge
last_collider.position.y -= (colliders_to_merge / 2.0 - 0.5) * tile_size.y
second_colliders_arr.append(last_collider)
print("Finished baking tilemap collisions!")
break
if last_collider_pos == Vector2(-99999, -99999):
last_collider_pos = temp_collider.position
last_collider = temp_collider
continue
var tile_y_distance = abs(temp_collider.position.y - last_collider_pos.y) / tile_size.y
if last_collider_pos.x == temp_collider.position.x and tile_y_distance == 1 and temp_collider.shape.size.x == last_collider.shape.size.x:
#print("Adding 1 to the merge")
colliders_to_merge += 1
last_collider_pos = temp_collider.position
else:
#print("Merging %s colliders" % colliders_to_merge)
last_collider_pos = temp_collider.position
last_collider.shape.size.y = tile_size.y * colliders_to_merge
last_collider.position.y -= (colliders_to_merge / 2.0 - 0.5) * tile_size.y
second_colliders_arr.append(last_collider)
colliders_to_merge = 1
last_collider = temp_collider
## Adds all colliders as children to this node
for collider in second_colliders_arr:
add_child(collider, true)
collider.owner = get_tree().edited_scene_root
## Sets the collider to position itself on top of the layer (if the tilemaplayer has been moved)
collider.position += tile_map.position
func createCollisionShape(pos, size, tile_size) -> CollisionShape2D:
var collisionShape = CollisionShape2D.new()
var rectangleShape = RectangleShape2D.new()
rectangleShape.size = size * tile_size
collisionShape.set_shape(rectangleShape)
collisionShape.position = pos
return collisionShape
func delete_children():
for child in get_children():
child.queue_free()
## Sorts array of vectors in ascending order with respect to Y
func sortVectorsByY(a, b):
if a.y > b.y:
return true
if a.y == b.y:
if a.x > b.x:
return true
return false
## Sorts array of nodes in ascending order with respects to position
func sortNodesByX(a, b):
if a.position.x > b.position.x:
return true
if a.position.x == b.position.x:
if a.position.y > b.position.y:
return true
return false