Skip to content

Commit

Permalink
feat: adds SlideShow node for your game intros (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
OctoD committed Jun 30, 2023
1 parent 250e24b commit 92bdebf
Show file tree
Hide file tree
Showing 15 changed files with 484 additions and 8 deletions.
5 changes: 5 additions & 0 deletions addons/godot_gameplay_systems/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ const camera_shake_plugin_script = preload("res://addons/godot_gameplay_systems/
const extended_character_nodes_script = preload("res://addons/godot_gameplay_systems/extended_character_nodes/plugin.gd")
const inventory_system_script = preload("res://addons/godot_gameplay_systems/inventory_system/plugin.gd")
const interactables_script = preload("res://addons/godot_gameplay_systems/interactables/plugin.gd")
const slideshow_script = preload("res://addons/godot_gameplay_systems/slideshow/plugin.gd")


var attributes_and_abilities_plugin: EditorPlugin
var camera_shake_plugin: EditorPlugin
var extended_character_nodes: EditorPlugin
var inventory_system: EditorPlugin
var interactables: EditorPlugin
var slideshow: EditorPlugin


func _init() -> void:
Expand All @@ -21,6 +23,7 @@ func _init() -> void:
extended_character_nodes = extended_character_nodes_script.new()
inventory_system = inventory_system_script.new()
interactables = interactables_script.new()
slideshow = slideshow_script.new()


func _enter_tree():
Expand All @@ -29,6 +32,7 @@ func _enter_tree():
extended_character_nodes._enter_tree()
inventory_system._enter_tree()
interactables._enter_tree()
slideshow._enter_tree()


func _exit_tree():
Expand All @@ -37,3 +41,4 @@ func _exit_tree():
extended_character_nodes._exit_tree()
inventory_system._exit_tree()
interactables._exit_tree()
slideshow._exit_tree()
13 changes: 13 additions & 0 deletions addons/godot_gameplay_systems/slideshow/plugin.gd
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")

158 changes: 158 additions & 0 deletions addons/godot_gameplay_systems/slideshow/slide_show.gd
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)


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")

21 changes: 15 additions & 6 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,23 @@ Some docs

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

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

[About gameplay attributes](gameplay-attributes.md)
- [About ability system](ability-system.md)
- [About gameplay attributes](gameplay-attributes.md)

[About inventory](inventory/inventory.md)
## Inventory and equipment

[About equipment](inventory/equipment.md)
- [About inventory](inventory/inventory.md)
- [About equipment](inventory/equipment.md)
- [About items dropping](inventory/drop.md)
- [About interacting with items](interactions-system.md)

[About items dropping](inventory/drop.md)
## Character related nodes

[About interacting with items](interactions-system.md)
- [About point and click](point-and-click.md)
- [About camera shake](camera-shake.md)

## Miscellaneous

- [About intro screens](slide_show.md)
13 changes: 13 additions & 0 deletions docs/slide_show.md
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

11 changes: 10 additions & 1 deletion examples/examples.gd
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()
)

0 comments on commit 92bdebf

Please sign in to comment.