diff --git a/.gitignore b/.gitignore index c93756164..048abf3db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Addons other than Popochiu # (we use some for development) -# addons/** -# !addons/popochiu/** +addons/* +!addons/popochiu/ # Godot-specific ignores .import/ diff --git a/addons/popochiu/editor/config/config.gd b/addons/popochiu/editor/config/config.gd index 50ec2c43d..4d2f9c308 100644 --- a/addons/popochiu/editor/config/config.gd +++ b/addons/popochiu/editor/config/config.gd @@ -2,12 +2,15 @@ extends RefCounted # EDITOR SETTINGS +const _ASEPRITE_IMPORTER_ENABLED_KEY = 'popochiu/import/aseprite/enable_aseprite_importer' const _ASEPRITE_COMMAND_KEY = 'popochiu/import/aseprite/command_path' const _REMOVE_SOURCE_FILES_KEY = 'popochiu/import/aseprite/remove_json_file' # PROJECT SETTINGS const _DEFAULT_IMPORT_ENABLED = 'popochiu/import/aseprite/import_animation_by_default' const _DEFAULT_LOOP_ENABLED = 'popochiu/import/aseprite/loop_animation_by_default' +const _DEFAULT_PROP_VISIBLE_ENABLED = 'popochiu/import/aseprite/new_props_visible_by_default' +const _DEFAULT_PROP_CLICKABLE_ENABLED = 'popochiu/import/aseprite/new_props_clickable_by_default' const _DEFAULT_WIPE_OLD_ANIMS_ENABLED = 'popochiu/import/aseprite/wipe_old_animations' @@ -20,6 +23,7 @@ var _plugin_icons: Dictionary # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func initialize_editor_settings(): editor_settings = ei.get_editor_settings() + _initialize_editor_cfg(_ASEPRITE_IMPORTER_ENABLED_KEY, false, TYPE_BOOL) _initialize_editor_cfg(_ASEPRITE_COMMAND_KEY, _default_command(), TYPE_STRING) _initialize_editor_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL) @@ -27,6 +31,8 @@ func initialize_editor_settings(): func initialize_project_settings(): _initialize_project_cfg(_DEFAULT_IMPORT_ENABLED, true, TYPE_BOOL) _initialize_project_cfg(_DEFAULT_LOOP_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_DEFAULT_PROP_VISIBLE_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_DEFAULT_PROP_CLICKABLE_ENABLED, true, TYPE_BOOL) _initialize_project_cfg(_DEFAULT_WIPE_OLD_ANIMS_ENABLED, true, TYPE_BOOL) _set_icons() @@ -34,6 +40,10 @@ func initialize_project_settings(): # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ +func aseprite_importer_enabled() -> bool: + return _get_editor_setting(_ASEPRITE_IMPORTER_ENABLED_KEY, false) + + func get_command() -> String: return _get_editor_setting(_ASEPRITE_COMMAND_KEY, _default_command()) @@ -54,6 +64,14 @@ func is_default_animation_loop_enabled() -> bool: return _get_project_setting(_DEFAULT_LOOP_ENABLED, true) +func is_default_animation_prop_visible() -> bool: + return _get_project_setting(_DEFAULT_PROP_VISIBLE_ENABLED, true) + + +func is_default_animation_prop_clickable() -> bool: + return _get_project_setting(_DEFAULT_PROP_CLICKABLE_ENABLED, true) + + func is_default_wipe_old_anims_enabled() -> bool: return _get_project_setting(_DEFAULT_WIPE_OLD_ANIMS_ENABLED, true) @@ -81,6 +99,7 @@ func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = P "hint": hint, }) + func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE): if not ProjectSettings.has_setting(key): ProjectSettings.set_setting(key, default_value) diff --git a/addons/popochiu/editor/config/result_codes.gd b/addons/popochiu/editor/config/result_codes.gd index 59d07c89f..a30ee3d41 100644 --- a/addons/popochiu/editor/config/result_codes.gd +++ b/addons/popochiu/editor/config/result_codes.gd @@ -1,21 +1,32 @@ @tool extends RefCounted +class_name ResultCodes - -const SUCCESS = 0 -const ERR_ASEPRITE_CMD_NOT_FULL_PATH = 1 -const ERR_ASEPRITE_CMD_NOT_FOUND = 2 -const ERR_SOURCE_FILE_NOT_FOUND = 3 -const ERR_OUTPUT_FOLDER_NOT_FOUND = 4 -const ERR_ASEPRITE_EXPORT_FAILED = 5 -const ERR_UNKNOWN_EXPORT_MODE = 6 -const ERR_NO_VALID_LAYERS_FOUND = 7 -const ERR_INVALID_ASEPRITE_SPRITESHEET = 8 -const ERR_NO_ANIMATION_PLAYER_FOUND = 9 -const ERR_NO_SPRITE_FOUND = 10 -const ERR_UNNAMED_TAG_DETECTED = 11 -const ERR_TAGS_OPTIONS_ARRAY_EMPTY = 12 - +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 ░░░░ @@ -23,6 +34,7 @@ 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: @@ -45,5 +57,19 @@ static func get_error_message(code: int): 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 diff --git a/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd b/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd new file mode 100644 index 000000000..42502e83a --- /dev/null +++ b/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd @@ -0,0 +1,234 @@ +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' + +const Constants := preload('res://addons/popochiu/popochiu_resources.gd') +const MainDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +const PopochiuObjectRow := preload('res://addons/popochiu/editor/main_dock/object_row/popochiu_object_row.gd') + +var _main_dock: Panel = null + +# 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 := '' +# The following variable is needed because the room factory +# must set a property on the dock row if the room is the +# primary one. +# TODO: remove the need for this using signals #67 +var _dock_row: PopochiuObjectRow +# 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 + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ +func _init(main_dock: Panel) -> void: + _main_dock = main_dock + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ +func get_obj_scene() -> Node: + return _scene + + +func get_obj_resource() -> Resource: + return _resource + + +func get_state_resource() -> Resource: + return _state_resource + + +func get_obj_script() -> Resource: + return _script + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 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: Check if another object was created in the same PATH. + # TODO: Remove created files if the creation process failed. + if DirAccess.make_dir_recursive_absolute(_path_base.get_base_dir()) != OK: + push_error( + '[Popochiu] 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: + push_error( + '[Popochiu] 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: + push_error( + "[Popochiu] Couldn't 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: + push_error( + "[Popochiu] Couldn't 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: + push_error( + "[Popochiu] Couldn't 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 + ) + + _script = load(EMPTY_SCRIPT).duplicate() + _script.source_code = new_code + + if ResourceSaver.save( _script, _path_script) != OK: + push_error( + "[Popochiu] Couldn't 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: + push_error( + "[Popochiu] Couldn't 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).instantiate() + + return ResultCodes.SUCCESS + + +func _save_obj_resource(obj: Resource) -> int: + if ResourceSaver.save(obj, _path_resource) != OK: + push_error( + "[Popochiu] Couldn't 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] + ).instantiate() + + # 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 + if _main_dock.add_resource_to_popochiu( + _type_target, + ResourceLoader.load(_path_resource) + ) != OK: + push_error( + "[Popochiu] Couldn't 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 + _dock_row = (_main_dock as MainDock).add_to_list( + _type, _pascal_name + ) diff --git a/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd b/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd new file mode 100644 index 000000000..ce5be4894 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd @@ -0,0 +1,65 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' + +const CHILD_VISIBLE_IN_ROOM_META = '_popochiu_obj_factory_child_visible_in_room_' +const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") + +var _room_tab: VBoxContainer = null + +# 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 := '' + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(main_dock: Panel) -> void: + super(main_dock) + _room_tab = _main_dock.get_opened_room_tab() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 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() + + # Update the correct list in the Room tab + (_room_tab as TabRoom).add_to_list( + _type, + _pascal_name, + _path_scene + ) diff --git a/addons/popochiu/editor/factories/factory_popochiu_character.gd b/addons/popochiu/editor/factories/factory_popochiu_character.gd new file mode 100644 index 000000000..97a4a9477 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_character.gd @@ -0,0 +1,50 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuCharacterFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.CHARACTER + _type_label = 'character' + _type_target = 'characters' + _path_template = _main_dock.CHARACTERS_PATH + '%s/character_%s' + + +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: 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 = Constants.CURSOR_TYPE.TALK + # ▓▓▓ 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 diff --git a/addons/popochiu/editor/factories/factory_popochiu_dialog.gd b/addons/popochiu/editor/factories/factory_popochiu_dialog.gd new file mode 100644 index 000000000..e606fe63e --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_dialog.gd @@ -0,0 +1,44 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuDialogFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.DIALOG + _type_label = 'dialog' + _type_target = 'dialogs' + _path_template = _main_dock.DIALOGS_PATH + '%s/dialog_%s' + + +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 diff --git a/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd b/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd new file mode 100644 index 000000000..4f4294c77 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd @@ -0,0 +1,45 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuHotspotFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.HOTSPOT + _type_label = 'hotspot' + _obj_room_group = 'Hotspots' + _path_template = '/hotspots/%s/hotspot_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _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 instance + var new_obj: PopochiuHotspot = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + new_obj.cursor = Constants.CURSOR_TYPE.ACTIVE + + # Save the hostspot 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 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd b/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd new file mode 100644 index 000000000..97d4fe6e7 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd @@ -0,0 +1,51 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuInventoryItemFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.INVENTORY_ITEM + _type_label = 'inventory_item' + _type_target = 'inventory_items' + _path_template = _main_dock.INVENTORY_ITEMS_PATH + '%s/inventory_item_%s' + + +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 = Constants.CURSOR_TYPE.USE + new_obj.size_flags_vertical = new_obj.SIZE_SHRINK_CENTER + # ▓▓▓ 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 diff --git a/addons/popochiu/editor/factories/factory_popochiu_prop.gd b/addons/popochiu/editor/factories/factory_popochiu_prop.gd new file mode 100644 index 000000000..1d93d310f --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_prop.gd @@ -0,0 +1,57 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuPropFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + + _type = Constants.Types.PROP + _type_label = 'prop' + _obj_room_group = 'Props' + _path_template = '/props/%s/prop_%s' + + +func create( + obj_name: String, room: PopochiuRoom, is_interactive:bool = false, is_visible:bool = true +) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _setup_name(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) + 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 = Constants.CURSOR_TYPE.ACTIVE + new_obj.clickable = is_interactive + new_obj.visible = is_visible + + 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 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_region.gd b/addons/popochiu/editor/factories/factory_popochiu_region.gd new file mode 100644 index 000000000..b90386068 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_region.gd @@ -0,0 +1,50 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuRegionFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.REGION + _type_label = 'region' + _obj_room_group = 'Regions' + _path_template = '/regions/%s/region_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _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 instance + var new_obj: PopochiuRegion = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + + # 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 + + # Create a collision polygon as a child in the room scene + var collision := CollisionPolygon2D.new() + collision.name = 'InteractionPolygon' + collision.modulate = Color.CYAN + _add_visible_child(collision) + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_room.gd b/addons/popochiu/editor/factories/factory_popochiu_room.gd new file mode 100644 index 000000000..6ba302e70 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_room.gd @@ -0,0 +1,58 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuRoomFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.ROOM + _type_label = 'room' + _type_target = 'rooms' + _path_template = _main_dock.ROOMS_PATH + '%s/room_%s' + + +func create(obj_name: String, set_as_main:bool = 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 + # ▓▓▓ 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: + _main_dock.set_main_scene( _scene.scene) + # TODO: next line should be in set_main_scene() function! + _dock_row.is_main = true # So the Heart icon shows + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd b/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd new file mode 100644 index 000000000..07b240bf7 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd @@ -0,0 +1,56 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuWalkableAreaFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.WALKABLE_AREA + _type_label = 'walkable_area' + _obj_room_group = 'WalkableAreas' + _path_template = '/walkable_areas/%s/walkable_area_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _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 instance + var new_obj: PopochiuWalkableArea = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + + # 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 + + # Create a NavigationRegion2D with its polygon as a child in the room scene + var perimeter := NavigationRegion2D.new() + perimeter.name = 'Perimeter' + var polygon := NavigationPolygon.new() + polygon.add_outline(PackedVector2Array([ + Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10) + ])) + polygon.make_polygons_from_outlines() + perimeter.navpoly = polygon + perimeter.modulate = Color.GREEN + _add_visible_child(perimeter) + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/helpers/popochiu_types_helper.gd b/addons/popochiu/editor/helpers/popochiu_types_helper.gd index e6b28c5e8..4be432f90 100644 --- a/addons/popochiu/editor/helpers/popochiu_types_helper.gd +++ b/addons/popochiu/editor/helpers/popochiu_types_helper.gd @@ -1,9 +1,11 @@ extends Resource -# Class used to know the type of a Node (or group of Nodes) when this type of -# validation is required from the plugin. + +# This class holds logic to help with the management of Popochiu objects. # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +# Functions used to know the type of a Node (or group of Nodes) when this type of +# validation is required from the plugin. static func is_prop(node: Node) -> bool: return node is PopochiuProp @@ -18,3 +20,9 @@ static func is_character(node: Node) -> bool: static func is_walkable_area(node: Node) -> bool: return node is PopochiuWalkableArea + +## TODO: provide more helpers like this maybe? + +## TODO: If and when #67 is ready, add static facade metods to create items +## so we can just pass this object as entry point for all operations +## on Popochiu objects diff --git a/addons/popochiu/editor/importers/aseprite/animation_creator.gd b/addons/popochiu/editor/importers/aseprite/animation_creator.gd index 8d4d4b5f8..37808bef4 100644 --- a/addons/popochiu/editor/importers/aseprite/animation_creator.gd +++ b/addons/popochiu/editor/importers/aseprite/animation_creator.gd @@ -8,164 +8,208 @@ extends RefCounted const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd") const _DEFAULT_AL = "" # Empty string equals default "Global" animation library -var _aseprite = preload("./aseprite_controller.gd").new() - +# Vars configured on initialization var _config: RefCounted var _file_system: EditorFileSystem -var _tags_options_lookup = {} +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 + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ -func init(config, editor_file_system: EditorFileSystem = null): +func init(config, aseprite: RefCounted, editor_file_system: EditorFileSystem = null): _config = config _file_system = editor_file_system - _aseprite.init(config) + _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 -func check_aseprite() -> int: - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + # Duly check everything is valid and cleanup animations + var result = _perform_common_checks() + if result != RESULT_CODE.SUCCESS: + return result - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND - - return RESULT_CODE.SUCCESS + # 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 -func create_animations(target_node: Node, player: AnimationPlayer, options: Dictionary): - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + # Set the texture in the sprite and configure + # the animations in the AnimationPlayer + _setup_texture() + result = _configure_animations() - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + return result - 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 +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 target_sprite = _find_sprite_in_target(target_node) + var prop_animation_name = aseprite_tag.to_snake_case() - 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 - - _load_tags_options_lookup(options.get("tags")) + # Duly check everything is valid and cleanup animations + var result = _perform_common_checks() + if result != RESULT_CODE.SUCCESS: + return result - if (options.wipe_old_animations): - _remove_animations_from_player(player) - - var result = await _create_animations_from_file(target_sprite, player, options) - + # Create the spritesheet + result = await _create_spritesheet_from_tag(aseprite_tag) if result != RESULT_CODE.SUCCESS: - printerr(RESULT_CODE.get_error_message(result)) + return result + # Load tags information + result = await _load_spritesheet_metadata(aseprite_tag) + if result != RESULT_CODE.SUCCESS: + return result -## TODO: Keep 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) + # 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() -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) - + return result # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ -func _load_tags_options_lookup(tags: Array = []): - for t in tags: - _tags_options_lookup[t.tag_name] = t - - -func _find_sprite_in_target(target_sprite: Node) -> Node: - if not target_sprite.has_node("Sprite2D"): - return null - return target_sprite.get_node("Sprite2D") - - -func _create_animations_from_file(target_sprite: Node, player: AnimationPlayer, options: Dictionary): - var output +# 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(): + _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 - await _scan_filesystem() - - var result = _import(target_sprite, player, output, options) - if _config.should_remove_source_files(): - DirAccess.remove_absolute(output.data_file) - await _scan_filesystem() - - return result +# 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 _remove_animations_from_player(player: AnimationPlayer): - player.remove_animation_library(_DEFAULT_AL) +func _load_spritesheet_metadata(selected_tag: String = ""): + _spritesheet_metadata = { + tags = {}, + frames = {}, + meta = {}, + sprite_sheet = {} + } -func _import(target_sprite: Node, player: AnimationPlayer, data: Dictionary, options: Dictionary): - var source_file = data.data_file - var sprite_sheet = data.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 - _setup_texture(target_sprite, sprite_sheet, content) - var result = _configure_animations(target_sprite, player, content) - if result != RESULT_CODE.SUCCESS: - return result - - return RESULT_CODE.SUCCESS + # 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")) -func _load_texture(sprite_sheet: String) -> Texture2D: - var texture = ResourceLoader.load(sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE) - texture.take_over_path(sprite_sheet) - return texture + 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 _config.should_remove_source_files(): + DirAccess.remove_absolute(_output.data_file) + await _scan_filesystem() + return RESULT_CODE.SUCCESS -func _configure_animations(target_sprite: Node, player: AnimationPlayer, content: Dictionary): - var frames = _aseprite.get_content_frames(content) - if not player.has_animation_library(_DEFAULT_AL): - player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) +func _configure_animations(): + if not _player.has_animation_library(_DEFAULT_AL): + _player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) - if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + if _spritesheet_metadata.tags.size() > 0: var result = RESULT_CODE.SUCCESS - for tag in content.meta.frameTags: - if not _tags_options_lookup.get(tag.name).get("import"): - continue - var selected_frames = frames.slice(tag.from, tag.to+1) # slice is [) - result = _add_animation_frames(target_sprite, player, tag.name, selected_frames, tag.direction) + # 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(target_sprite, player, "default", frames) + return _add_animation_frames("default", _spritesheet_metadata.frames) -func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_name: String, frames: Array, direction = 'forward'): +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 @@ -173,25 +217,25 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na # 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 = _tags_options_lookup.get(anim_name).get("loops") + 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.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()) + 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(target_sprite, player, animation) - var frame_track = _get_property_track_path(player, target_sprite, "frame") - var frame_track_index = _create_track(target_sprite, animation, frame_track) + 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() @@ -199,7 +243,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na var animation_length = 0 for frame in frames: - var frame_key = _get_frame_key(target_sprite, frame) + 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 @@ -210,7 +254,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na frames.reverse() for frame in frames: - var frame_key = _get_frame_key(target_sprite, frame) + 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 @@ -220,8 +264,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na return RESULT_CODE.SUCCESS -## TODO: insert validate tokens in amination name - +## 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) @@ -238,8 +281,8 @@ func _create_track(target_sprite: Node, animation: Animation, track: String): return track_index -func _get_property_track_path(player: AnimationPlayer, target_sprite: Node, prop: String) -> String: - var node_path = player.get_node(player.root_node).get_path_to(target_sprite) +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] @@ -263,38 +306,39 @@ func _remove_properties_from_path(path: NodePath) -> NodePath: # 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 -func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary): - var texture = _load_texture(sprite_sheet) - sprite.texture = texture - - if content.frames.is_empty(): + if _spritesheet_metadata.frames.is_empty(): return - sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w - sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h + _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(sprite: Node, player: AnimationPlayer, animation: Animation): - var texture_track = _get_property_track_path(player, sprite, "texture") - var texture_track_index = _create_track(sprite, animation, texture_track) - animation.track_insert_key(texture_track_index, 0, sprite.texture) +func _create_meta_tracks(animation: Animation): + var texture_track = _get_property_track_path("texture") + var texture_track_index = _create_track(_target_sprite, animation, texture_track) + animation.track_insert_key(texture_track_index, 0, _target_sprite.texture) - var hframes_track = _get_property_track_path(player, sprite, "hframes") - var hframes_track_index = _create_track(sprite, animation, hframes_track) - animation.track_insert_key(hframes_track_index, 0, sprite.hframes) + 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(player, sprite, "vframes") - var vframes_track_index = _create_track(sprite, animation, vframes_track) - animation.track_insert_key(vframes_track_index, 0, sprite.vframes) + 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(player, sprite, "visible") - var visible_track_index = _create_track(sprite, animation, visible_track) + 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(sprite: Node, frame: Dictionary): - return _calculate_frame_index(sprite,frame) +func _get_frame_key(frame: Dictionary): + return _calculate_frame_index(_target_sprite,frame) func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: @@ -302,3 +346,41 @@ func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: 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) diff --git a/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd b/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd index 644fb6432..d4ddc78e3 100644 --- a/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd +++ b/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd @@ -79,6 +79,32 @@ func export_layer(file_name: String, layer_name: String, output_folder: String, } +# 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] @@ -118,6 +144,10 @@ 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"]: diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd index 9d7b0b334..294c16648 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd +++ b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd @@ -11,14 +11,19 @@ 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 # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready(): - loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons') + # 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') # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ @@ -32,6 +37,10 @@ func init(config, tag_cfg: Dictionary): _anim_tag_state.merge(tag_cfg, true) _setup_scene() +func show_prop_buttons(): + separator.visible = true + visible_toggle.visible = true + clickable_toggle.visible = true # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -39,12 +48,14 @@ func get_cfg() -> Dictionary: return _anim_tag_state - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 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: @@ -52,10 +63,11 @@ func _load_default_tag_state() -> Dictionary: "tag_name": "", "import": _config.is_default_animation_import_enabled(), "loops": _config.is_default_animation_loop_enabled(), + "prop_visible": _config.is_default_animation_prop_visible(), + "prop_clickable": _config.is_default_animation_prop_clickable(), } - func _on_import_toggled(button_pressed): _anim_tag_state.import = button_pressed emit_signal("tag_state_changed") @@ -64,3 +76,12 @@ func _on_import_toggled(button_pressed): 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") diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn index 2e7044082..3193d194e 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn +++ b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn @@ -1,10 +1,10 @@ -[gd_scene load_steps=5 format=3 uid="uid://rphyltbm12m4"] +[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_t6px5"] +[sub_resource type="Image" id="Image_33eob"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 131, 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, 93, 93, 131, 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, 93, 93, 131, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 55, 255, 97, 97, 58, 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, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 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, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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", @@ -13,8 +13,10 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_317mf"] -image = SubResource("Image_t6px5") +[sub_resource type="ImageTexture" id="ImageTexture_i0lab"] +image = SubResource("Image_33eob") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sd1l8"] [node name="AnimationTagRow" type="HBoxContainer"] offset_right = 320.0 @@ -34,11 +36,39 @@ size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxEmpty_77wem") [node name="HBoxContainer" type="HBoxContainer" parent="Panel"] -layout_mode = 0 +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_i0lab") +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_i0lab") +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 @@ -46,7 +76,7 @@ size_flags_horizontal = 4 size_flags_vertical = 3 tooltip_text = "Import this animation" toggle_mode = true -icon = SubResource("ImageTexture_317mf") +icon = SubResource("ImageTexture_i0lab") flat = true [node name="Loops" type="Button" parent="Panel/HBoxContainer"] @@ -55,8 +85,10 @@ size_flags_horizontal = 4 size_flags_vertical = 3 tooltip_text = "Set animation as looping" toggle_mode = true -icon = SubResource("ImageTexture_317mf") +icon = SubResource("ImageTexture_i0lab") 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"] diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd similarity index 72% rename from addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd rename to addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd index 3bacf4c0a..78c380bdf 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd @@ -1,26 +1,33 @@ @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: import a class without breaking coding standards... how? -const AnimationTagRow = preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.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 config: RefCounted +var main_dock: Panel var file_system: EditorFileSystem # External logic -var _animation_creator = preload("res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new() -var _animation_tag_row_scene: PackedScene = preload('res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn') +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 _animation_player_path: String var _file_dialog_aseprite: FileDialog var _output_folder_dialog: FileDialog var _importing := false @@ -31,45 +38,77 @@ var _out_folder_default := "[Same as scene]" # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready(): _set_elements_styles() - - if not target_node.has_node("AnimationPlayer"): - printerr(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(config, file_system) + if not config.aseprite_importer_enabled(): + _show_info() + return + # Init Aseprite helper library + _aseprite.init(config) # Check access to Aseprite executable - var result = _animation_creator.check_aseprite() + var result = _check_aseprite() if result == RESULT_CODE.SUCCESS: _show_importer() else: printerr(RESULT_CODE.get_error_message(result)) - _hide_importer() - + _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_options_visible(cfg.get("op_exp")) + _set_options_visible(cfg.get("op_exp")) # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ +func _check_aseprite() -> int: + _aseprite.init(config) + + 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('%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)) + 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_options_visible(cfg.get("op_exp", false)) _populate_tags(cfg.get("tags", [])) @@ -77,8 +116,8 @@ func _load_config(cfg): func _save_config(): _update_tags_cache() + var cfg := { - "player": _animation_player_path, "source": _source, "tags": _tags_cache, "op_exp": get_node('%Options').visible, @@ -106,7 +145,9 @@ func _load_default_config(): get_node('%OutFolderButton').text = "[empty]" get_node('%OutFileName').clear() get_node('%VisibleLayersCheckButton').set_pressed_no_signal(false) - get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal(config.is_default_wipe_old_anims_enabled()) + get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal( + config.is_default_wipe_old_anims_enabled() + ) func _set_source(source): @@ -136,39 +177,27 @@ func _on_rescan_pressed(): func _on_import_pressed(): if _importing: return + _importing = true - - var root = get_tree().get_edited_scene_root() - - if _animation_player_path == "" or not root.has_node(_animation_player_path): - _show_message("AnimationPlayer not found") - _importing = false - return - + _root_node = get_tree().get_edited_scene_root() + if _source == "": _show_message("Aseprite file not selected") _importing = false return - - var options = { + + _options = { "source": ProjectSettings.globalize_path(_source), "tags": _tags_cache, - "output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(), + "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() - - var result = await _animation_creator.create_animations(target_node, root.get_node(_animation_player_path), options) - _importing = false - if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS: - printerr(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!") + _save_config() func _on_reset_pressed(): @@ -220,16 +249,23 @@ func _populate_tags(tags: Array): get_node('%Tags').add_child(tag_row) tag_row.init(config, 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() @@ -261,7 +297,7 @@ func _get_tags_from_ui() -> Array: func _get_tags_from_source() -> Array: - var tags_found = _animation_creator.list_tags(ProjectSettings.globalize_path(_source)) + var tags_found = _list_tags(ProjectSettings.globalize_path(_source)) if typeof(tags_found) == TYPE_INT: printerr(RESULT_CODE.get_error_message(tags_found)) return [] @@ -275,14 +311,28 @@ func _get_tags_from_source() -> Array: return tags_list -func _show_message(message: String, title: String = ""): +func _show_message( + message: String, title: String = "", object: Object = null, method := "" +): var _warning_dialog = AcceptDialog.new() - get_parent().add_child(_warning_dialog) + 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) + + main_dock.add_child(_warning_dialog) _warning_dialog.popup_centered() - _warning_dialog.connect("close_requested", Callable(_warning_dialog, "queue_free")) func _show_confirmation(message: String, title: String = ""): @@ -302,7 +352,9 @@ func _on_options_title_toggled(button_pressed): func _set_options_visible(is_visible): get_node('%Options').visible = is_visible - get_node('%OptionsTitle').icon = config.get_icon("expanded") if is_visible else config.get_icon("collapsed") + get_node('%OptionsTitle').icon = ( + config.get_icon("expanded") if is_visible else config.get_icon("collapsed") + ) func _on_out_folder_pressed(): @@ -324,7 +376,9 @@ func _create_output_folder_selection(): func _on_output_folder_selected(path): _output_folder = path - get_node('%OutFolderButton').text = _output_folder if _output_folder != "" else _out_folder_default + get_node('%OutFolderButton').text = ( + _output_folder if _output_folder != "" else _out_folder_default + ) _output_folder_dialog.queue_free() _save_config() @@ -345,15 +399,21 @@ func _set_elements_styles(): get_node('%WarningLabel').add_theme_color_override('font_color', Color('c46c71')) -func _hide_importer(): +func _show_info(): + get_node('%Info').visible = true + get_node('%Warning').visible = false get_node('%Importer').visible = false - get_node('%Warning').visible = true +func _show_warning(): + get_node('%Info').visible = false + get_node('%Warning').visible = true + get_node('%Importer').visible = false + + func _show_importer(): - get_node('%Importer').visible = true + get_node('%Info').visible = false get_node('%Warning').visible = false + get_node('%Importer').visible = true - -## TODO: IMPORTANT AND FIRST IN LINE! The importer has different behavior for Characters, Rooms and Inventory items! ## TODO: Introduce layer selection list, more or less as tags diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn similarity index 85% rename from addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn rename to addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn index dce657373..6fe2020ee 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn @@ -1,30 +1,23 @@ -[gd_scene load_steps=5 format=3 uid="uid://qtefsrha1b7s"] - -[ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd" id="1"] +[gd_scene load_steps=4 format=3 uid="uid://bcanby6n3eahm"] [sub_resource type="StyleBoxEmpty" id="1"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lr3up"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwoxk"] bg_color = Color(0, 0, 0, 1) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uvby7"] +[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.365, 0.365, 1) +bg_color = Color(1, 0.364706, 0.364706, 1) draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 corner_detail = 1 [node name="sprite_inspector_dock" type="PanelContainer"] offset_right = 14.0 offset_bottom = 14.0 theme_override_styles/panel = SubResource("1") -script = ExtResource("1") [node name="margin" type="MarginContainer" parent="."] layout_mode = 2 @@ -62,7 +55,7 @@ text = "Rescan" [node name="TagsTitleBar" type="PanelContainer" parent="margin/Importer"] unique_name_in_owner = true layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_lr3up") +theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk") [node name="TagsTitle" type="Button" parent="margin/Importer/TagsTitleBar"] layout_mode = 2 @@ -77,7 +70,7 @@ 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_lr3up") +theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk") [node name="OptionsTitle" type="Button" parent="margin/Importer/OptionsTitleBar"] unique_name_in_owner = true @@ -168,6 +161,7 @@ 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"] @@ -179,7 +173,7 @@ custom_minimum_size = Vector2(222, 50) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_uvby7") +theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1") [node name="WarningLabel" type="Label" parent="margin/Warning/HBoxContainer/WarningPanel"] unique_name_in_owner = true @@ -194,6 +188,30 @@ 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_options_title_toggled"] diff --git a/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd new file mode 100644 index 000000000..70a5750b2 --- /dev/null +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd @@ -0,0 +1,44 @@ +@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() + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ +func _ready(): + if not target_node.has_node("AnimationPlayer"): + printerr(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(config, _aseprite, file_system) + + super() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 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: + printerr(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 diff --git a/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd new file mode 100644 index 000000000..28bf21653 --- /dev/null +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd @@ -0,0 +1,121 @@ +@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() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ +func _ready(): + # Instantiate animation creator + _animation_creator.init(config, _aseprite, file_system) + + super() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 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 = 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: + printerr(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!", + self, + "_clean_props" + ) + + +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(main_dock) + if factory.create(name, _root_node, is_clickable, is_visible) != 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: + push_error( + "[Popochiu] 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 + + +## Removes the script in all the created props so the properties (basically their +## texture) is not overwritten by the room's .tscn file. Then the scene is saved +## and reloaded so the nodes in the tree doesn't appear as Area2D but as the +## corresponding ones: PopochiuProp. +func _clean_props() -> void: + for prop in _root_node.get_node("Props").get_children(): + prop.set_script(null) + prop.remove_meta("ANIM_NAME") + + main_dock.ei.save_scene() + main_dock.ei.reload_scene_from_path(_root_node.scene_file_path) diff --git a/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd b/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd index fa07c1250..8d44843c2 100644 --- a/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd +++ b/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd @@ -1,30 +1,48 @@ extends EditorInspectorPlugin ## TODO: create a base class with pointer variables -const INSPECTOR_DOCK = preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn") +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 ei: EditorInterface var fs: EditorFileSystem var config: RefCounted +var main_dock: Panel var _target_node: Node - + + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _can_handle(object): if object.has_method("get_parent") and object.get_parent() is Node2D: return false - return object is PopochiuCharacter #|| object is PopochiuInventoryItem || object is PopochiuProp + + return object is PopochiuCharacter || object is PopochiuRoom #|| object is PopochiuInventoryItem -func _parse_begin(object): +func _parse_begin(object: Object): _target_node = object -func _parse_category(object, category): - if category == 'Aseprite': - var dock = INSPECTOR_DOCK.instantiate() - dock.target_node = object - dock.config = config - dock.file_system = fs - add_custom_control(dock) func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide): - return name == 'popochiu_placeholder' + if name != 'popochiu_placeholder': return false + + # Instanciate 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.config = config + dock.file_system = fs + dock.main_dock = main_dock # TODO: change for SignalBus + + # Add the dock to the inspector + add_custom_control(dock) + return true diff --git a/addons/popochiu/editor/main_dock/popochiu_dock.gd b/addons/popochiu/editor/main_dock/popochiu_dock.gd index deb670e39..d261ac9e3 100644 --- a/addons/popochiu/editor/main_dock/popochiu_dock.gd +++ b/addons/popochiu/editor/main_dock/popochiu_dock.gd @@ -55,7 +55,7 @@ var _rows_paths := [] path = INVENTORY_ITEMS_PATH, group = find_child('ItemsGroup'), popup = find_child('CreateInventoryItem'), - scene = INVENTORY_ITEMS_PATH + ('%s/item_%s.tscn') + scene = INVENTORY_ITEMS_PATH + ('%s/inventory_item_%s.tscn') }, Constants.Types.DIALOG: { path = DIALOGS_PATH, @@ -261,6 +261,10 @@ func get_opened_room() -> PopochiuRoom: return _tab_room.opened_room +func get_opened_room_tab() -> VBoxContainer: + return _tab_room + + func open_setup() -> void: setup_dialog.appear() diff --git a/addons/popochiu/editor/main_dock/tab_room.gd b/addons/popochiu/editor/main_dock/tab_room.gd index 02202d5fa..c309e849d 100644 --- a/addons/popochiu/editor/main_dock/tab_room.gd +++ b/addons/popochiu/editor/main_dock/tab_room.gd @@ -1,5 +1,6 @@ @tool extends VBoxContainer + # Handles the Room tab in Popochiu's dock # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ @@ -121,10 +122,99 @@ func scene_changed(scene_root: Node) -> void: PopochiuUtils.print_error( "This room doesn't have a [code]script_name[/code] value!" ) - return if not scene_root is PopochiuRoom: + _room_name.text = opened_room.script_name + + _room_name.show() + _tool_buttons.show() + $PopochiuFilter.show() + + # Fill info of Props, Hotspots, Walkable areas, Regions and Points + for t in _types: + for c in opened_room.call(_types[t].method): + var row_path := '' + + if c is Marker2D: + var row: PopochiuObjectRow = _create_object_row(t, c.name) + _types[t].group.add(row) + continue + elif c is PopochiuCharacter: + # Get the script_name of the character + var char_name: String =\ + c.name.lstrip('Character').rstrip(' *') + _characters_in_room.append(char_name) + + # Create the row for the character + var row: PopochiuObjectRow = _create_object_row( + t, + char_name, + 'res://popochiu/Characters/%s/Character%s.tscn' % [ + char_name, char_name + ], + c.name + ) + row.is_menu_hidden = true + + _types[t].group.add(row) + + # Create button to remove the character from the room + var remove_btn := Button.new() + remove_btn.icon = get_theme_icon("Remove", "EditorIcons") + remove_btn.tooltip_text = 'Remove character from room' + remove_btn.flat = true + + remove_btn.pressed.connect( + _on_remove_character_pressed.bind(row) + ) + + row.add_button(remove_btn) + + continue + + if t == Constants.Types.PROP: + row_path = '%s/props/%s/prop_%s.tscn' % [ + opened_room.scene_file_path.get_base_dir(), + (c.name as String).to_snake_case(), + (c.name as String).to_snake_case() + ] + elif c.script.resource_path.find('addons') == -1: + row_path = c.script.resource_path + else: + row_path = '%s/%s' % [ + opened_room.scene_file_path.get_base_dir(), + (_types[t].parent as String).to_snake_case() + ] + + var node_path: String = String(c.get_path()).split( + '%s/' % _types[t].parent + )[1] + + if row_path in _rows_paths: continue + + if is_instance_of(c, _types[t].type_class): + var row: PopochiuObjectRow = _create_object_row( + t, c.name, row_path, node_path + ) + + _types[t].group.add(row) + + # NOTE: Temporary fix for incorrect row naming after importing + # the props from Aseprite + if row.name != c.name: + row.name = c.name + row._label.text = str(c.name) + + if _types[t].has('popup'): + _types[t].popup.room_opened(opened_room) + + _types[t].group.enable_create() + + _no_room_info.hide() + + get_parent().current_tab = 1 + else: get_parent().current_tab = 0 return diff --git a/addons/popochiu/editor/popups/create_character/create_character.gd b/addons/popochiu/editor/popups/create_character/create_character.gd index a99673a84..bc25f20d6 100644 --- a/addons/popochiu/editor/popups/create_character/create_character.gd +++ b/addons/popochiu/editor/popups/create_character/create_character.gd @@ -9,21 +9,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const CHARACTER_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/character_state_template.gd' -const CHARACTER_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/character_template.gd' -const CHARACTER_SCENE :=\ -'res://addons/popochiu/engine/objects/character/popochiu_character.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_character_name := '' -var _new_character_path := '' -var _character_path_template := '' -var _pascal_name := '' - +var _factory: PopochiuCharacterFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready() -> void: @@ -37,133 +28,22 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check that there is not a character in the same PATH. - # TODO: Delete created files if creation is not complete. - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the character - DirAccess.make_dir_absolute( - (_main_dock as PopochiuDock).CHARACTERS_PATH + _new_character_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the character and a script so devs - # can add extra properties to that state - var state_template: Script = load(CHARACTER_STATE_TEMPLATE).duplicate() - if ResourceSaver.save( - state_template, _new_character_path + '_state.gd' - ) != OK: - push_error('[Popochiu] Could not create character state script: %s' %\ - _new_character_name) - # TODO: Show feedback in the popup - return - - var character_resource: PopochiuCharacterData =\ - load(_new_character_path + '_state.gd').new() - character_resource.script_name = _pascal_name - character_resource.scene = _new_character_path + '.tscn' - character_resource.resource_name = _pascal_name - - if ResourceSaver.save( - character_resource, _new_character_path + '.tres' - ) != OK: - push_error( - "[Popochiu] Couldn't create PopochiuCharacterData for: %s" %\ - _new_character_name - ) - # TODO: Show feedback in the popup - return + # Setup the prop helper and use it to create the prop + _factory = PopochiuCharacterFactory.new(_main_dock) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the character - var character_script: Script = load(CHARACTER_SCRIPT_TEMPLATE).duplicate() - var new_code := character_script.source_code - - character_script.source_code = '' - - if ResourceSaver.save(character_script, _new_character_path + '.gd') != OK: - push_error( - '[Popochiu] Could not create script: %s.gd' % _new_character_name - ) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'character_state_template', - 'character_%s_state' % _new_character_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _new_character_path - ) - - character_script = load(_new_character_path + '.gd') - character_script.source_code = new_code - - if ResourceSaver.save(character_script, _new_character_path + '.gd') != OK: - push_error( - '[Popochiu] Could not update script: %s.gd' % _new_character_name - ) - # TODO: Show feedback in the popup + if _factory.create(_new_character_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the Character instance - var new_character: PopochiuCharacter = load(CHARACTER_SCENE).instantiate() - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_character.set_script(load(_new_character_path + '.gd')) - - new_character.name = 'Character' + _pascal_name - new_character.script_name = _pascal_name - new_character.description = _new_character_name.capitalize() - new_character.cursor = Constants.CURSOR_TYPE.TALK - - if PopochiuResources.get_settings().is_pixel_art_game: - new_character.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the character scene (.tscn) - var new_character_packed_scene: PackedScene = PackedScene.new() - new_character_packed_scene.pack(new_character) - if ResourceSaver.save( - new_character_packed_scene, _new_character_path + '.tscn' - ) != OK: - push_error( - "[Popochiu] Couldn't create character: %s.tscn" % _new_character_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created character to Popochiu's characters list - if _main_dock.add_resource_to_popochiu( - 'characters', ResourceLoader.load(_new_character_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created character to Popochiu: %s" %\ - _new_character_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the character to the C singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of characters in the dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.CHARACTER, _pascal_name - ) + var character_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_character_path + '.tscn') - _main_dock.ei.open_scene_from_path(_new_character_path + '.tscn') + _main_dock.ei.select_file(character_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(character_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all!!! @@ -172,7 +52,6 @@ func _create() -> void: func _clear_fields() -> void: _new_character_name = '' - _new_character_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -181,9 +60,6 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/characters - _character_path_template = _main_dock.CHARACTERS_PATH + '%s/character_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: @@ -191,9 +67,6 @@ func _update_name(new_text: String) -> void: if _name: _new_character_name = _name.to_snake_case() - _pascal_name = _name - _new_character_path = _character_path_template %\ - [_new_character_name, _new_character_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ diff --git a/addons/popochiu/editor/popups/create_dialog/create_dialog.gd b/addons/popochiu/editor/popups/create_dialog/create_dialog.gd index 4bcc872f9..0cc6d87b7 100644 --- a/addons/popochiu/editor/popups/create_dialog/create_dialog.gd +++ b/addons/popochiu/editor/popups/create_dialog/create_dialog.gd @@ -7,16 +7,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const DIALOG_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/dialog_template.gd' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_dialog_name := '' -var _new_dialog_path := '' -var _dialog_path_template := '' -var _pascal_name := '' +var _factory: PopochiuDialogFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -31,64 +27,21 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check if there is not already a dialog on the same PATH. - # TODO: Delete already created files if something breaks before finishing. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the directory where the new dialog will be saved - DirAccess.make_dir_absolute(_main_dock.DIALOGS_PATH + _new_dialog_name) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script of the new dialog - var dialog_template := load(DIALOG_SCRIPT_TEMPLATE).duplicate() - - if ResourceSaver.save(dialog_template, _new_dialog_path + '.gd') != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_dialog_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create dialog Resource - var dialog_resource := PopochiuDialog.new() - dialog_resource.set_script(load(_new_dialog_path + '.gd')) - - dialog_resource.script_name = _pascal_name - dialog_resource.resource_name = _new_dialog_name - - if ResourceSaver.save(dialog_resource, _new_dialog_path + '.tres') != OK: - push_error("[Popochiu] Couldn't create dialog: %s" %_new_dialog_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the dialog to Popochiu - if _main_dock.add_resource_to_popochiu( - 'dialogs', ResourceLoader.load(_new_dialog_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created dialog to Popochiu: %s" %\ - _new_dialog_name - ) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuDialogFactory.new(_main_dock) + + if _factory.create(_new_dialog_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the dialog to the D singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of dialogs in the Dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.DIALOG, _pascal_name - ) - + + var dialog_resource = _factory.get_obj_resource() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open dialog in the Inspector await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_dialog_path + '.tres') - _main_dock.ei.edit_resource(load(_new_dialog_path + '.tres')) + _main_dock.ei.select_file(dialog_resource.resource_path) + _main_dock.ei.edit_resource(load(dialog_resource.resource_path)) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # The end @@ -97,7 +50,6 @@ func _create() -> void: func _clear_fields() -> void: _new_dialog_name = '' - _new_dialog_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -105,9 +57,6 @@ func set_main_dock(node: Panel) -> void: super(node) if not _main_dock: return - - # res://popochiu/dialogs - _dialog_path_template = _main_dock.DIALOGS_PATH + '%s/dialog_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -116,9 +65,6 @@ func _update_name(new_text: String) -> void: if _name: _new_dialog_name = _name.to_snake_case() - _pascal_name = _name - _new_dialog_path = _dialog_path_template %\ - [_new_dialog_name, _new_dialog_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ diff --git a/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd b/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd index fcc356a6f..67271ecf6 100644 --- a/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd +++ b/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd @@ -1,23 +1,13 @@ +# Creates a new hotspot in the room. @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -# Permite crear un nuevo Hotspot para una habitación. - -const SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/hotspot_template.gd' -const HOTSPOT_SCENE :=\ -'res://addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_hotspot_name := '' -var _new_hotspot_path := '' -var _hotspot_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuHotspotFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -28,79 +18,35 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _new_hotspot_name.is_empty(): - _error_feedback.show() - return - - # TODO: Check if another Hotspot was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_hotspot_path + '.gd' - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Hotspot - assert( - DirAccess.make_dir_recursive_absolute( - _new_hotspot_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create Hotspot folder for ' + _new_hotspot_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el script de el hotspot (si tiene interacción) - var hotspot_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(hotspot_template, script_path) != OK: - push_error('[Popochiu] Could not create: %s.gd' % _new_hotspot_name) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the hotspot + _factory = PopochiuHotspotFactory.new(_main_dock) + + if _factory.create(_new_hotspot_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el hotspot a agregar a la habitación - var hotspot: PopochiuHotspot = ResourceLoader.load(HOTSPOT_SCENE).instantiate() - hotspot.set_script(ResourceLoader.load(script_path)) - hotspot.name = _pascal_name - hotspot.script_name = _pascal_name - hotspot.description = _new_hotspot_name.capitalize() - hotspot.cursor = Constants.CURSOR_TYPE.ACTIVE - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Agregar el hotspot a su habitación - _room.get_node('Hotspots').add_child(hotspot) - - hotspot.owner = _room - hotspot.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - hotspot.add_child(collision) - collision.owner = _room - collision.modulate = Color.BLUE - - _main_dock.ei.save_scene() - + + var hotspot = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades del hotspot creado en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(hotspot) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() + func _clear_fields() -> void: _new_hotspot_name = '' - _new_hotspot_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _hotspot_path_template = _room_dir + '/hotspots/%s/hotspot_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -109,14 +55,11 @@ func _update_name(new_text: String) -> void: if _name: _new_hotspot_name = _name.to_snake_case() - _pascal_name = _name - _new_hotspot_path = _hotspot_path_template %\ - [_new_hotspot_name, _new_hotspot_name] _info.text = ( 'In [b]%s[/b] the following file will be created:\n[code]%s[/code]'\ % [ - _new_hotspot_path.get_base_dir(), + _room.scene_file_path.get_base_dir() + '/hotspots', 'hotspot_' + _new_hotspot_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd b/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd index 8af5e6133..b90ff1476 100644 --- a/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd +++ b/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd @@ -9,20 +9,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const INVENTORY_ITEM_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/inventory_item_state_template.gd' -const INVENTORY_ITEM_SCRIPT_TEMPLATE := \ -'res://addons/popochiu/engine/templates/inventory_item_template.gd' -const BASE_INVENTORY_ITEM_PATH := \ -'res://addons/popochiu/engine/objects/inventory_item/popochiu_inventory_item.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_item_name := '' -var _new_item_path := '' -var _item_path_template := '' -var _pascal_name := '' +var _factory: PopochiuInventoryItemFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -37,122 +29,20 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check that there is not an inventory item in the same PATH. - # TODO: Delete created files if something fails. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the item - DirAccess.make_dir_absolute(_main_dock.INVENTORY_ITEMS_PATH + _new_item_name) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the item and a script so devs - # can add extra properties to that state - var state_template: Script = load(INVENTORY_ITEM_STATE_TEMPLATE).duplicate() - if ResourceSaver.save(state_template, _new_item_path + '_state.gd') != OK: - push_error( - "[Popochiu] Couldn't create item state script: %s" %\ - _new_item_name - ) - # TODO: Show feedback in the popup - return - - var item_resource: PopochiuInventoryItemData =\ - load(_new_item_path + '_state.gd').new() - item_resource.script_name = _pascal_name - item_resource.scene = _new_item_path + '.tscn' - item_resource.resource_name = _pascal_name - - if ResourceSaver.save(item_resource, _new_item_path + '.tres') != OK: - push_error( - "[Popochiu] Couldn't create PopochiuInventoryItemData for item: %s"\ - % _new_item_name - ) - # TODO: Show feedback in the popup - return - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the item - var item_script: Script = load(INVENTORY_ITEM_SCRIPT_TEMPLATE).duplicate() - var new_code := item_script.source_code - - item_script.source_code = '' - - if ResourceSaver.save(item_script, _new_item_path + '.gd') != OK: - push_error('[Popochiu] Could not create script: %s.gd' % _new_item_name) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'inventory_item_state_template', - 'item_%s_state' % _new_item_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _new_item_path - ) - - item_script = load(_new_item_path + '.gd') - item_script.source_code = new_code - - if ResourceSaver.save(item_script, _new_item_path + '.gd') != OK: - push_error('[Popochiu] Could not update script: %s.gd' % _new_item_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the item instance - var new_item: PopochiuInventoryItem =\ - preload(BASE_INVENTORY_ITEM_PATH).instantiate() - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_item.set_script(load(_new_item_path + '.gd')) - - new_item.name = 'Item' + _pascal_name - new_item.script_name = _pascal_name - new_item.description = _pascal_name.capitalize() - new_item.cursor = Constants.CURSOR_TYPE.USE - new_item.size_flags_vertical = new_item.SIZE_SHRINK_CENTER - - if PopochiuResources.get_settings().is_pixel_art_game: - new_item.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the item scene (.tscn) - var new_item_packed_scene: PackedScene = PackedScene.new() - new_item_packed_scene.pack(new_item) - if ResourceSaver.save(new_item_packed_scene, _new_item_path + '.tscn') != OK: - push_error('[Popochiu] Could not create item: %s.tscn' % _new_item_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created inventory item to Popochiu's inventory_items list - if _main_dock.add_resource_to_popochiu( - 'inventory_items', ResourceLoader.load(_new_item_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created inventory item to Popochiu: %s"\ - % _new_item_name - ) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuInventoryItemFactory.new(_main_dock) + + if _factory.create(_new_item_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the inventory item to the I singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of inventory items in the dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.INVENTORY_ITEM, _pascal_name - ) + var item_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_item_path + '.tscn') - _main_dock.ei.open_scene_from_path(_new_item_path + '.tscn') + _main_dock.ei.select_file(item_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(item_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all!!!!!!! @@ -161,7 +51,6 @@ func _create() -> void: func _clear_fields() -> void: _new_item_name = '' - _new_item_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -170,9 +59,6 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/inventory_items/ - _item_path_template = _main_dock.INVENTORY_ITEMS_PATH + '%s/item_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: @@ -180,18 +66,15 @@ func _update_name(new_text: String) -> void: if _name: _new_item_name = _name.to_snake_case() - _pascal_name = _name - _new_item_path = _item_path_template %\ - [_new_item_name, _new_item_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ \n[code]%s, %s and %s[/code]' \ % [ _main_dock.INVENTORY_ITEMS_PATH + _new_item_name, - 'item_' + _new_item_name + '.tscn', - 'item_' + _new_item_name + '.gd', - 'item_' + _new_item_name + '.tres' + 'inventory_item_' + _new_item_name + '.tscn', + 'inventory_item_' + _new_item_name + '.gd', + 'inventory_item_' + _new_item_name + '.tres' ] ) _info.show() diff --git a/addons/popochiu/editor/popups/create_prop/create_prop.gd b/addons/popochiu/editor/popups/create_prop/create_prop.gd index d41b136d4..1b0e644e4 100644 --- a/addons/popochiu/editor/popups/create_prop/create_prop.gd +++ b/addons/popochiu/editor/popups/create_prop/create_prop.gd @@ -5,22 +5,15 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const PROP_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/prop_template.gd' -const BASE_PROP_PATH :=\ -'res://addons/popochiu/engine/objects/prop/popochiu_prop.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... +#const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_prop_name := '' -var _new_prop_path := '' -var _prop_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuPropFactory @onready var _interaction_checkbox: CheckBox = find_child('InteractionCheckbox') @@ -38,108 +31,41 @@ func _create() -> void: if _new_prop_name.is_empty(): _error_feedback.show() return - - # TODO: Check if another Prop was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_prop_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Prop - assert( - DirAccess.make_dir_recursive_absolute(_new_prop_path.get_base_dir()) == OK, - '[Popochiu] Could not create prop folder for ' + _new_prop_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the prop (if it has interaction) - if _interaction_checkbox.button_pressed: - var prop_template := load(PROP_SCRIPT_TEMPLATE) - - if ResourceSaver.save(prop_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_prop_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the prop - var prop: PopochiuProp = ResourceLoader.load(BASE_PROP_PATH).instantiate() - - if _interaction_checkbox.button_pressed: - prop.set_script(ResourceLoader.load(script_path)) - - prop.name = _pascal_name - prop.script_name = _pascal_name - prop.description = _new_prop_name.capitalize() - prop.clickable = _interaction_checkbox.button_pressed - prop.cursor = Constants.CURSOR_TYPE.ACTIVE - - if PopochiuResources.get_settings().is_pixel_art_game: - prop.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - if _new_prop_name in ['bg', 'background']: - prop.baseline =\ - -ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) / 2.0 - prop.z_index = -1 - + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the prop scene (.tscn) - var prop_packed_scene: PackedScene = PackedScene.new() - prop_packed_scene.pack(prop) - if ResourceSaver.save( - prop_packed_scene, _new_prop_path + '.tscn' - ) != OK: - push_error("[Popochiu] Couldn't create prop: %s.tscn" % _new_prop_name) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuPropFactory.new(_main_dock) + + if _factory.create( + _new_prop_name, _room, + _interaction_checkbox.button_pressed + ) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the prop to its room - # Instancing the created .tscn file fixes #58 - var prop_instance: PopochiuProp = load(_new_prop_path + '.tscn').instantiate() - - _room.get_node('Props').add_child(prop_instance) - - prop_instance.owner = _room - prop_instance.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - if _interaction_checkbox.button_pressed: - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - - prop_instance.add_child(collision) - collision.owner = _room - - _main_dock.ei.save_scene() - + + var prop_instance = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la prop creada en el Inspector + # Open the properties of the created prop in the inspector + # Done here because the creation is interactive in this case _main_dock.fs.scan() await get_tree().create_timer(0.1).timeout - PopochiuEditorHelper.select_node(prop_instance) - _main_dock.ei.select_file(_new_prop_path + '.tscn') + _main_dock.ei.edit_node(prop_instance) + _main_dock.ei.select_file(prop_instance.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() func _clear_fields() -> void: _new_prop_name = '' - _new_prop_path = '' _interaction_checkbox.button_pressed = false # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _prop_path_template = _room_dir + '/props/%s/prop_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -148,8 +74,6 @@ func _update_name(new_text: String) -> void: if _name: _new_prop_name = _name.to_snake_case() - _pascal_name = _name - _new_prop_path = _prop_path_template % [_new_prop_name, _new_prop_name] _update_info() _info.show() @@ -169,7 +93,7 @@ func _update_info() -> void: _info.text = ( 'In [b]%s[/b] the following file will be created:\n[code]%s[/code]' \ % [ - _new_prop_path.get_base_dir(), + _room.scene_file_path.get_base_dir() + '/props', 'prop_' + _new_prop_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_region/create_region.gd b/addons/popochiu/editor/popups/create_region/create_region.gd index 7a981a374..6257d779d 100644 --- a/addons/popochiu/editor/popups/create_region/create_region.gd +++ b/addons/popochiu/editor/popups/create_region/create_region.gd @@ -1,20 +1,14 @@ +# Allows you to create a new Region for a room. + @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -# Permite crear una nueva Region para una habitación. - -const SCRIPT_TEMPLATE := 'res://addons/popochiu/engine/templates/region_template.gd' -const REGION_SCENE := 'res://addons/popochiu/engine/objects/region/popochiu_region.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_region_name := '' -var _new_region_path := '' -var _region_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuRegionFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -29,75 +23,34 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check if another Region was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_region_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Region - assert( - DirAccess.make_dir_recursive_absolute( - _new_region_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create region folder for ' + _new_region_name - ) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el script de la región - var region_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(region_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_region_name - ) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the region + _factory = PopochiuRegionFactory.new(_main_dock) + + if _factory.create(_new_region_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear la región a agregar a la habitación - var region: PopochiuRegion = ResourceLoader.load(REGION_SCENE).instantiate() - region.set_script(ResourceLoader.load(script_path)) - region.name = _pascal_name - region.script_name = _pascal_name - region.description = _new_region_name.capitalize() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Agregar la región a su habitación - _room.get_node('Regions').add_child(region) - region.owner = _room - region.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - region.add_child(collision) - collision.owner = _room - collision.modulate = Color.CYAN - - _main_dock.ei.save_scene() - + + var region = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la región creada en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(region) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() func _clear_fields() -> void: _new_region_name = '' - _new_region_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _region_path_template = _room_dir + '/regions/%s/region_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -106,14 +59,10 @@ func _update_name(new_text: String) -> void: if _name: _new_region_name = _name.to_snake_case() - _pascal_name = _name - _new_region_path = _region_path_template %\ - [_new_region_name, _new_region_name] - _info.text = ( 'In [b]%s[/b] the following files will be created:\n[code]%s[/code]' \ % [ - _room_dir + '/regions', + _room.scene_file_path.get_base_dir() + '/regions', 'region_' + _new_region_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_room/create_room.gd b/addons/popochiu/editor/popups/create_room/create_room.gd index 88e5622bf..fb5b08e89 100644 --- a/addons/popochiu/editor/popups/create_room/create_room.gd +++ b/addons/popochiu/editor/popups/create_room/create_room.gd @@ -9,22 +9,14 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const ROOM_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/room_state_template.gd' -const ROOM_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/room_template.gd' -const BASE_ROOM_PATH :=\ -'res://addons/popochiu/engine/objects/room/popochiu_room.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var show_set_as_main := false : set = _set_show_set_as_main -var _room_name := '' -var _room_path := '' -var _room_path_template := '' -var _pascal_name := '' +var _new_room_name := '' +var _factory: PopochiuRoomFactory @onready var _set_as_main: PanelContainer = find_child('SetAsMainContainer') @onready var _set_as_main_check: CheckBox = _set_as_main.find_child('CheckBox') @@ -50,125 +42,28 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _room_name.is_empty(): + if _new_room_name.is_empty(): _error_feedback.show() return - # TODO: Check that there is not a room in the same PATH. - # TODO: Delete created files if creation is not complete. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the room - DirAccess.make_dir_absolute(_main_dock.ROOMS_PATH + _room_name) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the room and a script so devs can add extra - # properties to that state - var state_template: Script = load(ROOM_STATE_TEMPLATE).duplicate() - if ResourceSaver.save(state_template, _room_path + '_state.gd') != OK: - push_error('[Popochiu] Could not create room state script: %s' %\ - _room_name) - # TODO: Show feedback in the popup - return - - var room_resource: PopochiuRoomData = load(_room_path + '_state.gd').new() - room_resource.script_name = _pascal_name - room_resource.scene = _room_path + '.tscn' - room_resource.resource_name = _pascal_name - - if ResourceSaver.save(room_resource, _room_path + '.tres') != OK: - push_error( - "[Popochiu] Couldn't create PopochiuRoomData for room: %s" %\ - _room_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the room - var room_script: Script = load(ROOM_SCRIPT_TEMPLATE).duplicate() - var new_code := room_script.source_code - - room_script.source_code = '' - - if ResourceSaver.save(room_script, _room_path + '.gd') != OK: - push_error("[Popochiu] Couldn't create script: %s" % _room_name) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'room_state_template', - 'room_%s_state' % _room_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _room_path - ) - - room_script = load(_room_path + '.gd') - room_script.source_code = new_code - - if ResourceSaver.save(room_script, _room_path + '.gd') != OK: - push_error('[Popochiu] Could not update script: %s' %\ - _room_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the room instance - var new_room: PopochiuRoom = load(BASE_ROOM_PATH).instantiate() - - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_room.set_script(load(_room_path + '.gd')) - - new_room.name = 'Room' + _pascal_name - new_room.script_name = _pascal_name - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the room scene (.tscn) - var new_room_packed_scene: PackedScene = PackedScene.new() - new_room_packed_scene.pack(new_room) - if ResourceSaver.save(new_room_packed_scene, _room_path + '.tscn') != OK: - push_error("[Popochiu] Couldn't create room: %s" % _room_name) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuRoomFactory.new(_main_dock) + + if _factory.create( + _new_room_name, + _set_as_main_check.button_pressed + ) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created room to Popochiu's rooms list - if _main_dock.add_resource_to_popochiu( - 'rooms', ResourceLoader.load(_room_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created room to Popochiu: %s" %\ - _room_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the room to the R singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of rooms in the dock - var row := (_main_dock as PopochiuDock).add_to_list( - Constants.Types.ROOM, _pascal_name - ) - - # Establecer como la escena principal - # Changed _set_as_main_check.pressed to _set_as_main_check.button_pressed - # in order to fix #56 - if _set_as_main_check.button_pressed: - _main_dock.set_main_scene(room_resource.scene) - row.is_main = true # So the Heart icon shows + + var room_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_room_path + '.tscn') - _main_dock.ei.open_scene_from_path(_room_path + '.tscn') + _main_dock.ei.select_file(room_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(room_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all! @@ -177,8 +72,7 @@ func _create() -> void: func _clear_fields() -> void: - _room_name = '' - _room_path = '' + _new_room_name = '' _set_as_main_check.button_pressed = false @@ -188,27 +82,22 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/rooms - _room_path_template = _main_dock.ROOMS_PATH + '%s/room_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: super(new_text) if _name: - _room_name = _name.to_snake_case() - _pascal_name = _name - _room_path = _room_path_template % [_room_name, _room_name] + _new_room_name = _name.to_snake_case() _info.text = ( 'In [b]%s[/b] the following files will be created:\ \n[code]%s, %s and %s[/code]' \ % [ - _main_dock.ROOMS_PATH + _room_name, - 'room_' + _room_name + '.tscn', - 'room_' + _room_name + '.gd', - 'room_' + _room_name + '.tres' + _main_dock.ROOMS_PATH + _new_room_name, + 'room_' + _new_room_name + '.tscn', + 'room_' + _new_room_name + '.gd', + 'room_' + _new_room_name + '.tres' ] ) _info.show() diff --git a/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd b/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd index cc78890b3..0bc047a53 100644 --- a/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd +++ b/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd @@ -2,22 +2,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/walkable_area_template.gd' -const WALKABLE_AREA_SCENE :=\ -'res://addons/popochiu/engine/objects/walkable_area/popochiu_walkable_area.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") - +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_walkable_area_name := '' -var _new_walkable_area_path := '' -var _walkable_area_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuWalkableAreaFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -28,82 +18,24 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _new_walkable_area_name.is_empty(): - _error_feedback.show() - return - - # TODO: Check if another WalkableArea was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_walkable_area_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the WalkableArea - assert( - DirAccess.make_dir_recursive_absolute( - _new_walkable_area_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create walkable_area folder for '\ - + _new_walkable_area_name - ) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the WalkableArea - var walkable_area_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(walkable_area_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_walkable_area_name - ) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the region + _factory = PopochiuWalkableAreaFactory.new(_main_dock) + + if _factory.create(_new_walkable_area_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return + var walkable_area = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the new WalkableArea and add it to the room - var walkable_area: PopochiuWalkableArea = ResourceLoader.load( - WALKABLE_AREA_SCENE - ).instantiate() - walkable_area.set_script(ResourceLoader.load(script_path)) - walkable_area.name = _pascal_name - walkable_area.script_name = _pascal_name - walkable_area.description = _new_walkable_area_name.capitalize() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the NavigationRegion2D (instead of using the one that - # was part of the original scene) - var perimeter := NavigationRegion2D.new() - walkable_area.add_child(perimeter) - perimeter.name = 'Perimeter' - - var polygon := NavigationPolygon.new() - polygon.agent_radius = 0 - polygon.add_outline(PackedVector2Array([ - Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10) - ])) - polygon.make_polygons_from_outlines() - - perimeter.navpoly = polygon - perimeter.modulate = Color.GREEN - - # Attach the walkable area to the room - _room.get_node('WalkableAreas').add_child(walkable_area) - - # Make the room the owner of both the Node2D and its NavigationRegion2D - walkable_area.owner = _room - perimeter.owner = _room - - walkable_area.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - _main_dock.ei.save_scene() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la walkable area creada en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(walkable_area) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() @@ -111,16 +43,11 @@ func _clear_fields() -> void: super() _new_walkable_area_name = '' - _new_walkable_area_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _walkable_area_path_template = _room_dir +\ - '/walkable_areas/%s/walkable_area_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -129,14 +56,11 @@ func _update_name(new_text: String) -> void: if _name: _new_walkable_area_name = _name.to_snake_case() - _pascal_name = _name - _new_walkable_area_path = _walkable_area_path_template %\ - [_new_walkable_area_name, _new_walkable_area_name] _info.text = ( - 'In [b]%s[/b] the following files will be created:\n[code]%s[/code]'\ + 'In [b]%s[/b] the following files will be created: [code]%s[/code]'\ % [ - _room_dir + '/walkable_areas', + _room.scene_file_path.get_base_dir() + '/walkable_areas', 'walkable_area_' + _new_walkable_area_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/creation_popup.gd b/addons/popochiu/editor/popups/creation_popup.gd index a9975d48e..71a0f435e 100644 --- a/addons/popochiu/editor/popups/creation_popup.gd +++ b/addons/popochiu/editor/popups/creation_popup.gd @@ -56,11 +56,4 @@ func _update_name(new_text: String) -> void: if _error_feedback.visible: _error_feedback.hide() - var casted_name := PackedStringArray() - for idx in new_text.length(): - if idx == 0: - casted_name.append(new_text[idx].to_upper()) - else: - casted_name.append(new_text[idx]) - - _name = ''.join(casted_name).strip_edges() + _name = new_text.to_pascal_case() diff --git a/addons/popochiu/engine/objects/character/popochiu_character.gd b/addons/popochiu/engine/objects/character/popochiu_character.gd index 049f93e49..72669a049 100644 --- a/addons/popochiu/engine/objects/character/popochiu_character.gd +++ b/addons/popochiu/engine/objects/character/popochiu_character.gd @@ -22,6 +22,7 @@ signal move_ended @export var can_move := true @export var ignore_walkable_areas := false @export var anti_glide_animation: bool = false +@export_category("Aseprite") var position_stored = null var last_room := '' @@ -54,20 +55,12 @@ func _ready(): child.frame_changed.connect(_update_position) func _get_property_list(): - var properties = [] - properties.append({ - name = "Aseprite", - type = TYPE_NIL, - usage = PROPERTY_USAGE_CATEGORY - }) - # This is needed or the category won't be shown in the - # inspector. AsepriteImporterInspectorPlugin hides it. - properties.append({ - name = "popochiu_placeholder", - type = TYPE_NIL, - }) - - return properties + return [ + { + name = "popochiu_placeholder", + type = TYPE_NIL, + } + ] # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ diff --git a/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd b/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd index 6f9619842..048fb9638 100644 --- a/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd +++ b/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd @@ -13,10 +13,12 @@ const CURSOR := preload('res://addons/popochiu/engine/cursor/cursor.gd') @export var walk_to_point := Vector2.ZERO : set = set_walk_to_point @export var cursor: CURSOR.Type = CURSOR.Type.NONE @export var always_on_top := false +@export var interaction_polygon := PackedVector2Array() var room: Node2D = null : set = set_room # It is a PopochiuRoom var times_clicked := 0 var times_right_clicked := 0 +var editing_polygon := false @onready var _description_code := description @@ -27,10 +29,21 @@ func _ready(): if Engine.is_editor_hint(): hide_helpers() + + if get_node_or_null("InteractionPolygon") == null: return + + if interaction_polygon.is_empty(): + interaction_polygon = get_node('InteractionPolygon').polygon + else: + get_node('InteractionPolygon').polygon = interaction_polygon + return else: $BaselineHelper.free() $WalkToHelper.free() + + if get_node_or_null("InteractionPolygon"): + get_node("InteractionPolygon").polygon = interaction_polygon visibility_changed.connect(_toggle_input) @@ -78,12 +91,15 @@ func _process(delta): if Engine.is_editor_hint(): if walk_to_point != get_node('WalkToHelper').position: walk_to_point = get_node('WalkToHelper').position - + notify_property_list_changed() elif baseline != get_node('BaselineHelper').position.y: baseline = get_node('BaselineHelper').position.y - + notify_property_list_changed() + + if editing_polygon: + interaction_polygon = get_node('InteractionPolygon').polygon # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ @@ -111,11 +127,17 @@ func _on_item_used(item: PopochiuInventoryItem) -> void: func hide_helpers() -> void: $BaselineHelper.hide() $WalkToHelper.hide() + + if get_node_or_null("InteractionPolygon"): + $InteractionPolygon.hide() func show_helpers() -> void: $BaselineHelper.show() $WalkToHelper.show() + + if get_node_or_null("InteractionPolygon"): + $InteractionPolygon.show() func queue_disable() -> Callable: @@ -199,6 +221,10 @@ func _translate() -> void: ) +func _on_interaction_polygon_changed() -> void: + prints("Me la cambiaron") + + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ func set_baseline(value: int) -> void: baseline = value diff --git a/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn b/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn index d1b731ebc..6dee23323 100644 --- a/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn +++ b/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn @@ -4,7 +4,6 @@ [node name="Hotspot" type="Area2D"] script = ExtResource("1") -walk_to_point = Vector2(0, 0) cursor = 1 [node name="BaselineHelper" type="Line2D" parent="."] @@ -19,3 +18,7 @@ offset_top = -2.5 offset_right = 2.5 offset_bottom = 2.5 color = Color(0, 1, 1, 1) + +[node name="InteractionPolygon" type="CollisionPolygon2D" parent="."] +modulate = Color(0, 0, 1, 1) +polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) diff --git a/addons/popochiu/engine/objects/prop/popochiu_prop.tscn b/addons/popochiu/engine/objects/prop/popochiu_prop.tscn index d76cec511..a4b467add 100644 --- a/addons/popochiu/engine/objects/prop/popochiu_prop.tscn +++ b/addons/popochiu/engine/objects/prop/popochiu_prop.tscn @@ -18,4 +18,10 @@ offset_right = 2.5 offset_bottom = 2.5 color = Color(0, 1, 1, 1) +[node name="InteractionPolygon" type="CollisionPolygon2D" parent="."] +polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) + [node name="Sprite2D" type="Sprite2D" parent="."] +texture_filter = 1 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] diff --git a/addons/popochiu/engine/objects/room/popochiu_room.gd b/addons/popochiu/engine/objects/room/popochiu_room.gd index 5f37f1aee..92304f76f 100644 --- a/addons/popochiu/engine/objects/room/popochiu_room.gd +++ b/addons/popochiu/engine/objects/room/popochiu_room.gd @@ -17,6 +17,7 @@ extends Node2D @export var limit_right := INF @export var limit_top := INF @export var limit_bottom := INF +@export_category("Aseprite") var is_current := false : set = set_is_current @@ -59,6 +60,15 @@ func _ready(): E.room_readied(self) +func _get_property_list() -> Array[Dictionary]: + return [ + { + name = "popochiu_placeholder", + type = TYPE_NIL, + } + ] + + func _physics_process(delta): if _moving_characters.is_empty(): return diff --git a/addons/popochiu/engine/popochiu.gd b/addons/popochiu/engine/popochiu.gd index 2e710e0e5..9dd3f255d 100644 --- a/addons/popochiu/engine/popochiu.gd +++ b/addons/popochiu/engine/popochiu.gd @@ -159,7 +159,7 @@ func _input(event: InputEvent) -> void: if event.is_action_released('popochiu-skip'): cutscene_skipped = true $TransitionLayer.play_transition( - TransitionLayer.PASS_DOWN_IN, + $TransitionLayer.PASS_DOWN_IN, settings.skip_cutscene_time ) diff --git a/addons/popochiu/engine/templates/empty_script_template.gd b/addons/popochiu/engine/templates/empty_script_template.gd new file mode 100644 index 000000000..530bfd45b --- /dev/null +++ b/addons/popochiu/engine/templates/empty_script_template.gd @@ -0,0 +1,8 @@ +# This script is empty and is necessary to programmatically +# create an empty script to hold the _template.gd +# source file, after the placeholders have been populated. +# +# It is necessary from Godot 4.1 or an error is raised when the +# template script is loaded. +# +# Do not erase this script. \ No newline at end of file diff --git a/addons/popochiu/icons/interaction_polygon.png b/addons/popochiu/icons/interaction_polygon.png new file mode 100644 index 000000000..9e9f2eef0 Binary files /dev/null and b/addons/popochiu/icons/interaction_polygon.png differ diff --git a/addons/popochiu/icons/interaction_polygon.png.import b/addons/popochiu/icons/interaction_polygon.png.import new file mode 100644 index 000000000..68363e01b --- /dev/null +++ b/addons/popochiu/icons/interaction_polygon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cesndtvbchw7v" +path="res://.godot/imported/interaction_polygon.png-28f3d05312276a3121232981bf4f2bee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/popochiu/icons/interaction_polygon.png" +dest_files=["res://.godot/imported/interaction_polygon.png-28f3d05312276a3121232981bf4f2bee.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 diff --git a/addons/popochiu/popochiu_export_plugin.gd b/addons/popochiu/popochiu_export_plugin.gd index b02d5d710..7ebae6bef 100644 --- a/addons/popochiu/popochiu_export_plugin.gd +++ b/addons/popochiu/popochiu_export_plugin.gd @@ -9,11 +9,11 @@ func _export_begin(features: PackedStringArray, is_debug: bool, path: String, fl file.close() - -## Logic for Aseprite Importer -## This code removes importer's metadata from nodes before exporting them -## Thanks to Vinicius Gerevini and his Aseprite Wizard plugin for that! -## TODO: may be moved to another file so we keep things separated +# Logic for Aseprite Importer +# This code removes importer's metadata from nodes before exporting them +# Thanks to Vinicius Gerevini and his Aseprite Wizard plugin for that! +# This code is run independent of the importer to be enabled, to clean things up. +# TODO: may be moved to another file so we keep things separated func _export_file(path: String, type: String, features: PackedStringArray) -> void: if type != "PackedScene": return diff --git a/addons/popochiu/popochiu_plugin.gd b/addons/popochiu/popochiu_plugin.gd index 669ab8a45..5d47dd451 100644 --- a/addons/popochiu/popochiu_plugin.gd +++ b/addons/popochiu/popochiu_plugin.gd @@ -27,6 +27,7 @@ var _inspector_plugins := [] var _selected_node: Node = null var _btn_baseline := Button.new() var _btn_walk_to := Button.new() +var _btn_interaction_polygon := Button.new() var _types_helper: Resource = null var _tool_btn_stylebox :=\ _editor_interface.get_base_control().get_theme_stylebox("normal", "Button") @@ -70,7 +71,15 @@ func _enter_tree() -> void: config.ei = _editor_interface config.initialize_editor_settings() config.initialize_project_settings() - + + # Configure main dock to be passed down the plugin chain + # TODO: Get rid of this cascading assignment and switch to + # a signalbus instead! + main_dock = load(PopochiuResources.MAIN_DOCK_PATH).instantiate() + main_dock.ei = _editor_interface + main_dock.fs = _editor_file_system + main_dock.focus_mode = Control.FOCUS_ALL + for path in [ 'res://addons/popochiu/editor/inspector/character_inspector_plugin.gd', 'res://addons/popochiu/editor/inspector/walkable_area_inspector_plugin.gd', @@ -82,6 +91,7 @@ func _enter_tree() -> void: eip.set('ei', _editor_interface) eip.set('fs', _editor_file_system) eip.set('config', config) + eip.set('main_dock', main_dock) # TODO: change with SignalBus _inspector_plugins.append(eip) add_inspector_plugin(eip) @@ -360,10 +370,15 @@ func _check_nodes() -> void: or _types_helper.is_hotspot(_selected_node): _btn_baseline.set_pressed_no_signal(false) _btn_walk_to.set_pressed_no_signal(false) + _btn_interaction_polygon.set_pressed_no_signal(false) - _btn_baseline.get_parent().show() + _btn_baseline.show() + _btn_walk_to.show() + _btn_interaction_polygon.show() else: - _btn_baseline.get_parent().hide() + _btn_baseline.hide() + _btn_walk_to.hide() + _btn_interaction_polygon.hide() func _on_files_moved(old_file: String, new_file: String) -> void: @@ -393,21 +408,31 @@ func _create_container_buttons() -> void: _btn_walk_to.add_theme_stylebox_override('hover', _tool_btn_stylebox) _btn_walk_to.pressed.connect(_select_walk_to) + _btn_interaction_polygon.icon = preload('res://addons/popochiu/icons/interaction_polygon.png') + _btn_interaction_polygon.tooltip_text = 'Interaction Polygon' + _btn_interaction_polygon.toggle_mode = true + _btn_interaction_polygon.add_theme_stylebox_override('normal', _tool_btn_stylebox) + _btn_interaction_polygon.add_theme_stylebox_override('hover', _tool_btn_stylebox) + _btn_interaction_polygon.pressed.connect(_select_interaction_polygon) + + hbox.add_child(_vsep) hbox.add_child(_btn_baseline) hbox.add_child(_btn_walk_to) + hbox.add_child(_btn_interaction_polygon) add_control_to_container( EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, hbox ) - - hbox.hide() + + _vsep.hide() + _btn_baseline.hide() + _btn_walk_to.hide() func _select_walk_to() -> void: _btn_walk_to.set_pressed_no_signal(true) _btn_baseline.set_pressed_no_signal(false) - _editor_interface.get_selection().clear() if _types_helper.is_prop(_selected_node)\ @@ -422,9 +447,7 @@ func _select_walk_to() -> void: func _select_baseline() -> void: - _btn_baseline.set_pressed_no_signal(true) _btn_walk_to.set_pressed_no_signal(false) - _editor_interface.get_selection().clear() if _types_helper.is_prop(_selected_node)\ @@ -438,6 +461,24 @@ func _select_baseline() -> void: ) +func _select_interaction_polygon() -> void: + _btn_walk_to.set_pressed_no_signal(false) + _btn_baseline.set_pressed_no_signal(false) + _btn_interaction_polygon.set_pressed_no_signal(true) + _vsep.hide() + + var collision_polygon: CollisionPolygon2D = null + + if _types_helper.is_prop(_selected_node)\ + or _types_helper.is_hotspot(_selected_node): + collision_polygon = _selected_node.get_node('InteractionPolygon') + else: + collision_polygon = _selected_node.get_node('../InteractionPolygon') + + _editor_interface.edit_node(collision_polygon) + collision_polygon.get_parent().editing_polygon = true + + func _move_to_project(id: int) -> void: # Move files and folders so developer can overwrite them if id == PopochiuResources.GI: diff --git a/addons/popochiu/popochiu_resources.gd b/addons/popochiu/popochiu_resources.gd index 5176a3d50..caaae5bd5 100644 --- a/addons/popochiu/popochiu_resources.gd +++ b/addons/popochiu/popochiu_resources.gd @@ -141,7 +141,7 @@ const SNGL_SETUP := { I_SNGL : { interface = IINVENTORY, section = 'inventory_items', - 'class' = 'res://popochiu/inventory_items/%s/item_%s.gd', + 'class' = 'res://popochiu/inventory_items/%s/inventory_item_%s.gd', 'const' = "const PII%s := preload('%s')\n", node = "var %s: PII%s : get = get_%s\n", 'func' = "func get_%s() -> PII%s: return super.get_item_instance('%s')\n", diff --git a/popochiu/rooms/house/room_house.tscn b/popochiu/rooms/house/room_house.tscn new file mode 100644 index 000000000..078a092b0 --- /dev/null +++ b/popochiu/rooms/house/room_house.tscn @@ -0,0 +1,79 @@ +[gd_scene load_steps=12 format=3 uid="uid://bhth611pb4a2l"] + +[ext_resource type="Script" path="res://popochiu/rooms/house/room_house.gd" id="1_5s51l"] +[ext_resource type="PackedScene" uid="uid://c0yuinu6anryy" path="res://popochiu/rooms/house/props/bg/prop_bg.tscn" id="2_s2nyx"] +[ext_resource type="PackedScene" uid="uid://uvtywyf3pq1a" path="res://addons/popochiu/engine/objects/walkable_area/popochiu_walkable_area.tscn" id="2_s8q7b"] +[ext_resource type="Script" path="res://popochiu/rooms/house/walkable_areas/floor/walkable_area_floor.gd" id="3_xsa8n"] +[ext_resource type="PackedScene" uid="uid://61e3u8ba5jrq" path="res://popochiu/rooms/house/props/toy_car/prop_toy_car.tscn" id="4_0fq0w"] +[ext_resource type="Texture2D" uid="uid://bgr746xh7k5ed" path="res://popochiu/rooms/house/props/bg/background.png" id="4_lih8o"] +[ext_resource type="Texture2D" uid="uid://ct4gttx7etote" path="res://popochiu/rooms/house/props/toy_car/toy_car.png" id="7_ys5mq"] +[ext_resource type="PackedScene" uid="uid://c7oacc86kxt3e" path="res://popochiu/characters/goddiu/character_goddiu.tscn" id="8_kbv2w"] +[ext_resource type="PackedScene" uid="uid://dpnqksegjn80g" path="res://popochiu/characters/popsy/character_popsy.tscn" id="10_cxlhm"] +[ext_resource type="PackedScene" uid="uid://fc5mg17vx1r2" path="res://popochiu/characters/01/character_01.tscn" id="10_pnj5o"] + +[sub_resource type="NavigationPolygon" id="NavigationPolygon_ig1rh"] +vertices = PackedVector2Array(-88, 16, -40, 7, 81, 12, 111, 54, 59, 71, -78, 68, -111, 49) +polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3, 4, 5, 6)]) +outlines = Array[PackedVector2Array]([PackedVector2Array(-88, 16, -40, 7, 81, 12, 111, 54, 59, 71, -78, 68, -111, 49)]) +source_geometry_group_name = &"navigation_polygon_source_group" + +[node name="RoomHouse" type="Node2D"] +script = ExtResource("1_5s51l") +script_name = "House" +popochiu_placeholder = null + +[node name="WalkableAreas" type="Node2D" parent="."] + +[node name="Floor" parent="WalkableAreas" instance=ExtResource("2_s8q7b")] +position = Vector2(160, 90) +script = ExtResource("3_xsa8n") +script_name = "Floor" +description = "Floor" + +[node name="Perimeter" type="NavigationRegion2D" parent="WalkableAreas/Floor"] +modulate = Color(0, 1, 0, 1) +navigation_polygon = SubResource("NavigationPolygon_ig1rh") + +[node name="Props" type="Node2D" parent="."] + +[node name="Bg" parent="Props" instance=ExtResource("2_s2nyx")] +position = Vector2(160, 90) +texture = ExtResource("4_lih8o") +interaction_polygon = PackedVector2Array() + +[node name="ToyCar" parent="Props" instance=ExtResource("4_0fq0w")] +position = Vector2(104, 127) +texture = ExtResource("7_ys5mq") +walk_to_point = Vector2(26, -2) +interaction_polygon = PackedVector2Array(-8, 1, -7, -4, 0, -5, 7, -2, 7, 3, 2, 5, -6, 4) + +[node name="InteractionPolygon" type="CollisionPolygon2D" parent="Props/ToyCar"] +visible = false +polygon = PackedVector2Array(-8, 1, -7, -4, 0, -5, 7, -2, 7, 3, 2, 5, -6, 4) + +[node name="Hotspots" type="Node2D" parent="."] + +[node name="Regions" type="Node2D" parent="."] + +[node name="Markers" type="Node2D" parent="."] + +[node name="Left" type="Marker2D" parent="Markers"] +position = Vector2(73, 129) + +[node name="Right" type="Marker2D" parent="Markers"] +position = Vector2(244, 127) + +[node name="Characters" type="Node2D" parent="."] + +[node name="CharacterGoddiu *" parent="Characters" instance=ExtResource("8_kbv2w")] +position = Vector2(109, 112) +interaction_polygon = PackedVector2Array(-10, -10, 10, -10, 10, 10, -10, 10) + +[node name="CharacterPopsy *" parent="Characters" instance=ExtResource("10_cxlhm")] +position = Vector2(221, 128) +popochiu_placeholder = null +interaction_polygon = PackedVector2Array(-9, -23, 7, -24, 16, -18, 16, -2, -4, 3, -19, -1) + +[node name="Character01 *" parent="Characters" instance=ExtResource("10_pnj5o")] +position = Vector2(172, 156) +interaction_polygon = PackedVector2Array(-10, -10, 10, -10, 10, 10, -10, 10) diff --git a/popochiu/sources/aseprite/room_exterior.aseprite b/popochiu/sources/aseprite/room_exterior.aseprite new file mode 100644 index 000000000..8024404e4 Binary files /dev/null and b/popochiu/sources/aseprite/room_exterior.aseprite differ diff --git a/project.godot b/project.godot index 3633cb643..124e47f03 100644 --- a/project.godot +++ b/project.godot @@ -13,6 +13,7 @@ config_version=5 config/name="popochiu 2.0" config/description="Make 2D point n' click games with a smooth workflow (like in Adventure Game Studio or PowerQuest)." config/tags=PackedStringArray("4.2.x") +run/main_scene="res://popochiu/rooms/house/room_house.tscn" config/features=PackedStringArray("4.2") config/icon="res://icon_v2.png" config/windows_native_icon="res://popochiu_v2.ico"