Fun with Physics

Justin · 12/09/2021

Greetings, robots, robot lovers, and robot fearers. If you identify with none of those categories, greetings to you, too.


We’ve been busy converting our chaotic code into something readable, expandable, and capable of becoming a quality game. With our digital house in order, we were free to focus on other endeavours, such as pondering the laws of physics.


Our game, Part-Droid, requires specific physical rules to function well. While Godot’s physics may be ideal for many projects, we were struggling to achieve the desired results and realised we needed something custom-built. Pixel perfect motion and flawless collisions are essential to our game, and we were failing to make the magic happen. Therefore, we solved the issue by designing our own physics engine—and it wasn’t easy. Fortunately, Part-Droid only requires us to work with rectangles, so AABB collision is sufficient for the task.


func aabb_collision(collider_a, collider_b, direction=Vector2.ZERO) -> bool:
	return collider_a.left + direction.x < collider_b.right and \
		   collider_a.right + direction.x > collider_b.left and \
		   collider_a.top + direction.y < collider_b.bottom and \
		   collider_a.bottom + direction.y > collider_b.top


Upon initialisation, all in game colliders must register to a singleton class. Colliders then call to the singleton to learn whether or not a collision is occurring on a specific layer. Layers are, of course, dictionary string keys. We can see in the register_collider method how the dictionary keys index specific collision layers. Each collider is placed under a unique key for its layer in the dictionary.


func register_collider(collider, layers: Array):
	for layer in layers:
		if not layer in collision_layer_map:
			collision_layer_map[layer] = []
		collision_layer_map[layer].append(collider)


Using this collision_layer_map, we now have access to any collider by its layer. As each collider knows which layer it cares about, it can request collisions from that layer and the singleton can reply for either true or false.


func is_colliding_with_layer(collider_a, layer, direction=Vector2.ZERO) -> bool:
	for collider_b in collision_layer_map.get(layer, []):
		if collider_b.enabled and aabb_collision(collider_a, collider_b, direction):
			return true
	return false


Part-Droid is a game of puzzles, and objects, such as moveable boxes, are crucial tools in many of the challenges. Evidently, our physics engine had to be fit for purpose. We wouldn’t want anyone blaming the laws of the universe when things don’t go their way.