979 lines
36 KiB
GDScript
979 lines
36 KiB
GDScript
@tool
|
|
@icon('res://addons/popochiu/icons/character.png')
|
|
class_name PopochiuCharacter
|
|
extends PopochiuClickable
|
|
## Any object that can move, walk, navigate rooms, or have an inventory.
|
|
|
|
## Determines when to flip the [b]$Sprite2D[/b] child.
|
|
enum FlipsWhen {
|
|
## The [b]$Sprite2D[/b] child is not flipped.
|
|
NONE,
|
|
## The [b]$Sprite2D[/b] child is flipped when the character is looking to the right.
|
|
LOOKING_RIGHT,
|
|
## The [b]$Sprite2D[/b] child is flipped when the character is looking to the left.
|
|
LOOKING_LEFT
|
|
}
|
|
## Determines the direction the character is facing
|
|
enum Looking {
|
|
RIGHT,
|
|
## The character is facing down-right [code](x, y)[/code].
|
|
DOWN_RIGHT,
|
|
## The character is facing down [code](0, y)[/code].
|
|
DOWN,
|
|
## The character is facing down-left [code](-x, y)[/code].
|
|
DOWN_LEFT,
|
|
## The character is facing left [code](-x, 0)[/code].
|
|
LEFT,
|
|
## The character is facing up-left [code](-x, -y)[/code].
|
|
UP_LEFT,
|
|
## The character is facing up [code](0, -y)[/code].
|
|
UP,
|
|
## The character is facing up-right [code](x, -y)[/code].
|
|
UP_RIGHT
|
|
## The character is facing right [code](x, 0)[/code].
|
|
}
|
|
|
|
## Emitted when a [param character] starts moving from [param start] to [param end]. [PopochiuRoom]
|
|
## connects to this signal in order to make characters move inside them from one point to another.
|
|
signal started_walk_to(character: PopochiuCharacter, start: Vector2, end: Vector2)
|
|
## Emitted when the character is forced to stop while walking.
|
|
signal stopped_walk
|
|
## Emitted when the character reaches the ending position when moving from one point to another.
|
|
signal move_ended
|
|
## Emitted when the animation to grab things has finished.
|
|
signal grab_done
|
|
|
|
## Empty string constant to perform type checks (String is not nullable in GDScript. See #381, #382).
|
|
const EMPTY_STRING = ""
|
|
|
|
## The [Color] in which the dialogue lines of the character are rendered.
|
|
@export var text_color := Color.WHITE
|
|
## Depending on its value, the [b]$Sprite2D[/b] child will be flipped horizontally depending on
|
|
## which way the character is facing. If the value is [constant NONE], then the
|
|
## [b]$Sprite2D[/b] child won't be flipped.
|
|
@export var flips_when: FlipsWhen = FlipsWhen.NONE
|
|
## Array of [Dictionary] where each element has
|
|
## [code]{ emotion: String, variations: Array[PopochiuAudioCue] }[/code].
|
|
## You can use this to define which [PopochiuAudioCue]s to play when the character speaks using a
|
|
## specific emotion.
|
|
@export var voices := []: set = set_voices
|
|
## Whether the character should follow the player-controlled character (PC) when it moves through
|
|
## the room.
|
|
@export var follow_player := false: set = set_follow_player
|
|
## The offset between the player-controlled character (PC) and this character when it follows the
|
|
## former one.
|
|
@export var follow_player_offset := Vector2(20, 0)
|
|
## Array of [Dictionary] where each element has [code]{ emotion: String, avatar: Texture }[/code].
|
|
## You can use this to define which [Texture] to use as avatar for the character when it speaks
|
|
## using a specific emotion.
|
|
@export var avatars := []: set = set_avatars
|
|
## The speed at which the character will move in pixels per frame.
|
|
@export var walk_speed := 200.0
|
|
## Whether the character can or not move.
|
|
@export var can_move := true
|
|
## Whether the character ignores or not walkable areas. If [code]true[/code], the character will
|
|
## move to any point in the room clicked by players without taking into account the walkable areas
|
|
## in it.
|
|
@export var ignore_walkable_areas := false
|
|
## Whether the character will move only when the frame changes on its animation.
|
|
@export var anti_glide_animation: bool = false
|
|
## Used by the GUI to calculate where to render the dialogue lines said by the character when it
|
|
## speaks.
|
|
@export var dialog_pos: Vector2
|
|
# This category is used by the Aseprite Importer in order to allow the creation of a section in the
|
|
# Inspector for the character.
|
|
@export_category("Aseprite")
|
|
|
|
## The stored position of the character. Used when [member anti_glide_animation] is
|
|
## [code]true[/code].
|
|
var position_stored = null
|
|
## Stores the [member PopochiuRoom.script_name] of the previously visited [PopochiuRoom].
|
|
var last_room := EMPTY_STRING
|
|
## The suffix text to add to animation names.
|
|
var anim_suffix := EMPTY_STRING
|
|
## Whether the character is or not moving through the room.
|
|
var is_moving := false
|
|
## The current emotion used by the character.
|
|
var emotion := EMPTY_STRING
|
|
##
|
|
var on_scaling_region: Dictionary = {}
|
|
## Stores the default walk speed defined in [member walk_speed]. Used by [PopochiuRoom] when scaling
|
|
## the character if it is inside a [PopochiuRegion] that modifies the scale.
|
|
var default_walk_speed := 0
|
|
## Stores the default scale. Used by [PopochiuRoom] when scaling the character if it is inside a
|
|
## [PopochiuRegion] that modifies the scale.
|
|
var default_scale := Vector2.ONE
|
|
# Holds the direction the character is looking at.
|
|
# Initialized to DOWN.
|
|
var _looking_dir: int = Looking.DOWN
|
|
# Holds a suffixes fallback list for the animations to play.
|
|
# Initialized to the suffixes corresponding to the DOWN direction.
|
|
var _animation_suffixes: Array = ['_d', '_dr', '_dl', '_r', '_l', EMPTY_STRING]
|
|
# Holds the last PopochiuClickable that the character reached.
|
|
var _last_reached_clickable: PopochiuClickable = null
|
|
# Holds the animation that's currently selected in the character's AnimationPlayer.
|
|
var _current_animation: String = "null"
|
|
# Holds the last animation category requested for the character (idle, walk, talk, grab, ...).
|
|
var _last_requested_animation_label: String = "null"
|
|
# Holds the direction the character was looking at when the current animation was requested.
|
|
var _last_requested_animation_dir: int = -1
|
|
|
|
@onready var animation_player: AnimationPlayer = $AnimationPlayer
|
|
|
|
# Array of the animation suffixes to search for
|
|
# based on the angle the character is facing.
|
|
var _valid_animation_suffixes = [
|
|
['_r', '_l', '_dr', '_dl', '_d'], # 0 - 22.5 degrees
|
|
['_dr', '_dl', '_r' , '_l', '_d'], # 22.5 - 45 degrees
|
|
['_dr', '_dl', '_d' , '_r', '_l'], # 45 - 67.5 degrees
|
|
['_d', '_dr', '_dl', '_r', '_l'], # 67.5 - 90 degrees
|
|
['_d', '_dl', '_dr', '_l', '_r'], # 90 - 112.5 degrees
|
|
['_dl', '_dr', '_d', '_l', '_r'], # 112.5 - 135 degrees
|
|
['_dl', '_dr', '_l', '_r', '_d'], # 135 - 157.5 degrees
|
|
['_l', '_r', '_dl', '_dr', '_d'], # 157.5 - 180 degrees
|
|
['_l', '_r', '_ul', '_ur', '_u'], # 180 - 202.5 degrees
|
|
['_ul', '_ur', '_l', '_r', '_u'], # 202.5 - 225 degrees
|
|
['_ul', '_ur', '_u', '_l', '_r'], # 225 - 247.5 degrees
|
|
['_u', '_ul', '_ur', '_l', '_r'], # 247.5 - 270 degrees
|
|
['_u', '_ur', '_ul', '_r', '_l'], # 270 - 292.5 degrees
|
|
['_ur', '_ul', '_u', '_r', '_l'], # 292.5 - 315 degrees
|
|
['_ur', '_ul', '_r', '_l', '_u'], # 315 - 337.5 degrees
|
|
['_r', '_l', '_ur', '_ul', '_u']] # 337.5 - 360 degrees
|
|
|
|
#region Godot ######################################################################################
|
|
func _ready():
|
|
super()
|
|
|
|
default_walk_speed = walk_speed
|
|
default_scale = Vector2(scale)
|
|
|
|
if Engine.is_editor_hint():
|
|
hide_helpers()
|
|
set_process(true)
|
|
else:
|
|
set_process(follow_player)
|
|
|
|
for child in get_children():
|
|
if not child is Sprite2D:
|
|
continue
|
|
child.frame_changed.connect(_update_position)
|
|
|
|
move_ended.connect(_on_move_ended)
|
|
|
|
|
|
func _get_property_list():
|
|
return [
|
|
{
|
|
name = "popochiu_placeholder",
|
|
type = TYPE_NIL,
|
|
}
|
|
]
|
|
|
|
|
|
#endregion
|
|
|
|
#region Virtual ####################################################################################
|
|
## Use it to play the idle animation of the character.
|
|
## [i]Virtual[/i].
|
|
func _play_idle() -> void:
|
|
play_animation('idle')
|
|
|
|
|
|
## Use it to play the walk animation of the character.
|
|
## [i]Virtual[/i].
|
|
func _play_walk(target_pos: Vector2) -> void:
|
|
# Set the default parameters for play_animation()
|
|
var animation_label = 'walk'
|
|
var animation_fallback = 'idle'
|
|
|
|
play_animation(animation_label, animation_fallback)
|
|
|
|
|
|
## Use it to play the talk animation of the character.
|
|
## [i]Virtual[/i].
|
|
func _play_talk() -> void:
|
|
play_animation('talk')
|
|
|
|
|
|
## Use it to play the grab animation of the character.
|
|
## [i]Virtual[/i].
|
|
func _play_grab() -> void:
|
|
play_animation('grab')
|
|
|
|
|
|
func _on_move_ended() -> void:
|
|
pass
|
|
|
|
|
|
#endregion
|
|
|
|
#region Public #####################################################################################
|
|
## Puts the character in the idle state by playing its idle animation, then waits for
|
|
## [code]0.2[/code] seconds.
|
|
## If the character has a [b]$Sprite2D[/b] child, it makes it flip based on the [member flips_when]
|
|
## value.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_idle() -> Callable:
|
|
return func(): await idle()
|
|
|
|
|
|
## Puts the character in the idle state by playing its idle animation, then waits for
|
|
## [code]0.2[/code] seconds.
|
|
## If the character has a [b]$Sprite2D[/b] child, it makes it flip based on the [member flips_when]
|
|
## value.
|
|
func idle() -> void:
|
|
if PopochiuUtils.e.cutscene_skipped:
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
_flip_left_right(
|
|
_looking_dir in [Looking.LEFT, Looking.DOWN_LEFT, Looking.UP_LEFT],
|
|
_looking_dir in [Looking.RIGHT, Looking.DOWN_RIGHT, Looking.UP_RIGHT]
|
|
)
|
|
|
|
# Call the virtual that plays the idle animation
|
|
_play_idle()
|
|
|
|
await get_tree().create_timer(0.2).timeout
|
|
|
|
|
|
## Makes the character move to [param target_pos] and plays its walk animation.
|
|
## If the character has a [b]$Sprite2D[/b] child, it makes it flip based on the [member flips_when]
|
|
## value.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk(target_pos: Vector2) -> Callable:
|
|
return func(): await walk(target_pos)
|
|
|
|
|
|
## Makes the character move to [param target_pos] and plays its walk animation.
|
|
## If the character has a [b]$Sprite2D[/b] child, it makes it flip based on the [member flips_when]
|
|
## value.
|
|
func walk(target_pos: Vector2) -> void:
|
|
is_moving = true
|
|
_last_reached_clickable = null
|
|
|
|
# The ROOM will take care of moving the character
|
|
# and face her in the correct direction from here
|
|
|
|
_flip_left_right(
|
|
target_pos.x < position.x,
|
|
target_pos.x > position.x
|
|
)
|
|
|
|
if PopochiuUtils.e.cutscene_skipped:
|
|
is_moving = false
|
|
await get_tree().process_frame
|
|
|
|
position = target_pos
|
|
PopochiuUtils.e.camera.position = target_pos
|
|
await get_tree().process_frame
|
|
|
|
return
|
|
|
|
# Call the virtual that plays the walk animation
|
|
_play_walk(target_pos)
|
|
|
|
# Trigger the signal for the room to start moving the character
|
|
started_walk_to.emit(self, position, target_pos)
|
|
await move_ended
|
|
|
|
is_moving = false
|
|
|
|
|
|
func turn_towards(target_pos: Vector2) -> void:
|
|
_flip_left_right(
|
|
target_pos.x < position.x,
|
|
target_pos.x > position.x
|
|
)
|
|
face_direction(target_pos)
|
|
_play_walk(target_pos)
|
|
|
|
## Makes the character stop moving and emits [signal stopped_walk].[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_stop_walking() -> Callable:
|
|
return func(): await stop_walking()
|
|
|
|
|
|
## Makes the character stop moving and emits [signal stopped_walk].
|
|
func stop_walking() -> void:
|
|
is_moving = false
|
|
|
|
stopped_walk.emit()
|
|
|
|
await get_tree().process_frame
|
|
|
|
|
|
## Makes the character to look up by setting [member _looking_dir] to [constant UP] and waits until
|
|
## [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_up() -> Callable:
|
|
return func(): await face_up()
|
|
|
|
|
|
## Makes the character to look up by setting [member _looking_dir] to [constant UP] and waits until
|
|
## [method idle] finishes.
|
|
func face_up() -> void:
|
|
face_direction(position + Vector2.UP)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look up and right by setting [member _looking_dir] to [constant UP_RIGHT]
|
|
## and waits until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_up_right() -> Callable:
|
|
return func(): await face_up_right()
|
|
|
|
|
|
## Makes the character to look up and right by setting [member _looking_dir] to [constant UP_RIGHT]
|
|
## and waits until [method idle] finishes.
|
|
func face_up_right() -> void:
|
|
face_direction(position + Vector2.UP + Vector2.RIGHT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look right by setting [member _looking_dir] to [constant RIGHT] and waits
|
|
## until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_right() -> Callable:
|
|
return func(): await face_right()
|
|
|
|
|
|
## Makes the character to look right by setting [member _looking_dir] to [constant RIGHT] and waits
|
|
## until [method idle] finishes.
|
|
func face_right() -> void:
|
|
face_direction(position + Vector2.RIGHT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look down and right by setting [member _looking_dir] to
|
|
## [constant DOWN_RIGHT] and waits until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_down_right() -> Callable:
|
|
return func(): await face_down_right()
|
|
|
|
|
|
## Makes the character to look down and right by setting [member _looking_dir] to
|
|
## [constant DOWN_RIGHT] and waits until [method idle] finishes.
|
|
func face_down_right() -> void:
|
|
face_direction(position + Vector2.DOWN + Vector2.RIGHT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look down by setting [member _looking_dir] to [constant DOWN] and waits
|
|
## until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_down() -> Callable:
|
|
return func(): await face_down()
|
|
|
|
|
|
## Makes the character to look down by setting [member _looking_dir] to [constant DOWN] and waits
|
|
## until [method idle] finishes.
|
|
func face_down() -> void:
|
|
face_direction(position + Vector2.DOWN)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look down and left by setting [member _looking_dir] to
|
|
## [constant DOWN_LEFT] and waits until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_down_left() -> Callable:
|
|
return func(): await face_down_left()
|
|
|
|
|
|
## Makes the character to look down and left by setting [member _looking_dir] to
|
|
## [constant DOWN_LEFT] and waits until [method idle] finishes.
|
|
func face_down_left() -> void:
|
|
face_direction(position + Vector2.DOWN + Vector2.LEFT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look left by setting [member _looking_dir] to [constant LEFT] and waits
|
|
## until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_left() -> Callable:
|
|
return func(): await face_left()
|
|
|
|
|
|
## Makes the character to look left by setting [member _looking_dir] to [constant LEFT] and waits
|
|
## until [method idle] finishes.
|
|
func face_left() -> void:
|
|
face_direction(position + Vector2.LEFT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character to look up and left by setting [member _looking_dir] to [constant UP_LEFT]
|
|
## and waits until [method idle] finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_up_left() -> Callable:
|
|
return func(): await face_up_left()
|
|
|
|
|
|
## Makes the character to look up and left by setting [member _looking_dir] to [constant UP_LEFT]
|
|
## and waits until [method idle] finishes.
|
|
func face_up_left() -> void:
|
|
face_direction(position + Vector2.UP + Vector2.LEFT)
|
|
await idle()
|
|
|
|
|
|
## Makes the character face in the direction of the last clicked [PopochiuClickable], which is
|
|
## stored in [member Popochiu.clicked].[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_face_clicked() -> Callable:
|
|
return func(): await face_clicked()
|
|
|
|
|
|
## Makes the character face in the direction of the last clicked [PopochiuClickable], which is
|
|
## stored in [member Popochiu.clicked].
|
|
func face_clicked() -> void:
|
|
var global_lap = PopochiuUtils.e.clicked.to_global(PopochiuUtils.e.clicked.look_at_point)
|
|
|
|
_flip_left_right(
|
|
global_lap.x < global_position.x,
|
|
global_lap.x > global_position.x
|
|
)
|
|
|
|
await face_direction(global_lap)
|
|
|
|
|
|
## Calls [method _play_talk] and emits [signal character_spoke] sending itself as parameter, and the
|
|
## [param dialog] line to show on screen. You can specify the emotion to use with [param emo]. If an
|
|
## [AudioCue] is defined for the emotion, it is played. Once the talk animation finishes, the
|
|
## characters return to its idle state.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_say(dialog: String, emo := EMPTY_STRING) -> Callable:
|
|
return func(): await say(dialog, emo)
|
|
|
|
|
|
## Calls [method _play_talk] and emits [signal character_spoke] sending itself as parameter, and the
|
|
## [param dialog] line to show on screen. You can specify the emotion to use with [param emo]. If an
|
|
## [AudioCue] is defined for the emotion, it is played. Once the talk animation finishes, the
|
|
## characters return to its idle state.
|
|
func say(dialog: String, emo := EMPTY_STRING) -> void:
|
|
if PopochiuUtils.e.cutscene_skipped:
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
if not emo.is_empty():
|
|
emotion = emo
|
|
|
|
# Call the virtual that plays the talk animation
|
|
_play_talk()
|
|
|
|
var vo_name := _get_vo_cue(emotion)
|
|
if not vo_name.is_empty() and PopochiuUtils.a.get(vo_name):
|
|
PopochiuUtils.a[vo_name].play(false, global_position)
|
|
|
|
PopochiuUtils.c.character_spoke.emit(self, dialog)
|
|
|
|
await PopochiuUtils.g.dialog_line_finished
|
|
|
|
# Stop the voice if it is still playing (feature #202)
|
|
# Fix: Check if the vo_name is valid in order to stop it
|
|
if not vo_name.is_empty() and PopochiuUtils.a[vo_name].is_playing():
|
|
PopochiuUtils.a[vo_name].stop(0.3)
|
|
|
|
emotion = EMPTY_STRING
|
|
idle()
|
|
|
|
|
|
## Calls [method _play_grab] and waits until the [signal grab_done] is emitted, then goes back to
|
|
## [method idle].[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_grab() -> Callable:
|
|
return func(): await grab()
|
|
|
|
|
|
## Calls [method _play_grab] and waits until the [signal grab_done] is emitted, then goes back to
|
|
## [method idle].
|
|
func grab() -> void:
|
|
if PopochiuUtils.e.cutscene_skipped:
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
# Call the virtual that plays the grab animation
|
|
_play_grab()
|
|
|
|
await grab_done
|
|
|
|
idle()
|
|
|
|
|
|
## Calls [method PopochiuClickable.hide_helpers].
|
|
func hide_helpers() -> void:
|
|
super()
|
|
# TODO: add visibility logic for dialog_pos gizmo
|
|
|
|
|
|
## Calls [method PopochiuClickable.show_helpers].
|
|
func show_helpers() -> void:
|
|
super()
|
|
# TODO: add visibility logic for dialog_pos gizmo
|
|
|
|
|
|
## Makes the character walk to [param pos].[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to(pos: Vector2) -> Callable:
|
|
return func(): await walk_to(pos)
|
|
|
|
|
|
## Makes the character walk to [param pos].
|
|
func walk_to(pos: Vector2) -> void:
|
|
await walk(PopochiuUtils.r.current.to_global(pos))
|
|
|
|
|
|
## Makes the character walk to the last clicked [PopochiuClickable], which is stored in
|
|
## [member Popochiu.clicked]. You can set an [param offset] relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to_clicked(offset := Vector2.ZERO) -> Callable:
|
|
return func(): await walk_to_clicked(offset)
|
|
|
|
|
|
## Makes the character walk (NON-BLOCKING) to the last clicked [PopochiuClickable], which is stored
|
|
## in [member Popochiu.clicked]. You can set an [param offset] relative to the target position.
|
|
func walk_to_clicked(offset := Vector2.ZERO) -> void:
|
|
var clicked_id: String = PopochiuUtils.e.clicked.script_name
|
|
|
|
if PopochiuUtils.e.clicked == _last_reached_clickable:
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
await _walk_to_node(PopochiuUtils.e.clicked, offset)
|
|
_last_reached_clickable = PopochiuUtils.e.clicked
|
|
|
|
# Check if the action was cancelled
|
|
if not PopochiuUtils.e.clicked or clicked_id != PopochiuUtils.e.clicked.script_name:
|
|
await PopochiuUtils.e.await_stopped
|
|
|
|
|
|
## Makes the character walk (BLOCKING the GUI) to the last clicked [PopochiuClickable], which is
|
|
## stored in [member Popochiu.clicked]. You can set an [param offset] relative to the target position.
|
|
func walk_to_clicked_blocking(offset := Vector2.ZERO) -> void:
|
|
PopochiuUtils.g.block()
|
|
|
|
await _walk_to_node(PopochiuUtils.e.clicked, offset)
|
|
|
|
PopochiuUtils.g.unblock()
|
|
|
|
|
|
## Makes the character walk (BLOCKING the GUI) to the last clicked [PopochiuClickable], which is
|
|
## stored in [member Popochiu.clicked]. You can set an [param offset] relative to the target position.
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to_clicked_blocking(offset := Vector2.ZERO) -> Callable:
|
|
return func(): await walk_to_clicked_blocking(offset)
|
|
|
|
|
|
## Makes the character walk to the [PopochiuProp] (in the current room) which
|
|
## [member PopochiuClickable.script_name] is equal to [param id]. You can set an [param offset]
|
|
## relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to_prop(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await walk_to_prop(id, offset)
|
|
|
|
|
|
## Makes the character walk to the [PopochiuProp] (in the current room) which
|
|
## [member PopochiuClickable.script_name] is equal to [param id]. You can set an [param offset]
|
|
## relative to the target position.
|
|
func walk_to_prop(id: String, offset := Vector2.ZERO) -> void:
|
|
await _walk_to_node(PopochiuUtils.r.current.get_prop(id), offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [PopochiuProp] (in the current room) which [member PopochiuClickable.script_name] is equal to
|
|
## [param id]. You can set an [param offset] relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_teleport_to_prop(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await teleport_to_prop(id, offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [PopochiuProp] (in the current room) which [member PopochiuClickable.script_name] is equal to
|
|
## [param id]. You can set an [param offset] relative to the target position.
|
|
func teleport_to_prop(id: String, offset := Vector2.ZERO) -> void:
|
|
await _teleport_to_node(PopochiuUtils.r.current.get_prop(id), offset)
|
|
|
|
|
|
## Makes the character walk to the [PopochiuHotspot] (in the current room) which
|
|
## [member PopochiuClickable.script_name] is equal to [param id]. You can set an [param offset]
|
|
## relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to_hotspot(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await walk_to_hotspot(id, offset)
|
|
|
|
|
|
## Makes the character walk to the [PopochiuHotspot] (in the current room) which
|
|
## [member PopochiuClickable.script_name] is equal to [param id]. You can set an [param offset]
|
|
## relative to the target position.
|
|
func walk_to_hotspot(id: String, offset := Vector2.ZERO) -> void:
|
|
await _walk_to_node(PopochiuUtils.r.current.get_hotspot(id), offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [PopochiuHotspot] (in the current room) which [member PopochiuClickable.script_name] is equal to
|
|
## [param id]. You can set an [param offset] relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_teleport_to_hotspot(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await teleport_to_hotspot(id, offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [PopochiuHotspot] (in the current room) which [member PopochiuClickable.script_name] is equal to
|
|
## [param id]. You can set an [param offset] relative to the target position.
|
|
func teleport_to_hotspot(id: String, offset := Vector2.ZERO) -> void:
|
|
await _teleport_to_node(PopochiuUtils.r.current.get_hotspot(id), offset)
|
|
|
|
|
|
## Makes the character walk to the [Marker2D] (in the current room) which [member Node.name] is
|
|
## equal to [param id]. You can set an [param offset] relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_walk_to_marker(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await walk_to_marker(id, offset)
|
|
|
|
|
|
## Makes the character walk to the [Marker2D] (in the current room) which [member Node.name] is
|
|
## equal to [param id]. You can set an [param offset] relative to the target position.
|
|
func walk_to_marker(id: String, offset := Vector2.ZERO) -> void:
|
|
await _walk_to_node(PopochiuUtils.r.current.get_marker(id), offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [Marker2D] (in the current room) which [member Node.name] is equal to [param id]. You can set an
|
|
## [param offset] relative to the target position.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_teleport_to_marker(id: String, offset := Vector2.ZERO) -> Callable:
|
|
return func(): await teleport_to_marker(id, offset)
|
|
|
|
|
|
## Makes the character teleport (disappear at one location and instantly appear at another) to the
|
|
## [Marker2D] (in the current room) which [member Node.name] is equal to [param id]. You can set an
|
|
## [param offset] relative to the target position.
|
|
func teleport_to_marker(id: String, offset := Vector2.ZERO) -> void:
|
|
await _teleport_to_node(PopochiuUtils.r.current.get_marker(id), offset)
|
|
|
|
|
|
## Sets [member emotion] to [param new_emotion] when in a [method Popochiu.queue].
|
|
func queue_set_emotion(new_emotion: String) -> Callable:
|
|
return func(): emotion = new_emotion
|
|
|
|
|
|
## Sets [member ignore_walkable_areas] to [param new_value] when in a [method Popochiu.queue].
|
|
func queue_ignore_walkable_areas(new_value: bool) -> Callable:
|
|
return func(): ignore_walkable_areas = new_value
|
|
|
|
|
|
## Plays the [param animation_label] animation. You can specify a fallback animation to play with
|
|
## [param animation_fallback] in case the former one doesn't exists.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_play_animation(
|
|
animation_label: String, animation_fallback := 'idle', blocking := false
|
|
) -> Callable:
|
|
return func(): await play_animation(animation_label, animation_fallback)
|
|
|
|
|
|
## Plays the [param animation_label] animation. You can specify a fallback animation to play with
|
|
## [param animation_fallback] in case the former one doesn't exists.
|
|
func play_animation(animation_label: String, animation_fallback := 'idle'):
|
|
if (animation_label != _last_requested_animation_label) or (_looking_dir != _last_requested_animation_dir):
|
|
if not has_node("AnimationPlayer"):
|
|
PopochiuUtils.print_error(
|
|
"Can't play character animation. Required AnimationPlayer not found in character %s" %
|
|
[script_name]
|
|
)
|
|
return
|
|
|
|
if animation_player.get_animation_list().is_empty():
|
|
return
|
|
|
|
# Search for a valid animation corresponding to animation_label
|
|
_current_animation = _get_valid_oriented_animation(animation_label)
|
|
# If is not present, do the same for the the fallback animation.
|
|
if _current_animation.is_empty():
|
|
_current_animation = _get_valid_oriented_animation(animation_fallback)
|
|
# In neither are available, exit and throw an error to check for the presence of the animations.
|
|
if _current_animation.is_empty(): # Again!
|
|
PopochiuUtils.print_error(
|
|
"Neither the requested nor the fallback animation could be found for character %s.\
|
|
Requested:%s - Fallback: %s" % [script_name, animation_label, animation_fallback]
|
|
)
|
|
return
|
|
# Cache the the _current_animation context to avoid re-searching for it.
|
|
_last_requested_animation_label = animation_label
|
|
_last_requested_animation_dir = _looking_dir
|
|
# Play the animation in the best available orientation
|
|
animation_player.play(_current_animation)
|
|
# If the playing is blocking, wait for the animation to finish
|
|
await animation_player.animation_finished
|
|
|
|
# Go back to idle state
|
|
_play_idle()
|
|
|
|
|
|
## Makes the animation that is currently playing to stop. Works only if it is looping and is not an
|
|
## idle animation. The animation stops when the current loop finishes.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_stop_animation():
|
|
return func(): await stop_animation()
|
|
|
|
|
|
## Makes the animation that is currently playing to stop. Works only if it is looping and is not an
|
|
## idle animation. The animation stops when the current loop finishes.
|
|
func stop_animation():
|
|
# If the animation is not looping or is an idle one, do nothing
|
|
if (
|
|
animation_player.get_animation(
|
|
animation_player.current_animation
|
|
).loop_mode == Animation.LOOP_NONE
|
|
or animation_player.current_animation == 'idle'
|
|
or animation_player.current_animation.begins_with('idle_')
|
|
):
|
|
return
|
|
|
|
# Save the loop mode, wait for the anim to be over as designed, then restore the mode
|
|
var animation: Animation = animation_player.get_animation(animation_player.current_animation)
|
|
var animation_loop_mode := animation.loop_mode
|
|
animation.loop_mode = Animation.LOOP_NONE
|
|
await animation_player.animation_finished
|
|
|
|
_play_idle()
|
|
animation.loop_mode = animation_loop_mode
|
|
|
|
|
|
## Immediately stops the animation that is currently playing by changing to the idle animation.
|
|
## [br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_halt_animation():
|
|
return func(): halt_animation()
|
|
|
|
|
|
## Immediately stops the animation that is currently playing by changing to the idle animation.
|
|
func halt_animation():
|
|
_play_idle()
|
|
|
|
|
|
## Pauses the animation that is currently playing.[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_pause_animation():
|
|
return func(): pause_animation()
|
|
|
|
|
|
## Pauses the animation that is currently playing.
|
|
func pause_animation():
|
|
animation_player.pause()
|
|
|
|
|
|
## Resumes the current animation (that was previously paused).[br][br]
|
|
## [i]This method is intended to be used inside a [method Popochiu.queue] of instructions.[/i]
|
|
func queue_resume_animation():
|
|
return func(): resume_animation()
|
|
|
|
|
|
## Resumes the current animation (that was previously paused).
|
|
func resume_animation():
|
|
animation_player.play()
|
|
|
|
|
|
## Makes the character look in the direction of [param destination]. The result is one of the values
|
|
## defined by [enum Looking].
|
|
func face_direction(destination: Vector2):
|
|
# Determine the direction the character is facing.
|
|
# Remember: Y coordinates have opposite sign in Godot.
|
|
# This means that negative angles are up movements.
|
|
# Set the direction using the _looking property.
|
|
# We cannot use the face_* functions because they
|
|
# set the state as IDLE.
|
|
|
|
# Based on the character facing direction, define a set of
|
|
# animation suffixes in reference order.
|
|
# Notice how we seek for opposite directions for left and
|
|
# right. Flipping is done in other functions. We just define
|
|
# a preference order for animations when available.
|
|
|
|
# Get the vector from the origin to the destination.
|
|
var angle = wrapf(rad_to_deg((destination - position).angle()) , 0, 360)
|
|
# The angle calculation uses 16 angles rather than 8 for greater accuracy
|
|
# in choosing the facing direction fallback animations.
|
|
var _looking_angle := int(angle / 22.5) % 16
|
|
# Selecting the animation suffixes for the current facing direction.
|
|
# Note that we add a fallback empty string to the list, in case the only
|
|
# available animation is the base one ('walk', 'talk', etc).
|
|
_animation_suffixes = _valid_animation_suffixes[_looking_angle] + [EMPTY_STRING]
|
|
# The 16 directions used for animation suffixes are simplified to 8 general directions
|
|
_looking_dir = int(angle / 45) % 8
|
|
|
|
|
|
## Returns the [Texture] of the avatar defined for the [param emo] emotion.
|
|
## Returns [code]null[/code] if no avatar is found. If there is an avatar defined for the
|
|
## [code]""[/code] emotion, that one is returned by default.
|
|
func get_avatar_for_emotion(emo := EMPTY_STRING) -> Texture:
|
|
var texture: Texture = null
|
|
|
|
while not texture and not avatars.is_empty():
|
|
for dic in avatars:
|
|
if dic.emotion.is_empty():
|
|
texture = dic.avatar
|
|
elif dic.emotion == emo:
|
|
texture = dic.avatar
|
|
break
|
|
|
|
return texture
|
|
|
|
|
|
## Returns the [code]y[/code] value of the dialog_pos [Vector2] that defines the
|
|
## position of the dialog lines said by the character when it talks.
|
|
func get_dialog_pos() -> float:
|
|
return dialog_pos.y
|
|
|
|
|
|
func update_position() -> void:
|
|
position = (
|
|
position_stored
|
|
if position_stored
|
|
else position
|
|
)
|
|
|
|
|
|
## Updates the scale depending on the properties of the scaling region where it is located.
|
|
func update_scale():
|
|
if on_scaling_region:
|
|
var polygon_range = (
|
|
on_scaling_region["polygon_bottom_y"] - on_scaling_region["polygon_top_y"]
|
|
)
|
|
var scale_range = (
|
|
on_scaling_region["scale_bottom"] - on_scaling_region["scale_top"]
|
|
)
|
|
|
|
var position_from_the_top_of_region = (
|
|
position.y - on_scaling_region["polygon_top_y"]
|
|
)
|
|
|
|
var scale_for_position = (
|
|
on_scaling_region["scale_top"] + (
|
|
scale_range / polygon_range * position_from_the_top_of_region
|
|
)
|
|
)
|
|
scale.x = [
|
|
[scale_for_position, on_scaling_region["scale_min"]].max(),
|
|
on_scaling_region["scale_max"]
|
|
].min()
|
|
scale.y = [
|
|
[scale_for_position, on_scaling_region["scale_min"]].max(),
|
|
on_scaling_region["scale_max"]
|
|
].min()
|
|
walk_speed = default_walk_speed / default_scale.x * scale_for_position
|
|
else:
|
|
scale = default_scale
|
|
walk_speed = default_walk_speed
|
|
|
|
|
|
#endregion
|
|
|
|
#region SetGet #####################################################################################
|
|
func set_voices(value: Array) -> void:
|
|
voices = value
|
|
|
|
for idx in value.size():
|
|
if not value[idx]:
|
|
var arr: Array[AudioCueSound] = []
|
|
|
|
voices[idx] = {
|
|
emotion = EMPTY_STRING,
|
|
variations = arr
|
|
}
|
|
elif not value[idx].variations.is_empty():
|
|
if value[idx].variations[-1] == null:
|
|
value[idx].variations[-1] = AudioCueSound.new()
|
|
|
|
|
|
func set_follow_player(value: bool) -> void:
|
|
follow_player = value
|
|
|
|
if not Engine.is_editor_hint():
|
|
set_process(follow_player)
|
|
|
|
|
|
func set_avatars(value: Array) -> void:
|
|
avatars = value
|
|
|
|
for idx in value.size():
|
|
if not value[idx]:
|
|
avatars[idx] = {
|
|
emotion = EMPTY_STRING,
|
|
avatar = Texture.new(),
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Private ####################################################################################
|
|
func _translate() -> void:
|
|
if Engine.is_editor_hint() or not is_inside_tree(): return
|
|
description = PopochiuUtils.e.get_text(_description_code)
|
|
|
|
|
|
func _get_vo_cue(emotion := EMPTY_STRING) -> String:
|
|
for v in voices:
|
|
if v.emotion.to_lower() == emotion.to_lower():
|
|
var cue_name := EMPTY_STRING
|
|
|
|
if not v.variations.is_empty():
|
|
if not v.has('not_played') or v.not_played.is_empty():
|
|
v['not_played'] = range(v.variations.size())
|
|
|
|
var idx: int = (v['not_played'] as Array).pop_at(
|
|
PopochiuUtils.get_random_array_idx(v['not_played'])
|
|
)
|
|
|
|
cue_name = v.variations[idx].resource_name
|
|
|
|
return cue_name
|
|
return EMPTY_STRING
|
|
|
|
|
|
func _get_valid_oriented_animation(animation_label):
|
|
# The list of prefixes is in order of preference
|
|
# Eg. walk_dl, walk_l, walk
|
|
# Scan the AnimationPlayer and return the first that matches.
|
|
for suffix in _animation_suffixes:
|
|
var animation = "%s%s" % [animation_label, suffix]
|
|
if animation_player.has_animation(animation):
|
|
return animation
|
|
|
|
return EMPTY_STRING
|
|
|
|
|
|
func _walk_to_node(node: Node2D, offset: Vector2) -> void:
|
|
if not is_instance_valid(node):
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
await walk(
|
|
node.to_global(node.walk_to_point if node is PopochiuClickable else Vector2.ZERO) + offset
|
|
)
|
|
|
|
|
|
# Instantly move to the node position
|
|
func _teleport_to_node(node: Node2D, offset: Vector2) -> void:
|
|
if not is_instance_valid(node):
|
|
await get_tree().process_frame
|
|
return
|
|
|
|
position = node.to_global(
|
|
node.walk_to_point if node is PopochiuClickable else Vector2.ZERO
|
|
) + offset
|
|
|
|
|
|
func _update_position():
|
|
PopochiuUtils.r.current.update_characters_position(self)
|
|
|
|
# Flips sprites depending on user preferences: requires two boolean conditions
|
|
# as arguments for flipping left [param left_cond] or right [param right_cond]
|
|
func _flip_left_right(left_cond: bool, right_cond: bool) -> void:
|
|
if has_node('Sprite2D'):
|
|
$Sprite2D.flip_h = false
|
|
match flips_when:
|
|
FlipsWhen.LOOKING_LEFT:
|
|
$Sprite2D.flip_h = left_cond
|
|
FlipsWhen.LOOKING_RIGHT:
|
|
$Sprite2D.flip_h = right_cond
|
|
|
|
|
|
#endregion
|