Skip to content

Commit 92bdebf

Browse files
authored
feat: adds SlideShow node for your game intros (#35)
1 parent 250e24b commit 92bdebf

File tree

15 files changed

+484
-8
lines changed

15 files changed

+484
-8
lines changed

addons/godot_gameplay_systems/plugin.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ const camera_shake_plugin_script = preload("res://addons/godot_gameplay_systems/
66
const extended_character_nodes_script = preload("res://addons/godot_gameplay_systems/extended_character_nodes/plugin.gd")
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")
9+
const slideshow_script = preload("res://addons/godot_gameplay_systems/slideshow/plugin.gd")
910

1011

1112
var attributes_and_abilities_plugin: EditorPlugin
1213
var camera_shake_plugin: EditorPlugin
1314
var extended_character_nodes: EditorPlugin
1415
var inventory_system: EditorPlugin
1516
var interactables: EditorPlugin
17+
var slideshow: EditorPlugin
1618

1719

1820
func _init() -> void:
@@ -21,6 +23,7 @@ func _init() -> void:
2123
extended_character_nodes = extended_character_nodes_script.new()
2224
inventory_system = inventory_system_script.new()
2325
interactables = interactables_script.new()
26+
slideshow = slideshow_script.new()
2427

2528

2629
func _enter_tree():
@@ -29,6 +32,7 @@ func _enter_tree():
2932
extended_character_nodes._enter_tree()
3033
inventory_system._enter_tree()
3134
interactables._enter_tree()
35+
slideshow._enter_tree()
3236

3337

3438
func _exit_tree():
@@ -37,3 +41,4 @@ func _exit_tree():
3741
extended_character_nodes._exit_tree()
3842
inventory_system._exit_tree()
3943
interactables._exit_tree()
44+
slideshow._exit_tree()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
extends EditorPlugin
2+
3+
4+
const slideshow_script = preload("res://addons/godot_gameplay_systems/slideshow/slide_show.gd")
5+
6+
7+
func _enter_tree() -> void:
8+
add_custom_type("SlideShow", "Node2D", slideshow_script, null)
9+
10+
11+
func _exit_tree() -> void:
12+
remove_custom_type("SlideShow")
13+
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
class_name SlideShow extends Node2D
2+
3+
4+
## The initial slideshow in a videogame
5+
##
6+
## This is made easy
7+
8+
9+
enum {
10+
SKIP_PREV = -1,
11+
SKIP_NEXT = +1,
12+
}
13+
14+
## Emitted when the slideshow is finished
15+
signal finished()
16+
## Emitted when a slide is skipped
17+
signal slide_skipped(skip_direction: int)
18+
19+
@export_category("Presentation settings")
20+
## Starts the presentation automatically when ready
21+
@export var autoplay: bool = true
22+
## How much long the slide is shown. It does not take in the [member SlideShow.slide_fade_duration] fadein/fadeout time.
23+
@export_range(1.0, 10.0, 0.1) var slide_duration: float = 6.0
24+
@export_range(0.0, 3.0) var slide_fade_duration: float = 1.0
25+
26+
## Current slide index.
27+
var current_slide: int = 0
28+
## Is [code]true[/code] if there is a previous slide, [code]false[/code] otherwise.
29+
var has_prev: bool:
30+
get:
31+
return current_slide > 0 and slides.size() > 0
32+
## Is [code]true[/code] if there is a next slide, [code]false[/code] otherwise.
33+
var has_next: bool:
34+
get:
35+
return current_slide < slides.size()
36+
## If [code]true[/code] the slide is playing, [code]false[/code] otherwise.
37+
var playing: bool = true:
38+
get:
39+
return playing
40+
set(value):
41+
playing = value
42+
43+
if value and autoplay:
44+
_handle_next_slide()
45+
var slides: Array[Node2D]:
46+
get:
47+
var _s = [] as Array[Node2D]
48+
49+
for child in get_children():
50+
if child is Node2D:
51+
_s.append(child)
52+
53+
return _s
54+
55+
56+
func _handle_slide_in(slide: Node2D) -> void:
57+
if slide.has_method("_slide_in"):
58+
slide.call("_slide_in")
59+
60+
61+
func _handle_slide_out(slide: Node2D) -> void:
62+
if slide.has_method("_slide_out"):
63+
slide.call("_slide_out")
64+
65+
66+
## Forcefully
67+
func _forcefully_fade_current() -> Tween:
68+
var tween = create_tween()
69+
var slide = slides[current_slide] as Node2D
70+
71+
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration)
72+
73+
return tween
74+
75+
76+
## Handles next slide. Called internally, use [method SlideShow.skip_to_prev], [method SlideShow.skip_to_next], [method SlideShow.skip_to_nth] or [method SlideShow.skip_all]
77+
func _handle_next_slide(direction: int = SKIP_NEXT) -> void:
78+
if current_slide >= slides.size():
79+
finished.emit()
80+
else:
81+
var tween = create_tween()
82+
var slide = slides[current_slide] as Node2D
83+
84+
if slide == null:
85+
printerr("This should NEVER happen, what have you done?")
86+
_handle_next_slide()
87+
88+
_handle_slide_in(slide)
89+
90+
tween.tween_property(slide, "modulate:a", 1.0, slide_fade_duration)
91+
tween.tween_interval(slide_duration - (slide_fade_duration * 2))
92+
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration)
93+
94+
tween.finished.connect(func ():
95+
_handle_slide_out(slide)
96+
current_slide += direction
97+
_handle_next_slide()
98+
)
99+
100+
## Ready fn
101+
func _ready() -> void:
102+
playing = autoplay
103+
104+
for slide in slides:
105+
slide.modulate.a = 0.0
106+
107+
108+
## Sets [member SlideShow.playing] to [code]true[/code]
109+
func play() -> void:
110+
current_slide = 0
111+
playing = true
112+
113+
114+
## Skips all slides and the [signal SlideShow.finished] is emitted.
115+
## [br]
116+
## GG mate, we worked hard for this.
117+
func skip_all() -> void:
118+
skip_slide_to_nth(get_child_count() + 1)
119+
120+
121+
## Skips to the next slide if any, otherwise the slideshow ends and the [signal SlideShow.finished] is emitted.
122+
func skip_slide_to_next() -> void:
123+
skip_slide_to_nth(current_slide + 1)
124+
125+
126+
## Skips to a nth slide. If out of bound, the slideshow ends and the [signal SlideShow.finished] is emitted.
127+
func skip_slide_to_nth(slide_index: int) -> void:
128+
var direction = SKIP_NEXT if slide_index > current_slide else SKIP_PREV
129+
var inbound = slide_index >= 0 and slide_index <= slides.size()
130+
131+
if not inbound:
132+
playing = false
133+
finished.emit()
134+
return
135+
136+
if current_slide >= slides.size():
137+
finished.emit()
138+
else:
139+
var tween = create_tween()
140+
var slide = slides[current_slide] as Node2D
141+
142+
slide_skipped.emit(direction)
143+
144+
## Forcefully fades out current slide. You asked for it, do not complain plis.
145+
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration)
146+
147+
tween.finished.connect(func ():
148+
current_slide += direction
149+
_handle_slide_out(slide)
150+
_handle_next_slide(direction)
151+
)
152+
153+
154+
## Skips to the previous slide if any, otherwise the slideshow ends and the [signal SlideShow.finished] is emitted.
155+
func skip_slide_to_prev() -> void:
156+
skip_slide_to_nth(current_slide - 1)
157+
158+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
extends GutTest
2+
3+
4+
func _add_slides(slideshow: SlideShow, count: int) -> void:
5+
for x in range(0, count):
6+
var slide = Node2D.new()
7+
slide.name = "Slide" + str(count)
8+
slideshow.add_child(slide)
9+
10+
11+
func _slideshow() -> SlideShow:
12+
var slideshow = SlideShow.new()
13+
add_child_autofree(slideshow)
14+
return slideshow
15+
16+
17+
18+
func test_normal_flow() -> void:
19+
var s = _slideshow()
20+
21+
watch_signals(s)
22+
23+
s.slide_duration = 1.0
24+
s.slide_fade_duration = 1.0
25+
s.autoplay = true
26+
27+
assert_eq(s.current_slide, 0, "it should always start from the beginning")
28+
29+
_add_slides(s, 3)
30+
31+
s.skip_slide_to_nth(0)
32+
33+
assert_eq(s.current_slide, 0, "even after adding slides programmatically, it should always start from the beginning")
34+
35+
s.skip_slide_to_next()
36+
37+
assert_signal_not_emitted(s, "finished", "finished should not have been emitted")
38+
assert_signal_emitted(s, "slide_skipped", "slide_skipped should have been emitted")
39+
40+
s.skip_slide_to_next()
41+
s.skip_slide_to_next()
42+
s.skip_slide_to_next()
43+
s.skip_slide_to_next()
44+
s.skip_slide_to_next()
45+
46+
await wait_seconds(4.0)
47+
48+
assert_signal_emitted(s, "finished", "finished should have been emitted")
49+
50+
# wow, it worked
51+
52+
53+
func test_trying_to_break_everything() -> void:
54+
var s = _slideshow()
55+
56+
watch_signals(s)
57+
58+
# Let's add unusable children
59+
60+
s.add_child(Node3D.new())
61+
s.add_child(Node.new())
62+
63+
# copy and paste of the "good" scenario test
64+
65+
s.slide_duration = 1.0
66+
s.slide_fade_duration = 1.0
67+
s.autoplay = true
68+
69+
assert_eq(s.current_slide, 0, "it should always start from the beginning")
70+
71+
_add_slides(s, 3)
72+
73+
assert_eq(s.slides.size(), 3, "slides should be only 3")
74+
75+
s.skip_slide_to_nth(0)
76+
77+
assert_eq(s.current_slide, 0, "even after adding slides programmatically, it should always start from the beginning")
78+
79+
s.skip_slide_to_next()
80+
81+
assert_signal_not_emitted(s, "finished", "finished should not have been emitted")
82+
assert_signal_emitted(s, "slide_skipped", "slide_skipped should have been emitted")
83+
84+
s.skip_slide_to_next()
85+
s.skip_slide_to_next()
86+
s.skip_slide_to_next()
87+
s.skip_slide_to_next()
88+
s.skip_slide_to_next()
89+
90+
await wait_seconds(4.0)
91+
92+
assert_signal_emitted(s, "finished", "finished should have been emitted")
93+

