-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds SlideShow node for your game intros (#35)
- Loading branch information
Showing
15 changed files
with
484 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
extends EditorPlugin | ||
|
||
|
||
const slideshow_script = preload("res://addons/godot_gameplay_systems/slideshow/slide_show.gd") | ||
|
||
|
||
func _enter_tree() -> void: | ||
add_custom_type("SlideShow", "Node2D", slideshow_script, null) | ||
|
||
|
||
func _exit_tree() -> void: | ||
remove_custom_type("SlideShow") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
class_name SlideShow extends Node2D | ||
|
||
|
||
## The initial slideshow in a videogame | ||
## | ||
## This is made easy | ||
|
||
|
||
enum { | ||
SKIP_PREV = -1, | ||
SKIP_NEXT = +1, | ||
} | ||
|
||
## Emitted when the slideshow is finished | ||
signal finished() | ||
## Emitted when a slide is skipped | ||
signal slide_skipped(skip_direction: int) | ||
|
||
@export_category("Presentation settings") | ||
## Starts the presentation automatically when ready | ||
@export var autoplay: bool = true | ||
## How much long the slide is shown. It does not take in the [member SlideShow.slide_fade_duration] fadein/fadeout time. | ||
@export_range(1.0, 10.0, 0.1) var slide_duration: float = 6.0 | ||
@export_range(0.0, 3.0) var slide_fade_duration: float = 1.0 | ||
|
||
## Current slide index. | ||
var current_slide: int = 0 | ||
## Is [code]true[/code] if there is a previous slide, [code]false[/code] otherwise. | ||
var has_prev: bool: | ||
get: | ||
return current_slide > 0 and slides.size() > 0 | ||
## Is [code]true[/code] if there is a next slide, [code]false[/code] otherwise. | ||
var has_next: bool: | ||
get: | ||
return current_slide < slides.size() | ||
## If [code]true[/code] the slide is playing, [code]false[/code] otherwise. | ||
var playing: bool = true: | ||
get: | ||
return playing | ||
set(value): | ||
playing = value | ||
|
||
if value and autoplay: | ||
_handle_next_slide() | ||
var slides: Array[Node2D]: | ||
get: | ||
var _s = [] as Array[Node2D] | ||
|
||
for child in get_children(): | ||
if child is Node2D: | ||
_s.append(child) | ||
|
||
return _s | ||
|
||
|
||
func _handle_slide_in(slide: Node2D) -> void: | ||
if slide.has_method("_slide_in"): | ||
slide.call("_slide_in") | ||
|
||
|
||
func _handle_slide_out(slide: Node2D) -> void: | ||
if slide.has_method("_slide_out"): | ||
slide.call("_slide_out") | ||
|
||
|
||
## Forcefully | ||
func _forcefully_fade_current() -> Tween: | ||
var tween = create_tween() | ||
var slide = slides[current_slide] as Node2D | ||
|
||
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration) | ||
|
||
return tween | ||
|
||
|
||
## 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] | ||
func _handle_next_slide(direction: int = SKIP_NEXT) -> void: | ||
if current_slide >= slides.size(): | ||
finished.emit() | ||
else: | ||
var tween = create_tween() | ||
var slide = slides[current_slide] as Node2D | ||
|
||
if slide == null: | ||
printerr("This should NEVER happen, what have you done?") | ||
_handle_next_slide() | ||
|
||
_handle_slide_in(slide) | ||
|
||
tween.tween_property(slide, "modulate:a", 1.0, slide_fade_duration) | ||
tween.tween_interval(slide_duration - (slide_fade_duration * 2)) | ||
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration) | ||
|
||
tween.finished.connect(func (): | ||
_handle_slide_out(slide) | ||
current_slide += direction | ||
_handle_next_slide() | ||
) | ||
|
||
## Ready fn | ||
func _ready() -> void: | ||
playing = autoplay | ||
|
||
for slide in slides: | ||
slide.modulate.a = 0.0 | ||
|
||
|
||
## Sets [member SlideShow.playing] to [code]true[/code] | ||
func play() -> void: | ||
current_slide = 0 | ||
playing = true | ||
|
||
|
||
## Skips all slides and the [signal SlideShow.finished] is emitted. | ||
## [br] | ||
## GG mate, we worked hard for this. | ||
func skip_all() -> void: | ||
skip_slide_to_nth(get_child_count() + 1) | ||
|
||
|
||
## Skips to the next slide if any, otherwise the slideshow ends and the [signal SlideShow.finished] is emitted. | ||
func skip_slide_to_next() -> void: | ||
skip_slide_to_nth(current_slide + 1) | ||
|
||
|
||
## Skips to a nth slide. If out of bound, the slideshow ends and the [signal SlideShow.finished] is emitted. | ||
func skip_slide_to_nth(slide_index: int) -> void: | ||
var direction = SKIP_NEXT if slide_index > current_slide else SKIP_PREV | ||
var inbound = slide_index >= 0 and slide_index <= slides.size() | ||
|
||
if not inbound: | ||
playing = false | ||
finished.emit() | ||
return | ||
|
||
if current_slide >= slides.size(): | ||
finished.emit() | ||
else: | ||
var tween = create_tween() | ||
var slide = slides[current_slide] as Node2D | ||
|
||
slide_skipped.emit(direction) | ||
|
||
## Forcefully fades out current slide. You asked for it, do not complain plis. | ||
tween.tween_property(slide, "modulate:a", 0.0, slide_fade_duration) | ||
|
||
tween.finished.connect(func (): | ||
current_slide += direction | ||
_handle_slide_out(slide) | ||
_handle_next_slide(direction) | ||
) | ||
|
||
|
||
## Skips to the previous slide if any, otherwise the slideshow ends and the [signal SlideShow.finished] is emitted. | ||
func skip_slide_to_prev() -> void: | ||
skip_slide_to_nth(current_slide - 1) | ||
|
||
|
93 changes: 93 additions & 0 deletions
93
addons/godot_gameplay_systems/slideshow/test/unit/test_slideshow.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
extends GutTest | ||
|
||
|
||
func _add_slides(slideshow: SlideShow, count: int) -> void: | ||
for x in range(0, count): | ||
var slide = Node2D.new() | ||
slide.name = "Slide" + str(count) | ||
slideshow.add_child(slide) | ||
|
||
|
||
func _slideshow() -> SlideShow: | ||
var slideshow = SlideShow.new() | ||
add_child_autofree(slideshow) | ||
return slideshow | ||
|
||
|
||
|
||
func test_normal_flow() -> void: | ||
var s = _slideshow() | ||
|
||
watch_signals(s) | ||
|
||
s.slide_duration = 1.0 | ||
s.slide_fade_duration = 1.0 | ||
s.autoplay = true | ||
|
||
assert_eq(s.current_slide, 0, "it should always start from the beginning") | ||
|
||
_add_slides(s, 3) | ||
|
||
s.skip_slide_to_nth(0) | ||
|
||
assert_eq(s.current_slide, 0, "even after adding slides programmatically, it should always start from the beginning") | ||
|
||
s.skip_slide_to_next() | ||
|
||
assert_signal_not_emitted(s, "finished", "finished should not have been emitted") | ||
assert_signal_emitted(s, "slide_skipped", "slide_skipped should have been emitted") | ||
|
||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
|
||
await wait_seconds(4.0) | ||
|
||
assert_signal_emitted(s, "finished", "finished should have been emitted") | ||
|
||
# wow, it worked | ||
|
||
|
||
func test_trying_to_break_everything() -> void: | ||
var s = _slideshow() | ||
|
||
watch_signals(s) | ||
|
||
# Let's add unusable children | ||
|
||
s.add_child(Node3D.new()) | ||
s.add_child(Node.new()) | ||
|
||
# copy and paste of the "good" scenario test | ||
|
||
s.slide_duration = 1.0 | ||
s.slide_fade_duration = 1.0 | ||
s.autoplay = true | ||
|
||
assert_eq(s.current_slide, 0, "it should always start from the beginning") | ||
|
||
_add_slides(s, 3) | ||
|
||
assert_eq(s.slides.size(), 3, "slides should be only 3") | ||
|
||
s.skip_slide_to_nth(0) | ||
|
||
assert_eq(s.current_slide, 0, "even after adding slides programmatically, it should always start from the beginning") | ||
|
||
s.skip_slide_to_next() | ||
|
||
assert_signal_not_emitted(s, "finished", "finished should not have been emitted") | ||
assert_signal_emitted(s, "slide_skipped", "slide_skipped should have been emitted") | ||
|
||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
s.skip_slide_to_next() | ||
|
||
await wait_seconds(4.0) | ||
|
||
assert_signal_emitted(s, "finished", "finished should have been emitted") | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
SlideShow | ||
========= | ||
|
||
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. | ||
|
||
It's a 2D node, and accepts only `Node2D` nodes as children (all the other will be discarded). | ||
|
||
It has three parameters: | ||
|
||
- `autoplay`: if set to `true` *(default)*, the slideshow will start when ready. | ||
- `slide_duration`: how long a slide will be visible | ||
- `slide_fade_duration`: how long a fade in or fade out will take | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,32 @@ | ||
extends Node | ||
|
||
|
||
@onready var intro: SlideShow = $Intro | ||
@onready var running_example: Node = $RunningExample | ||
@onready var examples_menu = $ExamplesMenu | ||
|
||
|
||
|
||
func _input(event: InputEvent) -> void: | ||
if event.is_action_pressed("close_example"): | ||
if event.is_action_pressed("close_example") and intro != null and not intro.playing: | ||
for child in running_example.get_children(): | ||
running_example.remove_child(child) | ||
Input.mouse_mode = Input.MOUSE_MODE_CONFINED | ||
examples_menu.show_menu() | ||
elif event.is_action_pressed("close_example") and intro != null and intro.playing: | ||
intro.skip_slide_to_next() | ||
|
||
|
||
func _ready() -> void: | ||
Input.mouse_mode = Input.MOUSE_MODE_CONFINED | ||
|
||
examples_menu.modulate.a = 0.0 | ||
examples_menu.scene_selected.connect(func (scene): | ||
examples_menu.hide_menu() | ||
running_example.add_child(scene) | ||
) | ||
|
||
intro.finished.connect(func (): | ||
create_tween().tween_property(examples_menu, "modulate:a", 1.0, 1.0) | ||
intro.queue_free() | ||
) |
Oops, something went wrong.