The Global Database

All your Attack and Projectile Blueprints have a name property that is used to archive them, along with their in-game objects, in the Global Database.

By default all your Attacks and Projectiles are automatically added to the Global Database so you can access them anytime, anywhere.

Keep in mind that your objects will be stored only if there is no other item from the same category with the same name already in the database.



Modifying Database elements in real-time

With the default configuration you will get copies of the requested attacks/projectiles, so any changes you make on them will not be reflected on the allocated objects and resources in the Global Database.

If you want your changes to be reflected in the instances allocated in the Global Database, you will have to uncheck the Get X Copies From Database option.

Now all the changes you make to your objects will be reflected on the objects in the database itself.

You can also specify whether you want a copy or not when requesting your objects through code:

func _ready() -> void:
	# This will return a copy
	var proj_1: Projectile2D = APDatabase.get_projectile_2d("MAGIC_BOLT")
	# This will not
	var proj_2: Projectile2D = APDatabase.get_projectile_2d("MAGIC_BOLT", false)

Or set new elements yourself:

func _ready() -> void:
	APDatabase.set_projectile_2d("BLIZZARD", MyProjectile.new())
	APDatabase.set_attack_2d("AK_47", MyAttack.new())
	APDatabase.set_modifier(Projectile2DModifier.new("PROJECTILE_MODIFIER", 1.0))
	APDatabase.set_modifier(Attack2DModifier.new("ATTACK_MODIFIER", 1.0))

Using the Database across your Project

It is advisable to set your objects in a different node from the one in where you use them in. If we apply this statement to our previous project, it would look something like this:

Player's Node:

Setter's Node:

Player's Script:

extends Node2D

@export var can_move: bool
@export var speed: float = 300
@export var separation: int = 30

@onready var projectile_manager: ProjectileManager2D = $ProjectileManager2D
@onready var bow: Sprite2D = $Bow

# Using enums will allow us to expand and read our code more easily
enum Attacks{
	BOW
}

enum Projectiles{
	AREA_PROJECTILE
}


func _ready() -> void:
	projectile_manager.reassing_projectiles_from_database(Projectiles.keys())
	projectile_manager.reassing_attacks_from_database(Attacks.keys())

	for attack: Attack2D in projectile_manager.attacks:
		attack.individual_properties.set("TARGET", self)


func _process(_delta: float) -> void:
	var direction: Vector2 = (get_global_mouse_position() - position).normalized()
	bow.position = direction * separation
	bow.rotation = direction.angle()
	
	projectile_manager.position = bow.position
	projectile_manager.rotation = bow.rotation

	if (Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)):
		projectile_manager.request_execution(Attacks.BOW, Projectiles.AREA_PROJECTILE, bow.position, get_global_mouse_position())
	
	if (can_move):
		position += input_direction * speed * _delta

Setter's Script:

extends Node2D

@onready var projectile_manager: ProjectileManager2D = $ProjectileManager2D


func _ready() -> void:
	projectile_manager.get_attack(0).set_on_start(on_start).set_on_charge_enter(on_charge_enter).set_on_charge_exit(on_charge_exit)
	projectile_manager.get_projectile(0).set_on_move(custom_movement).set_on_collision(custom_collision)



func on_start(attack: Attack2D) -> void:
	var target: Node2D = attack.individual_properties.get("TARGET")
	if (target != null):
		attack.individual_properties["ANIMATION_PLAYER"] = target.get_node("AnimationPlayer")


func on_charge_enter(attack: Attack2D) -> void:
	var animation_player: AnimationPlayer = attack.individual_properties.get("ANIMATION_PLAYER")
	if (animation_player != null):
		animation_player.play("charge")
	attack.charge_enter()


func on_charge_exit(attack: Attack2D) -> void:
	var animation_player: AnimationPlayer = attack.individual_properties.get("ANIMATION_PLAYER")
	if (animation_player != null):
		animation_player.play("idle")
	attack.charge_exit()



func custom_collision(proj: Projectile2D, area_rid: RID, area_node: Node2D, target_node: Node2D, area_shape_index: int, local_shape_index: int) -> void:
	if not proj.validate_collision(area_rid, area_node):
		return
	
	var wall_layer: int = proj.global_properties["WALL_LAYER"]
	if wall_layer & area_node.collision_layer != 0:
		proj.is_expired = true
		return

	if target_node.has_method(proj.on_hit_call):
		target_node.call(proj.on_hit_call, proj)
	proj.on_pierced(area_rid)


func custom_movement(proj: Projectile2D, _delta: float) -> Vector2:
	var local_time: float = proj.individual_properties["LOCAL_TIME"] + _delta
	proj.individual_properties["LOCAL_TIME"] = local_time

	var frecuency: float = proj.global_properties["FRECUENCY"]
	var amplitude: float = proj.global_properties["AMPLITUDE"]

	return proj.direction + (proj.direction.orthogonal() * cos(local_time * frecuency)) * amplitude

Note: Remember to place your setter hierarchically above your Player's node. That way, Godot will call its ready method first!