Table of Contents
Items:
While searching ideas for the combat system the first days of development, I was inspired by Minecraft 1.8 PVP. Its main feature being the usage of the sword and other tools to slow down or annoy the player, like the flint & steel or the fishing rod.
Following that idea I started sketching some of the uses for these items:
Flint & steel:
Used to start fires, ignite TNTs, damage close up enemies and deactivate magnets. This elegantly encompasses a lot of features into a single item, making it a useful addition to your inventory. It also adds a risk reward-mechanic, there is a damage boost if the enemy is hit using the flint & steel from up close.
Fishing rod:
I wanted this item to be used as a grappling hook, used even to bring enemies closer, making them hang high and then let them take fall damage.
But in the end I scrapped this idea to make space for a more elegant and more appealing idea. This free movement mechanic took form into the Amulet and all the Supercharged Redstone mechanic.
Bag of seeds:
This was the first iteration of the Magneto-Resonant Controller. Initially this item would only attract and control hordes of chickens, ending the final sequence with a giant chicken made out of chickens wrecking the villain's mansion door.
It was replaced with the Controller to make it more in line with the other items, also I didn't like the idea of a simple bag of seeds being this powerful. The giant chicken idea got scrapped too because during early prototypes it didn't look as I wanted it to be. The ins and outs of this mechanic will be discussed in the System Design part.
Redstone Contraptions:
Another very important, but also silent gameplay mechanic are all the redstone contraptions. They make possible all of the complex features such as: moving platforms, dispensers, TNTs and also the final bossfight.
Redstone is implemented in Engine using 2 kinds of object:
Sender
which is capable of sending an activation signal to all its connected receivers.
1 class_name Sender
2 extends Node2D
3
4 @export var receivers : Array[Receiver]
5 @export var signal_scene : PackedScene
6 @export var signal_cooldown_time := 2.0
13
14 ## This method emits a signal to all connected receivers,
15 ## it can be set only visible, only triggering or both at the same time.
16 ## (I think this method is still a bit messy)
17 func send(triggering : bool = true, signal_type : bool = true, is_signal_visible : bool = true) -> void:
18 if not has_setup_signal_cooldown:
19 _setup_signal_cooldown()
20 has_setup_signal_cooldown = true
21 for receiver in receivers:
22 if receiver != null:
23 if triggering:
24 receiver._sent()
25 if is_signal_visible:
26 _generate_signal(receiver, signal_type)
27
28
29 func _generate_signal(target : Receiver, signal_type : bool = false) -> void:
30 if is_in_signaling_cooldown and last_signaled_value == signal_type: return
31
32 # Spawning signal particles
40
41 signal_cooldown.start(signal_cooldown_time)
42 last_signaled_value = signal_type
43 is_in_signaling_cooldown = true
44
45 func _setup_signal_cooldown():
46 signal_cooldown = Timer.new()
47 signal_cooldown.wait_time = signal_cooldown_time
48 signal_cooldown.one_shot = true
49 signal_cooldown.timeout.connect(_on_signal_cooldown_timeout)
50 add_child(signal_cooldown)
51
52 func _on_signal_cooldown_timeout():
53 is_in_signaling_cooldown = false
Receiver
which receives signals to perform particular actions.
1 class_name Receiver
2 extends Sender
3
4 @export var activation_delay_time := 1.0
5 @export var deactivation_delay_time := 1.0
6
7 @export var relay_time := 0.0
8
9 @export var waiter_scene : PackedScene
10
11 var was_deactivated := true
12
13 ## This is the method that gets overridden when
14 ## a receiver detects a redstone signal
15 func triggered() -> void:
16 pass
17
18 func _sent():
19 if was_deactivated:
20 if activation_delay_time > 0.0:
21 await get_tree().create_timer(activation_delay_time).timeout
22 else:
23 if deactivation_delay_time > 0.0:
24 await get_tree().create_timer(deactivation_delay_time).timeout
25 was_deactivated = not was_deactivated
26 _received()
27
28 func _received() -> void:
29 triggered()
30 if relay_time <= 0.0: return
31
32 var waiter_instance := waiter_scene.instantiate()
33
34 if waiter_instance is Waiter:
35 waiter_instance.receivers = receivers
36 get_parent().add_child(waiter_instance, true)
37 waiter_instance.start(relay_time)
A receiver is also a sender with some other functionality attached to it. This makes it possible to chain actions together even with custom timings.