First work on dialogic, resized guild, and started implementing portraits.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
@tool
|
||||
extends "res://addons/dialogic/Modules/LayeredPortrait/layered_portrait.gd"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://cdldiv2b5kxvy
|
||||
17
addons/dialogic/Modules/LayeredPortrait/index.gd
Normal file
17
addons/dialogic/Modules/LayeredPortrait/index.gd
Normal file
@@ -0,0 +1,17 @@
|
||||
@tool
|
||||
extends DialogicIndexer
|
||||
|
||||
|
||||
func _get_portrait_scene_presets() -> Array[Dictionary]:
|
||||
return [
|
||||
{
|
||||
"path": this_folder.path_join("layered_portrait.tscn"),
|
||||
"name": "Layered Portrait",
|
||||
"description": "Base for a charcter made up of multiple sprites. Allows showing/switching/hiding the layers with the character event extra data.",
|
||||
"author":"Cake for Dialogic",
|
||||
"type": "Preset",
|
||||
"icon":"",
|
||||
"preview_image":[this_folder.path_join("layered_portrait_thumbnail.png")],
|
||||
"documentation":"https://docs.dialogic.pro/layered-portraits.html",
|
||||
},
|
||||
]
|
||||
1
addons/dialogic/Modules/LayeredPortrait/index.gd.uid
Normal file
1
addons/dialogic/Modules/LayeredPortrait/index.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://qgb3ivnhdi7l
|
||||
252
addons/dialogic/Modules/LayeredPortrait/layered_portrait.gd
Normal file
252
addons/dialogic/Modules/LayeredPortrait/layered_portrait.gd
Normal file
@@ -0,0 +1,252 @@
|
||||
@tool
|
||||
## Layered portrait scene.
|
||||
##
|
||||
## The parent class has a character and portrait variable.
|
||||
extends DialogicPortrait
|
||||
|
||||
## The term used for hiding a layer.
|
||||
const _HIDE_COMMAND := "hide"
|
||||
## The term used for showing a layer.
|
||||
const _SHOW_COMMAND := "show"
|
||||
## The term used for setting a layer to be the only visible layer.
|
||||
const _SET_COMMAND := "set"
|
||||
|
||||
## A collection of all possible layer commands.
|
||||
const _OPERATORS = [_HIDE_COMMAND, _SHOW_COMMAND, _SET_COMMAND]
|
||||
|
||||
static var _OPERATORS_EXPRESSION := "|".join(_OPERATORS)
|
||||
static var _REGEX_STRING := "(" + _OPERATORS_EXPRESSION + ") (\\S+)"
|
||||
static var _REGEX := RegEx.create_from_string(_REGEX_STRING)
|
||||
|
||||
var _initialized := false
|
||||
|
||||
var _is_coverage_rect_cached := false
|
||||
var _cached_coverage_rect := Rect2(0, 0, 0, 0)
|
||||
|
||||
|
||||
@export_group("Private")
|
||||
## Attempts to fix the offset based on the first child node
|
||||
@export var fix_offset := true
|
||||
|
||||
## Overriding [class DialogicPortrait]'s method.
|
||||
##
|
||||
## Load anything related to the given character and portrait
|
||||
func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void:
|
||||
if not _initialized:
|
||||
_apply_layer_adjustments()
|
||||
_initialized = true
|
||||
|
||||
apply_character_and_portrait(passed_character, passed_portrait)
|
||||
|
||||
|
||||
## Modifies all layers to fit the portrait preview and appear correctly in
|
||||
## portrait containers.
|
||||
##
|
||||
## This method is not changing the scene itself and is intended for the
|
||||
## Dialogic editor preview and in-game rendering only.
|
||||
func _apply_layer_adjustments() -> void:
|
||||
var coverage := _find_largest_coverage_rect()
|
||||
var offset_fix := Vector2()
|
||||
if fix_offset and get_child_count():
|
||||
offset_fix = -get_child(0).position
|
||||
if "centered" in get_child(0) and get_child(0).centered:
|
||||
offset_fix += get_child(0).get_rect().size/2.0
|
||||
|
||||
for node: Node in get_children():
|
||||
var node_position: Vector2 = node.position
|
||||
node_position += offset_fix
|
||||
node.position = _reposition_with_rect(coverage, node_position)
|
||||
|
||||
|
||||
## Returns a position based on [param rect]'s size where Dialogic expects the
|
||||
## scene part to be positioned at. [br]
|
||||
## If the node has an offset or extra position, pass it as [param node_offset].
|
||||
func _reposition_with_rect(rect: Rect2, node_offset := Vector2(0.0, 0.0)) -> Vector2:
|
||||
return rect.size * Vector2(-0.5, -1.0) + node_offset
|
||||
|
||||
|
||||
## Iterates over all children in [param start_node] and its children, looking
|
||||
## for [class Sprite2D] nodes with a texture (not `null`).
|
||||
## All found sprites are returned in an array, eventually returning all
|
||||
## sprites in the scene.
|
||||
func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]:
|
||||
var sprites: Array[Sprite2D] = []
|
||||
|
||||
# Iterate through the children of the current node
|
||||
for child: Node in start_node.get_children():
|
||||
|
||||
if child is Sprite2D:
|
||||
var sprite := child as Sprite2D
|
||||
|
||||
if sprite.texture:
|
||||
sprites.append(sprite)
|
||||
|
||||
|
||||
var sub := _find_sprites_recursively(child)
|
||||
sprites.append_array(sub)
|
||||
|
||||
return sprites
|
||||
|
||||
|
||||
## A command will apply an effect to the layered portrait.
|
||||
class LayerCommand:
|
||||
# The different types of effects.
|
||||
enum CommandType {
|
||||
## Additively Show a specific layer.
|
||||
SHOW_LAYER,
|
||||
## Subtractively hide a specific layer.
|
||||
HIDE_LAYER,
|
||||
## Exclusively show a specific layer, hiding all other sibling layers.
|
||||
## A sibling layer is a layer sharing the same parent node.
|
||||
SET_LAYER,
|
||||
}
|
||||
|
||||
var _path: String
|
||||
var _type: CommandType
|
||||
|
||||
## Executes the effect of the layer based on the [enum CommandType].
|
||||
func _execute(root: Node) -> void:
|
||||
var target_node := root.get_node(_path)
|
||||
|
||||
if target_node == null:
|
||||
printerr("Layered Portrait had no node matching the node path: '", _path, "'.")
|
||||
return
|
||||
|
||||
if not target_node is Node2D and not target_node is Sprite2D:
|
||||
printerr("Layered Portrait target path '", _path, "', is not a Sprite2D or Node2D type.")
|
||||
return
|
||||
|
||||
match _type:
|
||||
CommandType.SHOW_LAYER:
|
||||
target_node.show()
|
||||
|
||||
CommandType.HIDE_LAYER:
|
||||
target_node.hide()
|
||||
|
||||
CommandType.SET_LAYER:
|
||||
var target_parent := target_node.get_parent()
|
||||
|
||||
for child: Node in target_parent.get_children():
|
||||
|
||||
if child is Sprite2D:
|
||||
var sprite_child := child as Sprite2D
|
||||
sprite_child.hide()
|
||||
|
||||
target_node.show()
|
||||
|
||||
|
||||
## Turns the input into a single [class LayerCommand] object.
|
||||
## Returns `null` if the input cannot be parsed into a [class LayerCommand].
|
||||
func _parse_layer_command(input: String) -> LayerCommand:
|
||||
var regex_match: RegExMatch = _REGEX.search(input)
|
||||
|
||||
if regex_match == null:
|
||||
print("[Dialogic] Layered Portrait had an invalid command: ", input)
|
||||
return null
|
||||
|
||||
var _path: String = regex_match.get_string(2)
|
||||
var operator: String = regex_match.get_string(1)
|
||||
|
||||
var command := LayerCommand.new()
|
||||
|
||||
match operator:
|
||||
_SET_COMMAND:
|
||||
command._type = LayerCommand.CommandType.SET_LAYER
|
||||
|
||||
_SHOW_COMMAND:
|
||||
command._type = LayerCommand.CommandType.SHOW_LAYER
|
||||
|
||||
_HIDE_COMMAND:
|
||||
command._type = LayerCommand.CommandType.HIDE_LAYER
|
||||
|
||||
_SET_COMMAND:
|
||||
command._type = LayerCommand.CommandType.SET_LAYER
|
||||
|
||||
## We clean escape symbols and trim the spaces.
|
||||
command._path = _path.replace("\\", "").strip_edges()
|
||||
|
||||
return command
|
||||
|
||||
|
||||
## Parses [param input] into an array of [class LayerCommand] objects.
|
||||
func _parse_input_to_layer_commands(input: String) -> Array[LayerCommand]:
|
||||
var commands: Array[LayerCommand] = []
|
||||
var command_parts := input.split(",")
|
||||
|
||||
for command_part: String in command_parts:
|
||||
|
||||
if command_part.is_empty():
|
||||
continue
|
||||
|
||||
var _command := _parse_layer_command(command_part.strip_edges())
|
||||
|
||||
if not _command == null:
|
||||
commands.append(_command)
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
|
||||
## Overriding [class DialogicPortrait]'s method.
|
||||
##
|
||||
## The extra data will be turned into layer commands and then be executed.
|
||||
func _set_extra_data(data: String) -> void:
|
||||
var commands := _parse_input_to_layer_commands(data)
|
||||
|
||||
for _command: LayerCommand in commands:
|
||||
_command._execute(self)
|
||||
|
||||
|
||||
## Overriding [class DialogicPortrait]'s method.
|
||||
##
|
||||
## Handling all layers horizontal flip state.
|
||||
func _set_mirror(is_mirrored: bool) -> void:
|
||||
for child: Node in get_children():
|
||||
|
||||
if is_mirrored:
|
||||
child.position.x = child.position.x * -1
|
||||
child.scale.x = -child.scale.x
|
||||
|
||||
|
||||
## Scans all nodes in this scene and finds the largest rectangle that
|
||||
## covers encloses every sprite.
|
||||
func _find_largest_coverage_rect() -> Rect2:
|
||||
if _is_coverage_rect_cached:
|
||||
return _cached_coverage_rect
|
||||
|
||||
var coverage_rect := Rect2(0, 0, 0, 0)
|
||||
|
||||
for sprite: Sprite2D in _find_sprites_recursively(self):
|
||||
var sprite_size := sprite.get_rect().size
|
||||
var sprite_position: Vector2 = sprite.global_position-self.global_position
|
||||
|
||||
if sprite.centered:
|
||||
sprite_position -= sprite_size/2
|
||||
|
||||
var sprite_width := sprite_size.x * sprite.scale.x
|
||||
var sprite_height := sprite_size.y * sprite.scale.y
|
||||
|
||||
var texture_rect := Rect2(
|
||||
sprite_position.x,
|
||||
sprite_position.y,
|
||||
sprite_width,
|
||||
sprite_height
|
||||
)
|
||||
coverage_rect = coverage_rect.merge(texture_rect)
|
||||
|
||||
coverage_rect.position = _reposition_with_rect(coverage_rect)
|
||||
|
||||
_is_coverage_rect_cached = true
|
||||
_cached_coverage_rect = coverage_rect
|
||||
|
||||
return coverage_rect
|
||||
|
||||
|
||||
## Overriding [class DialogicPortrait]'s method.
|
||||
##
|
||||
## Called by Dialogic when the portrait is needed to be shown.
|
||||
## For instance, in the Dialogic editor or in-game.
|
||||
func _get_covered_rect() -> Rect2:
|
||||
var needed_rect := _find_largest_coverage_rect()
|
||||
|
||||
return needed_rect
|
||||
@@ -0,0 +1 @@
|
||||
uid://ces368maqr3gy
|
||||
@@ -0,0 +1,14 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://jac4eurttev1"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogic/Modules/LayeredPortrait/custom_layered_portrait.gd" id="1_uubi5"]
|
||||
|
||||
[node name="LayeredPortrait" type="CanvasGroup"]
|
||||
script = ExtResource("1_uubi5")
|
||||
|
||||
[node name="Layer1" type="Sprite2D" parent="."]
|
||||
|
||||
[node name="Group1" type="Node2D" parent="."]
|
||||
|
||||
[node name="Layer1" type="Sprite2D" parent="Group1"]
|
||||
|
||||
[node name="Layer2" type="Sprite2D" parent="Group1"]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://byo6bboroqw0q"
|
||||
path="res://.godot/imported/layered_portrait_thumbnail.png-107af8817a839c595823f628d8fb18dd.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogic/Modules/LayeredPortrait/layered_portrait_thumbnail.png"
|
||||
dest_files=["res://.godot/imported/layered_portrait_thumbnail.png-107af8817a839c595823f628d8fb18dd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
Reference in New Issue
Block a user