Skip to content

Commit 63ae822

Browse files
authored
Feat/turn-based-nodes (#36)
* feat: adds turn based nodes
1 parent 5f3ef26 commit 63ae822

File tree

14 files changed

+512
-1
lines changed

14 files changed

+512
-1
lines changed

addons/godot_gameplay_systems/interactables/nodes/interaction_manager.gd

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ var can_interact: bool:
6060
if not tags.has(tag):
6161
return false
6262

63-
return focused_interactable != null
63+
if focused_interactable != null:
64+
return tags_blocking_interaction.size() == 0 and tags_required_to_interact.size() == 0
65+
66+
return false
6467
## Is the interacting owner. Usually a [CharacterBody2D] or [CharacterBody3D].
6568
var interacting_owner: Node:
6669
get:

addons/godot_gameplay_systems/plugin.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const extended_character_nodes_script = preload("res://addons/godot_gameplay_sys
77
const inventory_system_script = preload("res://addons/godot_gameplay_systems/inventory_system/plugin.gd")
88
const interactables_script = preload("res://addons/godot_gameplay_systems/interactables/plugin.gd")
99
const slideshow_script = preload("res://addons/godot_gameplay_systems/slideshow/plugin.gd")
10+
const turn_based_script = preload("res://addons/godot_gameplay_systems/turn_based/plugin.gd")
1011

1112

1213
var attributes_and_abilities_plugin: EditorPlugin
@@ -15,6 +16,7 @@ var extended_character_nodes: EditorPlugin
1516
var inventory_system: EditorPlugin
1617
var interactables: EditorPlugin
1718
var slideshow: EditorPlugin
19+
var turn_based: EditorPlugin
1820

1921

2022
func _init() -> void:
@@ -24,6 +26,7 @@ func _init() -> void:
2426
inventory_system = inventory_system_script.new()
2527
interactables = interactables_script.new()
2628
slideshow = slideshow_script.new()
29+
turn_based = turn_based_script.new()
2730

2831

2932
func _enter_tree():
@@ -33,6 +36,7 @@ func _enter_tree():
3336
inventory_system._enter_tree()
3437
interactables._enter_tree()
3538
slideshow._enter_tree()
39+
turn_based._enter_tree()
3640

3741

3842
func _exit_tree():
@@ -42,3 +46,4 @@ func _exit_tree():
4246
inventory_system._exit_tree()
4347
interactables._exit_tree()
4448
slideshow._exit_tree()
49+
turn_based._exit_tree()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
extends Node
2+
3+
4+
## The [TurnBasedGame] node.
5+
var turn_based_game: TurnBasedGame = null
6+
7+
8+
## Called when the node enters the scene tree for the first time.
9+
func _ready() -> void:
10+
get_turn_based_game()
11+
12+
13+
## Ends the current turn sequence.
14+
func end_turn_sequence() -> void:
15+
if get_turn_based_game():
16+
get_turn_based_game().end_turn_sequence()
17+
18+
19+
## Gets the [TurnBasedGame] node.
20+
func get_turn_based_game() -> TurnBasedGame:
21+
if turn_based_game:
22+
return turn_based_game
23+
24+
for child in get_tree().get_nodes_in_group("ggs.turnbased"):
25+
if child is TurnBasedGame:
26+
turn_based_game = child
27+
return turn_based_game
28+
29+
return null
30+
31+
32+
## Starts a new turn sequence (aka, starts the turn based mode).
33+
func start_turn_sequence() -> void:
34+
if get_turn_based_game():
35+
get_turn_based_game().start_turn_sequence()
36+
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
class_name TurnBasedGame extends Node
2+
3+
4+
## Emitted when a turn changes.
5+
signal turn_changed(manager: TurnBasedGame)
6+
## Emitted when the turn based game starts.
7+
signal turn_game_started()
8+
## Emitted when the turn based game stops.
9+
signal turn_game_stopped()
10+
## Emitted when a subscriber is added.
11+
signal subscriber_added(subscriber: TurnSubscriber)
12+
## Emitted when a subscriber is removed.
13+
signal subscriber_removed(subscriber: TurnSubscriber)
14+
15+
16+
## The current turn.
17+
var current_turn: int = 0
18+
## The current turn subscriber.
19+
var current_turn_subscriber: TurnSubscriber:
20+
get:
21+
if subscribers.size() == 0:
22+
return null
23+
24+
return subscribers[current_turn]
25+
## The turn subscribers.
26+
var subscribers: Array[TurnSubscriber] = []
27+
28+
29+
func _ready() -> void:
30+
add_to_group("ggs.turnbased")
31+
32+
33+
func _sort_subscribers() -> void:
34+
subscribers.sort_custom(func (a, b):
35+
return a.priority < b.priority
36+
)
37+
38+
39+
## Called when a [TurnSubscriber] is subscribed.
40+
## [br]This is a virtual method
41+
func _subscriber_added(sub: TurnSubscriber) -> void:
42+
pass
43+
44+
45+
## Called when a [TurnSubscriber] is unsubscribed.
46+
## [br]This is a virtual method
47+
func _subscriber_removed(sub: TurnSubscriber) -> void:
48+
pass
49+
50+
51+
## Adds a [TurnSubscriber]
52+
func add_subscriber(sub: TurnSubscriber) -> bool:
53+
if subscribers.has(sub):
54+
return false
55+
56+
subscribers.append(sub)
57+
subscriber_added.emit(sub)
58+
59+
_sort_subscribers()
60+
61+
return true
62+
63+
64+
## Ends the current turn
65+
func end_turn_sequence() -> void:
66+
if subscribers.size() == 0:
67+
return
68+
69+
subscribers[current_turn].end_turn()
70+
71+
current_turn += 1
72+
73+
if current_turn >= subscribers.size():
74+
current_turn = 0
75+
76+
subscribers[current_turn].turn_started.emit()
77+
subscribers[current_turn]._turn_started()
78+
79+
turn_game_stopped.emit()
80+
81+
82+
## Calls next turn
83+
func next_turn() -> void:
84+
var subscribers_count := subscribers.size()
85+
86+
if current_turn < subscribers_count:
87+
subscribers[current_turn].turn_ended.emit()
88+
subscribers[current_turn]._turn_ended()
89+
90+
current_turn += 1
91+
92+
if current_turn >= subscribers_count:
93+
current_turn = 0
94+
95+
subscribers[current_turn].turn_started.emit()
96+
subscribers[current_turn]._turn_started()
97+
98+
99+
## Removes a [TurnSubscriber]
100+
func remove_subscriber(sub: TurnSubscriber) -> bool:
101+
if not subscribers.has(sub):
102+
return false
103+
104+
var sub_turn_index = subscribers.find(func (x): return x == sub)
105+
106+
subscribers.remove_at(sub_turn_index)
107+
subscriber_removed.emit(sub)
108+
109+
_sort_subscribers()
110+
111+
return true
112+
113+
114+
## Starts the turn based game.
115+
func start_turn_sequence() -> void:
116+
if subscribers.size() == 0:
117+
return
118+
119+
current_turn = 0
120+
121+
subscribers[current_turn]._turn_started()
122+
subscribers[current_turn].turn_started.emit()
123+
124+
turn_game_started.emit()
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
class_name TurnSubscriber extends Node
2+
3+
## A turn based participant
4+
##
5+
## It represents a scene which can partecipate to a turn based game
6+
7+
8+
## Emitted when this subscriber turn ends
9+
signal turn_ended()
10+
## Emitted when this subscriber turn starts
11+
signal turn_started()
12+
## Emitter when the turn round ended,
13+
signal turn_round_ended()
14+
15+
16+
@export_group("Turn based game")
17+
## Priority of this subscriber. The higher the priority, the sooner the turn starts.
18+
@export var priority: int = 0
19+
20+
21+
func _enter_tree() -> void:
22+
if TurnManager.get_turn_based_game() != null:
23+
TurnManager.get_turn_based_game().add_subscriber(self)
24+
25+
26+
func _exit_tree() -> void:
27+
if TurnManager.get_turn_based_game() != null:
28+
TurnManager.get_turn_based_game().remove_subscriber(self)
29+
30+
31+
func _ready() -> void:
32+
add_to_group("ggs.turnbased")
33+
34+
if TurnManager.get_turn_based_game() != null:
35+
TurnManager.get_turn_based_game().add_subscriber(self)
36+
37+
38+
## Called when the turn round ended
39+
## [br]It's a virtual method, it can be overrided
40+
func _turn_ended() -> void:
41+
pass
42+
43+
44+
## Called when the turn round started
45+
## [br]It's a virtual method, it can be overrided
46+
func _turn_started() -> void:
47+
pass
48+
49+
50+
## Ends the turn of this subscriber
51+
func end_turn() -> void:
52+
if TurnManager.get_turn_based_game() != null:
53+
TurnManager.get_turn_based_game().next_turn()
54+
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
extends EditorPlugin
2+
3+
4+
const TurnBasedGameScript = preload("res://addons/godot_gameplay_systems/turn_based/nodes/TurnBasedGame.gd")
5+
const TurnSubscriberScript = preload("res://addons/godot_gameplay_systems/turn_based/nodes/TurnSubscriber.gd")
6+
7+
8+
func _enter_tree() -> void:
9+
add_autoload_singleton("TurnManager", "res://addons/godot_gameplay_systems/turn_based/autoloads/turn_manager.gd")
10+
add_custom_type("TurnBasedGame", "Node", TurnBasedGameScript, null)
11+
add_custom_type("TurnSubscriber", "Node", TurnSubscriberScript, null)
12+
13+
14+
func _exit_tree() -> void:
15+
remove_autoload_singleton("TurnManager")
16+
remove_custom_type("TurnBasedGame")
17+
remove_custom_type("TurnSubscriber")
18+

docs/turn-based-nodes.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
Turn based nodes
2+
================
3+
4+
Turn based nodes are nodes that can be used to create turn based games.
5+
6+
To start adding functionalities to your game, you can add a `TurnBasedGame` node to your scene. This node should be at the highest level of your hierarchy. It will be used to manage the turns properly.
7+
8+
Then you should add a `TurnSubscriber` to each node which should be notified when a turn starts or ends.
9+
10+
This node has a priority property. The nodes with the highest priority will start their turns first. If two nodes have the same priority, the one that was added first will start its turn first.
11+
12+
To get the `TurnBasedGame` node, you have to use the singleton `TurnManager`.
13+
14+
This has a method which returns the `TurnBasedGame` node. You can use it like this:
15+
16+
```gdscript
17+
# To start a turn sequence
18+
var turn_based_game = TurnManager.get_turn_based_game()
19+
turn_based_game.start_turn_sequence();
20+
21+
# To end a turn sequence
22+
turn_based_game.end_turn_sequence();
23+
```
24+
25+
Each `TurnSubscriber` can terminate it's own turn by calling their `end_turn` method.
26+
27+
```gdscript
28+
# To end a turn
29+
self.end_turn();
30+
```

examples/examples_menu.tscn

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ text = "SOT-like (sail management)"
7676
script = ExtResource("3_jxaps")
7777
scene_to_load = "res://examples/sot_like/sot_game.tscn"
7878

79+
[node name="TurnBasedTopDown" type="Button" parent="ExamplesMenu/ExampleButtons"]
80+
layout_mode = 2
81+
text = "SOT-like (sail management)"
82+
script = ExtResource("3_jxaps")
83+
scene_to_load = "res://examples/turn_based_top_down/turn_based_top_down.tscn"
84+
7985
[node name="HSeparator" type="HSeparator" parent="ExamplesMenu"]
8086
custom_minimum_size = Vector2(2.08165e-12, 50)
8187
layout_mode = 2
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
extends CharacterBody3D
2+
3+
4+
@export var turn_priority: int = 0
5+
@onready var point_and_click_3d: PointAndClick3D = $PointAndClick3D
6+
@onready var turn_subscriber: TurnSubscriber = $TurnSubscriber
7+
@onready var current_turn_indicator: MeshInstance3D = $CurrentTurnIndicator
8+
9+
10+
func _ready() -> void:
11+
turn_subscriber.priority = turn_priority
12+
current_turn_indicator.visible = false
13+
14+
set_process_input(false)
15+
16+
turn_subscriber.turn_started.connect(func ():
17+
set_process_input(true)
18+
current_turn_indicator.visible = true
19+
)
20+
21+
turn_subscriber.turn_ended.connect(func ():
22+
set_process_input(false)
23+
current_turn_indicator.visible = false
24+
)
25+
26+
27+
func _input(event: InputEvent) -> void:
28+
if event.is_action_pressed("diablo_like_move_to"):
29+
point_and_click_3d.set_new_movement_position()
30+

0 commit comments

Comments
 (0)