First commit 🎉

This commit is contained in:
Tony Bark 2025-07-17 01:49:18 -04:00
commit 43ea213f9b
728 changed files with 37080 additions and 0 deletions

21
addons/popochiu/LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Mateo Robayo Rogríguez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,332 @@
@tool
extends HBoxContainer
## Used to show new buttons in the EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU (the top bar in the
## 2D editor) to select specific nodes in PopochiuClickable objects.
var _active_popochiu_object: Node = null
var _shown_helpers := []
@onready var btn_baseline: Button = %BtnBaseline
@onready var btn_walk_to_point: Button = %BtnWalkToPoint
@onready var btn_look_at_point: Button = %BtnLookAtPoint
@onready var btn_dialog_pos: Button = %BtnDialogPos
@onready var btn_interaction_polygon: Button = %BtnInteractionPolygon
#region Godot ######################################################################################
func _ready() -> void:
# Gizmos are always visible at editor load, so we'll set the buttons down
# to sync the status (hardcoded, not very good but enough for now)
_reset_buttons_state()
# Connect to child signals
btn_baseline.pressed.connect(_toggle_baseline_visibility)
btn_walk_to_point.pressed.connect(_toggle_walk_to_point_visibility)
btn_look_at_point.pressed.connect(_toggle_look_at_point_visibility)
btn_dialog_pos.pressed.connect(_toggle_dialog_pos_visibility)
btn_interaction_polygon.pressed.connect(_select_interaction_polygon)
# Connect to singleton signals
EditorInterface.get_selection().selection_changed.connect(_on_selection_changed)
EditorInterface.get_editor_settings().settings_changed.connect(_on_gizmo_settings_changed)
_set_toolbar_buttons_color()
hide()
#endregion
#region Private ####################################################################################
func _toggle_walk_to_point_visibility() -> void:
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
PopochiuGizmoClickablePlugin.WALK_TO_POINT,
btn_walk_to_point.button_pressed
)
func _toggle_look_at_point_visibility() -> void:
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
PopochiuGizmoClickablePlugin.LOOK_AT_POINT,
btn_look_at_point.button_pressed
)
func _toggle_baseline_visibility() -> void:
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
PopochiuGizmoClickablePlugin.BASELINE,
btn_baseline.button_pressed
)
func _toggle_dialog_pos_visibility() -> void:
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.emit(
PopochiuGizmoClickablePlugin.DIALOG_POS,
btn_dialog_pos.button_pressed
)
func _select_interaction_polygon() -> void:
# Since we are going to select the interaction polygon node
# inside the node, let's hide the gizmos buttons
btn_walk_to_point.hide()
btn_baseline.hide()
# If we are editing the polygon, go back and select the parent node
# then stop execution.
var selected_node := EditorInterface.get_selection().get_selected_nodes()[0]
if PopochiuEditorHelper.is_popochiu_obj_polygon(
selected_node
):
EditorInterface.get_selection().add_node(selected_node.get_parent())
_on_selection_changed()
return
# If we are editing a popochiu object holding a polygon, let's move on.
# This variable will hold the reference to the polygon we need to edit.
var obj_polygon: Node2D = null
# Let's find the node holding the polygon
# Since different Popochiu Objects have different polygons (NavigationRegion2D
# for Walkable Areas, InteractionPolygon2D for props, etc...) we tagged them
# by a special metadata
obj_polygon = PopochiuEditorHelper.get_first_child_by_group(
_active_popochiu_object,
PopochiuEditorHelper.POPOCHIU_OBJECT_POLYGON_GROUP
)
if obj_polygon == null:
return
EditorInterface.get_selection().clear()
EditorInterface.get_selection().add_node(obj_polygon)
obj_polygon.show()
func _on_gizmo_settings_changed() -> void:
# Pretty self explanatory
_set_walkable_areas_visibility()
_set_toolbar_buttons_color()
func _on_selection_changed() -> void:
# Always reset the walkable areas visibility depending on the user preferences
# Doing this immediately so, if this function exits early, the visibility is conditioned
# by the editor settings (partially fixes #325).
_set_walkable_areas_visibility()
# Make sure this function works only if the user is editing a
# supported scene
if not PopochiuEditorHelper.is_popochiu_object(
EditorInterface.get_edited_scene_root()
):
hide()
return
# If we have no selection in the tree (the user clicked on an
# empty area or pressed ESC), we hide the toolbar.
if EditorInterface.get_selection().get_selected_nodes().is_empty():
if _active_popochiu_object != null:
# TODO: this is not a helper function, because we want to get
# rid of this ASAP. The same logic is also in the function
# _set_polygons_visibility() in the base Popochiu object
# factory, and should be removed as well.
for node in _active_popochiu_object.get_children():
if PopochiuEditorHelper.is_popochiu_obj_polygon(node):
node.hide()
# This "if" solves "!p_node->is_inside_tree()" internal Godot error
# The line inside is the logic we need to make this block work
if EditorInterface.get_edited_scene_root() == _active_popochiu_object:
EditorInterface.get_selection().add_node.call_deferred(_active_popochiu_object)
# Reset the clickable reference and hide the toolbar
# (restart from a blank state)
_active_popochiu_object = null
hide()
# NOTE: Here we used to pop all the buttons up, by invoking _reset_buttons_state() but
# this is undesirable, since it overrides the user's visibility choices for the session.
# Leaving this comment here for future reference.
# Reset the walkable areas visibility depending on the user preferences
# Doing here because clicking on an empty area would hide the walkable areas
# ignoring the editor settings (fixes #325)
_set_walkable_areas_visibility()
return
# We identify which PopochiuClickable we are working on in the editor.
# Case 1:
# There is only one selected node in the editor. It can be anything the user
# clicked on, or the polygon selected by clicking the toolbar button.
# (The user can never select the polygon directly because the node is not visible
# in the scene tree)
if EditorInterface.get_selection().get_selected_nodes().size() == 1:
var selected_node = EditorInterface.get_selection().get_selected_nodes()[0]
if PopochiuEditorHelper.is_popochiu_obj_polygon(selected_node):
_active_popochiu_object = selected_node.get_parent()
elif PopochiuEditorHelper.is_popochiu_room_object(selected_node):
var polygon = null
if is_instance_valid(_active_popochiu_object):
polygon = PopochiuEditorHelper.get_first_child_by_group(
_active_popochiu_object,
PopochiuEditorHelper.POPOCHIU_OBJECT_POLYGON_GROUP
)
if (polygon != null):
polygon.hide()
btn_interaction_polygon.set_pressed_no_signal(false)
_active_popochiu_object = selected_node
else:
_active_popochiu_object = null
# Case 2:
# We have more than one node selected. This can happen because the user selected
# more than one node explicitly (holding shift, or ctrl), or because the user selected
# one node in the scene while editing the polygon.
# In this case, since the polygon was selected programmatically and it's not in the scene
# tree, Godot will NOT remove it from selection and we need to do it by hand.
elif EditorInterface.get_selection().get_selected_nodes().size() > 1:
for node in EditorInterface.get_selection().get_selected_nodes():
if PopochiuEditorHelper.is_popochiu_obj_polygon(node):
node.hide()
EditorInterface.get_selection().remove_node.call_deferred(node)
btn_interaction_polygon.set_pressed_no_signal(false)
# Reset the walkable areas visibility depending on the user preferences
# Doing this also at the end because the state can be reset by one of the steps
# above.
_set_walkable_areas_visibility()
# Always reset the button visibility depending on the state of the internal variables
_set_buttons_visibility()
## Handles the editor config that allows the WAs polygons to be always visible,
## not only during editing.
func _set_walkable_areas_visibility() -> void:
for child in PopochiuEditorHelper.get_all_children(
EditorInterface.get_edited_scene_root().find_child("WalkableAreas")
):
# Not a polygon? Skip
if not PopochiuEditorHelper.is_popochiu_obj_polygon(child):
continue
# Should we show all the polygons? Show and go to the next one
if PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_ALWAYS_SHOW_WA
):
child.show()
continue
# If we are editing the polygon, make sure it stays visible!
if child in EditorInterface.get_selection().get_selected_nodes():
child.show()
continue
# OK, we know we must hide this polygon now!
child.hide()
## Sets all the buttons color so that they are the same as the gizmos
## or make them theme-standard if the use so prefer (see editor settings)
func _set_toolbar_buttons_color() -> void:
if not PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_COLOR_TOOLBAR_BUTTONS):
# Reset button colors
_reset_toolbar_button_color(btn_baseline)
_reset_toolbar_button_color(btn_walk_to_point)
_reset_toolbar_button_color(btn_look_at_point)
_reset_toolbar_button_color(btn_dialog_pos)
_reset_toolbar_button_color(btn_interaction_polygon)
# Done
return
_set_toolbar_button_color(
btn_baseline,
PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_BASELINE_COLOR)
)
_set_toolbar_button_color(
btn_walk_to_point,
PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR)
)
_set_toolbar_button_color(
btn_look_at_point,
PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR)
)
_set_toolbar_button_color(
btn_dialog_pos,
PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR)
)
_set_toolbar_button_color(
btn_interaction_polygon,
Color.RED # no config for this at the moment
)
## Internal helper to reduce code duplication
func _set_toolbar_button_color(btn, color) -> void:
btn.add_theme_color_override("icon_normal_color", color)
btn.add_theme_color_override("icon_hover_color", color.lightened(1.0))
btn.add_theme_color_override("icon_focused_color", color.lightened(1.0))
btn.add_theme_color_override("icon_pressed_color", color.darkened(0.2))
btn.add_theme_color_override("icon_hover_pressed_color", color.lightened(1.0))
## Internal helper to reduce code duplication
func _reset_toolbar_button_color(btn) -> void:
btn.remove_theme_color_override("icon_normal_color")
btn.remove_theme_color_override("icon_hover_color")
btn.remove_theme_color_override("icon_focused_color")
btn.remove_theme_color_override("icon_pressed_color")
btn.remove_theme_color_override("icon_hover_pressed_color")
func _set_buttons_visibility() -> void:
# Let's assume the buttons are all hidden...
hide()
btn_baseline.hide()
btn_walk_to_point.hide()
btn_look_at_point.hide()
btn_dialog_pos.hide()
btn_interaction_polygon.hide()
# If we are not editing a Popochiu object, nothing to do
if not PopochiuEditorHelper.is_popochiu_room_object(_active_popochiu_object):
return
# Now we know we have to show the toolbar
show()
# Every Popochiu Object always shows the polygon editing button when edited
# unless we are in a room scene and selected a character
if not (
PopochiuEditorHelper.is_character(_active_popochiu_object)
and PopochiuEditorHelper.is_editing_room()
):
btn_interaction_polygon.show()
# If the selected node in the editor is actually a popochiu object polygon
# We don't have to show the other buttons, only the polygon editing toggle
if PopochiuEditorHelper.is_popochiu_obj_polygon(
EditorInterface.get_selection().get_selected_nodes()[0]
):
return
# If we are in a room scene, we may have selected a room object of sort, so check
# for the various types and hide the ones we don't need
if PopochiuEditorHelper.is_room(EditorInterface.get_edited_scene_root()):
# If we are editing a clickable object, let's show gizmos buttons too
if _active_popochiu_object is PopochiuClickable:
btn_baseline.show()
btn_walk_to_point.show()
btn_look_at_point.show()
# If we are in a Character scene, show polygon and dialogpos gizmo button
elif PopochiuEditorHelper.is_character(EditorInterface.get_edited_scene_root()):
btn_dialog_pos.show()
# Make all buttons pop-up
func _reset_buttons_state() -> void:
btn_baseline.set_pressed_no_signal(true)
btn_walk_to_point.set_pressed_no_signal(true)
btn_look_at_point.set_pressed_no_signal(true)
btn_dialog_pos.set_pressed_no_signal(true)

View file

@ -0,0 +1 @@
uid://15ys464umb3h

View file