docs/readme.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,23 @@ Some docs
33

44
Surely this will need some better docs. They are on the road to 1.0.0.
55

6-
[About ability system](ability-system.md)
6+
## Abilities and attributes
77

8-
[About gameplay attributes](gameplay-attributes.md)
8+
- [About ability system](ability-system.md)
9+
- [About gameplay attributes](gameplay-attributes.md)
910

10-
[About inventory](inventory/inventory.md)
11+
## Inventory and equipment
1112

12-
[About equipment](inventory/equipment.md)
13+
- [About inventory](inventory/inventory.md)
14+
- [About equipment](inventory/equipment.md)
15+
- [About items dropping](inventory/drop.md)
16+
- [About interacting with items](interactions-system.md)
1317

14-
[About items dropping](inventory/drop.md)
18+
## Character related nodes
1519

16-
[About interacting with items](interactions-system.md)
20+
- [About point and click](point-and-click.md)
21+
- [About camera shake](camera-shake.md)
22+
23+
## Miscellaneous
24+
25+
- [About intro screens](slide_show.md)

docs/slide_show.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
SlideShow
2+
=========
3+
4+
You played videogames before don't you? Well, this node is used to create the intro presentation screen where usually the company/deb logo, tech used logos and more a put in.
5+
6+
It's a 2D node, and accepts only `Node2D` nodes as children (all the other will be discarded).
7+
8+
It has three parameters:
9+
10+
- `autoplay`: if set to `true` *(default)*, the slideshow will start when ready.
11+
- `slide_duration`: how long a slide will be visible
12+
- `slide_fade_duration`: how long a fade in or fade out will take
13+

examples/examples.gd

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
extends Node
22

33

4+
@onready var intro: SlideShow = $Intro
45
@onready var running_example: Node = $RunningExample
56
@onready var examples_menu = $ExamplesMenu
67

78

89

910
func _input(event: InputEvent) -> void:
10-
if event.is_action_pressed("close_example"):
11+
if event.is_action_pressed("close_example") and intro != null and not intro.playing:
1112
for child in running_example.get_children():
1213
running_example.remove_child(child)
1314
Input.mouse_mode = Input.MOUSE_MODE_CONFINED
1415
examples_menu.show_menu()
16+
elif event.is_action_pressed("close_example") and intro != null and intro.playing:
17+
intro.skip_slide_to_next()
1518

1619

1720
func _ready() -> void:
1821
Input.mouse_mode = Input.MOUSE_MODE_CONFINED
1922

23+
examples_menu.modulate.a = 0.0
2024
examples_menu.scene_selected.connect(func (scene):
2125
examples_menu.hide_menu()
2226
running_example.add_child(scene)
2327
)
28+
29+
intro.finished.connect(func ():
30+
create_tween().tween_property(examples_menu, "modulate:a", 1.0, 1.0)
31+
intro.queue_free()
32+
)

0 commit comments

Comments
 (0)