You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
199 lines
7.5 KiB
199 lines
7.5 KiB
6 months ago
|
@tool
|
||
|
class_name DialogicNode_PortraitContainer
|
||
|
extends Control
|
||
|
|
||
|
## Node that defines a position for dialogic portraits and how to display portrait at that position.
|
||
|
|
||
|
enum PositionModes {
|
||
|
POSITION, ## This container has an index and can be joined/moved to with the Character Event
|
||
|
SPEAKER, ## This container has no index and is joined/left automatically based on the speaker.
|
||
|
}
|
||
|
|
||
|
@export var mode := PositionModes.POSITION
|
||
|
|
||
|
@export_subgroup('Mode: Position')
|
||
|
## The position this node corresponds to.
|
||
|
@export var position_index := 0
|
||
|
|
||
|
|
||
|
@export_subgroup('Mode: Speaker')
|
||
|
## Can be used to use a different portrait.
|
||
|
## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
|
||
|
@export var portrait_prefix := ''
|
||
|
|
||
|
@export_subgroup('Portrait Placement')
|
||
|
enum SizeModes {KEEP, FIT_STRETCH, FIT_IGNORE_SCALE, FIT_SCALE_HEIGHT}
|
||
|
## Defines how to affect the scale of the portrait
|
||
|
@export var size_mode : SizeModes = SizeModes.FIT_SCALE_HEIGHT :
|
||
|
set(mode):
|
||
|
size_mode = mode
|
||
|
_update_debug_portrait_size_position()
|
||
|
|
||
|
## If true, portraits will be mirrored in this position.
|
||
|
@export var mirrored := false :
|
||
|
set(mirror):
|
||
|
mirrored = mirror
|
||
|
_update_debug_portrait_scene()
|
||
|
|
||
|
|
||
|
@export_group('Origin', 'origin')
|
||
|
enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
|
||
|
## The portrait will be placed relative to this point in the container.
|
||
|
@export var origin_anchor : OriginAnchors = OriginAnchors.BOTTOM_CENTER :
|
||
|
set(anchor):
|
||
|
origin_anchor = anchor
|
||
|
_update_debug_origin()
|
||
|
|
||
|
## An offset to apply to the origin. Rarely useful.
|
||
|
@export var origin_offset := Vector2() :
|
||
|
set(offset):
|
||
|
origin_offset = offset
|
||
|
_update_debug_origin()
|
||
|
|
||
|
|
||
|
@export_group('Debug', 'debug')
|
||
|
## A character that will be displayed in the editor, useful for getting the right size.
|
||
|
@export var debug_character : DialogicCharacter = null:
|
||
|
set(character):
|
||
|
debug_character = character
|
||
|
_update_debug_portrait_scene()
|
||
|
@export var debug_character_portrait :String = "":
|
||
|
set(portrait):
|
||
|
debug_character_portrait = portrait
|
||
|
_update_debug_portrait_scene()
|
||
|
|
||
|
var debug_character_holder_node :Node2D = null
|
||
|
var debug_character_scene_node : Node = null
|
||
|
var debug_origin : Sprite2D = null
|
||
|
var default_portrait_scene :String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
|
||
|
# Used if no debug character is specified
|
||
|
var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))
|
||
|
|
||
|
|
||
|
func _ready():
|
||
|
match mode:
|
||
|
PositionModes.POSITION:
|
||
|
add_to_group('dialogic_portrait_con_position')
|
||
|
PositionModes.SPEAKER:
|
||
|
add_to_group('dialogic_portrait_con_speaker')
|
||
|
|
||
|
if Engine.is_editor_hint():
|
||
|
resized.connect(_update_debug_origin)
|
||
|
|
||
|
if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
|
||
|
default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
|
||
|
|
||
|
debug_origin = Sprite2D.new()
|
||
|
add_child(debug_origin)
|
||
|
debug_origin.texture = get_theme_icon("EditorPosition", "EditorIcons")
|
||
|
|
||
|
_update_debug_origin()
|
||
|
_update_debug_portrait_scene()
|
||
|
else:
|
||
|
resized.connect(update_portrait_transforms)
|
||
|
|
||
|
|
||
|
################################################################################
|
||
|
## MAIN METHODS
|
||
|
################################################################################
|
||
|
|
||
|
func update_portrait_transforms():
|
||
|
for child in get_children():
|
||
|
DialogicUtil.autoload().Portraits._update_portrait_transform(child)
|
||
|
|
||
|
## Returns a Rect2 with the position as the position and the scale as the size.
|
||
|
func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
|
||
|
var transform := Rect2()
|
||
|
transform.position = _get_origin_position()
|
||
|
|
||
|
# Mode that ignores the containers size
|
||
|
if size_mode == SizeModes.KEEP:
|
||
|
transform.size = Vector2(1,1)*character_scale
|
||
|
|
||
|
# Mode that makes sure neither height nor width go out of container
|
||
|
elif size_mode == SizeModes.FIT_IGNORE_SCALE:
|
||
|
if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
|
||
|
transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
|
||
|
else:
|
||
|
transform.size = Vector2(1,1) * size.y/portrait_rect.size.y
|
||
|
|
||
|
# Mode that stretches the portrait to fill the whole container
|
||
|
elif size_mode == SizeModes.FIT_STRETCH:
|
||
|
transform.size = size/portrait_rect.size
|
||
|
|
||
|
# Mode that size the character so 100% size fills the height
|
||
|
elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
|
||
|
transform.size = Vector2(1,1) * size.y/portrait_rect.size.y*character_scale
|
||
|
|
||
|
return transform
|
||
|
|
||
|
|
||
|
## Returns the current origin position
|
||
|
func _get_origin_position() -> Vector2:
|
||
|
return size*Vector2(origin_anchor%3/2.0, floor(origin_anchor/3.0)/2.0) + origin_offset
|
||
|
|
||
|
################################################################################
|
||
|
## DEBUG METHODS
|
||
|
################################################################################
|
||
|
|
||
|
## Loads the debug_character with the debug_character_portrait
|
||
|
## Creates a holder node and applies mirror
|
||
|
func _update_debug_portrait_scene() -> void:
|
||
|
if !Engine.is_editor_hint():
|
||
|
return
|
||
|
if is_instance_valid(debug_character_holder_node):
|
||
|
for child in get_children():
|
||
|
if child != debug_origin:
|
||
|
child.free()
|
||
|
|
||
|
var character := _get_debug_character()
|
||
|
if not character is DialogicCharacter or character.portraits.is_empty():
|
||
|
return
|
||
|
|
||
|
var debug_portrait := debug_character_portrait
|
||
|
if debug_portrait.is_empty(): debug_portrait = character.default_portrait
|
||
|
if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
|
||
|
if portrait_prefix+debug_portrait in character.portraits:
|
||
|
debug_portrait = portrait_prefix+debug_portrait
|
||
|
var portrait_info :Dictionary = character.get_portrait_info(debug_portrait)
|
||
|
var portrait_scene_path :String = portrait_info.get('scene', default_portrait_scene)
|
||
|
if portrait_scene_path.is_empty(): portrait_scene_path = default_portrait_scene
|
||
|
debug_character_scene_node = load(portrait_scene_path).instantiate()
|
||
|
if !is_instance_valid(debug_character_scene_node):
|
||
|
return
|
||
|
debug_character_scene_node._update_portrait(character, debug_portrait)
|
||
|
if !is_instance_valid(debug_character_holder_node):
|
||
|
debug_character_holder_node = Node2D.new()
|
||
|
add_child(debug_character_holder_node)
|
||
|
debug_character_holder_node.add_child(debug_character_scene_node)
|
||
|
move_child(debug_character_holder_node, 0)
|
||
|
debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))
|
||
|
_update_debug_portrait_size_position()
|
||
|
|
||
|
|
||
|
## Set's the size and position of the holder and scene node
|
||
|
## according to the size_mode
|
||
|
func _update_debug_portrait_size_position() -> void:
|
||
|
if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
|
||
|
return
|
||
|
var character := _get_debug_character()
|
||
|
var portrait_info := character.get_portrait_info(debug_character_portrait)
|
||
|
var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
|
||
|
debug_character_holder_node.position = transform.position
|
||
|
debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset
|
||
|
|
||
|
debug_character_holder_node.scale = transform.size
|
||
|
|
||
|
## Updates the debug origins position. Also calls _update_debug_portrait_size_position()
|
||
|
func _update_debug_origin() -> void:
|
||
|
if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
|
||
|
return
|
||
|
debug_origin.position = _get_origin_position()
|
||
|
_update_debug_portrait_size_position()
|
||
|
|
||
|
|
||
|
|
||
|
## Returns the debug character or the default debug character
|
||
|
func _get_debug_character() -> DialogicCharacter:
|
||
|
return debug_character if debug_character != null else default_debug_character
|