@ -0,0 +1,65 @@
[gd_scene load_steps=7 format=3 uid="uid://wd748u1vdybq"]
[ext_resource type="Script" path="res://addons/popochiu/editor/canvas_editor_menu/popochiu_canvas_editor_menu.gd" id="1_vs7c6"]
[ext_resource type="Texture2D" uid="uid://b3sku5v1n23ni" path="res://addons/popochiu/icons/btn_baseline.svg" id="2_w3cau"]
[ext_resource type="Texture2D" uid="uid://dmt2epjmlpv56" path="res://addons/popochiu/icons/btn_walk_to_point.svg" id="3_ifql1"]
[ext_resource type="Texture2D" uid="uid://skjlvpct7ah7" path="res://addons/popochiu/icons/btn_look_at_point.svg" id="4_bge33"]
[ext_resource type="Texture2D" uid="uid://cekffh7bsuanp" path="res://addons/popochiu/icons/btn_dialog_position.svg" id="5_803mj"]
[ext_resource type="Texture2D" uid="uid://cyun4rylrtrvm" path="res://addons/popochiu/icons/btn_interaction_polygon.svg" id="6_6oxl8"]
[node name="PopochiuCanvasEditorMenu" type="HBoxContainer"]
visible = false
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_vs7c6")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Popochiu "
vertical_alignment = 1
[node name="BtnBaseline" type="Button" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 2
tooltip_text = "Baseline"
theme_type_variation = &"FlatButton"
toggle_mode = true
button_pressed = true
icon = ExtResource("2_w3cau")
[node name="BtnWalkToPoint" type="Button" parent="."]
unique_name_in_owner = true
visible = false
layout_mode = 2
tooltip_text = "Walk-To Point"
theme_type_variation = &"FlatButton"
toggle_mode = true
button_pressed = true
icon = ExtResource("3_ifql1")
[node name="BtnLookAtPoint" type="Button" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Look-At Point"
theme_type_variation = &"FlatButton"
toggle_mode = true
button_pressed = true
icon = ExtResource("4_bge33")
[node name="BtnDialogPos" type="Button" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Dialog Position"
theme_type_variation = &"FlatButton"
toggle_mode = true
button_pressed = true
icon = ExtResource("5_803mj")
[node name="BtnInteractionPolygon" type="Button" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Interaction Polygon"
theme_type_variation = &"FlatButton"
toggle_mode = true
icon = ExtResource("6_6oxl8")

View file

@ -0,0 +1,291 @@
@tool
class_name PopochiuConfig
extends RefCounted
enum DialogStyle {
ABOVE_CHARACTER,
PORTRAIT,
CAPTION,
#PORTRAIT_ABOVE_CHARACTER, # TODO: Create a GUI node to make this option available
#BUBBLE_ABOVE_CHARACTER, # TODO: Create a GUI node to make this option available
}
# Thanks to @drbloop for providing the bases of the new approach for moving the popochiu settings to
# Godot's ProjectSettings instead of using a Resource file.
# ---- GUI -----------------------------------------------------------------------------------------
const SCALE_GUI = "popochiu/gui/experimental_scale_gui"
const FADE_COLOR = "popochiu/gui/fade_color"
const SKIP_CUTSCENE_TIME = "popochiu/gui/skip_cutscene_time"
const TL_FIRST_ROOM = "popochiu/gui/show_transition_layer_in_first_room"
# ---- Dialogs -------------------------------------------------------------------------------------
const TEXT_SPEED = "popochiu/dialogs/text_speed"
const AUTO_CONTINUE_TEXT = "popochiu/dialogs/auto_continue_text"
const USE_TRANSLATIONS = "popochiu/dialogs/use_translations"
const GIBBERISH_SPOKEN_TEXT = 'popochiu/dialogs/gibberish_spoken_text'
const GIBBERISH_DIALOG_OPTIONS = 'popochiu/dialogs/gibberish_dialog_options'
const DIALOG_STYLE = "popochiu/dialogs/dialog_style"
# ---- Inventory -----------------------------------------------------------------------------------
const INVENTORY_LIMIT = "popochiu/inventory/inventory_limit"
const INVENTORY_ITEMS_ON_START = "popochiu/inventory/items_on_start"
# ---- Aseprite Importing --------------------------------------------------------------------------
const ASEPRITE_IMPORT_ANIMATION = "popochiu/aseprite_import/import_animation_by_default"
const ASEPRITE_LOOP_ANIMATION = "popochiu/aseprite_import/loop_animation_by_default"
const ASEPRITE_PROPS_VISIBLE = "popochiu/aseprite_import/new_props_visible_by_default"
const ASEPRITE_PROPS_CLICKABLE = "popochiu/aseprite_import/new_props_clickable_by_default"
const ASEPRITE_WIPE_OLD_ANIMATIONS = "popochiu/aseprite_import/wipe_old_animations"
# ---- Pixel game ----------------------------------------------------------------------------------
const PIXEL_ART_TEXTURES = "popochiu/pixel/pixel_art_textures"
const PIXEL_PERFECT = "popochiu/pixel/pixel_perfect"
# ---- Audio ---------------------------------------------------------------------------------------
const PREFIX_CHARACTER = "popochiu/audio/prefix_character"
const MUSIC_PREFIXES = "popochiu/audio/music_prefixes"
const SOUND_EFFECT_PREFIXES = "popochiu/audio/sound_effect_prefixes"
const VOICE_PREFIXES = "popochiu/audio/voice_prefixes"
const UI_PREFIXES = "popochiu/audio/ui_prefixes"
# ---- DEV -----------------------------------------------------------------------------------------
const DEV_USE_ADDON_TEMPLATE = "popochiu/dev/use_addon_template"
static var defaults := {
SCALE_GUI: false,
FADE_COLOR: Color.BLACK,
SKIP_CUTSCENE_TIME: 0.2,
TL_FIRST_ROOM: false,
TEXT_SPEED: 0.1,
AUTO_CONTINUE_TEXT: false,
USE_TRANSLATIONS: false,
GIBBERISH_SPOKEN_TEXT: false,
GIBBERISH_DIALOG_OPTIONS: false,
DIALOG_STYLE: DialogStyle.ABOVE_CHARACTER,
INVENTORY_LIMIT: 0,
INVENTORY_ITEMS_ON_START: [],
ASEPRITE_IMPORT_ANIMATION: true,
ASEPRITE_LOOP_ANIMATION: true,
ASEPRITE_PROPS_VISIBLE: true,
ASEPRITE_PROPS_CLICKABLE: true,
ASEPRITE_WIPE_OLD_ANIMATIONS: true,
PIXEL_ART_TEXTURES: false,
PIXEL_PERFECT: false,
PREFIX_CHARACTER: "_",
MUSIC_PREFIXES: "mx,",
SOUND_EFFECT_PREFIXES: "sfx,",
VOICE_PREFIXES: "vo,",
UI_PREFIXES: "ui,",
DEV_USE_ADDON_TEMPLATE: false,
}
#region Public #####################################################################################
static func initialize_project_settings():
# ---- GUI -------------------------------------------------------------------------------------
_initialize_project_setting(SCALE_GUI, TYPE_BOOL)
_initialize_project_setting(FADE_COLOR, TYPE_COLOR)
_initialize_project_setting(SKIP_CUTSCENE_TIME, TYPE_FLOAT)
_initialize_project_setting(TL_FIRST_ROOM, TYPE_BOOL)
# ---- Dialogs ---------------------------------------------------------------------------------
_initialize_project_setting(TEXT_SPEED, TYPE_FLOAT, PROPERTY_HINT_RANGE, "0.0,0.1")
_initialize_project_setting(AUTO_CONTINUE_TEXT, TYPE_BOOL)
#_initialize_project_setting(USE_TRANSLATIONS, TYPE_BOOL)
#_initialize_project_setting(
#DIALOG_STYLE,
#TYPE_INT,
#PROPERTY_HINT_ENUM,
## TODO: Add other options: Portrait Above Character, Bubble Above Character
#"Above Character,Portrait,Caption"
#)
_initialize_project_setting(GIBBERISH_SPOKEN_TEXT, TYPE_BOOL)
_initialize_project_setting(GIBBERISH_DIALOG_OPTIONS, TYPE_BOOL)
# ---- Inventory -------------------------------------------------------------------------------
_initialize_project_setting(INVENTORY_LIMIT, TYPE_INT)
_initialize_project_setting(
INVENTORY_ITEMS_ON_START,
TYPE_ARRAY,
PROPERTY_HINT_TYPE_STRING,
"%d:" % [TYPE_STRING]
)
# ---- Aseprite Importing ----------------------------------------------------------------------
_initialize_project_setting(ASEPRITE_IMPORT_ANIMATION, TYPE_BOOL)
_initialize_project_setting(ASEPRITE_LOOP_ANIMATION, TYPE_BOOL)
_initialize_project_setting(ASEPRITE_PROPS_VISIBLE, TYPE_BOOL)
_initialize_project_setting(ASEPRITE_PROPS_CLICKABLE, TYPE_BOOL)
_initialize_project_setting(ASEPRITE_WIPE_OLD_ANIMATIONS, TYPE_BOOL)
# ---- Pixel game ------------------------------------------------------------------------------
_initialize_project_setting(PIXEL_ART_TEXTURES, TYPE_BOOL)
_initialize_project_setting(PIXEL_PERFECT, TYPE_BOOL)
# ---- Audio -----------------------------------------------------------------------------------
_initialize_project_setting(PREFIX_CHARACTER, TYPE_STRING)
_initialize_project_setting(MUSIC_PREFIXES, TYPE_STRING)
_initialize_project_setting(SOUND_EFFECT_PREFIXES, TYPE_STRING)
_initialize_project_setting(VOICE_PREFIXES, TYPE_STRING)
_initialize_project_setting(UI_PREFIXES, TYPE_STRING)
# ---- DEV -------------------------------------------------------------------------------------
_initialize_advanced_project_setting(DEV_USE_ADDON_TEMPLATE, TYPE_BOOL)
ProjectSettings.save()
static func set_project_setting(key: String, value) -> void:
ProjectSettings.set_setting(key, value)
ProjectSettings.save()
# ---- GUI -----------------------------------------------------------------------------------------
static func is_scale_gui() -> bool:
return _get_project_setting(SCALE_GUI)
static func get_fade_color() -> Color:
return _get_project_setting(FADE_COLOR)
static func get_skip_cutscene_time() -> float:
return _get_project_setting(SKIP_CUTSCENE_TIME)
static func should_show_tl_in_first_room() -> bool:
return _get_project_setting(TL_FIRST_ROOM)
# ---- Dialogs -------------------------------------------------------------------------------------
static func get_text_speed() -> float:
return _get_project_setting(TEXT_SPEED)
static func is_auto_continue_text() -> bool:
return _get_project_setting(AUTO_CONTINUE_TEXT)
static func is_use_translations() -> bool:
return _get_project_setting(USE_TRANSLATIONS)
static func get_dialog_style() -> int:
return _get_project_setting(DIALOG_STYLE)
static func should_talk_gibberish() -> bool:
return _get_project_setting(GIBBERISH_SPOKEN_TEXT)
static func should_dialog_options_be_gibberish() -> bool:
return _get_project_setting(GIBBERISH_DIALOG_OPTIONS)
# ---- Inventory -----------------------------------------------------------------------------------
static func get_inventory_limit() -> int:
return _get_project_setting(INVENTORY_LIMIT)
static func set_inventory_items_on_start(items: Array) -> void:
set_project_setting(INVENTORY_ITEMS_ON_START, items)
static func get_inventory_items_on_start() -> Array:
return _get_project_setting(INVENTORY_ITEMS_ON_START)
# ---- Aseprite Importing --------------------------------------------------------------------------
static func is_default_animation_import_enabled() -> bool:
return _get_project_setting(ASEPRITE_IMPORT_ANIMATION)
static func is_default_animation_loop_enabled() -> bool:
return _get_project_setting(ASEPRITE_LOOP_ANIMATION)
static func is_default_animation_prop_visible() -> bool:
return _get_project_setting(ASEPRITE_PROPS_VISIBLE)
static func is_default_animation_prop_clickable() -> bool:
return _get_project_setting(ASEPRITE_PROPS_CLICKABLE)
static func is_default_wipe_old_anims_enabled() -> bool:
return _get_project_setting(ASEPRITE_WIPE_OLD_ANIMATIONS)
# ---- Pixel game ----------------------------------------------------------------------------------
static func set_pixel_art_textures(use_pixel_art_textures: bool) -> void:
set_project_setting(PIXEL_ART_TEXTURES, use_pixel_art_textures)
static func is_pixel_art_textures() -> bool:
return _get_project_setting(PIXEL_ART_TEXTURES)
static func is_pixel_perfect() -> bool:
return _get_project_setting(PIXEL_PERFECT)
# ---- Audio ---------------------------------------------------------------------------------------
static func get_prefix_character() -> String:
return _get_project_setting(PREFIX_CHARACTER)
static func get_music_prefixes() -> String:
return _get_project_setting(MUSIC_PREFIXES)
static func get_sound_effect_prefixes() -> String:
return _get_project_setting(SOUND_EFFECT_PREFIXES)
static func get_voice_prefixes() -> String:
return _get_project_setting(VOICE_PREFIXES)
static func get_ui_prefixes() -> String:
return _get_project_setting(UI_PREFIXES)
# ---- DEV -----------------------------------------------------------------------------------------
static func is_use_addon_template() -> bool:
return _get_project_setting(DEV_USE_ADDON_TEMPLATE)
#endregion
#region Private ####################################################################################
static func _initialize_project_setting(
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
) -> void:
_create_setting(key, type, hint, hint_string)
ProjectSettings.set_as_basic(key, true)
static func _initialize_advanced_project_setting(
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
) -> void:
_create_setting(key, type, hint, hint_string)
static func _create_setting(
key: String, type: int, hint := PROPERTY_HINT_NONE, hint_string := ""
) -> void:
ProjectSettings.set_setting(key, ProjectSettings.get_setting(key, defaults[key]))
ProjectSettings.set_initial_value(key, defaults[key])
ProjectSettings.add_property_info({
"name": key,
"type": type,
"hint": hint,
"hint_string": hint_string,
})
static func _get_project_setting(key: String):
var p = ProjectSettings.get_setting(key)
return p if p != null else defaults[key]
#endregion

View file

@ -0,0 +1 @@
uid://blq0g140jdg7d

View file

@ -0,0 +1,128 @@
@tool
class_name PopochiuEditorConfig
extends RefCounted
enum Icons { COLLAPSED, EXPANDED }
# ASEPRITE IMPORTER --------------------------------------------------------------------------------
const ASEPRITE_IMPORTER_ENABLED = "popochiu/import/aseprite/enable_aseprite_importer"
const ASEPRITE_COMMAND_PATH = "popochiu/import/aseprite/command_path"
const ASEPRITE_REMOVE_JSON_FILE = "popochiu/import/aseprite/remove_json_file"
# GIZMOS -------------------------------------------------------------------------------------------
const GIZMOS_FONT_SIZE = "popochiu/gizmos/font_size"
const GIZMOS_BASELINE_COLOR = "popochiu/gizmos/baseline_color"
const GIZMOS_WALK_TO_POINT_COLOR = "popochiu/gizmos/walk_to_point_color"
const GIZMOS_LOOK_AT_POINT_COLOR = "popochiu/gizmos/look_at_point_color"
const GIZMOS_DIALOG_POS_COLOR = "popochiu/gizmos/dialog_position_color"
const GIZMOS_COLOR_TOOLBAR_BUTTONS = "popochiu/gizmos/apply_colors_to_toolbar_buttons"
const GIZMOS_HANDLER_SIZE = "popochiu/gizmos/handler_size"
const GIZMOS_SHOW_CONNECTORS = "popochiu/gizmos/show_connectors"
const GIZMOS_SHOW_OUTLINE = "popochiu/gizmos/show_handler_outline"
const GIZMOS_SHOW_NODE_NAME = "popochiu/gizmos/show_node_name"
const GIZMOS_ALWAYS_SHOW_WA = "popochiu/gizmos/always_show_walkable_areas"
# Settings default values
static var defaults := {
ASEPRITE_IMPORTER_ENABLED: false,
ASEPRITE_COMMAND_PATH: _default_aseprite_command(),
ASEPRITE_REMOVE_JSON_FILE: true,
GIZMOS_FONT_SIZE: _default_font_size(),
GIZMOS_BASELINE_COLOR: Color.CYAN,
GIZMOS_WALK_TO_POINT_COLOR: Color.GREEN,
GIZMOS_LOOK_AT_POINT_COLOR: Color.RED,
GIZMOS_DIALOG_POS_COLOR: Color.MAGENTA,
GIZMOS_COLOR_TOOLBAR_BUTTONS: true,
GIZMOS_HANDLER_SIZE: 32,
GIZMOS_SHOW_CONNECTORS: true,
GIZMOS_SHOW_OUTLINE: true,
GIZMOS_SHOW_NODE_NAME: true,
GIZMOS_ALWAYS_SHOW_WA: false,
}
static var editor_settings: EditorSettings
#region Public #####################################################################################
static func initialize_editor_settings():
editor_settings = EditorInterface.get_editor_settings()
# Aseprite importer
_initialize_editor_setting(ASEPRITE_IMPORTER_ENABLED, TYPE_BOOL)
_initialize_editor_setting(ASEPRITE_COMMAND_PATH, TYPE_STRING)
_initialize_editor_setting(ASEPRITE_REMOVE_JSON_FILE, TYPE_BOOL)
# Gizmos
_initialize_editor_setting(GIZMOS_BASELINE_COLOR, TYPE_COLOR)
_initialize_editor_setting(GIZMOS_WALK_TO_POINT_COLOR, TYPE_COLOR)
_initialize_editor_setting(GIZMOS_LOOK_AT_POINT_COLOR, TYPE_COLOR)
_initialize_editor_setting(GIZMOS_DIALOG_POS_COLOR, TYPE_COLOR)
_initialize_editor_setting(GIZMOS_COLOR_TOOLBAR_BUTTONS, TYPE_BOOL)
_initialize_editor_setting(GIZMOS_HANDLER_SIZE, TYPE_INT, PROPERTY_HINT_RANGE, "4,64")
_initialize_editor_setting(GIZMOS_FONT_SIZE, TYPE_INT, PROPERTY_HINT_RANGE, "4,64")
_initialize_editor_setting(GIZMOS_SHOW_CONNECTORS, TYPE_BOOL)
_initialize_editor_setting(GIZMOS_SHOW_OUTLINE, TYPE_BOOL)
_initialize_editor_setting(GIZMOS_SHOW_NODE_NAME, TYPE_BOOL)
_initialize_editor_setting(GIZMOS_ALWAYS_SHOW_WA, TYPE_BOOL)
static func get_icon(icon: Icons) -> Texture2D:
match icon:
Icons.COLLAPSED:
return EditorInterface.get_base_control().get_theme_icon(
"GuiTreeArrowRight", "EditorIcons"
)
Icons.EXPANDED:
return EditorInterface.get_base_control().get_theme_icon(
"GuiTreeArrowDown", "EditorIcons"
)
return null
# ASEPRITE IMPORTER --------------------------------------------------------------------------------
static func aseprite_importer_enabled() -> bool:
return get_editor_setting(ASEPRITE_IMPORTER_ENABLED)
static func get_command() -> String:
return get_editor_setting(ASEPRITE_COMMAND_PATH)
static func should_remove_source_files() -> bool:
return get_editor_setting(ASEPRITE_REMOVE_JSON_FILE)
#endregion
#region Private ####################################################################################
static func _default_aseprite_command() -> String:
return 'aseprite'
static func _default_font_size() -> int:
if Engine.is_editor_hint():
return EditorInterface.get_editor_theme().default_font_size
return 16
static func _initialize_editor_setting(
key: String, type: int, hint: int = PROPERTY_HINT_NONE, hint_string : String = ""
) -> void:
if editor_settings.has_setting(key): return
editor_settings.set_setting(key, defaults[key])
editor_settings.set_initial_value(key, defaults[key], false)
editor_settings.add_property_info({
"name": key,
"type": type,
"hint": hint,
"hint_string": hint_string
})
static func get_editor_setting(key: String):
var e = editor_settings.get_setting(key)
return e if e != null else defaults[e]
#endregion

View file

@ -0,0 +1 @@
uid://c2xn0h6paw2xy

View file

@ -0,0 +1,70 @@
@tool
extends RefCounted
const LOCAL_OBJ_CONFIG_META_NAME = "_popochiu_aseprite_config_"
const LOCAL_OBJ_CONFIG_MARKER = "popochiu_aseprite_config"
const SEPARATOR = "|="
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
static func encode(object: Dictionary):
var text = "%s\n" % LOCAL_OBJ_CONFIG_MARKER
for prop in object:
text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]]
return Marshalls.utf8_to_base64(text)
static func decode(string: String):
var decoded = _decode_base64(string)
if not _is_valid_config(decoded):
return null
print(decoded)
var cfg = decoded.split("\n")
var config = {}
for c in cfg:
var parts = c.split(SEPARATOR, 1)
if parts.size() == 2:
var key = parts[0].strip_edges()
var value = parts[1].strip_edges()
#Convert bool properties
if key in ["only_visible_layers", "wipe_old_anims", "op_exp"]:
match value:
"True":
config[key] = true
"False":
config[key] = false
_:
config[key] = false
else:
config[key] = value
return config
static func load_config(node:Node):
# Check if node is not null to avoid showing error messages in Output when inspecting nodes in
# the Debugger
if node and node.has_meta(LOCAL_OBJ_CONFIG_META_NAME):
return node.get_meta(LOCAL_OBJ_CONFIG_META_NAME)
static func save_config(node:Node, cfg:Dictionary):
node.set_meta(LOCAL_OBJ_CONFIG_META_NAME, cfg)
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
static func _decode_base64(string: String):
if string != "":
return Marshalls.base64_to_utf8(string)
return null
static func _is_valid_config(cfg) -> bool:
return cfg != null and cfg.begins_with(LOCAL_OBJ_CONFIG_MARKER)

View file

@ -0,0 +1 @@
uid://d26rqfocnhh7v

View file

@ -0,0 +1,75 @@
@tool
extends RefCounted
class_name ResultCodes
enum {
## Base codes
FAILURE, # generic failure state
SUCCESS, # generic success state
## Aseprite importer errors
ERR_ASEPRITE_CMD_NOT_FULL_PATH,
ERR_ASEPRITE_CMD_NOT_FOUND,
ERR_SOURCE_FILE_NOT_FOUND,
ERR_OUTPUT_FOLDER_NOT_FOUND,
ERR_ASEPRITE_EXPORT_FAILED,
ERR_UNKNOWN_EXPORT_MODE,
ERR_NO_VALID_LAYERS_FOUND,
ERR_INVALID_ASEPRITE_SPRITESHEET,
ERR_NO_ANIMATION_PLAYER_FOUND,
ERR_NO_SPRITE_FOUND,
ERR_UNNAMED_TAG_DETECTED,
ERR_TAGS_OPTIONS_ARRAY_EMPTY,
## Popochiu Object factories errors
ERR_CANT_CREATE_OBJ_FOLDER,
ERR_CANT_CREATE_OBJ_STATE,
ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE,
ERR_CANT_CREATE_OBJ_SCRIPT,
ERR_CANT_SAVE_OBJ_SCENE,
ERR_CANT_SAVE_OBJ_RESOURCE,
}
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
static func get_error_message(code: int):
## TODO: these messages are a bit dull, having params would be better.
## Maybe add a param argument
match code:
# Aseprite importers error messages
ERR_ASEPRITE_CMD_NOT_FULL_PATH:
return "Aseprite command not found at given path. Please check \"Editor Settings > Popochiu > Import > Command Path\" to hold the FULL path to a valid Aseprite executable."
ERR_ASEPRITE_CMD_NOT_FOUND:
return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Editor Settings > Popochiu > Import > Command Path\"."
ERR_SOURCE_FILE_NOT_FOUND:
return "Source file does not exist"
ERR_OUTPUT_FOLDER_NOT_FOUND:
return "Output location does not exist"
ERR_ASEPRITE_EXPORT_FAILED:
return "Unable to import file"
ERR_INVALID_ASEPRITE_SPRITESHEET:
return "Aseprite generated invalid data file"
ERR_NO_VALID_LAYERS_FOUND:
return "No valid layers found"
ERR_NO_ANIMATION_PLAYER_FOUND:
return "No AnimationPlayer found in target node"
ERR_NO_SPRITE_FOUND:
return "No sprite found in target node"
ERR_UNNAMED_TAG_DETECTED:
return "Unnamed tag detected"
ERR_TAGS_OPTIONS_ARRAY_EMPTY:
return "Tags options array is empty"
# Popochiu object factories error messages
ERR_CANT_CREATE_OBJ_FOLDER:
return "Can't create folder to host new Popochiu object"
ERR_CANT_CREATE_OBJ_STATE:
return "Can't create new Popochiu object's state resource (_state.tres, _state.gd)"
ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE:
return "Can't open script template for new Popochiu object"
ERR_CANT_CREATE_OBJ_SCRIPT:
return "Can't create new Popochiu object's script file (.gd)"
ERR_CANT_SAVE_OBJ_SCENE:
return "Can't create new Popochiu object's scene (.tscn)"
ERR_CANT_SAVE_OBJ_RESOURCE:
return "Can't create new Popochiu object's resource (.tres)"
# Generic error message
_:
return "Import failed with code %d" % code

View file

@ -0,0 +1 @@
uid://cbxik5o1u00hf

View file

@ -0,0 +1,237 @@
extends RefCounted
const BASE_STATE_TEMPLATE := "res://addons/popochiu/engine/templates/%s_state_template.gd"
const BASE_SCRIPT_TEMPLATE := "res://addons/popochiu/engine/templates/%s_template.gd"
const BASE_SCENE_PATH := "res://addons/popochiu/engine/objects/%s/popochiu_%s.tscn"
const EMPTY_SCRIPT := "res://addons/popochiu/engine/templates/empty_script_template.gd"
# The following variables are setup on creation Names variants and name parameter passed to the
# create method.
var _path_template := "" # always set by child class
var _snake_name := ""
var _pascal_name := ""
var _path_base := ""
var _path_scene = ""
var _path_resource = ""
var _path_state = ""
var _path_script := ""
# The following variables are setup by the sub-class constructor to define the type of object to be
# processed
# TODO: reduce this to just "type", too much redundancy
var _type := -1
var _type_label := ""
var _type_target := ""
var _type_method: Callable
# The following variables are references to the elements generated for the creation of the new
# Popochiu object, such as resources, scenes, scripts, state scripts, etc
var _scene: Node
var _resource: Resource
var _state_resource: Resource
var _script: Resource
#region Public #####################################################################################
func get_obj_scene() -> Node:
return _scene
func get_snake_name() -> String:
return _snake_name
func get_obj_resource() -> Resource:
return _resource
func get_state_resource() -> Resource:
return _state_resource
func get_obj_script() -> Resource:
return _script
func get_scene_path() -> String:
return _path_scene
func get_type() -> int:
return _type
func get_type_method() -> Callable:
return _type_method
#endregion
#region Private ####################################################################################
func _setup_name(obj_name: String) -> void:
_pascal_name = obj_name.to_pascal_case()
_snake_name = obj_name.to_snake_case()
_path_base = _path_template % [_snake_name, _snake_name]
_path_script = _path_base + ".gd"
_path_state = _path_base + "_state.gd"
_path_resource = _path_base + ".tres"
_path_scene = _path_base + ".tscn"
func _create_obj_folder() -> int:
# TODO: Remove created files if the creation process failed.
if DirAccess.make_dir_recursive_absolute(_path_base.get_base_dir()) != OK:
PopochiuUtils.print_error(
"Could not create %s directory: %s" %
[_path_base.get_base_dir(), _pascal_name]
)
return ResultCodes.ERR_CANT_CREATE_OBJ_FOLDER
return ResultCodes.SUCCESS
func _create_state_resource() -> int:
var state_template: Script = load(
BASE_STATE_TEMPLATE % _type_label
).duplicate()
if ResourceSaver.save(state_template, _path_state) != OK:
PopochiuUtils.print_error(
"Could not create %s state script: %s" %
[_type_label, _pascal_name]
)
return ResultCodes.FAILURE
_state_resource = load(_path_state).new()
_state_resource.script_name = _pascal_name
_state_resource.scene = _path_scene
_state_resource.resource_name = _pascal_name
if ResourceSaver.save(_state_resource, _path_resource) != OK:
PopochiuUtils.print_error(
"Could not create state resource for %s: %s" %
[_type_label, _pascal_name]
)
return ResultCodes.ERR_CANT_CREATE_OBJ_STATE
return ResultCodes.SUCCESS
func _copy_script_template() -> int:
var _script: Script = load(
BASE_SCRIPT_TEMPLATE % _type_label
).duplicate()
if ResourceSaver.save( _script, _path_script) != OK:
PopochiuUtils.print_error(
"Could not create %s script: %s" %
[_type_label, _path_script]
)
return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT
return ResultCodes.SUCCESS
## Create the script for the object based on the template of its type.
func _create_script_from_template() -> int:
var script_template_file = FileAccess.open(
BASE_SCRIPT_TEMPLATE % _type_label, FileAccess.READ
)
if script_template_file == null:
PopochiuUtils.print_error(
"Could not read script template from %s" %
[BASE_SCRIPT_TEMPLATE % _type_label]
)
return ResultCodes.ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE
var new_code: String = script_template_file.get_as_text()
script_template_file.close()
new_code = new_code.replace(
"%s_state_template" % _type_label,
"%s_%s_state" % [_type_label, _snake_name]
)
new_code = new_code.replace(
"Data = null",
'Data = load("%s.tres")' % _path_base
)
new_code = new_code.replace("PopochiuUtils.e", "E")
_script = load(EMPTY_SCRIPT).duplicate()
_script.source_code = new_code
if ResourceSaver.save( _script, _path_script) != OK:
PopochiuUtils.print_error(
"Could not create %s script: %s" %
[_type_label, _path_script]
)
return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT
return ResultCodes.SUCCESS
func _save_obj_scene(obj: Node) -> int:
var packed_scene: PackedScene = PackedScene.new()
packed_scene.pack(obj)
if ResourceSaver.save(packed_scene, _path_scene) != OK:
PopochiuUtils.print_error(
"Could not create %s: %s" %
[_type_label, _path_script]
)
return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE
# Load the scene to be get by the calling code
# Instancing the created .tscn file fixes #58
_scene = (load(_path_scene) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
return ResultCodes.SUCCESS
func _save_obj_resource(obj: Resource) -> int:
if ResourceSaver.save(obj, _path_resource) != OK:
PopochiuUtils.print_error(
"Could not create %s: %s" %
[_type_label, _pascal_name]
)
return ResultCodes.ERR_CANT_SAVE_OBJ_RESOURCE
# Load the resource to be get by the calling code
_resource = load(_path_resource)
return ResultCodes.SUCCESS
## Makes a copy of the base scene for the object (e.g. popochiu_room.tscn,
## popochiu_inventory_item.tscn, popochiu_prop.tscn).
func _load_obj_base_scene() -> Node:
var obj = (
load(BASE_SCENE_PATH % [_type_label, _type_label]) as PackedScene
).instantiate(PackedScene.GEN_EDIT_STATE_MAIN_INHERITED)
# The script is assigned first so that other properties will not be
# overwritten by that assignment.
if _script != null:
obj.set_script(load(_path_script))
return obj
func _add_resource_to_popochiu() -> void:
# Add the created obj to Popochiu's correct list
var resource := ResourceLoader.load(_path_resource)
if PopochiuResources.set_data_value(
_type_target,
resource.script_name,
resource.resource_path
) != OK:
PopochiuUtils.print_error(
"Could not add the created %s to Popochiu: %s" %
[_type_label, _pascal_name]
)
return
# Add the object to the proper singleton
PopochiuResources.update_autoloads(true)
# Update the related list in the dock
PopochiuEditorHelper.signal_bus.main_object_added.emit(_type, _pascal_name)
#endregion

View file

@ -0,0 +1 @@
uid://dsauwqcvbelb1

View file

@ -0,0 +1,98 @@
class_name PopochiuRoomObjFactory
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
const CHILD_VISIBLE_IN_ROOM_META = "_popochiu_obj_factory_child_visible_in_room_"
# The following variable is setup by the sub-class constructor to
# define the holder node for the new room object (Props, Hotspots, etc)
var _obj_room_group := ""
# The following variables are setup by the _setup_room method
var _room: Node2D = null
var _room_path := ""
var _room_dir := ""
#region Public #####################################################################################
func get_group() -> String:
return _obj_room_group
func create_from(node: Node, room: PopochiuRoom) -> int:
_setup_room(room)
_setup_name(node.name)
var param := _get_param(node)
param.room = room
param.obj_name = node.name
param.is_visible = node.visible
param.should_setup_room_and_name = false
param.should_add_to_room = false
param.should_create_script = !FileAccess.file_exists(_path_script)
return call("create", param)
func get_new_instance() -> PopochiuRoomObjFactory:
return new()
#endregion
#region Private ####################################################################################
func _setup_room(room: PopochiuRoom) -> void:
_room = room
_room_path = _room.scene_file_path
_room_dir = _room_path.get_base_dir()
# Adding room path to room object path template
_path_template = _room_dir + _path_template
# This function adds a child to the new object scene
# marking it as "visible in room scene"
func _add_visible_child(child: Node) -> void:
child.set_meta(CHILD_VISIBLE_IN_ROOM_META, true)
_scene.add_child(child)
func _add_resource_to_room() -> void:
# Add the newly created obj to its room
_room.get_node(_obj_room_group).add_child(_scene)
# Set the ownership for the node plus all it's children
# (this address colliders, polygons, etc)
_scene.owner = _room
for child in _scene.get_children():
if child.has_meta(CHILD_VISIBLE_IN_ROOM_META):
child.owner = _room
child.remove_meta(CHILD_VISIBLE_IN_ROOM_META)
# Center the object on the scene
_scene.position = Vector2(
ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH),
ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT)
) / 2.0
# Save the room scene (it's open in the editor)
EditorInterface.save_scene()
func _get_param(_node: Node) -> PopochiuRoomObjFactoryParam:
return PopochiuRoomObjFactoryParam.new()
#endregion
#region Subclass ###################################################################################
class PopochiuRoomObjFactoryParam extends RefCounted:
var obj_name: String
var room: PopochiuRoom
var is_visible := true
var should_setup_room_and_name := true
var should_create_script := true
var should_add_to_room := true
## Property used to store the vectors stored in the [member CollisionPolygon2D.polygon] for
## [PopochiuProp], [PopochiuHotspot], and [PopochiuRegion].
var interaction_polygon := PackedVector2Array()
#endregion

View file

@ -0,0 +1 @@
uid://ch67numd388l

View file

@ -0,0 +1,64 @@
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
class_name PopochiuCharacterFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.CHARACTER
_type_label = "character"
_type_target = "characters"
_path_template = PopochiuResources.CHARACTERS_PATH.path_join("%s/character_%s")
#endregion
#region Public #####################################################################################
func create(obj_name: String, is_pc := false) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
# Setup the class variables that depends on the object name
_setup_name(obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the state Resource and a script
# so devs can add extra properties to that state
result_code = _create_state_resource()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script populating the template with the right references
result_code = _create_script_from_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuCharacter = _load_obj_base_scene()
new_obj.name = "Character" + _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _pascal_name.capitalize()
new_obj.cursor = PopochiuResources.CURSOR_TYPE.TALK
if PopochiuConfig.is_pixel_art_textures():
new_obj.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
# Save the scene (.tscn)
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# Add the object to Popochiu dock list, plus open it in the editor
_add_resource_to_popochiu()
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Set as PC
if is_pc:
PopochiuEditorHelper.signal_bus.pc_changed.emit(new_obj.script_name)
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
return result_code
#endregion

View file

@ -0,0 +1 @@
uid://dstbnm6v0t6ot

View file

@ -0,0 +1,49 @@
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
class_name PopochiuDialogFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.DIALOG
_type_label = "dialog"
_type_target = "dialogs"
_path_template = PopochiuResources.DIALOGS_PATH.path_join("%s/dialog_%s")
#endregion
#region Public #####################################################################################
func create(obj_name: String) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
# Setup the class variables that depends on the object name
_setup_name(obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script
result_code = _copy_script_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the resource (dialogs are not scenes)
var new_obj := PopochiuDialog.new()
new_obj.set_script(load(_path_script))
new_obj.script_name = _pascal_name
new_obj.resource_name = _pascal_name
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
# Save resource (dialogs are not scenes)
result_code = _save_obj_resource(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# Add the object to Popochiu dock list, plus open it in the editor
_add_resource_to_popochiu()
return result_code
#endregion

View file

@ -0,0 +1 @@
uid://ddk6pykrms2tq

View file

@ -0,0 +1,76 @@
class_name PopochiuHotspotFactory
extends PopochiuRoomObjFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.HOTSPOT
_type_label = "hotspot"
_type_method = PopochiuEditorHelper.is_hotspot
_obj_room_group = "Hotspots"
_path_template = "/hotspots/%s/hotspot_%s"
#endregion
#region Public #####################################################################################
func create(param: PopochiuHotspotFactoryParam) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
if param.should_setup_room_and_name:
_setup_room(param.room)
_setup_name(param.obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script
if param.should_create_script:
result_code = _copy_script_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuHotspot = _load_obj_base_scene()
new_obj.set_script(ResourceLoader.load(_path_script))
new_obj.name = _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _snake_name.capitalize()
new_obj.cursor = PopochiuResources.CURSOR_TYPE.ACTIVE
new_obj.interaction_polygon = param.interaction_polygon
# Save the hotspot scene (.tscn) and put it into _scene class property
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
if param.should_add_to_room:
# Add the object to its room
_add_resource_to_room()
return result_code
#endregion
#region Private ####################################################################################
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
var param := PopochiuHotspotFactoryParam.new()
param.is_interactive = node.clickable
# TODO: Remove this line once the last gizmos PR is merged
param.interaction_polygon = node.interaction_polygon
return param
#endregion
#region Subclass ###################################################################################
class PopochiuHotspotFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
var is_interactive := true
#endregion

View file

@ -0,0 +1 @@
uid://bcnd7an67ulyf

View file

@ -0,0 +1,60 @@
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
class_name PopochiuInventoryItemFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.INVENTORY_ITEM
_type_label = "inventory_item"
_type_target = "inventory_items"
_path_template = PopochiuResources.INVENTORY_ITEMS_PATH.path_join("%s/inventory_item_%s")
#endregion
#region Public #####################################################################################
func create(obj_name: String) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
# Setup the class variables that depends on the object name
_setup_name(obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the state Resource and a script
# so devs can add extra properties to that state
result_code = _create_state_resource()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script populating the template with the right references
result_code = _create_script_from_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuInventoryItem = _load_obj_base_scene()
new_obj.name = "Item" + _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _pascal_name.capitalize()
new_obj.cursor = PopochiuResources.CURSOR_TYPE.USE
new_obj.size_flags_vertical = new_obj.SIZE_SHRINK_CENTER
if PopochiuConfig.is_pixel_art_textures():
new_obj.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
# Save the scene (.tscn)
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# Add the object to Popochiu dock list, plus open it in the editor
_add_resource_to_popochiu()
return result_code
#endregion

View file

@ -0,0 +1 @@
uid://bym4xs81a2ph4

View file

@ -0,0 +1,44 @@
class_name PopochiuMarkerFactory
extends PopochiuRoomObjFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.MARKER
_type_label = "marker"
_type_method = PopochiuEditorHelper.is_marker
_obj_room_group = "Markers"
_path_template = "/markers/%s/marker_%s"
#endregion
#region Public #####################################################################################
func create(param: PopochiuRoomObjFactoryParam) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
if param.should_setup_room_and_name:
_setup_room(param.room)
_setup_name(param.obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: Marker2D = Marker2D.new()
new_obj.name = _pascal_name
# Save the marker scene (.tscn) and put it into _scene class property
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
if param.should_add_to_room:
# Add the object to its room
_add_resource_to_room()
return result_code
#endregion

View file

@ -0,0 +1 @@
uid://ckjo1lcmxjlh6

View file

@ -0,0 +1,86 @@
class_name PopochiuPropFactory
extends PopochiuRoomObjFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.PROP
_type_label = "prop"
_type_method = PopochiuEditorHelper.is_prop
_obj_room_group = "Props"
_path_template = "/props/%s/prop_%s"
#endregion
#region Public #####################################################################################
func create(param: PopochiuPropFactoryParam) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
if param.should_setup_room_and_name:
_setup_room(param.room)
_setup_name(param.obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script (if the prop is interactive)
if param.should_create_script:
result_code = _copy_script_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuProp = _load_obj_base_scene()
new_obj.set_script(ResourceLoader.load(_path_script))
new_obj.name = _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _snake_name.capitalize()
new_obj.cursor = PopochiuResources.CURSOR_TYPE.ACTIVE
new_obj.clickable = param.is_interactive
new_obj.visible = param.is_visible
new_obj.interaction_polygon = param.interaction_polygon
if PopochiuConfig.is_pixel_art_textures():
new_obj.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
if _snake_name in ["bg", "background"]:
new_obj.baseline =\
-ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) / 2.0
new_obj.z_index = -1
# Save the scene (.tscn) and put it into _scene class property
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
if param.should_add_to_room:
# Add the object to its room
_add_resource_to_room()
return result_code
#endregion
#region Private ####################################################################################
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
var param := PopochiuPropFactoryParam.new()
param.is_interactive = node.clickable
# TODO: Remove this line once the last gizmos PR is merged
param.interaction_polygon = node.interaction_polygon
return param
#endregion
#region Subclass ###################################################################################
class PopochiuPropFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
var is_interactive := false
#endregion

View file

@ -0,0 +1 @@
uid://b8ajrxi1mp5up

View file

@ -0,0 +1,71 @@
class_name PopochiuRegionFactory
extends PopochiuRoomObjFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.REGION
_type_label = "region"
_type_method = PopochiuEditorHelper.is_region
_obj_room_group = "Regions"
_path_template = "/regions/%s/region_%s"
#endregion
#region Public #####################################################################################
func create(param: PopochiuRoomObjFactoryParam) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
if param.should_setup_room_and_name:
_setup_room(param.room)
_setup_name(param.obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script
if param.should_create_script:
result_code = _copy_script_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuRegion = _load_obj_base_scene()
new_obj.set_script(ResourceLoader.load(_path_script))
new_obj.name = _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _snake_name.capitalize()
new_obj.interaction_polygon = param.interaction_polygon
# Save the scene (.tscn) and put it into _scene class property
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
if param.should_add_to_room:
# Add the object to its room
_add_resource_to_room()
return result_code
#endregion
#region Private ####################################################################################
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
var param := PopochiuRegionFactoryParam.new()
param.interaction_polygon = node.interaction_polygon
return param
#endregion
#region Subclass ###################################################################################
class PopochiuRegionFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
var should_create_interaction_polygon := true
#endregion

View file

@ -0,0 +1 @@
uid://dphtxosh168sb

View file

@ -0,0 +1,63 @@
extends "res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd"
class_name PopochiuRoomFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.ROOM
_type_label = "room"
_type_target = "rooms"
_path_template = PopochiuResources.ROOMS_PATH.path_join("%s/room_%s")
#endregion
#region Public #####################################################################################
func create(obj_name: String, set_as_main := false) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
# Setup the class variables that depends on the object name
_setup_name(obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the state Resource and a script
# so devs can add extra properties to that state
result_code = _create_state_resource()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script populating the template with the right references
result_code = _create_script_from_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuRoom = _load_obj_base_scene()
new_obj.name = "Room" + _pascal_name
new_obj.script_name = _pascal_name
new_obj.width = ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH)
new_obj.height = ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT)
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
# Save the scene (.tscn)
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# Add the object to Popochiu dock list, plus open it in the editor
_add_resource_to_popochiu()
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Set as main room
# Changed _set_as_main_check.pressed to _set_as_main_check.button_pressed
# in order to fix #56
if set_as_main:
PopochiuEditorHelper.signal_bus.main_scene_changed.emit(_scene.scene_file_path)
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
return result_code
#endregion

View file

@ -0,0 +1 @@
uid://b7kd0q631qqt6

View file

@ -0,0 +1,94 @@
class_name PopochiuWalkableAreaFactory
extends PopochiuRoomObjFactory
#region Godot ######################################################################################
func _init() -> void:
_type = PopochiuResources.Types.WALKABLE_AREA
_type_label = "walkable_area"
_type_method = PopochiuEditorHelper.is_walkable_area
_obj_room_group = "WalkableAreas"
_path_template = "/walkable_areas/%s/walkable_area_%s"
#endregion
#region Public #####################################################################################
func create(param: PopochiuWalkableAreaFactoryParam) -> int:
# If everything goes well, this won't change.
var result_code := ResultCodes.SUCCESS
if param.should_setup_room_and_name:
_setup_room(param.room)
_setup_name(param.obj_name)
# Create the folder
result_code = _create_obj_folder()
if result_code != ResultCodes.SUCCESS: return result_code
# Create the script
if param.should_create_script:
result_code = _copy_script_template()
if result_code != ResultCodes.SUCCESS: return result_code
# ---- LOCAL CODE ------------------------------------------------------------------------------
# Create the instance
var new_obj: PopochiuWalkableArea = _load_obj_base_scene()
new_obj.set_script(ResourceLoader.load(_path_script))
new_obj.name = _pascal_name
new_obj.script_name = _pascal_name
new_obj.description = _snake_name.capitalize()
# Find the NavigationRegion2D for the WA and populate it with a default rectangle polygon
var perimeter := new_obj.find_child("Perimeter")
var polygon := NavigationPolygon.new()
polygon.add_outline(PackedVector2Array([
Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10)
]))
NavigationServer2D.bake_from_source_geometry_data(
polygon, NavigationMeshSourceGeometryData2D.new()
)
polygon.agent_radius = 0.0
perimeter.navigation_polygon = polygon
if not param.navigation_polygon.is_empty():
new_obj.interaction_polygon = param.navigation_polygon
new_obj.clear_and_bake(perimeter.navigation_polygon)
# Show the WA perimeter, depending on user prefs
perimeter.visible = PopochiuEditorConfig.get_editor_setting(
PopochiuEditorConfig.GIZMOS_ALWAYS_SHOW_WA
)
# Save the scene (.tscn) and put it into _scene class property
result_code = _save_obj_scene(new_obj)
if result_code != ResultCodes.SUCCESS: return result_code
# ---- END OF LOCAL CODE -----------------------------------------------------------------------
if param.should_add_to_room:
# Add the object to its room
_add_resource_to_room()
return result_code
#endregion
#region Private ####################################################################################
func _get_param(node: Node) -> PopochiuRoomObjFactoryParam:
var param := PopochiuWalkableAreaFactoryParam.new()
param.navigation_polygon = node.interaction_polygon
return param
#endregion
#region Subclass ###################################################################################
class PopochiuWalkableAreaFactoryParam extends PopochiuRoomObjFactory.PopochiuRoomObjFactoryParam:
var navigation_polygon := []
#endregion

View file

@ -0,0 +1 @@
uid://c66i7vwbgxym8

View file

@ -0,0 +1,274 @@
@tool
class_name Gizmo2D
extends RefCounted
# Gizmo types
enum {
GIZMO_POS, # square marker that represents (x,y) coordinates
GIZMO_HPOS, # vertical line that represents a horizontal coordinate
GIZMO_VPOS # horizontal line that represents a vertical coordinate
}
# Public vars
# Convenience accessors
var target_node: Node2D:
set = set_target_node,
get = get_target_node
var target_property: String:
set = set_target_property,
get = get_target_property
var position:
get = get_position
# Behavior flags
var show_connector: bool = true # Show gizmo-to-node connectors
var show_outlines: bool = true
var show_target_name: bool = true # Show target node name
var visible: bool = true # Gizmo visibility
# Private vars
# Context
var _type: int
var _target_node: Node2D
var _target_property: String
# Appearance
var _size: Vector2 # Gizmo width and height
var _color: Color # Gizmo color
var _label: String # A label to be painted near the Gizmo
var _font: Font # Label font
var _font_size: int # Label font size
# State
var _handle: Rect2 # Gizmo handle
var _current_position: Vector2 # The position the gizmo is representing in every moment
var _current_color: Color
var _is_grabbed: bool = false # Gizmo is moving
var _grab_center_pos: Vector2 # Starting center position when grabbing
var _grab_mouse_pos: Vector2 # Starting mouse position when grabbing
#region Virtual ####################################################################################
func _init(
node: Node,
property: String,
label: String,
type: int,
):
_target_node = node
_target_property = property
_type = type
_label = label
set_theme(
Color.AQUA,
24,
EditorInterface.get_editor_theme().default_font,
EditorInterface.get_editor_theme().default_font_size
)
_current_color = _color
#endregion
#region SetGet #####################################################################################
func set_theme(
color: Color,
size: int,
font: Font,
font_size: int
):
_color = color
_size = Vector2(size, size)
_font = font
_font_size = font_size
func set_target_node(node: Node2D):
_target_node = node
func get_target_node() -> Node2D:
return _target_node
func set_target_property(property: String):
_target_property = property
func get_target_property() -> String:
if _target_property:
return _target_property
return ""
#endregion
#region Private ####################################################################################
func _draw_outlines(viewport: Control):
viewport.draw_rect(
_handle,
Color.BLACK, false, 4
)
viewport.draw_string_outline(
_font,
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
_label, HORIZONTAL_ALIGNMENT_CENTER,
- 1,
_font_size,
6,
Color.BLACK
)
if show_target_name:
viewport.draw_string_outline(
_font,
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
_target_node.name,
HORIZONTAL_ALIGNMENT_CENTER,
- 1,
_font_size,
6,
Color.BLACK
)
func _draw_gizmo(viewport: Control):
# Draw the handle (on top of the line, if it's present)
viewport.draw_rect(
_handle,
_current_color
)
# Draw gizmo-to-node connector, if active
if show_connector:
viewport.draw_dashed_line(
(_target_node.get_viewport_transform() * _target_node.get_global_transform()).origin,
_handle.get_center(),
_current_color.darkened(0.2),
2,
4
)
viewport.draw_circle(
_handle.get_center(),
3,
_current_color.darkened(0.2)
)
# Draw the label, if it's set and non empty
if _label:
viewport.draw_string(
_font,
_handle.position + Vector2(0, _size.y + 2 + _font.get_ascent(_font_size)),
_label, HORIZONTAL_ALIGNMENT_CENTER,
- 1,
_font_size,
_current_color
)
if show_target_name:
viewport.draw_string(
_font,
_handle.position + Vector2(0, -_font.get_descent(_font_size)),
_target_node.name,
HORIZONTAL_ALIGNMENT_CENTER,
- 1,
_font_size,
_current_color
)
func _can_draw():
return (visible and _target_node != null and _target_node.is_visible_in_tree())
#endregion
#region Public #####################################################################################
func draw(viewport: Control, coord: Variant) -> void:
# Handmade coordinates type overloading
if not (coord is Vector2 or coord is int):
return
# Check if the gizmo can be drawn
if not _can_draw():
return
# Coordinates normalization (to vector) for horizontal or vertical gizmos
# Both axis are set to the same value, then ignore one or the other
# depending on the gizmo type
if coord is int:
coord = Vector2(coord, coord)
# Calculate the GLOBAL coordinates of the center of the square handle
# This only takes into account the node offset discarding its transform basis
# (representing rotation, skew and scale) then it applies the viewport transform
# to take into account the zoom level
var center = _target_node.get_viewport_transform() * (_target_node.get_global_transform().origin + Vector2(coord))
# Set handle color
_current_color = _color
# Highlight handle if held by the mouse click
if _is_grabbed:
_current_color = _color.lightened(0.5)
# Draw an horizontal or vertical line if the gizmo is one-dimensional
match _type:
GIZMO_VPOS:
var viewport_width = EditorInterface.get_editor_viewport_2d().size.x
center.x = viewport_width / 2
viewport.draw_line(
Vector2(0, center.y),
Vector2(viewport_width, center.y),
_current_color,
2
)
GIZMO_HPOS:
var viewport_height = EditorInterface.get_editor_viewport_2d().size.y
center.y = viewport_height / 2
viewport.draw_line(
Vector2(center.x, 0),
Vector2(center.x, viewport_height),
_current_color,
2
)
# Initialize the handle in the right position
_handle = Rect2(center - _size / 2, _size)
if show_outlines:
_draw_outlines(viewport)
_draw_gizmo(viewport)
func drag_to(pos: Vector2):
# Distance between the mouse position and the gizmo center
var d = _grab_center_pos - _grab_mouse_pos
# Gizmo center position in global coordinates
var current_gizmo_pos = pos + d
# Distance between gizmo center position in 2D world node coordinates and
# node position ignoring its transform basis (representing rotation, skew and scale)
_current_position = _target_node.get_viewport_transform().affine_inverse() * current_gizmo_pos - (target_node.get_global_transform().origin)
func release():
_is_grabbed = false
func grab(pos: Vector2):
_is_grabbed = true
_grab_mouse_pos = pos
_grab_center_pos = _handle.get_center()
func cancel():
_is_grabbed = false
func has_point(pos: Vector2):
return visible and _handle.abs().has_point(pos)
func get_position():
match _type:
GIZMO_POS:
return _current_position
GIZMO_HPOS:
return _current_position.x
GIZMO_VPOS:
return _current_position.y
#endregion

View file

@ -0,0 +1 @@
uid://71g54gbvyv7l

View file

@ -0,0 +1,278 @@
@tool
class_name PopochiuGizmoClickablePlugin
extends EditorPlugin
# TODO: move these out of the plugin and into Popochiu (enums) or PopochiuClickable
enum {
WALK_TO_POINT,
LOOK_AT_POINT,
BASELINE,
DIALOG_POS
}
# Private vars
# State
var _target_node: Node2D
var _undo: EditorUndoRedoManager
var _gizmos: Array
var _active_gizmos: Array
var _grabbed_gizmo: Gizmo2D
#region Godot ######################################################################################
func _enter_tree() -> void:
# TODO: remove the following 2 lines when the plugin is connected to the appropriate signal
# e.g. popochiu_ready
PopochiuEditorConfig.initialize_editor_settings()
PopochiuConfig.initialize_project_settings()
# Initialization of the plugin goes here.
_undo = get_undo_redo()
_gizmos.insert(WALK_TO_POINT, _init_popochiu_gizmo(WALK_TO_POINT))
_gizmos.insert(LOOK_AT_POINT, _init_popochiu_gizmo(LOOK_AT_POINT))
_gizmos.insert(BASELINE, _init_popochiu_gizmo(BASELINE))
_gizmos.insert(DIALOG_POS, _init_popochiu_gizmo(DIALOG_POS))
EditorInterface.get_editor_settings().settings_changed.connect(_on_gizmo_settings_changed)
PopochiuEditorHelper.signal_bus.gizmo_visibility_changed.connect(_on_gizmo_visibility_changed)
#endregion
#region Virtual ####################################################################################
func _edit(object: Object) -> void:
if object == null or object.get_class() == "EditorDebuggerRemoteObject":
return
_target_node = object
_active_gizmos.clear()
if EditorInterface.get_edited_scene_root() is PopochiuCharacter:
_active_gizmos.append(_gizmos[DIALOG_POS])
elif EditorInterface.get_edited_scene_root() is PopochiuRoom:
_active_gizmos.append(_gizmos[WALK_TO_POINT])
_active_gizmos.append(_gizmos[LOOK_AT_POINT])
_active_gizmos.append(_gizmos[BASELINE])
for gizmo in _active_gizmos:
gizmo.set_target_node(_target_node)
if not EditorInterface.get_inspector().property_edited.is_connected(_on_property_changed):
EditorInterface.get_inspector().property_edited.connect(_on_property_changed)
update_overlays()
func _forward_canvas_draw_over_viewport(viewport_control: Control) -> void:
for gizmo in _active_gizmos:
gizmo.draw(viewport_control, _target_node.get(gizmo.target_property))
func _handles(object: Object) -> bool:
return object is PopochiuClickable
func _forward_canvas_gui_input(event: InputEvent) -> bool:
if not _target_node or not _target_node.is_visible_in_tree():
return false
# For left mouse buttons, try to grab or release, depending on state
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
# Grab
if not _grabbed_gizmo and event.is_pressed():
return _try_grab_gizmo(event)
# Release
elif _grabbed_gizmo and event.is_released():
return _release_gizmo(event)
# For mouse movement, drag the grabbed gizmo
if event is InputEventMouseMotion:
return _drag_gizmo(event)
# For ESC key or comparable events, cancel the dragging if in place
if event.is_action_pressed("ui_cancel"):
return _cancel_dragging_gizmo(event)
## Nothing to handle outside the cases above
return false
#endregion
#region Private ####################################################################################
func _on_property_changed(property: String):
update_overlays()
func _on_gizmo_settings_changed() -> void:
var gizmo_id = 0
var default_font = EditorInterface.get_editor_theme().default_font
for gizmo in _gizmos:
match gizmo_id:
WALK_TO_POINT:
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
LOOK_AT_POINT:
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
BASELINE:
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_BASELINE_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
DIALOG_POS:
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
gizmo.show_connector = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_CONNECTORS)
gizmo.show_outlines = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_OUTLINE)
gizmo.show_target_name = PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_SHOW_NODE_NAME)
gizmo_id += 1
update_overlays()
func _on_gizmo_visibility_changed(gizmo_id:int, visibility:bool):
if gizmo_id < _gizmos.size():
_gizmos[gizmo_id].visible = visibility
update_overlays()
func _update_properties():
if _grabbed_gizmo and _grabbed_gizmo.target_property:
_target_node.set(
_grabbed_gizmo.target_property,
_grabbed_gizmo.get_position()
)
func _init_popochiu_gizmo(gizmo_id: int) -> Gizmo2D:
var gizmo: Gizmo2D
var default_font = EditorInterface.get_editor_theme().default_font
match gizmo_id:
WALK_TO_POINT:
gizmo = Gizmo2D.new(_target_node, "walk_to_point", "Walk To Point", Gizmo2D.GIZMO_POS)
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_WALK_TO_POINT_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
LOOK_AT_POINT:
gizmo = Gizmo2D.new(_target_node, "look_at_point", "Look At Point", Gizmo2D.GIZMO_POS)
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_LOOK_AT_POINT_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
BASELINE:
gizmo = Gizmo2D.new(_target_node, "baseline", "Baseline", Gizmo2D.GIZMO_VPOS)
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_BASELINE_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
DIALOG_POS:
gizmo = Gizmo2D.new(_target_node, "dialog_pos", "Dialog Position", Gizmo2D.GIZMO_POS)
gizmo.set_theme(
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_DIALOG_POS_COLOR),
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_HANDLER_SIZE),
default_font,
PopochiuEditorConfig.get_editor_setting(PopochiuEditorConfig.GIZMOS_FONT_SIZE)
)
return gizmo
func _try_grab_gizmo(event: InputEventMouseButton) -> bool:
# Check if the mouse click happened on a gizmo
# The order is reversed to the topmost gizmo
# (the last been drawn) is selected
for i in range(_active_gizmos.size() - 1, -1, -1):
if not _active_gizmos[i].has_point(event.position):
continue
_grabbed_gizmo = _active_gizmos[i]
break
# If user clicked on no gizmos
# ignore the event
if not _grabbed_gizmo:
return false
# hold the gizmo with the mouse
_grabbed_gizmo.grab(event.position)
_undo.create_action("Move gizmo")
_undo.add_undo_property(
_grabbed_gizmo.target_node,
_grabbed_gizmo.target_property,
_grabbed_gizmo.target_node.get(_grabbed_gizmo.target_property)
)
update_overlays()
return true
func _release_gizmo(event: InputEvent) -> bool:
# If there is no gizmo to release
# ignore the event
if not _grabbed_gizmo:
return false
_grabbed_gizmo.release()
_undo.add_do_property(
_grabbed_gizmo.target_node,
_grabbed_gizmo.target_property,
_grabbed_gizmo.target_node.get(_grabbed_gizmo.target_property)
)
_undo.commit_action()
update_overlays()
_grabbed_gizmo = null
return true
func _drag_gizmo(event: InputEvent) -> bool:
# If no gizmo to drag
# ignore the event
if not _grabbed_gizmo:
return false
# Drag the gizmo
_grabbed_gizmo.drag_to(event.position)
_update_properties()
update_overlays()
return true
func _cancel_dragging_gizmo(event: InputEvent) -> bool:
# If ESC/Cancel happens but we're not dragging
# ignore the event
if not _grabbed_gizmo:
return false
# Cancel the action
_grabbed_gizmo.cancel()
_undo.commit_action()
_undo.get_history_undo_redo(_undo.get_object_history_id(_grabbed_gizmo.target_node)).undo()
update_overlays()
_grabbed_gizmo = null
return true
#endregion

View file

@ -0,0 +1 @@
uid://1t0c81s6wd7c

View file

@ -0,0 +1,7 @@
[plugin]
name="PopochiuGizmoClickable"
description="Provides viewport-drawn interactive 2D gizmos to Popochiu objects in the editor."
author="Carenalgas Dev Team"
version="2.0"
script="gizmo_clickable_plugin.gd"

View file

@ -0,0 +1,323 @@
@tool
class_name PopochiuEditorHelper
extends Resource
## Utils class for Editor related things.
# ---- Strings, paths, scenes, and other values ----------------------------------------------------
const POPUPS_FOLDER = "res://addons/popochiu/editor/popups/"
const CREATE_OBJECT_FOLDER = "res://addons/popochiu/editor/popups/create_object/"
const CREATE_ROOM = preload(CREATE_OBJECT_FOLDER + "create_room/create_room.tscn")
const CREATE_CHARACTER = preload(CREATE_OBJECT_FOLDER + "create_character/create_character.tscn")
const CREATE_INVENTORY_ITEM = preload(
CREATE_OBJECT_FOLDER + "create_inventory_item/create_inventory_item.tscn"
)
const CREATE_DIALOG = preload(CREATE_OBJECT_FOLDER + "create_dialog/create_dialog.tscn")
const CREATE_PROP = preload(CREATE_OBJECT_FOLDER + "create_prop/create_prop.tscn")
const CREATE_HOTSPOT = preload(CREATE_OBJECT_FOLDER + "create_hotspot/create_hotspot.tscn")
const CREATE_WALKABLE_AREA = preload(
CREATE_OBJECT_FOLDER + "create_walkable_area/create_walkable_area.tscn"
)
const CREATE_REGION = preload(CREATE_OBJECT_FOLDER + "create_region/create_region.tscn")
const CREATE_MARKER = preload(CREATE_OBJECT_FOLDER + "create_marker/create_marker.tscn")
const DELETE_CONFIRMATION_SCENE = preload(
POPUPS_FOLDER + "delete_confirmation/delete_confirmation.tscn"
)
const PROGRESS_DIALOG_SCENE = preload(POPUPS_FOLDER + "progress/progress.tscn")
const SETUP_SCENE = preload("res://addons/popochiu/editor/popups/setup/setup.tscn")
# ---- Identifiers ---------------------------------------------------------------------------------
const POPOCHIU_OBJECT_POLYGON_GROUP = "popochiu_object_polygon"
const MIGRATIONS_PANEL_SCENE = preload(
"res://addons/popochiu/editor/popups/migrations_panel/migrations_panel.tscn"
)
# ---- Classes -------------------------------------------------------------------------------------
const PopochiuSignalBus = preload("res://addons/popochiu/editor/helpers/popochiu_signal_bus.gd")
const DeleteConfirmation = preload(POPUPS_FOLDER + "delete_confirmation/delete_confirmation.gd")
const Progress = preload(POPUPS_FOLDER + "progress/progress.gd")
const CreateObject = preload(CREATE_OBJECT_FOLDER + "create_object.gd")
const MigrationsPanel = preload(
"res://addons/popochiu/editor/popups/migrations_panel/migrations_panel.gd"
)
static var signal_bus := PopochiuSignalBus.new()
static var ei := EditorInterface
static var undo_redo: EditorUndoRedoManager = null
static var dock: Panel = null
static var _room_scene_path_template := PopochiuResources.ROOMS_PATH.path_join("%s/room_%s.tscn")
#region Public #####################################################################################
static func select_node(node: Node) -> void:
ei.get_selection().clear()
ei.get_selection().add_node(node)
static func show_popup(popup_name: String) -> void:
PopochiuUtils.print_normal(popup_name)
static func add_resource_to_popochiu(target: String, resource: Resource) -> int:
return PopochiuResources.set_data_value(target, resource.script_name, resource.resource_path)
static func show_delete_confirmation(
content: DeleteConfirmation, min_size := Vector2i(640, 160)
) -> void:
var dialog := ConfirmationDialog.new()
dialog.title = content.title
dialog.confirmed.connect(
func () -> void:
if content.on_confirmed:
content.on_confirmed.call()
dialog.queue_free()
)
dialog.canceled.connect(
func () -> void:
if content.on_canceled:
content.on_canceled.call()
dialog.queue_free()
)
dialog.about_to_popup.connect(content.on_about_to_popup)
dialog.add_child(content)
await show_dialog(dialog, min_size)
static func show_progress(min_size := Vector2i(640, 80)) -> Progress:
var dialog := AcceptDialog.new()
var content: Progress = PROGRESS_DIALOG_SCENE.instantiate()
dialog.borderless = true
dialog.add_child(content)
dialog.get_ok_button().hide()
await show_dialog(dialog, min_size)
return content
static func show_creation_popup(scene: PackedScene, min_size := Vector2i(640, 180)) -> void:
var content: CreateObject = scene.instantiate()
var dialog := ConfirmationDialog.new()
content.content_changed.connect(
func () -> void:
content.custom_minimum_size = content.get_child(0).size
content.size = content.get_child(0).size
dialog.reset_size()
dialog.move_to_center()
)
dialog.confirmed.connect(content.create)
dialog.canceled.connect(dialog.queue_free)
dialog.about_to_popup.connect(content.on_about_to_popup)
dialog.add_child(content)
await show_dialog(dialog, min_size)
dialog.register_text_enter(content.input)
static func show_setup(is_welcome := false) -> void:
var dialog := ConfirmationDialog.new()
var content := SETUP_SCENE.instantiate()
dialog.title = "Setup"
dialog.confirmed.connect(content.on_close)
dialog.close_requested.connect(content.on_close)
dialog.about_to_popup.connect(content.on_about_to_popup)
dialog.add_child(content)
dock.add_child.call_deferred(dialog)
await dialog.ready
content.define_content(is_welcome)
content.size_calculated.connect(
func () -> void:
dialog.reset_size()
dialog.move_to_center()
)
await show_dialog(dialog, content.custom_minimum_size)
static func show_migrations(
content: MigrationsPanel, min_size := Vector2i(640, 640)
) -> AcceptDialog:
var dialog := AcceptDialog.new()
dialog.title = "Migration Tool"
content.anchors_preset = Control.PRESET_FULL_RECT
dialog.add_child(content)
await show_dialog(dialog, min_size)
return dialog
static func show_dialog(dialog: Window, min_size := Vector2i.ZERO) -> void:
if not dialog.is_inside_tree():
dock.add_child.call_deferred(dialog)
await dialog.ready
dialog.popup_centered(min_size * EditorInterface.get_editor_scale())
# Type-checking functions
static func is_popochiu_clickable(node: Node) -> bool:
return node is PopochiuCharacter \
or node is PopochiuProp \
or node is PopochiuHotspot
static func is_popochiu_object(node: Node) -> bool:
return node is PopochiuRoom \
or is_popochiu_room_object(node)
static func is_popochiu_room_object(node: Node) -> bool:
return node is PopochiuCharacter \
or node is PopochiuProp \
or node is PopochiuHotspot \
or node is PopochiuWalkableArea \
or node is PopochiuRegion
static func is_room(node: Node) -> bool:
return node is PopochiuRoom
static func is_character(node: Node) -> bool:
return node is PopochiuCharacter
static func is_prop(node: Node) -> bool:
return node is PopochiuProp
static func is_hotspot(node: Node) -> bool:
return node is PopochiuHotspot
static func is_walkable_area(node: Node) -> bool:
return node is PopochiuWalkableArea
static func is_region(node: Node) -> bool:
return node is PopochiuRegion
static func is_marker(node: Node) -> bool:
return node is Marker2D
static func is_popochiu_obj_polygon(node: Node):
return node.is_in_group(POPOCHIU_OBJECT_POLYGON_GROUP)
# Context-checking functions
static func is_editing_room() -> bool:
# If the open scene in the editor is a PopochiuRoom, return true
return is_room(ei.get_edited_scene_root())
# Quick-access functions
static func get_first_child_by_group(node: Node, group: StringName) -> Node:
if (node == null):
return null
for n in node.get_children():
if n.is_in_group(group):
return n
return null
static func get_all_children(node, children := []) -> Array:
if node == null:
return []
children.push_back(node)
for child in node.get_children():
children = get_all_children(child, children)
return children
## Overrides the font [param font_name] in [param node] by the theme [Font] identified by
## [param editor_font_name].
static func override_font(node: Control, font_name: String, editor_font_name: String) -> void:
node.add_theme_font_override(font_name, node.get_theme_font(editor_font_name, "EditorFonts"))
static func frame_processed() -> void:
await EditorInterface.get_base_control().get_tree().process_frame
static func secs_passed(secs := 1.0) -> void:
await EditorInterface.get_base_control().get_tree().create_timer(secs).timeout
static func filesystem_scanned() -> void:
EditorInterface.get_resource_filesystem().scan.call_deferred()
await EditorInterface.get_resource_filesystem().filesystem_changed
static func pack_scene(node: Node, path := "") -> int:
var packed_scene := PackedScene.new()
packed_scene.pack(node)
if path.is_empty():
path = node.scene_file_path
return ResourceSaver.save(packed_scene, path)
## Helper function to recursively remove all folders and files inside [param folder_path].
static func remove_recursive(folder_path: String) -> bool:
if DirAccess.dir_exists_absolute(folder_path):
# Delete subfolders and their contents recursively in folder_path
for subfolder_path: String in get_absolute_directory_paths_at(folder_path):
remove_recursive(subfolder_path)
# Delete all files in folder_path
for file_path: String in get_absolute_file_paths_at(folder_path):
if DirAccess.remove_absolute(file_path) != OK:
return false
# Once all files are deleted in folder_path, remove folder_path
if DirAccess.remove_absolute(folder_path) != OK:
return false
return true
## Helper function to get the absolute directory paths for all folders under [param folder_path].
static func get_absolute_directory_paths_at(folder_path: String) -> Array:
var dir_array : PackedStringArray = []
if DirAccess.dir_exists_absolute(folder_path):
for folder in DirAccess.get_directories_at(folder_path):
dir_array.append(folder_path.path_join(folder))
return Array(dir_array)
## Helper function to get the absolute file paths for all files under [param folder_path].
static func get_absolute_file_paths_at(folder_path: String) -> PackedStringArray:
var file_array : PackedStringArray = []
if DirAccess.dir_exists_absolute(folder_path):
for file in DirAccess.get_files_at(folder_path):
file_array.append(folder_path.path_join(file))
return file_array
## Returns an array of [PopochiuRoom] (instances) for all the rooms in the project.
static func get_rooms() -> Array[PopochiuRoom]:
var rooms: Array[PopochiuRoom] = []
rooms.assign(PopochiuResources.get_section_keys("rooms").map(
func (room_name: String) -> PopochiuRoom:
var scene_path := _room_scene_path_template.replace("%s", room_name.to_snake_case())
return (load(scene_path) as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
))
return rooms
#endregion

View file

@ -0,0 +1 @@
uid://bgogdq51wvqyn

View file

@ -0,0 +1,359 @@
@tool
extends Resource
## Helper class for operations related to the GUI templates
static var _template_id := ""
static var _template_theme_path := ""
#region Public #####################################################################################
## Creates a copy of the selected template, including its components. Also generate the necessary
## scripts to define custom logic for the graphical interface and its commands.
static func copy_gui_template(
template_name: String, on_progress: Callable, on_complete: Callable
) -> void:
if (
DirAccess.dir_exists_absolute(PopochiuResources.GUI_GAME_FOLDER)
and template_name == PopochiuResources.get_data_value("ui", "template", "")
):
PopochiuUtils.print_normal("No changes in GUI template.")
on_complete.call()
return
on_progress.call(0, "Starting GUI template application")
_template_theme_path = ""
var scene_path := PopochiuResources.GUI_CUSTOM_SCENE
var commands_template_path := PopochiuResources.GUI_CUSTOM_TEMPLATE
_template_id = template_name.to_snake_case()
if _template_id != PopochiuResources.GUI_CUSTOM:
scene_path = PopochiuResources.GUI_TEMPLATES_FOLDER.path_join(
"%s/%s_gui.tscn" % [_template_id, _template_id]
)
commands_template_path = PopochiuResources.GUI_SCRIPT_TEMPLATES_FOLDER.path_join(
"%s_commands_template.gd" % _template_id
)
var script_path := PopochiuResources.GUI_GAME_SCENE.replace(".tscn", ".gd")
await _wait()
on_progress.call(5, "Creating Graphic Interface scene")
# ---- Make a copy of the selected GUI template ------------------------------------------------
if _create_scene(scene_path) != OK:
PopochiuUtils.print_error("Couldn't create %s file" % PopochiuResources.GUI_GAME_SCENE)
return
await _wait(2.0)
on_progress.call(10, "Copying a bunch of components")
# Copy the components used by the GUI template to the res://game/gui/components
# folder so devs can play with them freely -----------------------------------------------------
await copy_components(scene_path, true)
on_progress.call(60, "Creating scripts")
# Create a copy of the corresponding commands template -----------------------------------------
_copy_commands_and_gui_scripts(
commands_template_path, PopochiuResources.GUI_COMMANDS, script_path, scene_path
)
await _wait(1.5)
on_progress.call(80, "Assigning scripts")
# Update the script of the created gui.tscn so it uses the copy created above ------------------
if _update_scene_script(script_path) != OK:
PopochiuUtils.print_error("Couldn't update gui.tscn script")
return
await _wait()
on_progress.call(90, "Updating config file")
# Update the info related to the GUI template and the GUI commands script
# in the popochiu_data.cfg file ----------------------------------------------------------------
PopochiuResources.set_data_value("ui", "template", template_name)
await _wait(0.8)
on_progress.call(100, "All in place. Thanks for your patience.")
PopochiuUtils.print_normal("[wave]Selected GUI template successfully applied[/wave]")
await _wait()
await PopochiuEditorHelper.filesystem_scanned()
on_complete.call()
static func copy_component(source_scene_path: String) -> String:
var file_name := source_scene_path.get_file()
var source_folder := source_scene_path.get_base_dir()
var target_folder := source_folder.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
target_folder = PopochiuResources.GUI_GAME_FOLDER + target_folder
if not DirAccess.dir_exists_absolute(target_folder):
DirAccess.make_dir_recursive_absolute(target_folder)
# Make a copy of the component and save it in the graphic interface components folder
var component_instance := (load(source_scene_path) as PackedScene).instantiate()
var target_scene_file := "%s/%s" % [target_folder, file_name]
var packed_scene := PackedScene.new()
packed_scene.pack(component_instance)
packed_scene.resource_path = target_scene_file
var err := ResourceSaver.save(packed_scene, target_scene_file)
if err != OK:
PopochiuUtils.print_error(
"Couldn't create instance of %s component. Error code: %d." % [
file_name.get_slice(".", 0).capitalize(),
err
]
)
return ""
# Move the dependencies of the source scene to the graphic interface folder
await copy_components(source_scene_path)
return target_scene_file
## Makes a copy of the components used by the original GUI template to the
## **res://game/gui/components/** folder so devs can play with those scenes without
## affecting the ones in the plugin's folder.
static func copy_components(source_scene_path: String, is_gui_game_scene := false) -> void:
var dependencies_to_update: Array[Dictionary] = []
# Make a copy of the dependencies of the graphic interface
for dep: String in ResourceLoader.get_dependencies(source_scene_path):
var source_file_path := dep.get_slice("::", 2)
if (
is_gui_game_scene and source_file_path.get_extension() == "gd"
and source_scene_path.get_base_dir() == source_file_path.get_base_dir()
):
# Ignore the script of the GUI template scene file
continue
var source_file_uid := ResourceUID.id_to_text(
ResourceLoader.get_resource_uid(source_file_path)
)
var dependency_data := {
source_path = source_file_path,
source_uid = source_file_uid,
target_path = "",
}
# ---- Create the folder of the file -------------------------------------------------------
var file_name := source_file_path.get_file()
var source_folder := source_file_path.get_base_dir()
var target_folder := source_folder.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
target_folder = target_folder.replace("templates/%s/" % _template_id, "")
target_folder = PopochiuResources.GUI_GAME_FOLDER + target_folder
dependency_data.target_path = "%s/%s" % [target_folder, file_name]
# Make sure all the copied components share the copied GUI theme
if source_file_path.get_extension() == "tres" and "_theme" in source_file_path:
if is_gui_game_scene and _template_theme_path.is_empty():
# Change the name of the GUI template theme so it differs from the original one
dependency_data.target_path = "%s/%s" % [
target_folder, "gui_theme.tres"
]
_template_theme_path = dependency_data.target_path
elif not is_gui_game_scene:
dependency_data.target_path = _template_theme_path
dependencies_to_update.append(dependency_data)
if FileAccess.file_exists(dependency_data.target_path):
# Ignore any file that has already been copied
continue
if not DirAccess.dir_exists_absolute(target_folder):
DirAccess.make_dir_recursive_absolute(target_folder)
# --- Make a copy of the original file -----------------------------------------------------
if source_file_path.get_extension() == "gd":
_copy_component_script(source_file_path, dependency_data.target_path)
else:
_copy_file(source_file_path, target_folder, dependency_data.target_path)
EditorInterface.get_resource_filesystem().scan()
await EditorInterface.get_resource_filesystem().filesystem_changed
# Repeat the process for each of the dependencies of the .tscn resources
if source_file_path.get_extension() == "tscn":
await copy_components(source_file_path)
if is_gui_game_scene:
_update_dependencies(PopochiuResources.GUI_GAME_SCENE, dependencies_to_update)
else:
var game_scene_path := source_scene_path.replace(PopochiuResources.GUI_ADDON_FOLDER, "")
game_scene_path = game_scene_path.replace("templates/%s/" % _template_id, "")
game_scene_path = PopochiuResources.GUI_GAME_FOLDER + game_scene_path
_update_dependencies(game_scene_path, dependencies_to_update)
EditorInterface.get_resource_filesystem().scan()
await EditorInterface.get_resource_filesystem().filesystem_changed
#endregion
#region Private ####################################################################################
## Create the **gui.tscn** file as a copy of the selected GUI template scene.
## If a template change is being made, all components of the previous template are removed along
## with the **.tscn** file before copying the new one.
static func _create_scene(scene_path: String) -> int:
# Create the res://game/gui/ folder
if not FileAccess.file_exists(PopochiuResources.GUI_GAME_SCENE):
DirAccess.make_dir_recursive_absolute(PopochiuResources.GUI_GAME_FOLDER)
else:
# Remove the gui.tscn file
DirAccess.remove_absolute(PopochiuResources.GUI_GAME_SCENE)
EditorInterface.get_resource_filesystem().scan()
for dir_name: String in DirAccess.get_directories_at(PopochiuResources.GUI_GAME_FOLDER):
_remove_components(PopochiuResources.GUI_GAME_FOLDER + dir_name)
# Make a copy of the selected GUI template (.tscn) and save it in
# res://game/gui/gui.tscn ------------------------------------------
var gi_scene := load(scene_path).duplicate(true)
gi_scene.resource_path = PopochiuResources.GUI_GAME_SCENE
return ResourceSaver.save(gi_scene, PopochiuResources.GUI_GAME_SCENE)
static func _remove_components(dir_path: String) -> void:
for file_name: String in DirAccess.get_files_at(dir_path):
DirAccess.remove_absolute(dir_path.path_join(file_name))
EditorInterface.get_resource_filesystem().scan()
for dir_name: String in DirAccess.get_directories_at(dir_path):
var sub_dir_path := dir_path.path_join(dir_name)
_remove_components(sub_dir_path)
# Once the directory is empty, remove it
DirAccess.remove_absolute(dir_path)
EditorInterface.get_resource_filesystem().scan()
## Makes a copy of a GUI component's script.
static func _copy_component_script(
source_file_path: String, target_file_path: String
) -> void:
# Make a copy of the original script -----------------------------------------------------------
var source_file := FileAccess.open(source_file_path, FileAccess.READ)
var source_code := source_file.get_as_text()
source_file.close()
if "class_name " in source_code:
source_code = source_code.replace("class_name Popochiu", "class_name GUI")
var file_write := FileAccess.open(target_file_path, FileAccess.WRITE)
file_write.store_string(source_code)
file_write.close()
# Create a script for devs to overwrite the functionality of the original's copy script --------
file_write = FileAccess.open(target_file_path.replace(".gd", "_custom.gd"), FileAccess.WRITE)
if "@tool" in source_code:
file_write.store_string("@tool\n")
file_write.store_string('extends "%s"' % target_file_path.get_file())
static func _copy_file(
source_file_path: String, target_folder: String, target_file_path: String
) -> void:
# ---- Create a copy of the scene file ---------------------------------------------------------
if source_file_path.get_extension() in ["tscn", "tres"]:
var file_resource := load(source_file_path).duplicate(true)
file_resource.resource_path = target_file_path
if ResourceSaver.save(file_resource, target_file_path) != OK:
DirAccess.remove_absolute(target_folder)
else:
DirAccess.copy_absolute(source_file_path, target_file_path)
## Replace the UID and paths of the components in the graphic interface scene
static func _update_dependencies(scene_path: String, dependencies_to_update: Array) -> void:
if dependencies_to_update.is_empty():
return
# ---- Update the UID and paths of the copied components ---------------------------------------
var file_read = FileAccess.open(scene_path, FileAccess.READ)
var text := file_read.get_as_text()
file_read.close()
for dic: Dictionary in dependencies_to_update:
var target_path: String = dic.target_path
if ".gd" in target_path:
target_path = target_path.replace(".gd", "_custom.gd")
text = text.replace(dic.source_path, target_path)
var target_uid := ResourceUID.id_to_text(ResourceLoader.get_resource_uid(target_path))
if "invalid" in target_uid: continue
text = text.replace(dic.source_uid, target_uid)
var file_write = FileAccess.open(scene_path, FileAccess.WRITE)
file_write.store_string(text)
file_write.close()
## Copy the commands and graphic interface scripts of the chosen GUI template. The new graphic
## interface scripts inherits from the one originally assigned to the .tscn file of the selected
## template.
static func _copy_commands_and_gui_scripts(
commands_template_path: String, commands_path: String, script_path: String, scene_path: String
) -> void:
DirAccess.copy_absolute(commands_template_path, commands_path)
# Create a copy of the graphic interface script template ---------------------------------------
var template_path := (
PopochiuResources.GUI_SCRIPT_TEMPLATES_FOLDER + "gui_template.gd"
)
var script_file := FileAccess.open(template_path, FileAccess.READ)
var source_code := script_file.get_as_text()
script_file.close()
source_code = source_code.replace(
"extends PopochiuGraphicInterface",
'extends "%s"' % scene_path.replace(".tscn", ".gd")
)
script_file = FileAccess.open(script_path, FileAccess.WRITE)
script_file.store_string(source_code)
script_file.close()
## Updates the script of the created [b]res://game/gui/gui.tscn[/b] file so it uses the one created
## in [method _copy_commands_and_gui_scripts].
static func _update_scene_script(script_path: String) -> int:
# Update the script of the GUI -----------------------------------------------------------------
var scene := (load(
PopochiuResources.GUI_GAME_SCENE
) as PackedScene).instantiate()
scene.set_script(load(script_path))
# Set the name of the root node
scene.name = "GUI"
var packed_scene: PackedScene = PackedScene.new()
packed_scene.pack(scene)
packed_scene.resource_path = PopochiuResources.GUI_GAME_SCENE
return ResourceSaver.save(packed_scene, PopochiuResources.GUI_GAME_SCENE)
static func _wait(max := 1.0) -> void:
await PopochiuEditorHelper.secs_passed(randf_range(0.5, max))
#endregion

View file

@ -0,0 +1 @@
uid://cn6emnkso14qe

View file

@ -0,0 +1,9 @@
extends RefCounted
## Helper Editor class to emit and connect to signals across different components in the plugin
signal main_scene_changed(scene_path: String)
signal pc_changed(script_name: String)
signal audio_cues_deleted(cue_file_paths: Array)
signal main_object_added(type: int, name_to_add: String)
signal gizmo_visibility_changed(gizmo: int, visible: bool)
signal migrations_done

View file

@ -0,0 +1 @@
uid://p35cn6n5xi6r

View file

@ -0,0 +1,390 @@
@tool
# This logic has been taken almost as-is from Vinicius Gerevini's
# Aseprite Wizard plugin. Credits goes to him for the real magic.
# See: https://godotengine.org/asset-library/asset/713
extends RefCounted
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
const _DEFAULT_AL = "" # Empty string equals default "Global" animation library
# Vars configured on initialization
var _file_system: EditorFileSystem
var _aseprite: RefCounted
# Vars configured on animations creation
var _target_node: Node
var _player: AnimationPlayer
var _options: Dictionary
# Class-logic vars
var _spritesheet_metadata = {}
var _target_sprite: Sprite2D
var _output: Dictionary
#region Public #####################################################################################
func init(aseprite: RefCounted, editor_file_system: EditorFileSystem = null):
_file_system = editor_file_system
_aseprite = aseprite
## Public interfaces, dedicated to specific popochiu objects
func create_character_animations(character: Node, player: AnimationPlayer, options: Dictionary):
# Chores
_target_node = character
_player = player
_options = options
# Duly check everything is valid and cleanup animations
var result = _perform_common_checks()
if result != RESULT_CODE.SUCCESS:
return result
# Create the spritesheet
result = await _create_spritesheet_from_file()
if result != RESULT_CODE.SUCCESS:
return result
# Load tags information
result = await _load_spritesheet_metadata()
if result != RESULT_CODE.SUCCESS:
return result
# Set the texture in the sprite and configure
# the animations in the AnimationPlayer
_setup_texture()
result = _configure_animations()
return result
func create_prop_animations(prop: Node, aseprite_tag: String, options: Dictionary):
# Chores
_target_node = prop
# TODO: if the prop has no AnimationPlayer, add one!
_player = prop.get_node("AnimationPlayer")
_options = options
var prop_animation_name = aseprite_tag.to_snake_case()
# Duly check everything is valid and cleanup animations
var result = _perform_common_checks()
if result != RESULT_CODE.SUCCESS:
return result
# Create the spritesheet
result = await _create_spritesheet_from_tag(aseprite_tag)
if result != RESULT_CODE.SUCCESS:
return result
# Load tags information
result = await _load_spritesheet_metadata(aseprite_tag)
if result != RESULT_CODE.SUCCESS:
return result
# Set the texture in the sprite and configure
# the animations in the AnimationPlayer
_setup_texture()
result = _configure_animations()
# Sorry, mom...
_player.autoplay = prop.name.to_snake_case()
return result
#endregion
#region Private ####################################################################################
## This function creates a spritesheet with the whole file content
func _create_spritesheet_from_file():
## TODO: See _aseprite.export_layer() when the time comes to add layers selection
_output = _aseprite.export_file(_options.source, _options.output_folder, _options)
if _output.is_empty():
return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED
return RESULT_CODE.SUCCESS
## This function creates a spritesheet with the frames of a specific tag
## WARNING: it's case sensitive
func _create_spritesheet_from_tag(selected_tag: String):
## TODO: See _aseprite.export_layer() when the time comes to add layers selection
_output = _aseprite.export_tag(_options.source, selected_tag, _options.output_folder, _options)
if _output.is_empty():
return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED
return RESULT_CODE.SUCCESS
func _load_spritesheet_metadata(selected_tag: String = ""):
_spritesheet_metadata = {
tags = {},
frames = {},
meta = {},
sprite_sheet = {}
}
# Refresh filesystem
await _scan_filesystem()
# Collect all needed info
var source_file = _output.data_file
var sprite_sheet = _output.sprite_sheet
# Try to access, decode and validate Aseprite JSON output
var file = FileAccess.open(source_file, FileAccess.READ)
if file == null:
return file.get_open_error()
var test_json_conv = JSON.new()
test_json_conv.parse(file.get_as_text())
var content = test_json_conv.get_data()
if not _aseprite.is_valid_spritesheet(content):
return RESULT_CODE.ERR_INVALID_ASEPRITE_SPRITESHEET
# Save image metadata from JSON data
_spritesheet_metadata.meta = content.meta
# Save frames metadata from JSON data
_spritesheet_metadata.frames = _aseprite.get_content_frames(content)
# Save tags metadata, starting from user's selection, and retrieving
# other information from JSON data
var tags = _options.get("tags").filter(func(tag): return tag.get("import"))
for t in tags:
# If a tag is specified, ignore every other ones
if not selected_tag.is_empty() and selected_tag != t.tag_name: continue
# Create a lookup table for tags
_spritesheet_metadata.tags[t.tag_name] = t
for ft in _aseprite.get_content_meta_tags(content):
if not _spritesheet_metadata.tags.has(ft.name): continue
_spritesheet_metadata.tags.get(ft.name).merge({
from = ft.from,
to = ft.to,
direction = ft.direction,
})
# If a tag is specified, the tags lookup table should contain
# a single tag information. In this case the to and from properties
# must be shifted back in the [1 - tag_length] range.
if not selected_tag.is_empty():
# Using a temp variable to make this readable
var t = _spritesheet_metadata.tags[selected_tag]
# NOTE: imagine this goes from 34 to 54, we need to shift
# the range back of a 33 amount, so it goes from 1 to (54 - 33)
t.to = t.to - t.from + 1
t.from = 0
_spritesheet_metadata.tags[selected_tag] = t
# Save spritesheet path from the command output
_spritesheet_metadata.sprite_sheet = sprite_sheet
# Remove the JSON file if config says so
if PopochiuEditorConfig.should_remove_source_files():
DirAccess.remove_absolute(_output.data_file)
await _scan_filesystem()
return RESULT_CODE.SUCCESS
func _configure_animations():
if not _player.has_animation_library(_DEFAULT_AL):
_player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new())
if _spritesheet_metadata.tags.size() > 0:
var result = RESULT_CODE.SUCCESS
# RESTART_FROM_HERE: WARNING: in case of prop and inventory, the JSON file contains
# the whole set of tags, so we must take the tag.from and tag.to and remap the range
# from "1" to "tag.to +1 - tag.from + 1" (do the math an you'll see that's correct)
for tag in _spritesheet_metadata.tags.values():
var selected_frames = _spritesheet_metadata.frames.slice(tag.from, tag.to + 1) # slice is [)
result = _add_animation_frames(tag.tag_name, selected_frames, tag.direction)
if result != RESULT_CODE.SUCCESS:
break
return result
else:
return _add_animation_frames("default", _spritesheet_metadata.frames)
func _add_animation_frames(anim_name: String, frames: Array, direction = 'forward'):
# TODO: ATM there is no way to assign a walk/talk/grab/idle animation
# with a different name than the standard ones. The engine is searching for
# lowercase names in the AnimationPlayer, thus we are forcing snake_case
# animations name conversion.
# We have to add methods or properties to the Character to assign different
# animations (but maybe we can do with anim_prefix or other strategies).
var animation_name = anim_name.to_snake_case()
var is_loopable = _spritesheet_metadata.tags.get(anim_name).get("loops")
# Create animation library if it doesn't exist
# This is always true if the user selected to wipe old animations.
# See _remove_animations_from_player() function.
if not _player.has_animation_library(_DEFAULT_AL):
_player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new())
if not _player.get_animation_library(_DEFAULT_AL).has_animation(animation_name):
_player.get_animation_library(_DEFAULT_AL).add_animation(animation_name, Animation.new())
# Here is where animations are created.
# TODO: we need to "fork" the logic so that Character has a single spritesheet
# containing all tags, while Rooms/Props and Inventory Items has a single spritesheet
# for each tag, so that you can have each prop with its own animation (PnC)
var animation = _player.get_animation(animation_name)
_create_meta_tracks(animation)
var frame_track = _get_property_track_path("frame")
var frame_track_index = _create_track(_target_sprite, animation, frame_track)
if direction == 'reverse':
frames.reverse()
var animation_length = 0
for frame in frames:
var frame_key = _get_frame_key(frame)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds
if direction == 'pingpong':
frames.remove_at(frames.size() - 1)
if is_loopable:
frames.remove_at(0)
frames.reverse()
for frame in frames:
var frame_key = _get_frame_key(frame)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds
animation.length = animation_length
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
return RESULT_CODE.SUCCESS
## TODO: insert validate tokens in animation name
func _create_track(target_sprite: Node, animation: Animation, track: String):
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
track_index = animation.add_track(Animation.TYPE_VALUE)
## Here we set a label for the track in the sprite_path:property_changed format
## so that _get_property_track_path can rebuild it by naming convention
animation.track_set_path(track_index, track)
animation.track_set_interpolation_loop_wrap(track_index, false)
animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE)
return track_index
func _get_property_track_path(prop: String) -> String:
var node_path = _player.get_node(_player.root_node).get_path_to(_target_sprite)
return "%s:%s" % [node_path, prop]
func _scan_filesystem():
_file_system.scan()
await _file_system.filesystem_changed
func _remove_properties_from_path(path: NodePath) -> NodePath:
var string_path := path as String
if !(":" in string_path):
return string_path as NodePath
var property_path := path.get_concatenated_subnames() as String
string_path = string_path.substr(0, string_path.length() - property_path.length() - 1)
return string_path as NodePath
# ---- SPRITE NODE LOGIC ---------------------------------------------------------------------------
## What follow is logic specifically gathered for Sprite elements. TextureRect should
## be treated in a different way (see texture_rect_animation_creator.gd file in
## original Aseprite Wizard plugin by Vinicius Gerevini)
func _setup_texture():
# Load texture in target sprite (ignoring cache and forcing a refres)
var texture = ResourceLoader.load(
_spritesheet_metadata.sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE
)
texture.take_over_path(_spritesheet_metadata.sprite_sheet)
_target_sprite.texture = texture
if _spritesheet_metadata.frames.is_empty():
return
_target_sprite.hframes = (
_spritesheet_metadata.meta.size.w / _spritesheet_metadata.frames[0].sourceSize.w
)
_target_sprite.vframes = (
_spritesheet_metadata.meta.size.h / _spritesheet_metadata.frames[0].sourceSize.h
)
func _create_meta_tracks(animation: Animation):
var hframes_track = _get_property_track_path("hframes")
var hframes_track_index = _create_track(_target_sprite, animation, hframes_track)
animation.track_insert_key(hframes_track_index, 0, _target_sprite.hframes)
var vframes_track = _get_property_track_path("vframes")
var vframes_track_index = _create_track(_target_sprite, animation, vframes_track)
animation.track_insert_key(vframes_track_index, 0, _target_sprite.vframes)
var visible_track = _get_property_track_path("visible")
var visible_track_index = _create_track(_target_sprite, animation, visible_track)
animation.track_insert_key(visible_track_index, 0, true)
func _get_frame_key(frame: Dictionary):
return _calculate_frame_index(_target_sprite,frame)
func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int:
var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width())
var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height())
return (row * sprite.hframes) + column
func _perform_common_checks():
# Checks
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
if not FileAccess.file_exists(_options.source):
return RESULT_CODE.ERR_SOURCE_FILE_NOT_FOUND
if not DirAccess.dir_exists_absolute(_options.output_folder):
return RESULT_CODE.ERR_OUTPUT_FOLDER_NOT_FOUND
_target_sprite = _find_sprite_in_target()
if _target_sprite == null:
return RESULT_CODE.ERR_NO_SPRITE_FOUND
if typeof(_options.get("tags")) != TYPE_ARRAY:
return RESULT_CODE.ERR_TAGS_OPTIONS_ARRAY_EMPTY
if (_options.wipe_old_animations):
_remove_animations_from_player(_player)
return RESULT_CODE.SUCCESS
func _find_sprite_in_target() -> Node:
if not _target_node.has_node("Sprite2D"):
return null
return _target_node.get_node("Sprite2D")
func _remove_animations_from_player(player: AnimationPlayer):
if player.has_animation_library(_DEFAULT_AL):
player.remove_animation_library(_DEFAULT_AL)
#endregion

