Pattern and Architecture

Base Paradigm

The official godot documentation favor traditional OOP over any other paradigm mainly ECS and we’re going to follow that, but it’s not restricting as in godot also offer a high level composition with scene but it’ll be primarily composed with Inheritance

Call Down Signal Up

As a rule of thumb Parent should manage it’s child, and the child shouldn’t call the parent directly. If the child need the parent to do something it should use a signal.

Example:

// Do
// Call Down
get_node("child").do_something()

// Signal Up
signal child_hurt
func damaged():
	emit_signal("child_hurt")
func _on_child_hurt():
	// do_something()

// Don't
// Call up
get_node("../..").parent_do_something()

Inheritance over Composition

As mentioned above godot favor Inheritance over Composition for example A player class would look like this

Entity -> Character

instead of

Character
	Control
	Sprite
	...

But since godot support combining scens togther it will usually looks like this

KinematicBody2D -> Entity -> Character
	-> Sprite
	-> Area2D -> Hitbox
	-> Area2d -> Hurtbox
	-> Node2D -> Weapon

In this specific project, we should encourage shallow Inheritance but wide.

Context Based Coupling

Objects should be coupled when they’re related enough.
Example:
A player will have a sprite and this instance of sprite is specifically exist for the player itself so any other object will not cares about its existence is the character base class therefore it’s safe to tightly couple them.

If it’s not related enough, do not couple them
Example:
An npc have the ability to prompt dialog, so at a first glance it is reasonable to put the dialog node inside the scene tree of that NPC but dialog can also can be called other than NPC so it’s a bad idea to couple them together, if this is the case please refer to Global Signal.

Signal and Global Signal

This is an extennsion of the regular Signal pattern the regular signal pattern usually used when a child needs to notify its parent that something happened, if that’s the case a regular signal will do wonders but when other Node needs to know something happened but doesn’t have a direct connection to the Node that’s when we should use Global Signal.

We will use a forbidden technique here:
Singleton

Calm dowm, this is probably one of the safest singleton that we can use because it doesn’t create any tight coupling
It will only provide an easy access to connect a signal. Example:
HUD <-> Global Signal <-> Character
Character will send a signal that something happened but doesn’t care who received that signal so even when the signal sender is gone there will be no crash since there’s no direct donnection

So when to use Global Signal?
We want to use signal if two or more object cares about it’s significance
Example:
When enemy hitbox enters player’s hurtbox the player base class needs to know that it happened so it can reduce the player’s health and at the same time the UI also needs to know that to update the Health Bar.

When to not use Global Signal?
If the receiver has a more convinient way to connect to the signal then it’s best to not use a Global Signal

So there will be a singleton named SignalManager that’ll act as a bridge between nodes, and any node can send and receive signal to and from this singleton.

Assumption is Evil

Do not assume that something will exist, that’s a quick and easy way to create a debugging hell.

For example:
The enemy needs player position at all times for it to works, this is an assumption it assumes that the player will exist no matter what but in reality this is hard to achieve, the enemy ai can exist before the player or the player may die and dissapear from meory in that case the entire game will break.

Since GDScript is not enforcing any kind of safety regarding this we have to improvise and restrain ourself from using unsafe reference.

Unsafe refrence usually is a fruit of a quick solution that our head conjure and more often than not the first solution that came into our head isn’t the greatest, so just keep in mind that when we create an unsafe reference there might be a better way of doing it.

Finite State Machine

We’ll be using FSM to manage controlled entity, because it’s simple enough to impelement but more importantly controlled entity is predictable this prevent the FSM to blow up in lines, and we’re not going to use it to control uncontrolled entity.

For the current implementation of the State Machine please read the code under common/state_machine.gd it heavily use polymorphism to generalize things.

Behaviour Tree

This is a solution to define the behaviour of an uncontrolled entity, Altho this is way more boiler plate up front and is harder to understand but it ultimately better for complex but predictable behavior.

If we’re using FSM for contorlling enemy behavior it can work for simpler enemy but at a certain threshold even adding just one state will require a lot of modification to existing code and that’s less than desirable with Behaviour Tree adding new behavior is more cost effective, and somewhere down the road we’ll a boss enemy which will definitely have a lot behavior and that’s going to be realy messy.

We’re not gonna implement our own Behaviour Tree instead we’re going to use a Plugin for it.

Singleton

I know, I know, I’ve been saying that I hate this one pattern but it’s somewhat unavoidable there’s no escaping it, we’re just going to have to use it better.

As a rule of them if the singleton can exist in another game without a lot of modification then it’s generic enough to be a singleton after all most of the problem with signleton is the tight coupling and it it’s not tight and just provide an easy acess to something then yeah that’s nice.