169 lines
5.6 KiB
GDScript
169 lines
5.6 KiB
GDScript
@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 TileMap Node
|
|
@export var tilemap_nodepath: NodePath
|
|
|
|
## The tilemap layer to bake collisions on.
|
|
## You can bake for multiple layers by disabling delete_children_on_run and running multiple times.
|
|
@export var target_tiles_layer: int = 0
|
|
|
|
## 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):
|
|
var tile_map: TileMap = get_node(tilemap_nodepath)
|
|
if tile_map == null:
|
|
print("Hey, you forgot to set your Tilemap Nodepath.")
|
|
return
|
|
|
|
if delete_children_on_run:
|
|
delete_children()
|
|
|
|
var tile_size = tile_map.tile_set.tile_size
|
|
var tilemap_locations = tile_map.get_used_cells(target_tiles_layer)
|
|
|
|
if tilemap_locations.size() == 0:
|
|
print("Hey, this tilemap is empty (did you choose the correct layer?)")
|
|
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:
|
|
#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
|
|
|
|
## Move this node's position to cover the tilemap
|
|
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
|