View file

@ -0,0 +1 @@
uid://c44sonibms74d

View file

@ -0,0 +1,249 @@
@tool
extends RefCounted
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░
func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = (
file_name if options.get('output_filename') == ""
else options.get('output_filename', file_name)
)
var basename = _get_file_basename(output_name)
var output_dir = output_folder.replace("res://", "./")
var data_file = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
if not only_visible_layers:
arguments.push_front("--all-layers")
_add_sheet_type_arguments(arguments, options)
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('[Popochiu] Aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
'data_file': data_file.replace("./", "res://"),
"sprite_sheet": sprite_sheet.replace("./", "res://")
}
func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var basename = _get_file_basename(file_name)
var layers = list_layers(file_name, only_visible_layers)
var exception_regex = _compile_regex(exception_pattern)
var output = []
for layer in layers:
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
output.push_back(export_layer(file_name, layer, output_folder, options))
return output
func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var output_prefix = options.get('output_filename', "").strip_edges()
var output_dir = output_folder.replace("res://", "./").strip_edges()
var data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name]
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
arguments.push_front(layer_name)
arguments.push_front("--layer")
_add_sheet_type_arguments(arguments, options)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('[Popochiu] Aseprite: Failed to export layer spritesheet. Command output follows:')
print(output)
return {}
return {
'data_file': data_file.replace("./", "res://"),
"sprite_sheet": sprite_sheet.replace("./", "res://")
}
# IMPROVE: See if we can extract JSON data limited to the single tag
# (so we don't have to reckon offset framerange)
func export_tag(file_name: String, tag_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var output_prefix = options.get('output_filename', "").strip_edges()
var output_dir = output_folder.replace("res://", "./").strip_edges()
var data_file = "%s/%s%s.json" % [output_dir, output_prefix, tag_name]
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, tag_name]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
arguments.push_front(tag_name)
arguments.push_front("--tag")
_add_sheet_type_arguments(arguments, options)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('[Popochiu] Aseprite: Failed to export tag spritesheet. Command output follows:')
print(output)
return {}
return {
'data_file': data_file.replace("./", "res://"),
"sprite_sheet": sprite_sheet.replace("./", "res://")
}
func list_layers(file_name: String, only_visible = false) -> Array:
var output = []
var arguments = ["-b", "--list-layers", file_name]
if not only_visible:
arguments.push_front("--all-layers")
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('[Popochiu] Aseprite: failed listing layers')
printerr(output)
return []
return _sanitize_list_output(output)
func list_tags(file_name: String) -> Array:
var output = []
var arguments = ["-b", "--list-tags", file_name]
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('[Popochiu] Aseprite: failed listing tags')
printerr(output)
return []
return _sanitize_list_output(output)
func is_valid_spritesheet(content):
return content.has("frames") and content.has("meta") and content.meta.has('image')
func get_content_frames(content):
return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values()
func get_content_meta_tags(content):
return content.meta.frameTags if content.meta.has("frameTags") else []
func check_command_path():
# On Linux, MacOS or other *nix platforms, nothing to do
if not OS.get_name() in ["Windows", "UWP"]:
return true
# On Windows, OS.Execute() calls trigger an uncatchable
# internal error if the invoked executable is not found.
# Since the error is unclear, we have to check that the aseprite
# command is given as a full path and return an error if it's not.
var regex = RegEx.new()
regex.compile("^[A-Z|a-z]:[\\\\|\\/].+\\.exe$")
return \
regex.search(_get_aseprite_command()) \
and \
FileAccess.file_exists(_get_aseprite_command())
func test_command():
var exit_code = OS.execute(_get_aseprite_command(), ['--version'], [], true)
return exit_code == 0
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String):
var layers = _get_exception_layers(file_name, exception_pattern)
if not layers.is_empty():
for l in layers:
arguments.push_front(l)
arguments.push_front('--ignore-layer')
func _add_sheet_type_arguments(arguments: Array, options : Dictionary):
var column_count : int = options.get("column_count", 0)
if column_count > 0:
arguments.push_back("--merge-duplicates") # Yes, this is undocumented
arguments.push_back("--sheet-columns")
arguments.push_back(column_count)
else:
arguments.push_back("--sheet-pack")
func _get_exception_layers(file_name: String, exception_pattern: String) -> Array:
var layers = list_layers(file_name)
var regex = _compile_regex(exception_pattern)
if regex == null:
return []
var exception_layers = []
for layer in layers:
if regex.search(layer) != null:
exception_layers.push_back(layer)
return exception_layers
func _sanitize_list_output(output) -> Array:
if output.is_empty():
return output
var raw = output[0].split('\n')
var sanitized = []
for s in raw:
sanitized.append(s.strip_edges())
return sanitized
func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
return [
"-b",
"--list-tags",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
spritesheet_path,
source_name
]
func _execute(arguments, output):
return OS.execute(_get_aseprite_command(), arguments, output, true, true)
func _get_aseprite_command() -> String:
return PopochiuEditorConfig.get_command()
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
func _compile_regex(pattern):
if pattern == "":
return
var rgx = RegEx.new()
if rgx.compile(pattern) == OK:
return rgx
printerr('[Popochiu] exception regex error')

