First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
258
addons/dialogic/Modules/Character/node_portrait_container.gd
Normal file
258
addons/dialogic/Modules/Character/node_portrait_container.gd
Normal file
@@ -0,0 +1,258 @@
|
||||
@tool
|
||||
class_name DialogicNode_PortraitContainer
|
||||
extends Control
|
||||
|
||||
## Node that defines a position for dialogic portraits and how to display portraits at that position.
|
||||
|
||||
enum PositionModes {
|
||||
POSITION, ## This container can be joined/moved to with the Character Event
|
||||
SPEAKER, ## This container 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 container_ids: PackedStringArray = ["1"]
|
||||
|
||||
|
||||
@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, ## The height and width of the container have no effect, only the origin.
|
||||
FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
|
||||
FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
|
||||
FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
|
||||
}
|
||||
## 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_transform()
|
||||
|
||||
## 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()
|
||||
|
||||
enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
|
||||
## Usually you want to rotate or scale around the portrait origin.
|
||||
## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
|
||||
@export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
|
||||
## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
|
||||
@export var pivot_value := Vector2()
|
||||
|
||||
@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 := "":
|
||||
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"))
|
||||
|
||||
var ignore_resize := false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
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 = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")
|
||||
|
||||
_update_debug_origin()
|
||||
_update_debug_portrait_scene()
|
||||
else:
|
||||
resized.connect(update_portrait_transforms)
|
||||
|
||||
|
||||
################################################################################
|
||||
## MAIN METHODS
|
||||
################################################################################
|
||||
|
||||
func update_portrait_transforms() -> void:
|
||||
if ignore_resize:
|
||||
return
|
||||
|
||||
match pivot_mode:
|
||||
PivotModes.AT_ORIGIN:
|
||||
pivot_offset = _get_origin_position()
|
||||
PivotModes.PERCENTAGE:
|
||||
pivot_offset = size*pivot_value
|
||||
PivotModes.PIXELS:
|
||||
pivot_offset = pivot_value
|
||||
|
||||
for child in get_children():
|
||||
DialogicUtil.autoload().Portraits._update_character_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(rect_size = null) -> Vector2:
|
||||
if rect_size == null:
|
||||
rect_size = size
|
||||
return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset
|
||||
|
||||
|
||||
func is_container(id:Variant) -> bool:
|
||||
return str(id) in container_ids
|
||||
|
||||
#region DEBUG METHODS
|
||||
################################################################################
|
||||
### USE THIS TO DEBUG THE POSITIONS
|
||||
#func _draw():
|
||||
#draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
|
||||
#draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
|
||||
#
|
||||
#func _process(delta:float) -> void:
|
||||
#queue_redraw()
|
||||
|
||||
|
||||
## 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()
|
||||
|
||||
# Get character
|
||||
var character := _get_debug_character()
|
||||
if not character is DialogicCharacter or character.portraits.is_empty():
|
||||
return
|
||||
|
||||
# Determine portrait
|
||||
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
|
||||
if not debug_portrait in character.portraits:
|
||||
debug_portrait = character.default_portrait
|
||||
|
||||
var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)
|
||||
|
||||
# Determine scene
|
||||
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
|
||||
|
||||
# Load portrait
|
||||
DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
|
||||
debug_character_scene_node._update_portrait(character, debug_portrait)
|
||||
|
||||
# Add character node
|
||||
if !is_instance_valid(debug_character_holder_node):
|
||||
debug_character_holder_node = Node2D.new()
|
||||
add_child(debug_character_holder_node)
|
||||
|
||||
# Add portrait 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_transform()
|
||||
|
||||
|
||||
## Set's the size and position of the holder and scene node
|
||||
## according to the size_mode
|
||||
func _update_debug_portrait_transform() -> 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_transform()
|
||||
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_transform()
|
||||
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
#endregion
|
||||
Reference in New Issue
Block a user