View file

@ -0,0 +1 @@
uid://d1vhl7uqwadfx

View file

@ -0,0 +1,94 @@
@tool
extends HBoxContainer
signal tag_state_changed
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
var _anim_tag_state: Dictionary = {}
@onready var tag_name_label = $HBoxContainer/TagName
@onready var import_toggle = $Panel/HBoxContainer/Import
@onready var loops_toggle = $Panel/HBoxContainer/Loops
@onready var separator = $Panel/HBoxContainer/Separator
@onready var visible_toggle = $Panel/HBoxContainer/Visible
@onready var clickable_toggle = $Panel/HBoxContainer/Clickable
#region Godot ######################################################################################
func _ready():
# Common toggle icons
import_toggle.icon = get_theme_icon('Load', 'EditorIcons')
loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons')
# Room-related toggle icons
visible_toggle.icon = get_theme_icon('GuiVisibilityVisible', 'EditorIcons')
clickable_toggle.icon = get_theme_icon('ToolSelect', 'EditorIcons')
#endregion
#region Public #####################################################################################
func init(tag_cfg: Dictionary):
if tag_cfg.tag_name == null or tag_cfg.tag_name == "":
printerr(RESULT_CODE.get_error_message(RESULT_CODE.ERR_UNNAMED_TAG_DETECTED))
return false
_anim_tag_state = _load_default_tag_state()
_anim_tag_state.merge(tag_cfg, true)
_setup_scene()
func show_prop_buttons():
separator.visible = true
visible_toggle.visible = true
clickable_toggle.visible = true
#endregion
#region SetGet #####################################################################################
func get_cfg() -> Dictionary:
return _anim_tag_state
#endregion
#region Private ####################################################################################
func _setup_scene():
import_toggle.button_pressed = _anim_tag_state.import
loops_toggle.button_pressed = _anim_tag_state.loops
tag_name_label.text = _anim_tag_state.tag_name
visible_toggle.button_pressed = _anim_tag_state.prop_visible
clickable_toggle.button_pressed = _anim_tag_state.prop_clickable
emit_signal("tag_state_changed")
func _load_default_tag_state() -> Dictionary:
return {
"tag_name": "",
"import": PopochiuConfig.is_default_animation_import_enabled(),
"loops": PopochiuConfig.is_default_animation_loop_enabled(),
"prop_visible": PopochiuConfig.is_default_animation_prop_visible(),
"prop_clickable": PopochiuConfig.is_default_animation_prop_clickable(),
}
func _on_import_toggled(button_pressed):
_anim_tag_state.import = button_pressed
emit_signal("tag_state_changed")
func _on_loops_toggled(button_pressed):
_anim_tag_state.loops = button_pressed
emit_signal("tag_state_changed")
func _on_visible_toggled(button_pressed):
_anim_tag_state.prop_visible = button_pressed
emit_signal("tag_state_changed")
func _on_clickable_toggled(button_pressed):
_anim_tag_state.prop_clickable = button_pressed
emit_signal("tag_state_changed")
#endregion

View file

@ -0,0 +1 @@
uid://krf8u35pkjn3

View file

@ -0,0 +1,94 @@
[gd_scene load_steps=6 format=3 uid="uid://rphyltbm12m4"]
[ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd" id="1"]
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_77wem"]
[sub_resource type="Image" id="Image_vdhps"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_c80ss"]
image = SubResource("Image_vdhps")
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sd1l8"]
[node name="AnimationTagRow" type="HBoxContainer"]
offset_right = 320.0
offset_bottom = 20.0
script = ExtResource("1")
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="TagName" type="Label" parent="HBoxContainer"]
layout_mode = 2
text = "Tag Name"
[node name="Panel" type="Panel" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxEmpty_77wem")
[node name="HBoxContainer" type="HBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 1
anchor_left = 1.0
anchor_right = 1.0
offset_left = -60.0
offset_bottom = 20.0
grow_horizontal = 0
[node name="Visible" type="Button" parent="Panel/HBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "This prop will be visible"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Clickable" type="Button" parent="Panel/HBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "This prop will be clickable"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Separator" type="Panel" parent="Panel/HBoxContainer"]
visible = false
custom_minimum_size = Vector2(1, 0)
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_sd1l8")
[node name="Import" type="Button" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Import this animation"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[node name="Loops" type="Button" parent="Panel/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Set animation as looping"
toggle_mode = true
icon = SubResource("ImageTexture_c80ss")
flat = true
[connection signal="toggled" from="Panel/HBoxContainer/Visible" to="." method="_on_visible_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Clickable" to="." method="_on_clickable_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Import" to="." method="_on_import_toggled"]
[connection signal="toggled" from="Panel/HBoxContainer/Loops" to="." method="_on_loops_toggled"]

View file

@ -0,0 +1,429 @@
@tool
extends PanelContainer
# TODO: review coding standards for those constants
const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd")
const LOCAL_OBJ_CONFIG = preload("res://addons/popochiu/editor/config/local_obj_config.gd")
# TODO: this can be specialized, even if for a two buttons... ?
const AnimationTagRow =\
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd")
var scene: Node
var target_node: Node
var file_system: EditorFileSystem
# ---- External logic
var _animation_tag_row_scene: PackedScene =\
preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn")
var _aseprite = preload("../aseprite_controller.gd").new() ## TODO: should be absolute?
# ---- References for children scripts
var _root_node: Node
var _options: Dictionary
# ---- Importer parameters variables
var _source: String = ""
var _tags_cache: Array = []
var _file_dialog_aseprite: FileDialog
var _output_folder_dialog: FileDialog
var _importing := false
var _output_folder := ""
var _out_folder_default := "[Same as scene]"
#region Godot ######################################################################################
func _ready():
_set_elements_styles()
if not PopochiuEditorConfig.aseprite_importer_enabled():
_show_info()
return
# Check access to Aseprite executable
var result = _check_aseprite()
if result == RESULT_CODE.SUCCESS:
_show_importer()
else:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_warning()
# Load inspector dock configuration from node
var cfg = LOCAL_OBJ_CONFIG.load_config(target_node)
if cfg == null:
_load_default_config()
_set_options_visible(true)
else:
_load_config(cfg)
_set_tags_visible(cfg.get("tags_exp"))
_set_options_visible(cfg.get("op_exp"))
#endregion
#region Private ####################################################################################
func _check_aseprite() -> int:
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return RESULT_CODE.SUCCESS
func _list_tags(file: String):
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return _aseprite.list_tags(file)
## TODO: Currently unused. keeping this as reference
## to populate a checkable list of layers
func _list_layers(file: String, only_visibles = false):
if not _aseprite.check_command_path():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH
if not _aseprite.test_command():
return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND
return _aseprite.list_layers(file, only_visibles)
func _load_config(cfg):
if cfg.has("source"):
_set_source(cfg.source)
_output_folder = cfg.get("o_folder", "")
get_node("%OutFolderButton").text = (
_output_folder if _output_folder != "" else _out_folder_default
)
get_node("%OutFileName").text = cfg.get("o_name", "")
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(
cfg.get("only_visible_layers", false)
)
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
cfg.get("wipe_old_anims", false)
)
_set_tags_visible(cfg.get("tags_exp", false))
_set_options_visible(cfg.get("op_exp", false))
_populate_tags(cfg.get("tags", []))
func _save_config():
_update_tags_cache()
var cfg := {
"source": _source,
"tags": _tags_cache,
"tags_exp": get_node("%Tags").visible,
"op_exp": get_node("%Options").visible,
"o_folder": _output_folder,
"o_name": get_node("%OutFileName").text,
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
"wipe_old_anims": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
}
LOCAL_OBJ_CONFIG.save_config(target_node, cfg)
func _load_default_config():
# Reset variables
_source = ""
_tags_cache = []
_output_folder = ""
# Empty tags list
_empty_tags_container()
# Reset inspector fields
get_node("%SourceButton").text = "[empty]"
get_node("%SourceButton").tooltip_text = ""
get_node("%OutFolderButton").text = "[empty]"
get_node("%OutFileName").clear()
get_node("%VisibleLayersCheckButton").set_pressed_no_signal(false)
get_node("%WipeOldAnimationsCheckButton").set_pressed_no_signal(
PopochiuConfig.is_default_wipe_old_anims_enabled()
)
func _set_source(source):
_source = source
get_node("%SourceButton").text = _source
get_node("%SourceButton").tooltip_text = _source
func _on_source_pressed():
_open_source_dialog()
func _on_aseprite_file_selected(path):
_set_source(ProjectSettings.localize_path(path))
_populate_tags(_get_tags_from_source())
_save_config()
_file_dialog_aseprite.queue_free()
func _on_rescan_pressed():
_populate_tags(\
_merge_with_cache(_get_tags_from_source())\
)
_save_config()
func _on_import_pressed():
if _importing:
return
_importing = true
_root_node = get_tree().get_edited_scene_root()
if _source == "":
_show_message("Aseprite file not selected")
_importing = false
return
_options = {
"source": ProjectSettings.globalize_path(_source),
"tags": _tags_cache,
"output_folder": (
_output_folder if _output_folder != "" else _root_node.scene_file_path.get_base_dir()
),
"output_filename": get_node("%OutFileName").text,
"only_visible_layers": get_node("%VisibleLayersCheckButton").is_pressed(),
"wipe_old_animations": get_node("%WipeOldAnimationsCheckButton").is_pressed(),
}
_save_config()
func _on_reset_pressed():
var _confirmation_dialog = _show_confirmation(\
"This will reset the importer preferences." + \
"This cannot be undone! Are you sure?", "Confirmation required!")
_confirmation_dialog.get_ok_button().connect("pressed", Callable(self, "_reset_prefs_metadata"))
func _reset_prefs_metadata():
if target_node.has_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME):
target_node.remove_meta(LOCAL_OBJ_CONFIG.LOCAL_OBJ_CONFIG_META_NAME)
_load_default_config()
notify_property_list_changed()
func _open_source_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().add_child(_file_dialog_aseprite)
if _source != "":
_file_dialog_aseprite.set_current_dir(
ProjectSettings.globalize_path(
_source.get_base_dir()
)
)
_file_dialog_aseprite.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = FileDialog.ACCESS_FILESYSTEM
file_dialog.title = "Select Aseprite animation file"
file_dialog.connect("file_selected", Callable(self, "_on_aseprite_file_selected"))
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _populate_tags(tags: Array):
## reset tags container
_empty_tags_container()
# Add each tag found
for t in tags:
if t.tag_name == "":
continue
var tag_row: AnimationTagRow = _animation_tag_row_scene.instantiate()
get_node("%Tags").add_child(tag_row)
tag_row.init(t)
tag_row.connect("tag_state_changed", Callable(self, "_save_config"))
_customize_tag_ui(tag_row)
# Invoke customization hook implementable in child classes
_update_tags_cache()
func _customize_tag_ui(tagrow: AnimationTagRow):
## This can be implemented by child classes if necessary
pass
func _empty_tags_container():
# Clean the inspector tags container empty
for tl in get_node("%Tags").get_children():
get_node("%Tags").remove_child(tl)
tl.queue_free()
func _update_tags_cache():
_tags_cache = _get_tags_from_ui()
func _merge_with_cache(tags: Array) -> Array:
var tags_cache_index = {}
var result = []
for t in _tags_cache:
tags_cache_index[t.tag_name] = t
for i in tags.size():
result.push_back(
tags_cache_index[tags[i].tag_name]
if tags_cache_index.has(tags[i].tag_name)
else tags[i]
)
return result
func _get_tags_from_ui() -> Array:
var tags_list = []
for tag_row in get_node("%Tags").get_children():
var tag_row_cfg = tag_row.get_cfg()
if tag_row_cfg.tag_name == "":
continue
tags_list.push_back(tag_row_cfg)
return tags_list
func _get_tags_from_source() -> Array:
var tags_found = _list_tags(ProjectSettings.globalize_path(_source))
if typeof(tags_found) == TYPE_INT:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(tags_found))
return []
var tags_list = []
for t in tags_found:
if t == "":
continue
tags_list.push_back({
tag_name = t
})
return tags_list
func _show_message(
message: String, title: String = "", object: Object = null, method := ""
):
var warning_dialog = AcceptDialog.new()
if title != "":
warning_dialog.title = title
warning_dialog.dialog_text = message
warning_dialog.popup_window = true
var callback := Callable(warning_dialog, "queue_free")
if is_instance_valid(object) and not method.is_empty():
callback = func():
object.call(method)
warning_dialog.confirmed.connect(callback)
warning_dialog.close_requested.connect(callback)
PopochiuEditorHelper.show_dialog(warning_dialog)
func _show_confirmation(message: String, title: String = ""):
var _confirmation_dialog = ConfirmationDialog.new()
get_parent().add_child(_confirmation_dialog)
if title != "":
_confirmation_dialog.title = title
_confirmation_dialog.dialog_text = message
_confirmation_dialog.popup_centered()
_confirmation_dialog.connect("close_requested", Callable(_confirmation_dialog, "queue_free"))
return _confirmation_dialog
func _on_options_title_toggled(button_pressed):
_set_options_visible(button_pressed)
_save_config()
func _set_options_visible(is_visible):
get_node("%Options").visible = is_visible
get_node("%OptionsTitle").icon = (
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
)
func _on_tags_title_toggled(button_pressed: bool) -> void:
_set_tags_visible(button_pressed)
_save_config()
func _set_tags_visible(is_visible: bool) -> void:
get_node("%Tags").visible = is_visible
get_node("%TagsTitle").icon = (
PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.EXPANDED) if is_visible
else PopochiuEditorConfig.get_icon(PopochiuEditorConfig.Icons.COLLAPSED)
)
func _on_out_folder_pressed():
_output_folder_dialog = _create_output_folder_selection()
get_parent().add_child(_output_folder_dialog)
if _output_folder != _out_folder_default:
_output_folder_dialog.current_dir = _output_folder
_output_folder_dialog.popup_centered_ratio()
func _create_output_folder_selection():
var file_dialog = FileDialog.new()
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = FileDialog.ACCESS_RESOURCES
file_dialog.title = "Select destination folder"
file_dialog.connect("dir_selected", Callable(self, "_on_output_folder_selected"))
return file_dialog
func _on_output_folder_selected(path):
_output_folder = path
get_node("%OutFolderButton").text = (
_output_folder if _output_folder != "" else _out_folder_default
)
_output_folder_dialog.queue_free()
_save_config()
func _set_elements_styles():
# Set sections title colors according to current theme
var section_color = get_theme_color("prop_section", "Editor")
var section_style = StyleBoxFlat.new()
section_style.set_bg_color(section_color)
get_node("%TagsTitleBar").set("theme_override_styles/panel", section_style)
get_node("%OptionsTitleBar").set("theme_override_styles/panel", section_style)
# Set style of warning panel
get_node("%WarningPanel").add_theme_stylebox_override(
"panel",
get_node("%WarningPanel").get_theme_stylebox("sub_inspector_bg11", "Editor")
)
get_node("%WarningLabel").add_theme_color_override("font_color", Color("c46c71"))
func _show_info():
get_node("%Info").visible = true
get_node("%Warning").visible = false
get_node("%Importer").visible = false
func _show_warning():
get_node("%Info").visible = false
get_node("%Warning").visible = true
get_node("%Importer").visible = false
func _show_importer():
get_node("%Info").visible = false
get_node("%Warning").visible = false
get_node("%Importer").visible = true
# TODO: Introduce layer selection list, more or less as tags
#endregion

View file

@ -0,0 +1 @@
uid://c5o55inhq2abl

View file

@ -0,0 +1,225 @@
[gd_scene load_steps=4 format=3 uid="uid://bcanby6n3eahm"]
[sub_resource type="StyleBoxEmpty" id="1"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwoxk"]
bg_color = Color(0, 0, 0, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ctsm1"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(1, 0.364706, 0.364706, 1)
draw_center = false
corner_detail = 1
[node name="AsepriteImporterInspectorDock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
theme_override_styles/panel = SubResource("1")
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_top = 2
theme_override_constants/margin_bottom = 2
[node name="Importer" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="Source" type="HBoxContainer" parent="Margin/Importer"]
layout_mode = 2
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
[node name="Label" type="Label" parent="Margin/Importer/Source"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Aseprite File"
[node name="SourceButton" type="Button" parent="Margin/Importer/Source"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
[node name="RescanButton" type="Button" parent="Margin/Importer/Source"]
unique_name_in_owner = true
layout_mode = 2
text = "Rescan"
[node name="TagsTitleBar" type="PanelContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
[node name="TagsTitle" type="Button" parent="Margin/Importer/TagsTitleBar"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
toggle_mode = true
text = "Animation tags"
[node name="Tags" type="VBoxContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
[node name="OptionsTitleBar" type="PanelContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk")
[node name="OptionsTitle" type="Button" parent="Margin/Importer/OptionsTitleBar"]
unique_name_in_owner = true
layout_mode = 2
theme_override_colors/font_pressed_color = Color(0.8, 0.807843, 0.827451, 1)
toggle_mode = true
text = "Options"
[node name="Options" type="VBoxContainer" parent="Margin/Importer"]
unique_name_in_owner = true
layout_mode = 2
[node name="OutFolder" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "Location where the spritesheet file should be saved."
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFolder"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output folder"
[node name="OutFolderButton" type="Button" parent="Margin/Importer/Options/OutFolder"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "[empty]"
clip_text = true
[node name="OutFile" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name."
[node name="Label" type="Label" parent="Margin/Importer/Options/OutFile"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Output file name"
[node name="OutFileName" type="LineEdit" parent="Margin/Importer/Options/OutFile"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="VisibleLayers" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="Margin/Importer/Options/VisibleLayers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Only visible layers"
[node name="VisibleLayersCheckButton" type="CheckButton" parent="Margin/Importer/Options/VisibleLayers"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="WipeOldAnimations" type="HBoxContainer" parent="Margin/Importer/Options"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="Margin/Importer/Options/WipeOldAnimations"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
tooltip_text = "Set this to OFF if you want to add new animations on top of old ones. Anims with same name will be updated."
mouse_filter = 0
text = "Wipe old animations"
[node name="WipeOldAnimationsCheckButton" type="CheckButton" parent="Margin/Importer/Options/WipeOldAnimations"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="Import" type="Button" parent="Margin/Importer"]
layout_mode = 2
text = "Import"
[node name="Reset" type="Button" parent="Margin/Importer"]
layout_mode = 2
text = "Reset Preferences"
[node name="Warning" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Warning"]
layout_mode = 2
[node name="WarningPanel" type="Panel" parent="Margin/Warning/HBoxContainer"]
unique_name_in_owner = true
custom_minimum_size = Vector2(222, 50)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
[node name="WarningLabel" type="Label" parent="Margin/Warning/HBoxContainer/WarningPanel"]
unique_name_in_owner = true
custom_minimum_size = Vector2(0, 42)
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 6
theme_override_colors/font_color = Color(0.768627, 0.423529, 0.443137, 1)
text = "Error loading Aseprite Importer!
Check Output panel for details."
horizontal_alignment = 1
[node name="Info" type="VBoxContainer" parent="Margin"]
unique_name_in_owner = true
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="Margin/Info"]
layout_mode = 2
[node name="InfoPanel" type="Panel" parent="Margin/Info/HBoxContainer"]
custom_minimum_size = Vector2(222, 50)
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1")
[node name="InfoLabel" type="Label" parent="Margin/Info/HBoxContainer/InfoPanel"]
custom_minimum_size = Vector2(0, 42)
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
size_flags_horizontal = 3
size_flags_vertical = 6
text = "Aseprite Importer disabled.
Can be enabled in Editor Settings."
[connection signal="pressed" from="Margin/Importer/Source/SourceButton" to="." method="_on_source_pressed"]
[connection signal="pressed" from="Margin/Importer/Source/RescanButton" to="." method="_on_rescan_pressed"]
[connection signal="toggled" from="Margin/Importer/TagsTitleBar/TagsTitle" to="." method="_on_tags_title_toggled"]
[connection signal="toggled" from="Margin/Importer/OptionsTitleBar/OptionsTitle" to="." method="_on_options_title_toggled"]
[connection signal="pressed" from="Margin/Importer/Options/OutFolder/OutFolderButton" to="." method="_on_out_folder_pressed"]
[connection signal="focus_exited" from="Margin/Importer/Options/OutFile/OutFileName" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Options/VisibleLayers/VisibleLayersCheckButton" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Options/WipeOldAnimations/WipeOldAnimationsCheckButton" to="." method="_save_config"]
[connection signal="pressed" from="Margin/Importer/Import" to="." method="_on_import_pressed"]
[connection signal="pressed" from="Margin/Importer/Reset" to="." method="_on_reset_pressed"]

View file

@ -0,0 +1,55 @@
@tool
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
var _animation_player_path: String
var _animation_creator = preload(
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd"
).new()
#region Godot ######################################################################################
func _ready():
if not target_node.has_node("AnimationPlayer"):
PopochiuUtils.print_error(
RESULT_CODE.get_error_message(RESULT_CODE.ERR_NO_ANIMATION_PLAYER_FOUND)
)
return
_animation_player_path = target_node.get_node("AnimationPlayer").get_path()
# Instantiate animation creator
_animation_creator.init(_aseprite, file_system)
super()
#endregion
#region Private ####################################################################################
func _on_import_pressed():
# Set everything up
# This will populate _root_node and _options class variables
super()
if _animation_player_path == "" or not _root_node.has_node(_animation_player_path):
_show_message("AnimationPlayer not found")
_importing = false
return
var result = await _animation_creator.create_character_animations(
target_node, _root_node.get_node(_animation_player_path), _options
)
_importing = false
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_message("Some errors occurred. Please check output panel.", "Warning!")
else:
_show_message("%d animation tags processed." % [_tags_cache.size()], "Done!")
func _customize_tag_ui(tag_row: AnimationTagRow):
# Nothing special has to be done for Character tags
pass
#endregion

View file

@ -0,0 +1,117 @@
@tool
extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd"
var _animation_creator = preload(\
"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new()
#region Godot ######################################################################################
func _ready():
# Instantiate animation creator
_animation_creator.init(_aseprite, file_system)
super()
#endregion
#region Private ####################################################################################
func _on_import_pressed():
# Set everything up
# This will populate _root_node and _options class variables
super()
var props_container = _root_node.get_node("Props")
var result: int = RESULT_CODE.SUCCESS
# Create a prop for each tag that must be imported
# and populate it with the right sprite
for tag in _options.get("tags"):
# Ignore unwanted tags
if not tag.import: continue
# Always convert to PascalCase as a standard
# TODO: check Godot 4 standards, I can't find info
var prop_name: String = tag.tag_name.to_pascal_case()
# In case the prop is there, use the one we already have
var prop = props_container.get_node_or_null(prop_name)
if prop == null:
# Create a new prop if necessary, specifying the
# interaction flags.
prop = _create_prop(prop_name, tag.prop_clickable, tag.prop_visible)
else:
# Force flags (a bit redundant but they may have been changed
# in the Importer interface, for already imported props)
prop.clickable = tag.prop_clickable
prop.visible = tag.prop_visible
prop.set_meta("ANIM_NAME", tag.tag_name)
for prop in props_container.get_children():
if not prop.has_meta("ANIM_NAME"): continue
# TODO: check if animation player exists in prop, if not add it
# same for Sprite2D even if it should be there...
# Make the output folder match the prop's folder
_options.output_folder = prop.scene_file_path.get_base_dir()
# Import a single tag animation
result = await _animation_creator.create_prop_animations(
prop,
prop.get_meta("ANIM_NAME"),
_options
)
for prop in props_container.get_children():
if not prop.has_meta("ANIM_NAME"): continue
# Save the prop
result = await _save_prop(prop)
# TODO: maybe check if this is better done with signals
_importing = false
if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS:
PopochiuUtils.print_error(RESULT_CODE.get_error_message(result))
_show_message("Some errors occurred. Please check output panel.", "Warning!")
else:
await get_tree().create_timer(0.1).timeout
# Once the popup is closed, call _clean_props()
_show_message(
"%d animation tags processed." % [_tags_cache.size()],
"Done!"
)
func _customize_tag_ui(tag_row: AnimationTagRow):
# Show props-related buttons if we are in a room
tag_row.show_prop_buttons()
func _create_prop(name: String, is_clickable: bool = true, is_visible: bool = true):
var factory = PopochiuPropFactory.new()
var param := PopochiuPropFactory.PopochiuPropFactoryParam.new()
param.obj_name = name
param.room = _root_node
param.is_interactive = is_clickable
param.is_visible = is_visible
if factory.create(param) != ResultCodes.SUCCESS:
return
return factory.get_obj_scene()
func _save_prop(prop: PopochiuProp):
var packed_scene: PackedScene = PackedScene.new()
packed_scene.pack(prop)
if ResourceSaver.save(packed_scene, prop.scene_file_path) != OK:
PopochiuUtils.print_error(
"Couldn't save animations for prop %s at %s" %
[prop.name, prop.scene_file_path]
)
return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE
return ResultCodes.SUCCESS
#endregion

View file

@ -0,0 +1,46 @@
extends EditorInspectorPlugin ## TODO: create a base class with pointer variables
const DOCKS_PATH := "res://addons/popochiu/editor/importers/aseprite/docks/"
const INSPECTOR_DOCK = preload(DOCKS_PATH + "aseprite_importer_inspector_dock.tscn")
const CONFIG_SCRIPT = preload("res://addons/popochiu/editor/config/config.gd")
const INSPECTOR_DOCK_CHARACTER := DOCKS_PATH + "aseprite_importer_inspector_dock_character.gd"
const INSPECTOR_DOCK_ROOM := DOCKS_PATH + "aseprite_importer_inspector_dock_room.gd"
var _target_node: Node
#region Godot ######################################################################################
func _can_handle(object):
if object.has_method("get_parent") and object.get_parent() is Node2D:
return false
return object is PopochiuCharacter || object is PopochiuRoom #|| object is PopochiuInventoryItem
func _parse_begin(object: Object):
# Fix showing error messages in Output when inspecting nodes in the Debugger
if not object is Node: return
_target_node = object
func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide) -> bool:
if object.get_class() == "EditorDebuggerRemoteObject":
return false
if name != 'popochiu_placeholder':
return false
# Instantiate and configure the dock
var dock = INSPECTOR_DOCK.instantiate()
# Load the specific script in the dock
if object is PopochiuCharacter:
dock.set_script(load(INSPECTOR_DOCK_CHARACTER))
if object is PopochiuRoom:
dock.set_script(load(INSPECTOR_DOCK_ROOM))
dock.target_node = object
dock.file_system = EditorInterface.get_resource_filesystem()
# Add the dock to the inspector
add_custom_control(dock)
return true
#endregion

View file

@ -0,0 +1 @@
uid://dpianc4xo6kcl

View file

@ -0,0 +1,52 @@
extends EditorInspectorPlugin
#region Godot ######################################################################################
func _can_handle(object: Object) -> bool:
return object is PopochiuAudioCue
func _parse_property(
object: Object,
type,
path: String,
hint,
hint_text: String,
usage,
wide: bool
) -> bool:
if not object is PopochiuAudioCue or path != "bus":
return false
var ep := EditorProperty.new()
var ob := OptionButton.new()
_update_buses_list(ob, object)
ob.item_selected.connect(_update_audio_cue_bus.bind(object))
ob.pressed.connect(_update_buses_list.bind(ob, object))
ep.add_child(ob)
add_property_editor(path, ep)
return true
#endregion
#region Private ####################################################################################
func _update_audio_cue_bus(idx: int, audio_cue: PopochiuAudioCue) -> void:
audio_cue.bus = AudioServer.get_bus_name(idx)
ResourceSaver.save(audio_cue, audio_cue.resource_path)
func _update_buses_list(ob: OptionButton, pac: PopochiuAudioCue) -> void:
ob.clear()
for idx in AudioServer.bus_count:
ob.add_item(AudioServer.get_bus_name(idx), idx)
ob.selected = AudioServer.get_bus_index(pac.bus)
#endregion

View file

@ -0,0 +1 @@
uid://d0emjgqvommw4

View file

@ -0,0 +1,83 @@
extends EditorInspectorPlugin
#region Virtual ####################################################################################
func _can_handle(object: Object) -> bool:
if object is PopochiuCharacter:
return true
return false
func _parse_begin(object: Object) -> void:
if object.get_class() == "EditorDebuggerRemoteObject":
return
if not object.get_parent() is Node2D: return
var panel := PanelContainer.new()
var hbox := HBoxContainer.new()
var button := Button.new()
hbox.custom_minimum_size.y = 42.0
button.text = "* Open Node' scene to edit its properties"
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
panel.add_theme_stylebox_override(
"panel",
panel.get_theme_stylebox("sub_inspector_bg11", "Editor")
)
button.add_theme_color_override("font_color", Color("c46c71"))
button.add_theme_color_override("font_color_hover", Color("c46c71"))
button.add_theme_color_override("font_color_pressed", Color("c46c71"))
button.pressed.connect(
_open_scene.bind((object as PopochiuCharacter).scene_file_path),
CONNECT_DEFERRED
)
hbox.add_child(button)
panel.add_child(hbox)
add_custom_control(panel)
func _parse_property(
object: Object,
type,
path: String,
hint,
hint_text: String,
usage,
wide: bool
) -> bool:
if object.get_class() == "EditorDebuggerRemoteObject":
return false
# NOTE: We could add this as an option of the plugin settings. So devs can add extra properties
# if needed.
if object and object.get_parent() is Node2D and not path in [
"baseline",
"walk_to_point",
"look_at_point",
"position",
"visible",
"modulate",
"self_modulate",
"light_mask",
]:
return true
return false
#endregion
#region Private ####################################################################################
func _open_scene(path: String) -> void:
EditorInterface.set_main_screen_editor("2D")
EditorInterface.open_scene_from_path(path)
#endregion

View file

@ -0,0 +1 @@
uid://m7hkqdx4jhem

View file

@ -0,0 +1,63 @@
extends EditorInspectorPlugin
#region Virtual ####################################################################################
func _can_handle(object: Object) -> bool:
if object is PopochiuProp:
return true
return false
func _parse_property(
object: Object,
type,
path: String,
hint,
hint_text: String,
usage,
wide: bool
) -> bool:
if (
object.get_class() == "EditorDebuggerRemoteObject"
or object is not PopochiuProp
or path != "link_to_item"
):
return false
var ep := EditorProperty.new()
var ob := OptionButton.new()
_update_items_list(ob, object)
ob.item_selected.connect(_update_link_to_item.bind(ob, object))
ob.pressed.connect(_update_items_list.bind(ob, object))
ep.add_child(ob)
add_property_editor(path, ep)
return true
#endregion
#region Private ####################################################################################
func _update_items_list(ob: OptionButton, prop: PopochiuProp) -> void:
ob.clear()
var inventory_items := PopochiuResources.get_section_keys("inventory_items")
var keys_ids_map := {}
inventory_items.sort()
ob.add_item("")
for key: String in inventory_items:
keys_ids_map[key] = ob.item_count
ob.add_item(key)
if keys_ids_map.has(prop.link_to_item):
ob.selected = ob.get_item_index(keys_ids_map[prop.link_to_item])
func _update_link_to_item(idx: int, ob: OptionButton, prop: PopochiuProp) -> void:
prop.link_to_item = ob.get_item_text(idx)
#endregion

View file

@ -0,0 +1 @@
uid://dsuhln67g2d8k

View file

@ -0,0 +1,117 @@
@tool
extends Panel
signal move_folders_pressed
@onready var tab_container: TabContainer = %TabContainer
@onready var tab_main: VBoxContainer = %Main
@onready var tab_room: VBoxContainer = %Room
@onready var tab_audio: VBoxContainer = %Audio
@onready var tab_gui: VBoxContainer = %GUI
# ---- FOOTER --------------------------------------------------------------------------------------
@onready var version: Label = %Version
@onready var btn_setup: Button = %BtnSetup
@onready var btn_docs: Button = %BtnDocs
#region Godot ######################################################################################
func _ready() -> void:
version.text = "v" + PopochiuResources.get_version()
btn_setup.icon = get_theme_icon("Edit", "EditorIcons")
btn_docs.icon = get_theme_icon("HelpSearch", "EditorIcons")
# Set the Main tab selected by default
tab_container.current_tab = 0
# Hide the GUI tab while we decide how it will work based on devs feedback
tab_container.set_tab_hidden(tab_gui.get_index(), true)
# Connect to children's signals
tab_container.tab_changed.connect(_on_tab_changed)
btn_setup.pressed.connect(open_setup)
btn_docs.pressed.connect(OS.shell_open.bind(PopochiuResources.DOCUMENTATION))
# Connect to parent signals
get_tree().node_added.connect(_check_node)
#endregion
#region Public #####################################################################################
func fill_data() -> void:
tab_main.fill_data()
tab_audio.fill_data()
func scene_changed(scene_root: Node) -> void:
if not is_instance_valid(tab_room): return
tab_room.scene_changed(scene_root)
# TODO: Uncomment these lines when working on the GUI tab again
#if not is_instance_valid(tab_gui): return
#tab_gui.on_scene_changed(scene_root)
if (
not scene_root
or (
not scene_root is PopochiuRoom
# TODO: Uncomment this line when working on the GUI tab again
#and not scene_root.scene_file_path == PopochiuResources.GUI_GAME_SCENE
)
):
# Open the Popochiu Main tab if the opened scene in the Editor2D is not a PopochiuRoom nor
# the GUI scene
tab_container.current_tab = 0
func scene_closed(filepath: String) -> void:
if not is_instance_valid(tab_room): return
tab_room.scene_closed(filepath)
check_open_scenes()
func search_audio_files() -> void:
if not is_instance_valid(tab_audio): return
tab_audio.search_audio_files()
func open_setup() -> void:
PopochiuEditorHelper.show_setup()
## If there are no other opened scenes in the Editor, this function connects to
## [signal EditorSelection.selection_changed] in order to make sure the Popochiu dock behaves as
## expected when the [signal EditorPlugin.scene_changed] signal is not emitted.
func check_open_scenes() -> void:
# Fixes #273: Since Godot is not triggering the EditorPlugin.scene_changed signal when opening a
# scene when no other scenes are opened, listen to the EditorSelection.selection_changed signal
await get_tree().process_frame
if EditorInterface.get_open_scenes().is_empty():
EditorInterface.get_selection().selection_changed.connect(_on_editor_selection_changed)
#endregion
#region Private ####################################################################################
func _on_tab_changed(tab: int) -> void:
if tab == tab_main.get_index():
tab_main.check_data()
if tab == tab_gui.get_index():
tab_gui.open_gui_scene()
func _check_node(node: Node) -> void:
if node is PopochiuCharacter and node.get_parent() is Node2D:
# The node is a PopochiuCharacter in a room
node.set_name.call_deferred("Character%s *" % node.script_name)
func _on_editor_selection_changed() -> void:
if EditorInterface.get_edited_scene_root():
EditorInterface.get_selection().selection_changed.disconnect(_on_editor_selection_changed)
scene_changed(EditorInterface.get_edited_scene_root())
#endregion

View file

@ -0,0 +1 @@
uid://bcwtfohnkna8t

View file

@ -0,0 +1,100 @@
[gd_scene load_steps=8 format=3 uid="uid://bardo4kb80rvg"]
[ext_resource type="PackedScene" uid="uid://bynwdds8o3tcx" path="res://addons/popochiu/editor/main_dock/tab_main/tab_main.tscn" id="2_oxyje"]
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_dock.gd" id="7"]
[ext_resource type="PackedScene" uid="uid://4etgd0rwjgct" path="res://addons/popochiu/editor/main_dock/tab_gui/tab_gui.tscn" id="10_82goo"]
[ext_resource type="PackedScene" uid="uid://q1bjkxavt2ay" path="res://addons/popochiu/editor/main_dock/tab_room/tab_room.tscn" id="12"]
[ext_resource type="PackedScene" uid="uid://bpj8jlet25coy" path="res://addons/popochiu/editor/main_dock/tab_audio/tab_audio.tscn" id="13"]
[sub_resource type="Image" id="Image_sgf6r"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_32xut"]
image = SubResource("Image_sgf6r")
[node name="Popochiu" type="Panel"]
clip_contents = true
custom_minimum_size = Vector2(340, 0)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("7")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 0
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="TabContainer" type="TabContainer" parent="MarginContainer/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
current_tab = 0
[node name="Main" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("2_oxyje")]
unique_name_in_owner = true
layout_mode = 2
[node name="Room" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("12")]
unique_name_in_owner = true
visible = false
layout_mode = 2
focus_mode = 2
metadata/_tab_index = 1
[node name="Audio" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("13")]
unique_name_in_owner = true
visible = false
layout_mode = 2
focus_mode = 2
metadata/_tab_index = 2
[node name="GUI" parent="MarginContainer/VBoxContainer/TabContainer" instance=ExtResource("10_82goo")]
unique_name_in_owner = true
visible = false
layout_mode = 2
metadata/_tab_index = 3
[node name="FooterPanel" type="PanelContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/FooterPanel"]
layout_mode = 2
size_flags_vertical = 3
alignment = 2
[node name="Version" type="Label" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "v2.0.1"
[node name="BtnSetup" type="Button" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Opens wiki in web browser"
text = "Setup"
icon = SubResource("ImageTexture_32xut")
[node name="BtnDocs" type="Button" parent="MarginContainer/VBoxContainer/FooterPanel/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Opens wiki in web browser"
text = "Documentation"
icon = SubResource("ImageTexture_32xut")

View file

@ -0,0 +1,51 @@
@tool
extends LineEdit
var groups := {}: set = set_groups
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░
func _ready() -> void:
right_icon = get_theme_icon('Search', 'EditorIcons')
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░
func set_groups(value: Dictionary) -> void:
groups = value
if groups:
text_changed.connect(
_filter_rows.bind(groups),
CONNECT_REFERENCE_COUNTED
)
# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░
# `source` is one of the `_types` dictionaries in PopochiuDock, TabRoom and
# TabAudio
func _filter_rows(new_text: String, source: Dictionary) -> void:
for type_dic in source.values():
type_dic.group.show()
var title_in_filter := false
if type_dic.group.title.findn(new_text) > -1:
title_in_filter = true
var hidden_rows := 0
# type_dic.group is a PopochiuGroup
var rows: Array = type_dic.group.get_elements()
for row in rows:
row.show()
if new_text.is_empty(): continue
if (row as Control).name.findn(new_text) < 0\
and not title_in_filter:
hidden_rows += 1
row.hide()
if hidden_rows == rows.size() and not new_text.is_empty():
type_dic.group.hide()

View file

@ -0,0 +1 @@
uid://l0id2f7cqbkj

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g id="PopochiuGroup" transform="matrix(0.5,0,0,0.5,0,0)">
<rect x="0" y="0" width="32" height="32" style="fill:none;"/>
<g transform="matrix(3.25,0,0,3.25,-32.75,-39.25)">
<path d="M19,13.615C19,13.276 18.724,13 18.385,13L11.615,13C11.276,13 11,13.276 11,13.615L11,20.385C11,20.724 11.276,21 11.615,21L18.385,21C18.724,21 19,20.724 19,20.385L19,13.615Z" style="fill:none;stroke:rgb(112,109,235);stroke-width:1.23px;"/>
</g>
<g transform="matrix(0.673999,0,0,0.570028,13,-2.18068)">
<path d="M0,24L8.902,31.902L-0,39.789" style="fill:none;stroke:rgb(112,109,235);stroke-width:6.41px;"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://uwjkfsrpfx3e"
path="res://.godot/imported/popochiu_group.svg-55d153202c16f767328f8314e84a5c37.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/popochiu/editor/main_dock/popochiu_group/images/popochiu_group.svg"
dest_files=["res://.godot/imported/popochiu_group.svg-55d153202c16f767328f8314e84a5c37.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,199 @@
@tool
@icon("res://addons/popochiu/editor/main_dock/popochiu_group/images/popochiu_group.svg")
class_name PopochiuGroup
extends PanelContainer
signal create_clicked
const PopochiuRow := preload("res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd")
@export var icon: Texture2D : set = set_icon
@export var is_open := true : set = set_is_open
@export var color: Color = Color("999999") : set = set_color
@export var title := "Group" : set = set_title
@export var can_create := true
@export var create_text := ""
@export var target_list: NodePath = ""
@export var custom_title_count := false
var _external_list: VBoxContainer = null
@onready var header: PanelContainer = %Header
@onready var arrow: TextureRect = %Arrow
@onready var trt_icon: TextureRect = %Icon
@onready var lbl_title: Label = %Title
@onready var body: Container = %Body
@onready var btn_create: Button = %BtnCreate
@onready var list: VBoxContainer = %List
#region Godot ######################################################################################
func _ready() -> void:
# Establecer estado inicial
add_theme_stylebox_override("panel", get_theme_stylebox("panel").duplicate())
(get_theme_stylebox("panel") as StyleBoxFlat).border_color = color
if is_instance_valid(icon):
trt_icon.texture = icon
lbl_title.text = title
btn_create.icon = get_theme_icon("Add", "EditorIcons")
btn_create.text = create_text
self.is_open = list.get_child_count() > 0
if not can_create:
btn_create.hide()
header.gui_input.connect(_on_input)
list.resized.connect(_update_child_count)
btn_create.pressed.connect(emit_signal.bind("create_clicked"))
if target_list:
_external_list = get_node(target_list) as VBoxContainer
self.is_open = _external_list.get_child_count() > 0
#endregion
#region Public #####################################################################################
func clear_list() -> void:
for c in list.get_children():
# Fix #216: Delete the row immediately so that it does not interfere with the creation of
# other rows that may have the same name as it
c.free()
func add(node: Node, sort := false) -> void:
if sort:
node.ready.connect(_order_list.bind(node))
list.add_child(node)
btn_create.disabled = false
if not is_open:
self.is_open = true
func clear_favs() -> void:
for popochiu_row: PopochiuRow in list.get_children():
popochiu_row.clear_tag()
func disable_create() -> void:
btn_create.disabled = true
func enable_create() -> void:
btn_create.disabled = false
func get_elements() -> Array:
return list.get_children()
func remove_by_name(node_name: String) -> void:
if list.has_node(node_name):
var node: HBoxContainer = list.get_node(node_name)
list.remove_child(node)
node.free()
func add_header_button(btn: Button) -> void:
btn_create.add_sibling(btn)
func set_title_count(count: int, max_count := 0) -> void:
if max_count > 0:
lbl_title.text = "%s (%d/%d)" % [title, count, max_count]
else:
lbl_title.text = "%s (%d)" % [title, count]
func get_by_name(node_name: String) -> HBoxContainer:
if list.has_node(node_name):
return list.get_node(node_name)
return null
#endregion
#region SetGet #####################################################################################
func set_icon(value: Texture2D) -> void:
icon = value
if is_instance_valid(trt_icon):
trt_icon.texture = value
func set_is_open(value: bool) -> void:
is_open = value
_toggled(value)
func set_color(value: Color) -> void:
color = value
if is_instance_valid(header):
(get_theme_stylebox("panel") as StyleBoxFlat).border_color = value
func set_title(value: String) -> void:
title = value
if is_instance_valid(lbl_title):
lbl_title.text = value
#endregion
#region Private ####################################################################################
func _on_input(event: InputEvent) -> void:
var mouse_event: = event as InputEventMouseButton
if mouse_event and mouse_event.button_index == MOUSE_BUTTON_LEFT \
and mouse_event.pressed:
is_open = !is_open
_toggled(is_open)
func _toggled(button_pressed: bool) -> void:
if is_instance_valid(arrow):
arrow.texture = (
get_theme_icon("GuiTreeArrowDown", "EditorIcons") if button_pressed
else get_theme_icon("GuiTreeArrowRight", "EditorIcons")
)
if is_instance_valid(body):
if button_pressed: body.show()
else: body.hide()
if is_instance_valid(_external_list):
_external_list.visible = button_pressed
func _update_child_count() -> void:
if custom_title_count: return
if is_instance_valid(lbl_title):
var children := list.get_child_count()
lbl_title.text = title + (" (%d)" % children) if children > 1 else title
func _order_list(node: Node) -> void:
node.ready.disconnect(_order_list)
# Place the new row in its place alphabetically
var place_before: Node = null
for row in list.get_children():
if str(node.name) < str(row.name):
place_before = row
break
if not place_before: return
list.move_child(node, place_before.get_index())
#endregion

View file

@ -0,0 +1 @@
uid://dfejejptym3da

View file

@ -0,0 +1,91 @@
[gd_scene load_steps=6 format=3 uid="uid://b55ialbvpilxv"]
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_group/popochiu_group.gd" id="1_lumyt"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qhsn2"]
content_margin_left = 8.0
content_margin_right = 8.0
draw_center = false
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.6, 0.6, 0.6, 1)
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t8mu1"]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 0.0
content_margin_bottom = 0.0
bg_color = Color(0.6, 0.6, 0.6, 0.211765)
draw_center = false
corner_detail = 5
expand_margin_left = 4.0
expand_margin_right = 4.0
[sub_resource type="Image" id="Image_e0ep0"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_uhpk4"]
image = SubResource("Image_e0ep0")
[node name="PopochiuGroup" type="PanelContainer"]
offset_right = 320.0
offset_bottom = 24.0
theme_override_styles/panel = SubResource("StyleBoxFlat_qhsn2")
script = ExtResource("1_lumyt")
is_open = false
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="Header" type="PanelContainer" parent="VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_t8mu1")
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/Header"]
layout_mode = 2
[node name="Arrow" type="TextureRect" parent="VBoxContainer/Header/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
texture = SubResource("ImageTexture_uhpk4")
stretch_mode = 4
[node name="Icon" type="TextureRect" parent="VBoxContainer/Header/HBoxContainer"]
unique_name_in_owner = true
texture_filter = 1
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
[node name="Title" type="Label" parent="VBoxContainer/Header/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "Group"
[node name="Body" type="VBoxContainer" parent="VBoxContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
[node name="BtnCreate" type="Button" parent="VBoxContainer/Body"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 8
icon = SubResource("ImageTexture_uhpk4")
[node name="List" type="VBoxContainer" parent="VBoxContainer/Body"]
unique_name_in_owner = true
layout_mode = 2

View file

@ -0,0 +1,237 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd"
signal group_selected(type: int)
signal deleted(file_path: String)
enum AudioOptions {
DELETE = MenuOptions.DELETE,
ADD_TO_MUSIC,
ADD_TO_SFX,
ADD_TO_VOICE,
ADD_TO_UI
}
const DELETE_AUDIO_CUE_MSG = "This will remove the [b]%s[/b] resource. Calls to this audio in \
scripts will not work anymore. This action cannot be reversed. Continue?"
const DELETE_AUDIO_CUE_ASK = "Delete [b]%s[/b] file too? (cannot be reversed)"
const DELETE_AUDIO_FILE_MSG = "[b]%s[/b] will be deleted in the file system. This action cannot be \
reversed. Continue?"
# Only used by rows that represent an audio file
var file_name: String
var audio_cue: AudioCue
var cue_group: String
var stream_player: AudioStreamPlayer
var audio_tab: VBoxContainer = null
var is_playing := false :
set = set_is_playing
var current_playback_position := 0.0
@onready var play_btn: Button = %Play
@onready var stop_btn: Button = %Stop
#region Godot ######################################################################################
func _ready() -> void:
super()
# Assign icons
play_btn.icon = get_theme_icon("MainPlay", "EditorIcons")
stop_btn.icon = get_theme_icon("Stop", "EditorIcons")
# Connect to children's signals
play_btn.pressed.connect(play)
stop_btn.pressed.connect(stop)
# Remove group options if this is a PopochiuAudioCue
if is_instance_valid(audio_cue):
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_MUSIC))
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_SFX))
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_VOICE))
menu_popup.remove_item(menu_popup.get_item_index(AudioOptions.ADD_TO_UI))
else:
label.text = file_name
#endregion
#region Virtual ####################################################################################
func _remove_object() -> void:
_delete_dialog = PopochiuEditorHelper.DELETE_CONFIRMATION_SCENE.instantiate()
if is_instance_valid(audio_cue):
_delete_dialog.title = "Remove %s cue" % audio_cue.resource_name
_delete_dialog.message = DELETE_AUDIO_CUE_MSG % audio_cue.resource_name
_delete_dialog.ask = DELETE_AUDIO_CUE_ASK % audio_cue.audio.resource_path
_delete_dialog.on_confirmed = _remove_from_popochiu
else:
_delete_dialog.title = "Delete %s" % file_name
_delete_dialog.message = DELETE_AUDIO_FILE_MSG % path
_delete_dialog.on_confirmed = _delete_from_file_system
PopochiuEditorHelper.show_delete_confirmation(_delete_dialog)
#endregion
#region Public #####################################################################################
func select() -> void:
EditorInterface.edit_resource(audio_cue)
super()
func play() -> void:
if is_playing:
# Pause the audio stream
is_playing = false
return
if is_instance_valid(audio_tab.last_played):
# Stop the currently playing row (which is different from this one)
audio_tab.last_played.stop()
if not is_instance_valid(audio_cue):
# If the row does not have a [PopochiuAudioCue] assigned, then it is the row of an audio
# file. Therefore, the [AudioStream] to play will be its own [path]
var stream: AudioStream = load(path)
stream.loop = false
stream_player.stream = stream
else:
# Otherwise, the [AudioStream] to play will be that of the audio file associated with this
# [PopochiuAudioCue.audio]
stream_player.stream = audio_cue.audio
# The values of [AudioStream.pitch_scale] and [AudioStream.volume_db] should be taken from
# the information stored in the [PopochiuAudioCue].
stream_player.pitch_scale = audio_cue.get_pitch_scale()
stream_player.volume_db = audio_cue.volume
is_playing = true
func stop() -> void:
is_playing = false
current_playback_position = 0.0
label.add_theme_color_override("font_color", dflt_font_color)
stream_player.stream = null
audio_tab.last_played = null
#endregion
#region SetGet #####################################################################################
func set_is_playing(value: bool) -> void:
is_playing = value
if is_playing:
if not stream_player.finished.is_connected(stop):
stream_player.finished.connect(stop)
stream_player.play(current_playback_position)
audio_tab.last_played = self
else:
current_playback_position = stream_player.get_playback_position()
if stream_player.playing:
stream_player.stop()
stream_player.finished.disconnect(stop)
play_btn.icon = play_btn.get_theme_icon("Pause" if is_playing else "MainPlay", "EditorIcons")
#endregion
#region Private ####################################################################################
func _get_menu_cfg() -> Array:
return [
{
id = AudioOptions.ADD_TO_MUSIC,
icon = preload("res://addons/popochiu/icons/music.png"),
label = "Add to Music"
},
{
id = AudioOptions.ADD_TO_SFX,
icon = preload("res://addons/popochiu/icons/sfx.png"),
label = "Add to Sound Effects"
},
{
id = AudioOptions.ADD_TO_VOICE,
icon = preload("res://addons/popochiu/icons/voice.png"),
label = "Add to Voices"
},
{
id = AudioOptions.ADD_TO_UI,
icon = preload("res://addons/popochiu/icons/ui.png"),
label = "Add to Graphic Interface"
}
] + super()
func _menu_item_pressed(id: int) -> void:
match id:
AudioOptions.ADD_TO_MUSIC:
group_selected.emit(PopochiuResources.AudioTypes.MUSIC)
AudioOptions.ADD_TO_SFX:
group_selected.emit(PopochiuResources.AudioTypes.SOUND_EFFECT)
AudioOptions.ADD_TO_VOICE:
group_selected.emit(PopochiuResources.AudioTypes.VOICE)
AudioOptions.ADD_TO_UI:
group_selected.emit(PopochiuResources.AudioTypes.UI)
_:
super(id)
func _remove_from_popochiu() -> void:
# Remove the AudioCue from popochiu_data.cfg ---------------------------------------------------
var group_data: Array = PopochiuResources.get_data_value(
"audio", cue_group, []
)
if group_data:
group_data.erase(audio_cue.resource_path)
if group_data.is_empty():
PopochiuResources.erase_data_value("audio", cue_group)
else:
group_data.sort_custom(
func (a: String, b: String) -> bool:
return PopochiuUtils.sort_by_file_name(a, b)
)
PopochiuResources.set_data_value("audio", cue_group, group_data)
# Remove the AudioCue from the A singleton -----------------------------------------------------
PopochiuResources.remove_audio_autoload(cue_group, name, audio_cue.resource_path)
# Delete the file in its corresponding group in Audio tab
deleted.emit(audio_cue.audio.resource_path)
if _delete_dialog.check_box.button_pressed:
_delete_from_file_system()
else:
queue_free()
func _delete_from_file_system() -> void:
# Delete the .tres file from the file system
var err: int = DirAccess.remove_absolute(path)
if err != OK:
PopochiuUtils.print_error("Couldn't delete audio cue %s (err_code: %d)" % [path, err])
return
# Delete the audio file linked to the cue
var audio_file_path := audio_cue.audio.resource_path
err = DirAccess.remove_absolute(audio_file_path)
if err != OK:
PopochiuUtils.print_error(
"Couldn't delete audio file %s (err_code: %d)" % [audio_file_path, err]
)
return
# Do this so Godot removes the .import file of the audio file
EditorInterface.get_resource_filesystem().update_file(audio_file_path)
EditorInterface.get_resource_filesystem().scan()
EditorInterface.get_resource_filesystem().scan_sources()
queue_free()
#endregion

View file

@ -0,0 +1 @@
uid://lhhhuwe3ye1f

View file

@ -0,0 +1,62 @@
[gd_scene load_steps=9 format=3 uid="uid://ds6ojs55q50ud"]
[ext_resource type="PackedScene" uid="uid://dwtwuqw2hpdpe" path="res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.tscn" id="1_i2mx0"]
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_row/audio_row/popochiu_audio_row.gd" id="2_24kri"]
[ext_resource type="Texture2D" uid="uid://d1dnmfkhscb7r" path="res://addons/popochiu/icons/music.png" id="3_hi2e1"]
[ext_resource type="Texture2D" uid="uid://cfh1uxtaff0ks" path="res://addons/popochiu/icons/sfx.png" id="4_1iw68"]
[ext_resource type="Texture2D" uid="uid://6ewpl4v0td2h" path="res://addons/popochiu/icons/voice.png" id="5_ray7p"]
[ext_resource type="Texture2D" uid="uid://528j2rksws2c" path="res://addons/popochiu/icons/ui.png" id="6_1bl3m"]
[sub_resource type="Image" id="Image_dygia"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_v80gh"]
image = SubResource("Image_dygia")
[node name="PopochiuAudioRow" instance=ExtResource("1_i2mx0")]
script = ExtResource("2_24kri")
[node name="Label" parent="HBoxContainer" index="0"]
text = ""
[node name="Play" type="Button" parent="Panel/ButtonsContainer" index="0"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
icon = SubResource("ImageTexture_v80gh")
flat = true
[node name="Stop" type="Button" parent="Panel/ButtonsContainer" index="1"]
unique_name_in_owner = true
layout_mode = 2
mouse_filter = 1
icon = SubResource("ImageTexture_v80gh")
flat = true
[node name="BtnMenu" parent="Panel/ButtonsContainer" index="2"]
icon = SubResource("ImageTexture_v80gh")
item_count = 6
popup/item_0/text = "Add to Music"
popup/item_0/icon = ExtResource("3_hi2e1")
popup/item_0/id = 1
popup/item_1/text = "Add to Sound effects"
popup/item_1/icon = ExtResource("4_1iw68")
popup/item_1/id = 2
popup/item_2/text = "Add to Voices"
popup/item_2/icon = ExtResource("5_ray7p")
popup/item_2/id = 3
popup/item_3/text = "Add to Graphic interface"
popup/item_3/icon = ExtResource("6_1bl3m")
popup/item_3/id = 4
popup/item_4/text = ""
popup/item_4/id = -1
popup/item_4/separator = true
popup/item_5/text = "Remove"
popup/item_5/icon = SubResource("ImageTexture_v80gh")
popup/item_5/id = 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bw3ie8wfwa2i2"
path="res://.godot/imported/add_to_core.png-52def14ca6e499df1e292c93f01c4349.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/add_to_core.png"
dest_files=["res://.godot/imported/add_to_core.png-52def14ca6e499df1e292c93f01c4349.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bahipxbrrss0o"
path="res://.godot/imported/delete.png-27dd9adc116bbf3fc8b20a99d1331933.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/delete.png"
dest_files=["res://.godot/imported/delete.png-27dd9adc116bbf3fc8b20a99d1331933.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://wm7qycjntmfr"
path="res://.godot/imported/open.png-eb4e739212f91fcaedbead9efc5f731f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/popochiu/editor/main_dock/popochiu_row/images/open.png"
dest_files=["res://.godot/imported/open.png-eb4e739212f91fcaedbead9efc5f731f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
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/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

View file

@ -0,0 +1,79 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
enum CharacterOptions {
DELETE = MenuOptions.DELETE,
ADD_TO_CORE = Options.ADD_TO_CORE,
SET_AS_PC,
}
const TAG_ICON = preload("res://addons/popochiu/icons/player_character.png")
const STATE_TEMPLATE = "res://addons/popochiu/engine/templates/character_state_template.gd"
var is_pc := false : set = set_is_pc
#region Godot ######################################################################################
func _ready() -> void:
super()
# Assign icons
tag.texture = TAG_ICON
#endregion
#region Virtual ####################################################################################
func _get_state_template() -> Script:
return load(STATE_TEMPLATE)
func _clear_tag() -> void:
if is_pc:
is_pc = false
#endregion
#region SetGet #####################################################################################
func set_is_pc(value: bool) -> void:
is_pc = value
if is_pc:
PopochiuEditorHelper.signal_bus.pc_changed.emit(name)
tag.visible = value
menu_popup.set_item_disabled(menu_popup.get_item_index(CharacterOptions.SET_AS_PC), value)
#endregion
#region Private ####################################################################################
func _get_menu_cfg() -> Array:
return [
{
id = CharacterOptions.SET_AS_PC,
icon = TAG_ICON,
label = "Set as Player-controlled Character (PC)",
},
] + super()
func _menu_item_pressed(id: int) -> void:
match id:
CharacterOptions.SET_AS_PC:
self.is_pc = true
_:
super(id)
func _remove_from_core() -> void:
# Delete the object from Popochiu
PopochiuResources.remove_autoload_obj(PopochiuResources.C_SNGL, name)
PopochiuResources.erase_data_value("characters", str(name))
# Continue with the deletion flow
super()
#endregion

View file

@ -0,0 +1,15 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
#region Private ####################################################################################
func _remove_from_core() -> void:
# Delete the object from Popochiu
PopochiuResources.remove_autoload_obj(PopochiuResources.D_SNGL, name)
PopochiuResources.erase_data_value("dialogs", str(name))
# Continue with the deletion flow
super()
#endregion

View file

@ -0,0 +1,78 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
enum InventoryItemOptions {
DELETE = MenuOptions.DELETE,
ADD_TO_CORE = Options.ADD_TO_CORE,
START_WITH_IT,
}
const TAG_ICON = preload("res://addons/popochiu/icons/inventory_item_start.png")
const STATE_TEMPLATE = "res://addons/popochiu/engine/templates/inventory_item_state_template.gd"
var is_on_start := false : set = set_is_on_start
#region Godot ######################################################################################
func _ready() -> void:
super()
# Assign icons
tag.texture = TAG_ICON
#endregion
#region Virtual ####################################################################################
func _get_state_template() -> Script:
return load(STATE_TEMPLATE)
#endregion
#region SetGet #####################################################################################
func set_is_on_start(value: bool) -> void:
is_on_start = value
tag.visible = value
#endregion
#region Private ####################################################################################
func _get_menu_cfg() -> Array:
return [
{
id = InventoryItemOptions.START_WITH_IT,
icon = TAG_ICON,
label = "Start with it",
},
] + super()
func _menu_item_pressed(id: int) -> void:
match id:
InventoryItemOptions.START_WITH_IT:
var items: Array = PopochiuConfig.get_inventory_items_on_start()
var script_name := str(name)
if script_name in items:
items.erase(script_name)
else:
items.append(script_name)
PopochiuConfig.set_inventory_items_on_start(items)
self.is_on_start = script_name in items
_:
super(id)
func _remove_from_core() -> void:
# Delete the object from Popochiu
PopochiuResources.remove_autoload_obj(PopochiuResources.I_SNGL, name)
PopochiuResources.erase_data_value("inventory_items", str(name))
# Continue with the deletion flow
super()
#endregion

View file

@ -0,0 +1,334 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.gd"
## Row for the main object types: Room, Character, Inventory item, Dialog
enum Options {
DELETE = MenuOptions.DELETE,
ADD_TO_CORE,
}
const DELETE_MESSAGE = "This will remove the [b]%s[/b] object in [b]%s[/b] scene. Uses of this \
object in scripts will not work anymore. This action cannot be undone. Continue?"
const DELETE_ASK_MESSAGE = "Do you want to delete the [b]%s[/b] folder too?%s (cannot be reversed)"
const ADD_TO_CORE_ICON = preload(
"res://addons/popochiu/editor/main_dock/popochiu_row/images/add_to_core.png"
)
const AUDIO_FILE_TYPES = ["AudioStreamOggVorbis", "AudioStreamMP3", "AudioStreamWAV"]
@onready var btn_open: Button = %BtnOpen
@onready var btn_script: Button = %BtnScript
@onready var btn_state: Button = %BtnState
@onready var btn_state_script: Button = %BtnStateScript
#region Godot ######################################################################################
func _ready() -> void:
# Assign icons
btn_open.icon = get_theme_icon("InstanceOptions", "EditorIcons")
btn_script.icon = get_theme_icon("Script", "EditorIcons")
btn_state.icon = get_theme_icon("Object", "EditorIcons")
btn_state_script.icon = get_theme_icon("GDScript", "EditorIcons")
# Connect to signals and create the options for the menu
super()
# Connect to children's signals
btn_open.pressed.connect(_open)
btn_script.pressed.connect(_open_script)
btn_state.pressed.connect(_edit_state)
btn_state_script.pressed.connect(_open_state_script)
# Disable some options by default
var add_to_core_idx := menu_popup.get_item_index(Options.ADD_TO_CORE)
if add_to_core_idx >= 0:
menu_popup.set_item_disabled(add_to_core_idx, true)
#endregion
#region Virtual ####################################################################################
## Shows a confirmation popup to ask the developer if the Popochiu object should be removed only
## from the core, or from the file system too.
func _remove_object() -> void:
var location := _get_location()
# Look into the Object"s folder for audio files and AudioCues to show the developer that those
# files will be removed too.
var audio_files := _search_audio_files(
EditorInterface.get_resource_filesystem().get_filesystem_path(path.get_base_dir())
)
_delete_dialog = PopochiuEditorHelper.DELETE_CONFIRMATION_SCENE.instantiate()
_delete_dialog.title = "Remove %s from %s" % [name, location]
_delete_dialog.message = DELETE_MESSAGE % [name, location]
_delete_dialog.ask = DELETE_ASK_MESSAGE % [
path.get_base_dir(),
"" if audio_files.is_empty()
else " ([b]%d[/b] audio cues will be deleted)" % audio_files.size()
]
_delete_dialog.on_confirmed = _remove_from_core
PopochiuEditorHelper.show_delete_confirmation(_delete_dialog)
func _get_state_template() -> Script:
return null
func _get_location() -> String:
return "Popochiu"
#endregion
#region Public #####################################################################################
## Called to make the row appear semitransparent to indicate that the object is in the project
## (has a folder with files inside) but is not part of the [code]popochiu_data.cfg[/code] file nor
## its corresponding autoload (e.g., R, C, I, D). This can happen when one removes an object from
## the project without removing its files, or when adding objects from another project.
func show_as_not_in_core() -> void:
label.modulate.a = 0.5
menu_popup.set_item_disabled(menu_popup.get_item_index(Options.ADD_TO_CORE), false)
#endregion
#region Private ####################################################################################
func _get_menu_cfg() -> Array:
return [
{
id = Options.ADD_TO_CORE,
icon = ADD_TO_CORE_ICON,
label = "Add to Popochiu",
types = PopochiuResources.MAIN_TYPES
},
] + super()
func _menu_item_pressed(id: int) -> void:
match id:
Options.ADD_TO_CORE:
_add_object_to_core()
_:
super(id)
## Add this Object (Room, Character, InventoryItem, Dialog) to popochiu_data.cfg so it can be used
## by Popochiu.
func _add_object_to_core() -> void:
var target_array := ""
var resource: Resource
if ".tscn" in path:
resource = load(path.replace(".tscn", ".tres"))
else:
resource = load(path)
match type:
PopochiuResources.Types.ROOM:
target_array = "rooms"
PopochiuResources.Types.CHARACTER:
target_array = "characters"
PopochiuResources.Types.INVENTORY_ITEM:
target_array = "inventory_items"
PopochiuResources.Types.DIALOG:
target_array = "dialogs"
if PopochiuEditorHelper.add_resource_to_popochiu(target_array, resource) != OK:
PopochiuUtils.print_error("Couldn't add Object [b]%s[/b] to Popochiu." % str(name))
return
# Add the object to its corresponding singleton
PopochiuResources.update_autoloads(true)
label.modulate.a = 1.0
menu_popup.set_item_disabled(menu_popup.get_item_index(Options.ADD_TO_CORE), true)
## Selects the main file of the object in the FileSystem and opens it so that it can be edited.
func _open() -> void:
EditorInterface.select_file(path)
if ".tres" in path:
EditorInterface.edit_resource(load(path))
else:
EditorInterface.set_main_screen_editor("2D")
EditorInterface.open_scene_from_path(path)
select()
func _open_script() -> void:
var script_path := path
if ".tscn" in path:
# A room, character, inventory item, or prop
script_path = path.replace(".tscn", ".gd")
elif ".tres" in path:
# A dialog
script_path = path.replace(".tres", ".gd")
elif not ".gd" in path:
return
EditorInterface.select_file(script_path)
EditorInterface.set_main_screen_editor("Script")
EditorInterface.edit_script(load(script_path))
select()
func _edit_state() -> void:
EditorInterface.select_file(path.replace(".tscn", ".tres"))
EditorInterface.edit_resource(load(path.replace(".tscn", ".tres")))
select()
func _open_state_script() -> void:
var state := load(path.replace(".tscn", ".tres"))
EditorInterface.select_file(state.get_script().resource_path)
EditorInterface.set_main_screen_editor("Script")
EditorInterface.edit_resource(state.get_script())
select()
func _search_audio_files(dir: EditorFileSystemDirectory) -> Array:
var files := []
for idx in dir.get_subdir_count():
files.append_array(_search_audio_files(dir.get_subdir(idx)))
for idx in dir.get_file_count():
match dir.get_file_type(idx):
AUDIO_FILE_TYPES:
files.append(dir.get_file_path(idx))
return files
func _remove_from_core() -> void:
# Check if the files should be deleted in the file system
if _delete_dialog.check_box.button_pressed:
_delete_from_file_system()
elif type in PopochiuResources.MAIN_TYPES:
show_as_not_in_core()
var edited_scene: Node = EditorInterface.get_edited_scene_root()
if edited_scene and edited_scene.get("script_name") and edited_scene.script_name == name:
# If the open scene matches the object being deleted, skip saving the scene
queue_free()
return
EditorInterface.save_scene()
queue_free()
## Remove this object's directory (subfolders included) from the file system.
func _delete_from_file_system() -> void:
var object_dir: EditorFileSystemDirectory = \
EditorInterface.get_resource_filesystem().get_filesystem_path(path.get_base_dir())
# Remove files, sub folders and its files.
_recursive_delete(object_dir)
## Remove the `dir` directory from the system. For Godot to be able to delete a directory, it has to
## be empty, so this method first deletes the files from from the directory and each of its
## subdirectories.
func _recursive_delete(dir: EditorFileSystemDirectory) -> void:
if dir.get_file_count() > 0:
assert(
_delete_files(dir) == OK,
"[Popochiu] Error removing files in recursive elimination of %s" % dir.get_path()
)
if dir.get_subdir_count() > 0:
for folder_idx in dir.get_subdir_count():
# Check if there are more folders inside the folder or delete the files inside it before
# deleting the folder itself
_recursive_delete(dir.get_subdir(folder_idx))
assert(
DirAccess.remove_absolute(dir.get_path()) == OK,
"[Popochiu] Error removing folder in recursive elimination of %s" % dir.get_path()
)
EditorInterface.get_resource_filesystem().scan()
## Delete files within [param dir] directory. First, get the paths to each file, then delete them
## one by one calling [method EditorFileSystem.update_file], so that in case it's an imported file,
## its [b].import[/b] is also deleted.
func _delete_files(dir: EditorFileSystemDirectory) -> int:
# Stores the paths of the files to be deleted.
var files_paths := []
# Stores the paths of the audio resources to delete
var deleted_audios := []
for file_idx: int in dir.get_file_count():
match dir.get_file_type(file_idx):
AUDIO_FILE_TYPES:
deleted_audios.append(dir.get_file_path(file_idx))
"Resource":
var resource: Resource = load(dir.get_file_path(file_idx))
if not resource is AudioCue:
# If the resource is not an AudioCue, then it should be ignored for deletion
# in the game data
continue
# Delete the [PopochiuAudioCue] in the project data file and the A singleton
assert(
_delete_audio_cue_in_data(resource) == true,
"[Popochiu] Couldn't remove [b]%s[/b] during deletion of [b]%s[/b]." %
[resource.resource_path, dir.get_path()]
)
deleted_audios.append(resource.audio.resource_path)
files_paths.append(dir.get_file_path(file_idx))
for fp: String in files_paths:
var err: int = DirAccess.remove_absolute(fp)
if err != OK:
PopochiuUtils.print_error("Couldn't delete file %s. err_code:%d" % [err, fp])
return err
EditorInterface.get_resource_filesystem().scan()
# Delete the rows of audio files and the deleted AudioCues in the Audio tab
if not deleted_audios.is_empty():
PopochiuEditorHelper.signal_bus.audio_cues_deleted.emit(deleted_audios)
# Remove extra files (like .import)
for file_name: String in DirAccess.get_files_at(dir.get_path()):
DirAccess.remove_absolute(dir.get_path() + "/" + file_name)
EditorInterface.get_resource_filesystem().scan()
return OK
## Looks to which audio group corresponds [param audio_cue] and deletes it both from
## [code]popochiu_data.cfg[/code] and the [b]A[/b] singleton (which is the one used to allow code
## autocompletion related to [PopochiuAudioCue]s).
func _delete_audio_cue_in_data(audio_cue: AudioCue) -> bool:
# TODO: This could be improved a lot if each PopochiuAudioCue has a variable to store the group
# to which it corresponds to.
# Delete the [PopochiuAudioCue] in the popochiu_data.cfg
for cue_group in ["mx_cues", "sfx_cues", "vo_cues", "ui_cues"]:
var cues: Array = PopochiuResources.get_data_value("audio", cue_group, [])
if not cues.has(audio_cue.resource_path): continue
cues.erase(audio_cue.resource_path)
if PopochiuResources.set_data_value("audio", cue_group, cues) != OK:
return false
# Fix #59 : remove the [PopochiuAudioCue] from the [A] singleton
PopochiuResources.remove_audio_autoload(
cue_group, audio_cue.resource_name, audio_cue.resource_path
)
break
return true
#endregion

View file

@ -0,0 +1 @@
uid://dj0dw4jcynb3p

View file

@ -0,0 +1,58 @@
[gd_scene load_steps=5 format=3 uid="uid://dwbo3pl372ugo"]
[ext_resource type="PackedScene" uid="uid://dwtwuqw2hpdpe" path="res://addons/popochiu/editor/main_dock/popochiu_row/popochiu_row.tscn" id="1_xi41g"]
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd" id="2_g05pm"]
[sub_resource type="Image" id="Image_15l6n"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_12sj2"]
image = SubResource("Image_15l6n")
[node name="PopochiuMainObjectRow" instance=ExtResource("1_xi41g")]
script = ExtResource("2_g05pm")
[node name="Label" parent="HBoxContainer" index="0"]
text = "PopochiuMainObjectRow"
[node name="BtnOpen" type="Button" parent="Panel/ButtonsContainer" index="0"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Open in Editor"
icon = SubResource("ImageTexture_12sj2")
flat = true
[node name="BtnScript" type="Button" parent="Panel/ButtonsContainer" index="1"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Open in Script"
icon = SubResource("ImageTexture_12sj2")
flat = true
[node name="BtnState" type="Button" parent="Panel/ButtonsContainer" index="2"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Open state"
icon = SubResource("ImageTexture_12sj2")
flat = true
[node name="BtnStateScript" type="Button" parent="Panel/ButtonsContainer" index="3"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 3
tooltip_text = "Open state Script"
icon = SubResource("ImageTexture_12sj2")
flat = true

View file

@ -0,0 +1,60 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
const PROP_TEMPLATE = "res://addons/popochiu/engine/templates/prop_template.gd"
var node_path := ""
#region Godot ######################################################################################
func _ready() -> void:
super()
if not FileAccess.file_exists(path.replace(".tscn", ".gd")):
btn_script.hide()
btn_state.hide()
btn_state_script.hide()
#endregion
#region Virtual ####################################################################################
func _get_location() -> String:
# Structure of path: "res://game/rooms/room_name/props/prop_name/"
# path split: [res:, popochiu, rooms, room_name, props, prop_name]
return "Room%s" % (path.split("/", false)[3]).to_pascal_case()
#endregion
#region Private ####################################################################################
func _remove_from_core() -> void:
var room_child_to_free: Node = null
if EditorInterface.get_edited_scene_root() is PopochiuRoom:
var opened_room: PopochiuRoom = EditorInterface.get_edited_scene_root()
match type:
PopochiuResources.Types.PROP:
room_child_to_free = opened_room.get_prop(str(name))
PopochiuResources.Types.HOTSPOT:
room_child_to_free = opened_room.get_hotspot(str(name))
PopochiuResources.Types.MARKER:
room_child_to_free = opened_room.get_marker(str(name))
PopochiuResources.Types.REGION:
room_child_to_free = opened_room.get_region(str(name))
PopochiuResources.Types.WALKABLE_AREA:
room_child_to_free = opened_room.get_walkable_area(str(name))
# Continue with the deletion flow
super()
# Fix #196: Remove the Node from the Room tree once the folder of the object has been deleted
# from the FileSystem (this applies to Props, Hotspots, Walkable areas and Regions).
if room_child_to_free:
room_child_to_free.queue_free()
EditorInterface.save_scene()
#endregion

View file

@ -0,0 +1,22 @@
[gd_scene load_steps=5 format=3 uid="uid://baei84pjb3fwj"]
[ext_resource type="PackedScene" uid="uid://dwbo3pl372ugo" path="res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.tscn" id="1_yvb3l"]
[ext_resource type="Script" path="res://addons/popochiu/editor/main_dock/popochiu_row/object_row/room_object_row/popochiu_room_object_row.gd" id="2_ulg85"]
[sub_resource type="Image" id="Image_8gjpm"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_4qvg7"]
image = SubResource("Image_8gjpm")
[node name="PopochiuRoomObjectRow" instance=ExtResource("1_yvb3l")]
script = ExtResource("2_ulg85")
[node name="BtnMenu" parent="Panel/ButtonsContainer" index="4"]
popup/item_1/icon = SubResource("ImageTexture_4qvg7")

View file

@ -0,0 +1,90 @@
@tool
extends "res://addons/popochiu/editor/main_dock/popochiu_row/object_row/popochiu_object_row.gd"
enum RoomOptions {
DELETE = MenuOptions.DELETE,
ADD_TO_CORE = Options.ADD_TO_CORE,
SET_AS_MAIN,
}
const STATE_TEMPLATE = "res://addons/popochiu/engine/templates/room_state_template.gd"
var is_main := false : set = set_is_main
@onready var btn_play: Button = %BtnPlay
#region Godot ######################################################################################
func _ready() -> void:
super()
# Assign icons
tag.texture = get_theme_icon("Heart", "EditorIcons")
btn_play.icon = get_theme_icon("MainPlay", "EditorIcons")
btn_play.pressed.connect(_play)
#endregion
#region Virtual ####################################################################################
func _get_state_template() -> Script:
return load(STATE_TEMPLATE)
func _clear_tag() -> void:
if is_main:
is_main = false
#endregion
#region SetGet #####################################################################################
func set_is_main(value: bool) -> void:
is_main = value
if is_main:
# Call this first since the favs will be cleared
PopochiuEditorHelper.signal_bus.main_scene_changed.emit(path)
tag.visible = value
menu_popup.set_item_disabled(menu_popup.get_item_index(RoomOptions.SET_AS_MAIN), value)
#endregion
#region Private ####################################################################################
func _get_menu_cfg() -> Array:
return [
{
id = RoomOptions.SET_AS_MAIN,
icon = get_theme_icon("Heart", "EditorIcons"),
label = "Set as Main scene",
},
] + super()
func _menu_item_pressed(id: int) -> void:
match id:
RoomOptions.SET_AS_MAIN:
is_main = true
_:
super(id)
## Plays the scene of the clicked row
func _play() -> void:
EditorInterface.select_file(path)
EditorInterface.play_custom_scene(path)
func _remove_from_core() -> void:
# Delete the object from Popochiu
PopochiuResources.remove_autoload_obj(PopochiuResources.R_SNGL, name)
PopochiuResources.erase_data_value("rooms", str(name))
# Continue with the deletion flow
super()
#endregion

View file

@ -0,0 +1 @@
uid://bbbtge112sbkf

Some files were not shown because too many files have changed in this diff Show more