1734 lines
74 KiB
GDScript
1734 lines
74 KiB
GDScript
# Outstanding proposed features
|
|
# v1.1 features
|
|
# * Have the editor kick in when an ESCPlayer is selected, i.e. "load/edit"
|
|
# * Add a settings page (if there's enough features to warrant it). This would have the path the scene gets written to, default angles for each direction so the developer can change them from the default 90s / 45s they are now, whether the ESCPlayer is click-through, errr can't think of anything else.
|
|
# * Redo the Escoria tutorial to use the plugin.
|
|
|
|
tool
|
|
extends Control
|
|
|
|
const METADATA_ANIM_NAME = "anim_name"
|
|
const METADATA_SPRITESHEET_SOURCE_FILE = "spritesheet_source_file"
|
|
const METADATA_SPRITESHEET_FRAMES_HORIZ = "spritesheet_frames_horiz"
|
|
const METADATA_SPRITESHEET_FRAMES_VERT = "spritesheet_frames_vert"
|
|
const METADATA_SPRITESHEET_FIRST_FRAME = "spritesheet_first_frame"
|
|
const METADATA_SPRITESHEET_LAST_FRAME = "spritesheet_last_frame"
|
|
const METADATA_SPEED = "speed"
|
|
const METADATA_IS_MIRROR = "is_mirror"
|
|
|
|
const DIR_UP = "up"
|
|
const DIR_UP_RIGHT = "upright"
|
|
const DIR_RIGHT = "right"
|
|
const DIR_DOWN_RIGHT = "downright"
|
|
const DIR_DOWN = "down"
|
|
const DIR_DOWN_LEFT = "downleft"
|
|
const DIR_LEFT = "left"
|
|
const DIR_UP_LEFT = "upleft"
|
|
|
|
const DIR_LIST_8 = [DIR_UP, DIR_UP_RIGHT, DIR_RIGHT, DIR_DOWN_RIGHT, DIR_DOWN, DIR_DOWN_LEFT, \
|
|
DIR_LEFT, DIR_UP_LEFT]
|
|
const DIR_LIST_4 = [DIR_UP, DIR_RIGHT, DIR_DOWN, DIR_LEFT]
|
|
const DIR_LIST_2 = [DIR_RIGHT, DIR_LEFT]
|
|
const DIR_LIST_1 = [DIR_DOWN]
|
|
|
|
const TYPE_WALK = "walk"
|
|
const TYPE_TALK = "talk"
|
|
const TYPE_IDLE = "idle"
|
|
|
|
const ANIM_IN_PROGRESS = "in_progress"
|
|
|
|
const ANIMATION_SPEED_LABEL = "Animation speed"
|
|
|
|
# Make the code more readable by shortening node references using constants
|
|
const NAME_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/node_name/MarginContainer2/GridContainer"
|
|
const DIR_COUNT_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/directions/HBoxContainer"
|
|
const CHAR_TYPE_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/charactertype/HBoxContainer"
|
|
const ANIM_TYPE_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/HBoxContainer"
|
|
const MIRROR_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/HBoxContainer2/HBoxContainer/MarginContainer3/mirror_checkbox"
|
|
const ARROWS_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/HBoxContainer2/HBoxContainer/MarginContainer2/GridContainer"
|
|
const PREVIEW_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/HBoxContainer2/preview/MarginContainer"
|
|
const PREVIEW_BGRND_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/HBoxContainer2/preview/anim_preview_background"
|
|
const ANIM_CONTROLS_NODE = "VBoxContainer/HBoxContainer/spritesheet_controls/VBoxContainer/spritesheet_details_container/GridContainer"
|
|
const STORE_ANIM_NODE = "VBoxContainer/HBoxContainer/spritesheet_controls/VBoxContainer/store_anim"
|
|
const SCROLL_VBOX_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/"
|
|
const SCROLL_CTRL_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/spritesheet_scroll_container/control"
|
|
const NO_SPRITESHEET_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/spritesheet_scroll_container/control/MarginContainer/no_spritesheet_found_sprite"
|
|
const CURRENT_SHEET_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/zoom_current/MarginContainer2/current_spritesheet_label"
|
|
const ZOOM_LABEL_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/zoom_scroll/zoom_label"
|
|
const ZOOM_SCROLL_NODE = "VBoxContainer/HBoxContainer/spritesheet/MarginContainer/VBoxContainer/zoom_scroll/MarginContainer/zoom_scrollbar"
|
|
const CHARACTER_PATH_NODE = "VBoxContainer/HBoxContainer/configuration/VBoxContainer/node_name/MarginContainer2/GridContainer/character_path"
|
|
const GENERIC_ERROR_NODE = "InformationWindows/generic_error_window"
|
|
const UNSTORED_CHANGE_NODE = "InformationWindows/unstored_changes_window"
|
|
const UNSTORED_ANIMTYPE_CHANGE_NODE = "InformationWindows/unstored_changes_window_anim_change"
|
|
const EXPORT_PROGRESS_NODE = "InformationWindows/export_progress"
|
|
const PROGRESS_LABEL_NODE = "InformationWindows/export_progress/progress_label"
|
|
const EXPORT_COMPLETE_NODE = "InformationWindows/export_complete"
|
|
const FILE_DIALOG_NODE = "ImageFileDialog"
|
|
const CHARACTER_FILE_NODE = "CharacterPathFileDialog"
|
|
const CONFIG_FILE = "escoria-wizard.conf"
|
|
|
|
|
|
# Test flag - set to true to load test data.
|
|
var test_mode: bool = false
|
|
|
|
# The currently loaded spritesheet image
|
|
var source_image: Image
|
|
|
|
# The current size of each animation frame (the spritesheet is broken into squares of this size.
|
|
var frame_size: Vector2
|
|
|
|
# The current spritesheet zoom level.
|
|
var zoom_value: float = 1
|
|
|
|
# The speed of the animation being previewed in frames-per-second.
|
|
var current_animation_speed: int = 5
|
|
|
|
# The animation direction currently being edited
|
|
var direction_selected: String
|
|
|
|
# This is the animation direction that has been clicked on by the user.
|
|
# Once it has been confirmed that there are no unstored changes to the current animation,
|
|
# the requested direction will become the "direction_selected".
|
|
var direction_requested: String
|
|
|
|
# The animation type currently being edited
|
|
var animation_type_selected: String
|
|
|
|
# This is the animation ty[e that has been clicked on by the user.
|
|
# Once it has been confirmed that there are no unstored changes to the current animation,
|
|
# the requested type will become the "animation_type_selected".
|
|
var animation_type_requested: String
|
|
|
|
# This is the array that stores the data for each animation.
|
|
var anim_metadata = []
|
|
|
|
# Track the page showing in the help window
|
|
var help_window_page = 1
|
|
|
|
# Array to track frame settings so if you do an illegal action (like changing the last sprite frame
|
|
# prior to a spritesheet being loaded) the value can be reset
|
|
var spritesheet_settings = [1, 1, 0, 0]
|
|
|
|
# To stop errors flagging when you change to a mirrored direction
|
|
var currently_changing_direction: bool = false
|
|
|
|
# Whether all changes are automatically 'saved' or need manual confirmation before storing
|
|
var autostore: bool = false
|
|
|
|
# Needed due to the yield method used for export to pass back the largest sprite used
|
|
# for the character. This will determine the collision shape size.
|
|
var export_largest_sprite: Vector2
|
|
|
|
|
|
func _ready() -> void:
|
|
load_settings()
|
|
character_creator_reset()
|
|
$InformationWindows/help_window.current_page = 1
|
|
|
|
if test_mode:
|
|
setup_test_data()
|
|
|
|
|
|
func character_creator_reset() -> void:
|
|
# Disconnect all the signals to stop program logic firing during setup
|
|
disconnect_selector_signals()
|
|
|
|
get_node(NAME_NODE).get_node("node_name").text = "replace_me"
|
|
get_node(NAME_NODE).get_node("global_id").text = ""
|
|
get_node(DIR_COUNT_NODE).get_node("four_directions").pressed = true
|
|
|
|
# For unknown reasons the above doesn't cause the trigger to fire so manual steps required
|
|
get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed = false
|
|
get_node(DIR_COUNT_NODE).get_node("two_directions").pressed = false
|
|
get_node(DIR_COUNT_NODE).get_node("one_direction").pressed = false
|
|
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = true
|
|
animation_type_selected = "walk"
|
|
|
|
# For unknown reasons the above doesn't cause the trigger to fire so manual steps required
|
|
if get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed:
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = false
|
|
|
|
if get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed:
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = false
|
|
|
|
get_node(NO_SPRITESHEET_NODE).visible = true
|
|
zoom_value = 1
|
|
get_node(ZOOM_SCROLL_NODE).value = zoom_value
|
|
get_node(ZOOM_LABEL_NODE).text = "Zoom: %sx" % str(zoom_value)
|
|
get_node(ANIM_CONTROLS_NODE).get_node("original_size_label").text = "Source sprite size: (0, 0)"
|
|
get_node(ANIM_CONTROLS_NODE).get_node("frame_size_label").text = "Frame size: (0, 0)"
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").animation = ANIM_IN_PROGRESS
|
|
|
|
preview_hide()
|
|
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
|
|
create_empty_animations()
|
|
|
|
direction_selected = DIR_UP
|
|
activate_direction(direction_selected)
|
|
|
|
reset_arrow_colours()
|
|
|
|
# Reset GUI controls to initial values
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = spritesheet_settings[0]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = spritesheet_settings[1]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = spritesheet_settings[2]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = spritesheet_settings[3]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value = current_animation_speed
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_label").text = "%s: 5 FPS" % ANIMATION_SPEED_LABEL
|
|
get_node(CURRENT_SHEET_NODE).text="No spritesheet loaded."
|
|
# Connect all the signals now the base settings are configured to stop program logic firing during setup
|
|
reset_frame_outlines()
|
|
|
|
# Make sure help window doesn't swallow mouse input
|
|
$InformationWindows.visible = false
|
|
autostore = $VBoxContainer/HBoxContainer/configuration/VBoxContainer/animation/autosave/HBoxContainer/AutoStoreCheckBox.pressed
|
|
connect_selector_signals()
|
|
|
|
|
|
func connect_selector_signals() -> void:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").connect("value_changed", self, "controls_on_h_frames_spin_box_value_changed")
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").connect("value_changed", self, "controls_on_v_frames_spin_box_value_changed")
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").connect("value_changed", self, "controls_on_start_frame_value_changed")
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").connect("value_changed", self, "controls_on_end_frame_value_changed")
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").connect("value_changed", self, "controls_on_anim_speed_scroll_bar_value_changed")
|
|
|
|
|
|
func disconnect_selector_signals() -> void:
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").is_connected("value_changed", self, "controls_on_h_frames_spin_box_value_changed"):
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").disconnect("value_changed", self, "controls_on_h_frames_spin_box_value_changed")
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").is_connected("value_changed", self, "controls_on_v_frames_spin_box_value_changed"):
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").disconnect("value_changed", self, "controls_on_v_frames_spin_box_value_changed")
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").is_connected("value_changed", self, "controls_on_start_frame_value_changed"):
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").disconnect("value_changed", self, "controls_on_start_frame_value_changed")
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("end_frame").is_connected("value_changed", self, "controls_on_end_frame_value_changed"):
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").disconnect("value_changed", self, "controls_on_end_frame_value_changed")
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").is_connected("value_changed", self, "controls_on_anim_speed_scroll_bar_value_changed"):
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").disconnect("value_changed", self, "controls_on_anim_speed_scroll_bar_value_changed")
|
|
|
|
|
|
func reset_frame_outlines() -> void:
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").zoom_factor = .01
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").total_num_columns = 1
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").total_num_rows = 1
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").start_cell = 0
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").end_cell = 0
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").cell_size = Vector2(1,1)
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").update()
|
|
|
|
|
|
func calc_sprite_size() -> void:
|
|
var source_size = source_image.get_size()
|
|
var horiz_size = int(source_size.x / get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value)
|
|
var vert_size = int(source_size.y / get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value)
|
|
|
|
frame_size = Vector2(horiz_size, vert_size)
|
|
|
|
get_node(ANIM_CONTROLS_NODE).get_node("original_size_label").text = "Source sprite size: %s" % source_size
|
|
get_node(ANIM_CONTROLS_NODE).get_node("frame_size_label").text = "Frame size: %s" % frame_size
|
|
|
|
|
|
# Load test data - primarily for testing export, but also for testing general functionality.
|
|
# Different spritesheets, mirroring settings, frame counts, and speeds are used deliberately.
|
|
func setup_test_data() -> void:
|
|
# load_spritesheet("res://addons/escoria-wizard/graphics/mark-animtest.png")
|
|
# # Up, right, down, left, up/r, down/r, down/l, up/l
|
|
# var start_frames = [15, 10, 7, 10, 12, 14, 1, 14, 12, 3, 1, 1]
|
|
# var end_frames = [17, 14, 9, 14, 13, 18, 3, 18, 12, 3, 1, 22]
|
|
# var mirrored = [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,]
|
|
# var sourcefile = [0, 0, 0, 0, 2, 2, 1, 2, 2, 0, 0, 0]
|
|
# var fps = [3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3]
|
|
#
|
|
# for loop in range(12): # 12 for a 4 direction character, 24 for an 8 direction character
|
|
# if sourcefile[loop] == 0:
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_SOURCE_FILE] = "res://addons/escoria-wizard/graphics/mark-animtest.png"
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_HORIZ] = 8
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_VERT] = 3
|
|
# elif sourcefile[loop] == 1:
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_SOURCE_FILE] = "res://game/characters/mark/png/mark_talk_down.png"
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_HORIZ] = 3
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_VERT] = 1
|
|
# else:
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_SOURCE_FILE] = "res://game/characters/mark/png/markjester_talk.png"
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_HORIZ] = 21
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FRAMES_VERT] = 1
|
|
#
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_FIRST_FRAME] = start_frames[loop]
|
|
# anim_metadata[loop * 2][METADATA_SPRITESHEET_LAST_FRAME] = end_frames[loop]
|
|
# anim_metadata[loop * 2][METADATA_SPEED] = fps[loop]
|
|
#
|
|
# anim_metadata[loop * 2][METADATA_IS_MIRROR] = mirrored[loop] != 0
|
|
#
|
|
# get_node(NO_SPRITESHEET_NODE).visible = false
|
|
#
|
|
# reset_arrow_colours()
|
|
# Up, right, down, left, up/r, down/r, down/l, up/l
|
|
var spritebase:String="addons/escoria-wizard/graphics/robot"
|
|
# load_spritesheet("" + $spritepath + "/walk_up.png")
|
|
var start_frames = [1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 5,6,4,3,1,2,4,6]
|
|
var end_frames = [16,16,16,16,16,16,16,16, 53,53,53,53,53,53,53,53, 5,6,4,3,1,2,4,6]
|
|
var mirrored = [0,0,0,0,0,0,1,1, 0,0,0,0,0,0,1,1, 0,1,1,0,0,0,0,0]
|
|
var frames_h = [16,16,16,16,16,16,16,16, 14,14,14,14,14,14,14,14, 6,6,6,6,6,6,6,6]
|
|
var frames_v = [1,1,1,1,1,1,1,1, 4,4,4,4,4,4,4,4, 1,1,1,1,1,1,1,1]
|
|
|
|
anim_metadata[0][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_up.png"
|
|
anim_metadata[1][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_upright.png"
|
|
anim_metadata[2][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_right.png"
|
|
anim_metadata[3][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_downright.png"
|
|
anim_metadata[4][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_down.png"
|
|
anim_metadata[5][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_downleft.png"
|
|
anim_metadata[6][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_right.png"
|
|
anim_metadata[7][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/walk_upright.png"
|
|
|
|
anim_metadata[8][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_up.png"
|
|
anim_metadata[9][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_upright.png"
|
|
anim_metadata[10][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_right.png"
|
|
anim_metadata[11][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_downright.png"
|
|
anim_metadata[12][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_down.png"
|
|
anim_metadata[13][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_downleft.png"
|
|
anim_metadata[14][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_right.png"
|
|
anim_metadata[15][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/talk_upright.png"
|
|
|
|
anim_metadata[16][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[17][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[18][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[19][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[20][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[21][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[22][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
anim_metadata[23][METADATA_SPRITESHEET_SOURCE_FILE] = spritebase + "/idle_all.png"
|
|
|
|
for loop in range(24):
|
|
anim_metadata[loop][METADATA_SPRITESHEET_FIRST_FRAME] = start_frames[loop]
|
|
anim_metadata[loop][METADATA_SPRITESHEET_LAST_FRAME] = end_frames[loop]
|
|
anim_metadata[loop][METADATA_SPEED] = 24
|
|
anim_metadata[loop][METADATA_IS_MIRROR] = mirrored[loop] != 0
|
|
anim_metadata[loop][METADATA_SPRITESHEET_FRAMES_HORIZ] = frames_h[loop]
|
|
anim_metadata[loop][METADATA_SPRITESHEET_FRAMES_VERT] = frames_v[loop]
|
|
|
|
get_node(NO_SPRITESHEET_NODE).visible = false
|
|
get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed = true
|
|
for loop in ["four_directions", "two_directions", "one_direction"]:
|
|
get_node(DIR_COUNT_NODE).get_node(loop).pressed = false
|
|
reset_arrow_colours()
|
|
|
|
|
|
# Animations are stored as metadata in an array. This creates the initial empty array.
|
|
# The preview animation ("in_progress") is the only sprite animation created prior to the final export.
|
|
func create_empty_animations() -> void:
|
|
var sframes = SpriteFrames.new()
|
|
|
|
var metadata_dict = {
|
|
METADATA_ANIM_NAME: "tbc",
|
|
METADATA_SPRITESHEET_SOURCE_FILE: "tbc",
|
|
METADATA_SPRITESHEET_FRAMES_HORIZ: -1,
|
|
METADATA_SPRITESHEET_FRAMES_VERT: -1,
|
|
METADATA_SPRITESHEET_FIRST_FRAME: 0,
|
|
METADATA_SPRITESHEET_LAST_FRAME: 0,
|
|
METADATA_SPEED: 30,
|
|
METADATA_IS_MIRROR: false
|
|
}
|
|
|
|
var local_dict
|
|
|
|
anim_metadata.clear()
|
|
|
|
for typeloop in [TYPE_WALK, TYPE_TALK, TYPE_IDLE]:
|
|
for dirloop in DIR_LIST_8:
|
|
local_dict = metadata_dict.duplicate()
|
|
local_dict[METADATA_ANIM_NAME] = "%s_%s" % [typeloop, dirloop]
|
|
anim_metadata.append(local_dict)
|
|
|
|
sframes.add_animation(ANIM_IN_PROGRESS)
|
|
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").frames = sframes
|
|
|
|
|
|
# Loads a spritesheet and calculates the size of each sprite frame if loading a spritesheet
|
|
# to show a previously stored animation.
|
|
func load_spritesheet(file_to_load, read_settings_from_metadata: bool = false, metadata_frame: int = 0) -> void:
|
|
if source_image == null:
|
|
source_image = Image.new()
|
|
|
|
var errorval = source_image.load(file_to_load)
|
|
|
|
assert(not errorval, "Error loading file %s" % str(file_to_load))
|
|
|
|
var texture = ImageTexture.new()
|
|
texture.create_from_image(source_image)
|
|
texture.set_flags(2)
|
|
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").texture = texture
|
|
|
|
frame_size = source_image.get_size()
|
|
|
|
if read_settings_from_metadata:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = anim_metadata[metadata_frame][METADATA_SPRITESHEET_FRAMES_HORIZ]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = anim_metadata[metadata_frame][METADATA_SPRITESHEET_FRAMES_VERT]
|
|
else:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = 1
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = 1
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = 1
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = 1
|
|
|
|
calc_sprite_size()
|
|
|
|
get_node(CURRENT_SHEET_NODE).text = file_to_load
|
|
draw_frame_outlines()
|
|
set_zoom_scale_automatically(source_image.get_size())
|
|
|
|
# Make scroll bars appear if necessary
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_min_size = source_image.get_size() * zoom_value
|
|
|
|
|
|
# spritesheet is loaded.
|
|
func set_zoom_scale_automatically(spritesheet_size) -> void:
|
|
|
|
var available_space = get_node(SCROLL_VBOX_NODE).get_node("spritesheet_scroll_container").rect_size
|
|
|
|
# Calculate the scale to make the preview as big as possible in the preview window depending on
|
|
# the height to width ratio of the frame
|
|
var spritesheet_scale = Vector2.ONE
|
|
spritesheet_scale.x = available_space.x / spritesheet_size.x
|
|
spritesheet_scale.y = available_space.y / spritesheet_size.y
|
|
var blah = Vector2.ONE
|
|
blah.x = spritesheet_size.x / available_space.x
|
|
blah.y = spritesheet_size.y / available_space.y
|
|
|
|
var newscale = 0.0
|
|
if spritesheet_scale.y > spritesheet_scale.x:
|
|
# Round to 1 decimal place
|
|
newscale = (int(spritesheet_scale.x * 10.0)) / 10.0
|
|
else:
|
|
# Round to 1 decimal place
|
|
newscale = (int(spritesheet_scale.y * 10.0)) / 10.0
|
|
if newscale < 0.1:
|
|
newscale = 0.1
|
|
if newscale > 5:
|
|
newscale = 5
|
|
get_node(ZOOM_SCROLL_NODE).value = newscale
|
|
|
|
|
|
# Draws an outline on the spritesheet to show which frames are included in the current animation
|
|
func draw_frame_outlines() -> void:
|
|
check_frame_limits()
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").zoom_factor = zoom_value
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").total_num_columns = get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").total_num_rows = get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").start_cell = get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").end_cell = get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").cell_size = frame_size
|
|
get_node(SCROLL_CTRL_NODE).get_node("frame_rectangles").update()
|
|
|
|
|
|
# When given a frame number, this calculates the pixel coordinates that frame in the spritesheet
|
|
# based on the number of horizontal/vertical frames configured for this spritesheet
|
|
func calc_frame_coords(Frame: int) -> Vector2:
|
|
var column = (Frame - 1) % int(get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value) * frame_size.x
|
|
var row = int((Frame - 1) / get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value) * frame_size.y
|
|
return Vector2(column, row)
|
|
|
|
|
|
# Updates the animation metadata to store the changed / new settings for a particular animation
|
|
func store_animation(animation_to_store: String) -> void:
|
|
var texture
|
|
var rect_location
|
|
var frame_being_copied = Image.new()
|
|
var frame_counter: int = 0
|
|
|
|
var metadata_dict = {
|
|
METADATA_ANIM_NAME: animation_to_store,
|
|
METADATA_SPRITESHEET_SOURCE_FILE: get_node(CURRENT_SHEET_NODE).text,
|
|
METADATA_SPRITESHEET_FRAMES_HORIZ: get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value,
|
|
METADATA_SPRITESHEET_FRAMES_VERT: get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value,
|
|
METADATA_SPRITESHEET_FIRST_FRAME: get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value,
|
|
METADATA_SPRITESHEET_LAST_FRAME: get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value,
|
|
METADATA_SPEED: get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value,
|
|
METADATA_IS_MIRROR: get_node(MIRROR_NODE).pressed
|
|
}
|
|
|
|
var metadata_array_offset: int = get_metadata_array_offset()
|
|
|
|
anim_metadata[metadata_array_offset] = metadata_dict
|
|
|
|
if direction_selected == DIR_UP or direction_selected == DIR_DOWN:
|
|
return
|
|
|
|
# If this direction has already been mirrored, replicate the changes
|
|
var opp_dir = find_opposite_direction(direction_selected)
|
|
var opp_metadata_array_offset: int = get_metadata_array_offset(opp_dir)
|
|
if anim_metadata[opp_metadata_array_offset][METADATA_IS_MIRROR]:
|
|
mirror_animation(direction_selected, opp_dir)
|
|
|
|
|
|
# Updates the metadata to mirror animation "source" to animation "dest"
|
|
# The "source" animation is the animation that really exists as sprite frames
|
|
# in the animated sprite
|
|
func mirror_animation(source: String, dest: String) -> void:
|
|
var texture
|
|
var rect_location
|
|
var frame_being_copied = Image.new()
|
|
var frame_counter: int = 0
|
|
#
|
|
var metadata_source_offset = get_metadata_array_offset(source)
|
|
var current_anim_type = return_current_animation_type()
|
|
var dest_anim_name = "%s_%s" % [current_anim_type, dest]
|
|
|
|
var metadata_dict = {
|
|
METADATA_ANIM_NAME: dest_anim_name,
|
|
METADATA_SPRITESHEET_SOURCE_FILE: anim_metadata[metadata_source_offset][METADATA_SPRITESHEET_SOURCE_FILE],
|
|
METADATA_SPRITESHEET_FRAMES_HORIZ: anim_metadata[metadata_source_offset][METADATA_SPRITESHEET_FRAMES_HORIZ],
|
|
METADATA_SPRITESHEET_FRAMES_VERT: anim_metadata[metadata_source_offset][METADATA_SPRITESHEET_FRAMES_VERT],
|
|
METADATA_SPRITESHEET_FIRST_FRAME: anim_metadata[metadata_source_offset][METADATA_SPRITESHEET_FIRST_FRAME],
|
|
METADATA_SPRITESHEET_LAST_FRAME: anim_metadata[metadata_source_offset][METADATA_SPRITESHEET_LAST_FRAME],
|
|
METADATA_SPEED: anim_metadata[metadata_source_offset][METADATA_SPEED],
|
|
METADATA_IS_MIRROR: true
|
|
}
|
|
|
|
var metadata_dest_offset = get_metadata_array_offset(dest)
|
|
anim_metadata[metadata_dest_offset] = metadata_dict
|
|
disconnect_selector_signals()
|
|
reset_arrow_colours()
|
|
connect_selector_signals()
|
|
|
|
func unmirror_animation(anim_to_unmirror: String) -> void:
|
|
var metadata_dict = {
|
|
METADATA_ANIM_NAME: "tbc",
|
|
METADATA_SPRITESHEET_SOURCE_FILE: "tbc",
|
|
METADATA_SPRITESHEET_FRAMES_HORIZ: -1,
|
|
METADATA_SPRITESHEET_FRAMES_VERT: -1,
|
|
METADATA_SPRITESHEET_FIRST_FRAME: 0,
|
|
METADATA_SPRITESHEET_LAST_FRAME: 0,
|
|
METADATA_SPEED: 30,
|
|
METADATA_IS_MIRROR: false
|
|
}
|
|
spritesheet_settings[2] = 0
|
|
spritesheet_settings[3] = 0
|
|
var metadata_dest_offset = get_metadata_array_offset(anim_to_unmirror)
|
|
anim_metadata[metadata_dest_offset] = metadata_dict
|
|
reset_arrow_colours()
|
|
|
|
|
|
# Shows the preview animation. Required as the no_anim_found sprite doesn't always cover the
|
|
# whole preview due to UI peculiarities.
|
|
func preview_show():
|
|
get_node(PREVIEW_NODE).get_node("no_anim_found_sprite").visible = false
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").visible = true
|
|
|
|
|
|
# Hides the preview animation. Required when the no_anim_found sprite doesn't cover the
|
|
# whole preview due to UI peculiarities.
|
|
func preview_hide():
|
|
get_node(PREVIEW_NODE).get_node("no_anim_found_sprite").visible = true
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").visible = false
|
|
|
|
|
|
# Creates the "in_progress" animation which is shown in the UI as the animation preview based
|
|
# on the currently selected settings.
|
|
#
|
|
# A mirrored animation (frames in reverse order and sprites horizontally flipped) is generated here
|
|
# for the purpose of the preview but isn't generated in the final export as it relies on ESCPlayer's
|
|
# is_mirrored setting.
|
|
func preview_update() -> void:
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value > 0:
|
|
check_frame_limits()
|
|
|
|
var current_anim_type = return_current_animation_type()
|
|
var anim_name = "%s_%s" % [current_anim_type, direction_selected]
|
|
var offset = get_metadata_array_offset()
|
|
var generate_mirror = get_node(MIRROR_NODE).pressed
|
|
|
|
var texture
|
|
var rect_location
|
|
var frame_being_copied = Image.new()
|
|
var frame_counter: int = 0
|
|
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").frames.clear(ANIM_IN_PROGRESS)
|
|
|
|
frame_being_copied.create(frame_size.x, frame_size.y, false, source_image.get_format())
|
|
|
|
for loop in range(get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value - get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value + 1):
|
|
texture = ImageTexture.new()
|
|
rect_location = calc_frame_coords(get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value + loop)
|
|
frame_being_copied.blit_rect(source_image, Rect2(rect_location, Vector2(frame_size.x, frame_size.y)), Vector2(0, 0))
|
|
|
|
if generate_mirror:
|
|
frame_being_copied.flip_x()
|
|
|
|
texture.create_from_image(frame_being_copied)
|
|
# Remove the image filter to make pixel correct graphics
|
|
texture.set_flags(2)
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").frames.add_frame(ANIM_IN_PROGRESS, texture, frame_counter)
|
|
frame_counter += 1
|
|
preview_show()
|
|
|
|
# Calculate the scale to make the preview as big as possible in the preview window depending on
|
|
# the height to width ratio of the frame
|
|
var preview_scale = Vector2.ONE
|
|
preview_scale.x = get_node(PREVIEW_BGRND_NODE).rect_size.x / frame_size.x
|
|
preview_scale.y = get_node(PREVIEW_BGRND_NODE).rect_size.y / frame_size.y
|
|
|
|
if preview_scale.y > preview_scale.x:
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").scale = Vector2(preview_scale.x, preview_scale.x)
|
|
else:
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").scale = Vector2(preview_scale.y, preview_scale.y)
|
|
else:
|
|
preview_hide()
|
|
|
|
|
|
# Ensure that the spritesheet settings are valid
|
|
func check_frame_limits():
|
|
var max_frame = get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value * get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value > max_frame:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = max_frame
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value > max_frame:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = max_frame
|
|
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value > get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value
|
|
|
|
# If any spritesheet settings have changed, display the "store animation" button to save the changes.
|
|
# If the values are manually reset after a change to the previously stored settings, the button will disappear.
|
|
func check_if_controls_have_changed():
|
|
var metadata_array_offset: int = get_metadata_array_offset()
|
|
var metadata_entry = anim_metadata[metadata_array_offset]
|
|
|
|
if autostore == true:
|
|
return
|
|
|
|
# Need to check this or it registers if you load a sprite and set the number of horizontal
|
|
# or vertical frames and haven't set a start/end frame yet
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value > 0:
|
|
get_node(STORE_ANIM_NODE).visible = \
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value != metadata_entry[METADATA_SPEED] \
|
|
or get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value != metadata_entry[METADATA_SPRITESHEET_FIRST_FRAME] \
|
|
or get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value != metadata_entry[METADATA_SPRITESHEET_LAST_FRAME] \
|
|
or get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value != metadata_entry[METADATA_SPRITESHEET_FRAMES_HORIZ] \
|
|
or get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value != metadata_entry[METADATA_SPRITESHEET_FRAMES_VERT]
|
|
|
|
|
|
# If the user tries to change settings before they've loaded a spritesheet, this will display
|
|
# a warning window instead of letting them change settings.
|
|
func has_spritesheet_been_loaded() -> bool:
|
|
if source_image == null:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "Please load a spritesheet to begin."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return false
|
|
return true
|
|
|
|
|
|
func animation_on_dir_up_pressed() -> void:
|
|
check_activate_direction(DIR_UP)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_right_pressed() -> void:
|
|
check_activate_direction(DIR_RIGHT)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_left_pressed() -> void:
|
|
check_activate_direction(DIR_LEFT)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_down_pressed() -> void:
|
|
check_activate_direction(DIR_DOWN)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_downright_pressed() -> void:
|
|
check_activate_direction(DIR_DOWN_RIGHT)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_downleft_pressed() -> void:
|
|
check_activate_direction(DIR_DOWN_LEFT)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_upright_pressed() -> void:
|
|
check_activate_direction(DIR_UP_RIGHT)
|
|
|
|
|
|
# Runs when the direction arrow is clicked
|
|
func animation_on_dir_upleft_pressed() -> void:
|
|
check_activate_direction(DIR_UP_LEFT)
|
|
|
|
|
|
# If the user tries to mirror an animation, ensure they're not trying to mirror an already
|
|
# mirrored direction, and that the direction they're trying to mirror has been created.
|
|
func animation_on_mirror_checkbox_toggled(button_pressed: bool) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "No animation has been configured."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
get_node(MIRROR_NODE).pressed = false
|
|
return
|
|
|
|
var opp_dir = find_opposite_direction(direction_selected)
|
|
var opp_anim_name="%s_%s" % [return_current_animation_type(), opp_dir]
|
|
var metadata_array_offset: int = get_metadata_array_offset(opp_dir)
|
|
|
|
if button_pressed:
|
|
if anim_metadata[metadata_array_offset][METADATA_IS_MIRROR]:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = \
|
|
"You can't mirror a direction that is already mirrored."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
get_node(MIRROR_NODE).set_pressed_no_signal(false)
|
|
return
|
|
|
|
if anim_metadata[metadata_array_offset][METADATA_SPRITESHEET_FIRST_FRAME] == 0:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = \
|
|
"You can't mirror an animation that hasn't been set up."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
get_node(MIRROR_NODE).set_pressed_no_signal(false)
|
|
return
|
|
|
|
mirror_animation(opp_dir, direction_selected)
|
|
preview_update()
|
|
else:
|
|
unmirror_animation(direction_selected)
|
|
|
|
|
|
# When the animation speed has been changed, update the speed and label
|
|
func controls_on_anim_speed_scroll_bar_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value = current_animation_speed
|
|
return
|
|
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_IS_MIRROR] and not currently_changing_direction:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value = current_animation_speed
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "You cannot change a mirrored animation."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
|
|
current_animation_speed = int(value)
|
|
|
|
check_if_controls_have_changed()
|
|
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_label").text = "%s: %s FPS" % [ANIMATION_SPEED_LABEL, value]
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").frames.set_animation_speed(ANIM_IN_PROGRESS, value)
|
|
|
|
preview_update()
|
|
|
|
if autostore == true:
|
|
store_on_anim_store_button_pressed()
|
|
|
|
|
|
# When the first animation frame setting is changed, update the animation preview appropriately
|
|
func controls_on_start_frame_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = spritesheet_settings[2]
|
|
return
|
|
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_IS_MIRROR] and not currently_changing_direction:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = spritesheet_settings[2]
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "You cannot change a mirrored animation."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
spritesheet_settings[2] = get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value > 0:
|
|
preview_show()
|
|
check_if_controls_have_changed()
|
|
draw_frame_outlines()
|
|
preview_update()
|
|
|
|
if autostore == true:
|
|
store_on_anim_store_button_pressed()
|
|
|
|
|
|
# When the last animation frame setting is changed, update the animation preview appropriately
|
|
func controls_on_end_frame_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = spritesheet_settings[3]
|
|
return
|
|
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_IS_MIRROR] and not currently_changing_direction:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = spritesheet_settings[3]
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "You cannot change a mirrored animation."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
|
|
spritesheet_settings[3] = get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value > 0:
|
|
if get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value == 0:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = 1
|
|
spritesheet_settings[2] = 1
|
|
preview_show()
|
|
check_if_controls_have_changed()
|
|
|
|
draw_frame_outlines()
|
|
preview_update()
|
|
|
|
if autostore == true:
|
|
store_on_anim_store_button_pressed()
|
|
|
|
|
|
# When the number of horizontal frames in the spritesheet setting is changed,
|
|
# update the animation preview appropriately
|
|
func controls_on_h_frames_spin_box_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = spritesheet_settings[0]
|
|
return
|
|
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_IS_MIRROR] and not currently_changing_direction:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = spritesheet_settings[0]
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "You cannot change a mirrored animation."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
|
|
spritesheet_settings[0] = get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value
|
|
preview_show()
|
|
|
|
check_if_controls_have_changed()
|
|
calc_sprite_size()
|
|
draw_frame_outlines()
|
|
preview_update()
|
|
|
|
if autostore == true:
|
|
store_on_anim_store_button_pressed()
|
|
|
|
|
|
# When the number of vertical frames in the spritesheet setting is changed,
|
|
# update the animation preview appropriately
|
|
func controls_on_v_frames_spin_box_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = spritesheet_settings[1]
|
|
return
|
|
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_IS_MIRROR] and not currently_changing_direction:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = spritesheet_settings[1]
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = "You cannot change a mirrored animation."
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
|
|
spritesheet_settings[1] = get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value
|
|
|
|
preview_show()
|
|
|
|
check_if_controls_have_changed()
|
|
calc_sprite_size()
|
|
draw_frame_outlines()
|
|
preview_update()
|
|
|
|
if autostore == true:
|
|
store_on_anim_store_button_pressed()
|
|
|
|
|
|
# Load a spritesheet when selected in the file browser
|
|
func controls_on_FileDialog_file_selected(path: String) -> void:
|
|
get_node(NO_SPRITESHEET_NODE).visible = false
|
|
load_spritesheet(path)
|
|
|
|
|
|
# Check all animations have been created when the user wants to export the ESCPlayer
|
|
func spritesheet_on_export_button_pressed() -> void:
|
|
var missing_walk_animations: int = 0
|
|
var missing_talk_animations: int = 0
|
|
var missing_idle_animations: int = 0
|
|
var anim_name: String = ""
|
|
var dirnames = []
|
|
|
|
var scene_name = "%s/%s.scn" % [get_node(CHARACTER_PATH_NODE).text, get_node(NAME_NODE).get_node("node_name").text]
|
|
|
|
var dest_file = File.new()
|
|
if dest_file.file_exists(scene_name):
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = \
|
|
"Scene file '%s' already exists.\nPlease change Global_ID or path,\nor delete scene before continuing.\n" \
|
|
% scene_name
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
return
|
|
|
|
if get_node(DIR_COUNT_NODE).get_node("four_directions").pressed:
|
|
dirnames = DIR_LIST_4
|
|
elif get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed:
|
|
dirnames = DIR_LIST_8
|
|
elif get_node(DIR_COUNT_NODE).get_node("two_directions").pressed:
|
|
dirnames = DIR_LIST_2
|
|
else:
|
|
dirnames = DIR_LIST_1
|
|
|
|
for dirloop in dirnames:
|
|
anim_name = "%s_%s" % [TYPE_WALK, dirloop]
|
|
|
|
if anim_metadata[get_metadata_array_offset(dirloop, TYPE_WALK)][METADATA_SPRITESHEET_FIRST_FRAME] == 0:
|
|
missing_walk_animations += 1
|
|
|
|
anim_name = "%s_%s" % [TYPE_TALK, dirloop]
|
|
|
|
if anim_metadata[get_metadata_array_offset(dirloop, TYPE_TALK)][METADATA_SPRITESHEET_FIRST_FRAME] == 0:
|
|
missing_talk_animations += 1
|
|
|
|
anim_name = "%s_%s" % [TYPE_IDLE, dirloop]
|
|
|
|
if anim_metadata[get_metadata_array_offset(dirloop, TYPE_IDLE)][METADATA_SPRITESHEET_FIRST_FRAME] == 0:
|
|
missing_idle_animations += 1
|
|
|
|
if missing_idle_animations + missing_talk_animations + missing_walk_animations > 0:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text = \
|
|
"One or more animations are not configured.\nPlease ensure all arrows are green for\nwalk, talk, and idle animations.\n\n"
|
|
|
|
if missing_walk_animations:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text += \
|
|
"%s walk animations not configured.\n" % missing_walk_animations
|
|
|
|
if missing_talk_animations:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text += \
|
|
"%s talk animations not configured.\n" % missing_talk_animations
|
|
|
|
if missing_idle_animations:
|
|
get_node(GENERIC_ERROR_NODE).dialog_text += \
|
|
"%s idle animations not configured." % missing_idle_animations
|
|
|
|
get_node(GENERIC_ERROR_NODE).popup()
|
|
|
|
return
|
|
|
|
# export_thread = Thread.new()
|
|
# export_thread.start(self, "export_player")
|
|
export_player(scene_name)
|
|
|
|
# Update the spritesheet zoom and scrollbars
|
|
func spritesheet_on_zoom_scrollbar_value_changed(value: float) -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
return
|
|
|
|
zoom_value = stepify(value, 0.1)
|
|
get_node(ZOOM_LABEL_NODE).text = "Zoom: %sx" % str(zoom_value)
|
|
if zoom_value > 1.0:
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_min_size = source_image.get_size() * zoom_value
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_scale = Vector2.ONE
|
|
else:
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_min_size = source_image.get_size()
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_scale.x = zoom_value
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").rect_scale.y = zoom_value
|
|
draw_frame_outlines()
|
|
|
|
|
|
# Show the file manager when the load spritesheet button is pressed
|
|
func spritesheet_on_load_spritesheet_button_pressed() -> void:
|
|
get_node(FILE_DIALOG_NODE).popup()
|
|
|
|
|
|
# Reset zoom settings when the reset button is pushed. Also called when a new
|
|
# spritesheet is loaded.
|
|
func spritesheet_on_zoom_reset_button_pressed() -> void:
|
|
if not has_spritesheet_been_loaded():
|
|
return
|
|
|
|
get_node(ZOOM_SCROLL_NODE).value = 1
|
|
|
|
get_node(SCROLL_VBOX_NODE).get_node("spritesheet_scroll_container").scroll_horizontal = 0
|
|
get_node(SCROLL_VBOX_NODE).get_node("spritesheet_scroll_container").scroll_vertical = 0
|
|
|
|
|
|
# If the node name is changed, update the global_id to match.
|
|
# NOTE : Updating the global_id doesn't update the nodename, allowing them to be different.
|
|
func nodename_on_node_name_text_changed(new_text: String) -> void:
|
|
get_node(NAME_NODE).get_node("global_id").text = new_text
|
|
|
|
|
|
# If 8 directions was already selected, don't let it be unselected.
|
|
# If 4 directions was selected, unselect it.
|
|
func directions_on_eight_directions_pressed() -> void:
|
|
if not get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed:
|
|
# Don't let them untick all boxes
|
|
get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed = true
|
|
|
|
for loop in ["four_directions", "two_directions", "one_direction"]:
|
|
get_node(DIR_COUNT_NODE).get_node(loop).pressed = false
|
|
reset_arrow_colours()
|
|
|
|
|
|
# If 4 directions was already selected, don't let it be unselected.
|
|
# If previously selected direction is now invalid, change it to a valid one.
|
|
func directions_on_four_directions_pressed() -> void:
|
|
if not get_node(DIR_COUNT_NODE).get_node("four_directions").pressed:
|
|
# Don't let them untick all boxes
|
|
get_node(DIR_COUNT_NODE).get_node("four_directions").pressed = true
|
|
else:
|
|
# Current direction is illegal
|
|
for loop in ["eight_directions", "two_directions", "one_direction"]:
|
|
get_node(DIR_COUNT_NODE).get_node(loop).pressed = false
|
|
if not direction_selected in DIR_LIST_4:
|
|
direction_selected = DIR_UP
|
|
activate_direction(DIR_UP)
|
|
reset_arrow_colours()
|
|
|
|
|
|
# If 2 directions was already selected, don't let it be unselected.
|
|
# If previously selected direction is now invalid, change it to a valid one.
|
|
func directions_on_two_directions_pressed() -> void:
|
|
if not get_node(DIR_COUNT_NODE).get_node("two_directions").pressed:
|
|
# Don't let them untick all boxes
|
|
get_node(DIR_COUNT_NODE).get_node("two_directions").pressed = true
|
|
else:
|
|
for loop in ["eight_directions", "four_directions", "one_direction"]:
|
|
get_node(DIR_COUNT_NODE).get_node(loop).pressed = false
|
|
# Current direction is illegal
|
|
if not direction_selected in DIR_LIST_2:
|
|
direction_selected = DIR_RIGHT
|
|
activate_direction(DIR_RIGHT)
|
|
reset_arrow_colours()
|
|
|
|
|
|
# If 1 direction was already selected, don't let it be unselected.
|
|
# If previously selected direction is now invalid, change it to a valid one.
|
|
func directions_on_one_direction_pressed() -> void:
|
|
if not get_node(DIR_COUNT_NODE).get_node("one_direction").pressed:
|
|
# Don't let them untick all boxes
|
|
get_node(DIR_COUNT_NODE).get_node("one_direction").pressed = true
|
|
else:
|
|
for loop in ["eight_directions", "four_directions", "two_directions"]:
|
|
get_node(DIR_COUNT_NODE).get_node(loop).pressed = false
|
|
# Current direction is illegal
|
|
if not direction_selected in DIR_LIST_1:
|
|
direction_selected = DIR_DOWN
|
|
activate_direction(DIR_DOWN)
|
|
reset_arrow_colours()
|
|
|
|
# Returns the currently selected animation type
|
|
func return_current_animation_type() -> String:
|
|
var animation_type: String = ""
|
|
|
|
if get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed:
|
|
animation_type = TYPE_WALK
|
|
elif get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed:
|
|
animation_type = TYPE_TALK
|
|
elif get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed:
|
|
animation_type = TYPE_IDLE
|
|
|
|
assert(not animation_type.empty(), "No animation type selected.")
|
|
|
|
return animation_type
|
|
|
|
|
|
# Runs whenever a direction arrow is clicked. If the store button is visible (i.e. the settings
|
|
# for the sprite frames have changed since they were last stored) the selected direction isn't
|
|
# changed and a confirmation window is shown instead.
|
|
func check_activate_direction(direction) -> void:
|
|
direction_requested = direction
|
|
|
|
if get_node(STORE_ANIM_NODE).visible:
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % direction).get_node("set_dir_%s" % direction).pressed = false
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % direction).get_node("unset_dir_%s" % direction).pressed = false
|
|
$InformationWindows/unstored_changes_window.popup()
|
|
else:
|
|
activate_direction(direction)
|
|
|
|
|
|
# Change the selected direction. This clears the selected direction arrow and mirror settings.
|
|
# If the selected direction has an animation, it will be displayed, if not, the "no animation"
|
|
# graphic will be displayed in the preview window.
|
|
# Spritesheet control values are set based on the direction chosen (if it had a previous animation)
|
|
# If it used a different spritesheet, that will be loaded.
|
|
func activate_direction(direction) -> void:
|
|
var anim_type = return_current_animation_type()
|
|
var arrows = get_tree().get_nodes_in_group("direction_buttons")
|
|
var anim_name = "%s_%s" % [anim_type, direction]
|
|
|
|
currently_changing_direction = true
|
|
direction_selected = direction
|
|
|
|
for arrow in arrows:
|
|
arrow.pressed = false
|
|
|
|
if direction == DIR_UP or direction == DIR_DOWN:
|
|
get_node(MIRROR_NODE).visible = false
|
|
else:
|
|
get_node(MIRROR_NODE).visible = true
|
|
|
|
get_node(MIRROR_NODE).set_pressed_no_signal(false)
|
|
|
|
# If no animation has been created yet for this direction
|
|
if anim_metadata[get_metadata_array_offset()][METADATA_SPRITESHEET_FIRST_FRAME] == 0:
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % direction).get_node("unset_dir_%s" % direction).pressed = true
|
|
spritesheet_settings[2] = 0
|
|
spritesheet_settings[3] = 0
|
|
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = 0
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = 0
|
|
preview_hide()
|
|
else:
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % direction).get_node("set_dir_%s" % direction).pressed = true
|
|
|
|
var metadata = anim_metadata[get_metadata_array_offset()]
|
|
|
|
assert(metadata[METADATA_ANIM_NAME] == anim_name, \
|
|
"Anim %s expected in metadata array. Found %s" % [anim_name, metadata[METADATA_ANIM_NAME]])
|
|
|
|
if metadata[METADATA_SPRITESHEET_SOURCE_FILE] != get_node(CURRENT_SHEET_NODE).text:
|
|
load_spritesheet(metadata[METADATA_SPRITESHEET_SOURCE_FILE])
|
|
|
|
# Disconnect the signals so if we're changing to a mirrored direction it doesn't complain
|
|
# when all the settings update
|
|
# disconnect_selector_signals()
|
|
get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value = metadata[METADATA_SPRITESHEET_FRAMES_HORIZ]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value = metadata[METADATA_SPRITESHEET_FRAMES_VERT]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = metadata[METADATA_SPRITESHEET_FIRST_FRAME]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = metadata[METADATA_SPRITESHEET_LAST_FRAME]
|
|
get_node(ANIM_CONTROLS_NODE).get_node("anim_speed_scroll_bar").value = metadata[METADATA_SPEED]
|
|
get_node(MIRROR_NODE).set_pressed_no_signal(metadata[METADATA_IS_MIRROR])
|
|
# connect_selector_signals()
|
|
preview_update()
|
|
|
|
# Restart animation otherwise it will first complete all the frames before changing to the new animation
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").playing = false
|
|
get_node(PREVIEW_NODE).get_node("anim_preview_sprite").playing = true
|
|
currently_changing_direction = false
|
|
|
|
# Store the metadata for the animation changes for the current direction
|
|
func store_on_anim_store_button_pressed() -> void:
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
|
|
var anim_type = return_current_animation_type()
|
|
store_animation("%s_%s" % [anim_type, direction_selected])
|
|
|
|
reset_arrow_colours()
|
|
|
|
|
|
# Based on the type of animation and direction, find its array position in the metadata array.
|
|
func get_metadata_array_offset(dir_to_retrieve = "default", anim_type = "default") -> int:
|
|
var offset: int = 0
|
|
|
|
if anim_type == "default":
|
|
anim_type = return_current_animation_type()
|
|
|
|
if anim_type == TYPE_TALK:
|
|
offset = 8
|
|
elif anim_type == TYPE_IDLE:
|
|
offset = 16
|
|
|
|
if dir_to_retrieve == "default":
|
|
dir_to_retrieve = direction_selected
|
|
|
|
var dir_offset: int = DIR_LIST_8.find(dir_to_retrieve)
|
|
|
|
assert(dir_offset > -1, "Could not find direction in list. This should never happen.")
|
|
|
|
return offset + dir_offset
|
|
|
|
|
|
# Using both set and unset buttons (instead of changing the texture to a different colour) as
|
|
# updating the direction arrow sprite was causing issues due to Godot storing the
|
|
# sprite by reference rather than by value. It was easier to duplicate the sprites/buttons.
|
|
func reset_arrow_colours() -> void:
|
|
var arrows = get_tree().get_nodes_in_group("direction_buttons")
|
|
for arrow in arrows:
|
|
arrow.visible = false
|
|
|
|
get_node(ARROWS_NODE).get_node("Container_up").get_node("ColorRectSpacer").visible = false
|
|
get_node(ARROWS_NODE).get_node("Container_left").get_node("ColorRectSpacer").visible = false
|
|
get_node(ARROWS_NODE).get_node("Container_down").get_node("ColorRectSpacer").visible = false
|
|
var dir_list=DIR_LIST_8
|
|
if get_node(DIR_COUNT_NODE).get_node("four_directions").pressed:
|
|
dir_list=DIR_LIST_4
|
|
if not direction_selected in DIR_LIST_4:
|
|
direction_selected = DIR_UP
|
|
# get_node(ARROWS_NODE).get_node("Container_up").get_node("ColorRectSpacer").visible = true
|
|
if get_node(DIR_COUNT_NODE).get_node("two_directions").pressed:
|
|
dir_list=DIR_LIST_2
|
|
if not direction_selected in DIR_LIST_2:
|
|
direction_selected = DIR_RIGHT
|
|
get_node(ARROWS_NODE).get_node("Container_up").get_node("ColorRectSpacer").visible = true
|
|
get_node(ARROWS_NODE).get_node("Container_down").get_node("ColorRectSpacer").visible = true
|
|
if get_node(DIR_COUNT_NODE).get_node("one_direction").pressed:
|
|
dir_list=DIR_LIST_1
|
|
if not direction_selected in DIR_LIST_1:
|
|
direction_selected = DIR_DOWN
|
|
get_node(ARROWS_NODE).get_node("Container_up").get_node("ColorRectSpacer").visible = true
|
|
get_node(ARROWS_NODE).get_node("Container_left").get_node("ColorRectSpacer").visible = true
|
|
|
|
|
|
for dir in dir_list:
|
|
if anim_metadata[get_metadata_array_offset(dir)][METADATA_SPRITESHEET_FIRST_FRAME] > 0:
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % dir).get_node("set_dir_%s" % dir).visible = true
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % dir).get_node("unset_dir_%s" % dir).visible = false
|
|
else:
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % dir).get_node("set_dir_%s" % dir).visible = false
|
|
get_node(ARROWS_NODE).get_node("Container_%s" % dir).get_node("unset_dir_%s" % dir).visible = true
|
|
|
|
# This works when you change between animation types
|
|
# eg. walk and talk - to ensure that the arrow will get changed back from selected with stored
|
|
# animation to selected with unstored animation if needs be. It also resets the preview.
|
|
activate_direction(direction_selected)
|
|
|
|
|
|
# If the user tries to change direction without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to save the changes, then will update
|
|
# the interface to select the new direction.
|
|
func unstored_warning_on_commit_button_pressed() -> void:
|
|
get_node(UNSTORED_CHANGE_NODE).visible = false
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
|
|
var anim_type = return_current_animation_type()
|
|
store_animation("%s_%s" % [anim_type, direction_selected])
|
|
|
|
reset_arrow_colours()
|
|
|
|
activate_direction(direction_requested)
|
|
|
|
|
|
# If the user tries to change direction without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to lose the changes, then will update
|
|
# the interface to select the new direction.
|
|
func unstored_warning_on_lose_button_pressed() -> void:
|
|
get_node(UNSTORED_CHANGE_NODE).visible = false
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
|
|
activate_direction(direction_requested)
|
|
|
|
|
|
# If the user tries to change direction without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to cancel the request to change direction
|
|
# and let the user continue to edit the current animation.
|
|
func unstored_warning_on_cancel_button_pressed() -> void:
|
|
get_node(UNSTORED_CHANGE_NODE).visible = false
|
|
|
|
|
|
# Returns the opposite direction for mirroring animations
|
|
func find_opposite_direction(direction:String) -> String:
|
|
var opposite_dir: String = ""
|
|
|
|
match direction:
|
|
DIR_UP_RIGHT:
|
|
opposite_dir = DIR_UP_LEFT
|
|
DIR_RIGHT:
|
|
opposite_dir = DIR_LEFT
|
|
DIR_DOWN_RIGHT:
|
|
opposite_dir = DIR_DOWN_LEFT
|
|
DIR_DOWN_LEFT:
|
|
opposite_dir = DIR_DOWN_RIGHT
|
|
DIR_LEFT:
|
|
opposite_dir = DIR_RIGHT
|
|
DIR_UP_LEFT:
|
|
opposite_dir = DIR_UP_RIGHT
|
|
|
|
assert(not opposite_dir.empty(), "This should never happen : direction = %s" % direction)
|
|
return opposite_dir
|
|
|
|
|
|
# Creates an ESCPlayer node based on the settings configured in the wizard.
|
|
# It will save it as a scene (named based on the name provided in the GUI text box)
|
|
# and open it in the Godot editor - which is why this utility has to run as a plugin.
|
|
# This will also create an ESCDialogue position, and a collision box. The collision box will
|
|
# be sized based on the widest and tallest frames encountered during export (note that the
|
|
# widest/tallest frame settings do not necessarily come from the same animation frame but are
|
|
# from all the animation frames.
|
|
func export_player(scene_name) -> void:
|
|
var num_directions
|
|
var start_angle_array
|
|
var angle_size
|
|
var dirnames
|
|
|
|
var plugin_reference = get_node("..").plugin_reference
|
|
|
|
disconnect_selector_signals()
|
|
get_node(EXPORT_PROGRESS_NODE).popup()
|
|
get_node(EXPORT_PROGRESS_NODE).get_node("progress_bar").value = 0
|
|
get_node(EXPORT_PROGRESS_NODE).get_node("progress_bar").visible = true
|
|
get_node(EXPORT_PROGRESS_NODE).get_node("progress_label").visible = true
|
|
|
|
if get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed:
|
|
num_directions = 8
|
|
if get_node(DIR_COUNT_NODE).get_node("four_directions").pressed:
|
|
num_directions = 4
|
|
if get_node(DIR_COUNT_NODE).get_node("two_directions").pressed:
|
|
num_directions = 2
|
|
else:
|
|
num_directions = 1
|
|
|
|
var new_character
|
|
# NPCs can't be ESCPlayers or the player won't walk up to them when
|
|
# you interact with them
|
|
if get_node(CHAR_TYPE_NODE).get_node("npc").pressed:
|
|
new_character = ESCItem.new()
|
|
else:
|
|
new_character = ESCPlayer.new()
|
|
new_character.selectable = true
|
|
new_character.name = get_node(NAME_NODE).get_node("node_name").text
|
|
|
|
if get_node(NAME_NODE).get_node("global_id").text == null:
|
|
new_character.global_id = new_character.name
|
|
|
|
new_character.global_id = get_node(NAME_NODE).get_node("global_id").text
|
|
new_character.tooltip_name = get_node(NAME_NODE).get_node("node_name").text
|
|
|
|
new_character.default_action = "look"
|
|
|
|
var animations_resource = ESCAnimationResource.new()
|
|
|
|
# This is necessary to avoid a Godot bug when appending to one array
|
|
# appends to all arrays in the same class (possibly for resources only).
|
|
animations_resource.dir_angles = []
|
|
animations_resource.directions = []
|
|
animations_resource.idles = []
|
|
animations_resource.speaks = []
|
|
|
|
if get_node(DIR_COUNT_NODE).get_node("four_directions").pressed:
|
|
num_directions = 4
|
|
start_angle_array = [315, 45, 135, 225]
|
|
angle_size = 90
|
|
dirnames = DIR_LIST_4
|
|
elif get_node(DIR_COUNT_NODE).get_node("eight_directions").pressed:
|
|
num_directions = 8
|
|
start_angle_array = [337, 22, 67, 112, 157, 202, 247, 292]
|
|
angle_size = 45
|
|
dirnames = DIR_LIST_8
|
|
elif get_node(DIR_COUNT_NODE).get_node("two_directions").pressed:
|
|
num_directions = 2
|
|
start_angle_array = [0, 180]
|
|
angle_size = 180
|
|
dirnames = DIR_LIST_2
|
|
else:
|
|
num_directions = 1
|
|
start_angle_array = [0]
|
|
angle_size = 360
|
|
dirnames = DIR_LIST_1
|
|
|
|
for loop in range(num_directions):
|
|
# Need to create new objects here each time in order to avoid having multiple references
|
|
# to the same objects.
|
|
var dir_angle = ESCDirectionAngle.new()
|
|
var anim_details: ESCAnimationName
|
|
|
|
dir_angle.angle_start = start_angle_array[loop]
|
|
dir_angle.angle_size = angle_size
|
|
animations_resource.dir_angles.append(dir_angle)
|
|
|
|
anim_details = _create_esc_animation(TYPE_WALK, dirnames[loop])
|
|
animations_resource.directions.append(anim_details)
|
|
|
|
anim_details = _create_esc_animation(TYPE_TALK, dirnames[loop])
|
|
animations_resource.speaks.append(anim_details)
|
|
|
|
anim_details = _create_esc_animation(TYPE_IDLE, dirnames[loop])
|
|
animations_resource.idles.append(anim_details)
|
|
|
|
# var largest_sprite = export_generate_animations(new_character, num_directions)
|
|
export_largest_sprite = Vector2.ONE
|
|
# Need to yield on the child function so this function doesn't continue
|
|
# when the child yields
|
|
var export_state = export_generate_animations(new_character, num_directions)
|
|
if export_state is GDScriptFunctionState:
|
|
export_state = yield(export_state, "completed")
|
|
# Add Collision shape to the ESCPlayer
|
|
var rectangle_shape = RectangleShape2D.new()
|
|
var collision_shape = CollisionShape2D.new()
|
|
progress_bar_update("Creating collision shape")
|
|
yield(get_tree(), "idle_frame")
|
|
|
|
collision_shape.shape = rectangle_shape
|
|
collision_shape.shape.extents = export_largest_sprite / 2
|
|
collision_shape.position.y = -(export_largest_sprite.y / 2)
|
|
|
|
new_character.add_child(collision_shape)
|
|
progress_bar_update("Setting up dialog position")
|
|
yield(get_tree(), "idle_frame")
|
|
|
|
# Add Dialog Position to the ESCPlayer
|
|
var dialog_position = ESCLocation.new()
|
|
dialog_position.name = "dialog_position"
|
|
dialog_position.position.y = -(export_largest_sprite.y * 1.2)
|
|
new_character.add_child(dialog_position)
|
|
|
|
if get_node(CHAR_TYPE_NODE).get_node("npc").pressed:
|
|
# Add Interaction Position to an NPC
|
|
var interaction_position = ESCLocation.new()
|
|
interaction_position.name = "interact_position"
|
|
interaction_position.position.y = +(export_largest_sprite.y * 1.2)
|
|
new_character.add_child(interaction_position)
|
|
interaction_position.set_owner(new_character)
|
|
|
|
progress_bar_update("Configuring animations")
|
|
yield(get_tree(), "idle_frame")
|
|
# Make it so all the nodes can be seen in the scene tree
|
|
new_character.animations = animations_resource
|
|
progress_bar_update("Adding child to scene tree")
|
|
yield(get_tree(), "idle_frame")
|
|
get_tree().edited_scene_root.add_child(new_character)
|
|
new_character.set_owner(get_tree().edited_scene_root)
|
|
|
|
# Making the owner "new_character" rather than "get_tree().edited_scene_root" means that
|
|
# when saving as a packed scene, the child nodes get saved under the parent (as the parent
|
|
# must own the child nodes). If the owner is not the scene root though, the nodes will NOT
|
|
# show up in the scene tree.
|
|
collision_shape.set_owner(new_character)
|
|
dialog_position.set_owner(new_character)
|
|
|
|
# Export scene
|
|
var packed_scene = PackedScene.new()
|
|
|
|
progress_bar_update("Packing scene - this might take up to 30 seconds")
|
|
yield(get_tree(), "idle_frame")
|
|
packed_scene.pack(get_tree().edited_scene_root.get_node(new_character.name))
|
|
|
|
progress_bar_update("Resource saving - this might take up to 30 seconds")
|
|
yield(get_tree(), "idle_frame")
|
|
# Flag suggestions from https://godotengine.org/qa/50437/how-to-turn-a-node-into-a-packedscene-via-gdscript
|
|
ResourceSaver.save(scene_name, packed_scene, ResourceSaver.FLAG_CHANGE_PATH|ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS|ResourceSaver.FLAG_COMPRESS)
|
|
|
|
progress_bar_update("Releasing resources - this might take up to 30 seconds")
|
|
yield(get_tree(), "idle_frame")
|
|
new_character.queue_free()
|
|
|
|
get_tree().edited_scene_root.get_node(new_character.name).queue_free()
|
|
plugin_reference.open_scene(scene_name)
|
|
plugin_reference.make_visible(false)
|
|
get_node(EXPORT_PROGRESS_NODE).hide()
|
|
get_node(EXPORT_COMPLETE_NODE).popup()
|
|
|
|
connect_selector_signals()
|
|
|
|
|
|
# Updates the text in the export window so the user knows what's happening
|
|
func progress_bar_update(message, bar_increase_amount = 1) -> void:
|
|
get_node(PROGRESS_LABEL_NODE).text = message
|
|
get_node(EXPORT_PROGRESS_NODE).get_node("progress_bar").value += bar_increase_amount
|
|
|
|
|
|
# When exporting the ESCPlayer, this function loads the relevant spritesheets based on the
|
|
# animation metadata, and copies the frames to the relevant animations within the animatedsprite
|
|
# attached to the ESCPlayer.
|
|
#func export_generate_animations(character_node, num_directions) -> Vector2:
|
|
func export_generate_animations(character_node, num_directions) -> void:
|
|
# This variable is used instead of running this function in a thread as I hit this issue
|
|
# when I tried to thread this - https://github.com/godotengine/godot/issues/38058
|
|
var display_refresh_timer:int = OS.get_ticks_msec()
|
|
var direction_names
|
|
var loaded_spritesheet: String
|
|
var largest_frame_dimensions: Vector2 = Vector2.ZERO
|
|
var sprite_frames = SpriteFrames.new()
|
|
var default_anim_length = 0
|
|
var default_anim_speed = 1
|
|
var texture
|
|
var frame_counter: int = 0
|
|
|
|
match num_directions:
|
|
1: direction_names = DIR_LIST_1
|
|
2: direction_names = DIR_LIST_2
|
|
4: direction_names = DIR_LIST_4
|
|
8: direction_names = DIR_LIST_8
|
|
|
|
for animtype in [TYPE_WALK, TYPE_TALK, TYPE_IDLE]:
|
|
for anim_dir in direction_names:
|
|
# Using this in place of threads due to the above mentioned issue so that the
|
|
# UI continues to update while the export is running
|
|
var current_ticks = OS.get_ticks_msec()
|
|
if current_ticks - display_refresh_timer > 30:
|
|
yield(get_tree(), "idle_frame")
|
|
|
|
display_refresh_timer = current_ticks
|
|
|
|
if num_directions == 4:
|
|
progress_bar_update("Processing "+str(animtype)+" "+str(anim_dir),2)
|
|
else:
|
|
progress_bar_update("Processing "+str(animtype)+" "+str(anim_dir),1)
|
|
|
|
var anim_name = "%s_%s" % [animtype, anim_dir]
|
|
var metadata = anim_metadata[get_metadata_array_offset(anim_dir, animtype)]
|
|
|
|
if metadata[METADATA_IS_MIRROR]:
|
|
continue
|
|
|
|
var rect_location
|
|
var frame_being_copied = Image.new()
|
|
sprite_frames.add_animation(anim_name)
|
|
|
|
if metadata[METADATA_SPRITESHEET_SOURCE_FILE] != loaded_spritesheet:
|
|
load_spritesheet(metadata[METADATA_SPRITESHEET_SOURCE_FILE], true, get_metadata_array_offset(anim_dir, animtype))
|
|
|
|
loaded_spritesheet = metadata[METADATA_SPRITESHEET_SOURCE_FILE]
|
|
calc_sprite_size()
|
|
if (frame_size.x / 2) > largest_frame_dimensions.x:
|
|
largest_frame_dimensions.x = frame_size.x
|
|
|
|
if (frame_size.y / 2) > largest_frame_dimensions.y:
|
|
largest_frame_dimensions.y = frame_size.y
|
|
frame_being_copied.create(frame_size.x, frame_size.y, false, source_image.get_format())
|
|
|
|
if animtype == TYPE_IDLE and anim_dir == "down":
|
|
default_anim_length = metadata[METADATA_SPRITESHEET_LAST_FRAME] - metadata[METADATA_SPRITESHEET_FIRST_FRAME] + 1
|
|
default_anim_speed = metadata[METADATA_SPEED]
|
|
|
|
for loop in range(metadata[METADATA_SPRITESHEET_LAST_FRAME] - metadata[METADATA_SPRITESHEET_FIRST_FRAME] + 1):
|
|
texture = ImageTexture.new()
|
|
rect_location = calc_frame_coords(metadata[METADATA_SPRITESHEET_FIRST_FRAME] + loop)
|
|
frame_being_copied.blit_rect(source_image, Rect2(rect_location, Vector2(frame_size.x, frame_size.y)), Vector2(0, 0))
|
|
texture.create_from_image(frame_being_copied)
|
|
|
|
# Remove "filter" flag so it's pixel perfect
|
|
texture.set_flags(2)
|
|
sprite_frames.add_frame (anim_name, texture, frame_counter )
|
|
sprite_frames.set_animation_speed(anim_name, metadata[METADATA_SPEED])
|
|
frame_counter += 1
|
|
|
|
# Generate default animation. This is used by the object manager to set the
|
|
# state when the object is registered. If there's no current state, the
|
|
# default animation will be used.
|
|
for loop in range(default_anim_length):
|
|
texture = ImageTexture.new()
|
|
texture = sprite_frames.get_frame("idle_down", loop)
|
|
|
|
# Remove "filter" flag so it's pixel perfect
|
|
texture.set_flags(2)
|
|
sprite_frames.add_frame ("default", texture, loop )
|
|
sprite_frames.set_animation_speed("default", default_anim_speed)
|
|
|
|
var animated_sprite = AnimatedSprite.new()
|
|
|
|
progress_bar_update("Adding sprite frames to node")
|
|
animated_sprite.frames = sprite_frames
|
|
if num_directions == 2:
|
|
animated_sprite.animation = "%s_%s" % [TYPE_IDLE, DIR_RIGHT]
|
|
else:
|
|
animated_sprite.animation = "%s_%s" % [TYPE_IDLE, DIR_DOWN]
|
|
animated_sprite.position.y = -(largest_frame_dimensions.y / 2) # Place feet at (0,0)
|
|
animated_sprite.animation = "default"
|
|
animated_sprite.playing = true
|
|
character_node.add_child(animated_sprite)
|
|
# Making the owner "character_node" rather than "get_tree().edited_scene_root" means that
|
|
# when saving as a packed scene, the child nodes get saved under the parent (as the parent
|
|
# must own the child nodes). If the owner is not the scene root though, the nodes will NOT
|
|
# show up in the scene tree.
|
|
animated_sprite.set_owner(character_node)
|
|
#return largest_frame_dimensions
|
|
export_largest_sprite = largest_frame_dimensions
|
|
|
|
|
|
# Open the help window
|
|
func spritesheet_on_help_button_pressed() -> void:
|
|
$InformationWindows/help_window.popup()
|
|
$InformationWindows/help_window.show_page()
|
|
|
|
|
|
func spritesheet_on_reset_button_pressed() -> void:
|
|
$InformationWindows/ConfirmationDialog.dialog_text = "WARNING!\n\n" + \
|
|
"If you continue you will lose the current character."
|
|
$InformationWindows/ConfirmationDialog.popup()
|
|
|
|
|
|
func spritesheet_on_reset_confirmed() -> void:
|
|
spritesheet_settings = [1, 1, 0, 0]
|
|
source_image = null
|
|
get_node(SCROLL_CTRL_NODE).get_node("spritesheet_sprite").texture = null
|
|
create_empty_animations()
|
|
character_creator_reset()
|
|
|
|
|
|
func spritesheet_on_MainMenuConfirmation_confirmed() -> void:
|
|
get_node("../Menu").visible = true
|
|
get_node(".").visible = false
|
|
|
|
|
|
func spritesheet_on_main_menu_button_up() -> void:
|
|
$InformationWindows/MainMenuConfirmation.popup()
|
|
|
|
|
|
func load_settings() -> void:
|
|
var file_path = "res://"
|
|
var file = File.new()
|
|
if file.file_exists(CONFIG_FILE):
|
|
file.open(CONFIG_FILE, File.READ)
|
|
file_path = file.get_pascal_string()
|
|
file.close()
|
|
get_node(CHARACTER_PATH_NODE).text = file_path
|
|
|
|
|
|
# Creates and returns an ESCAnimationName for use by ESCAnimationResource
|
|
#
|
|
# #### Parameters
|
|
#
|
|
# - type: One of TYPE_WALK, TYPE_TALK, TYPE_IDLE (these are consts defined at the top of this script)
|
|
# - dir_name: One of DIR_LIST_8's or DIR_LIST_4's entries (these are consts defined at the top of this script)
|
|
#
|
|
# *Returns* a valid ESCAnimationName object.
|
|
func _create_esc_animation(type: String, dir_name: String) -> ESCAnimationName:
|
|
var anim_details = ESCAnimationName.new()
|
|
|
|
anim_details.animation = "%s_%s" % [type, dir_name]
|
|
|
|
if anim_metadata[get_metadata_array_offset(dir_name, type)][METADATA_IS_MIRROR]:
|
|
anim_details.mirrored = true
|
|
anim_details.animation = "%s_%s" % [type, find_opposite_direction(dir_name)]
|
|
else:
|
|
anim_details.mirrored = false
|
|
return anim_details
|
|
|
|
|
|
func _on_character_path_change_button_pressed() -> void:
|
|
get_node(CHARACTER_FILE_NODE).popup_centered()
|
|
|
|
|
|
func _on_CharacterPathFileDialog_dir_selected(dir: String) -> void:
|
|
get_node(CHARACTER_PATH_NODE).text = dir
|
|
var file = File.new()
|
|
file.open(CONFIG_FILE, File.WRITE)
|
|
file.store_pascal_string(dir)
|
|
file.close()
|
|
|
|
|
|
# Mouse clicks inside the spritesheet
|
|
func _on_control_gui_input(event: InputEvent) -> void:
|
|
var ClickedTile:Vector2
|
|
if event.is_pressed():
|
|
var NumHorizFrames = get_node(ANIM_CONTROLS_NODE).get_node("h_frames_spin_box").value
|
|
var NumVertFrames = get_node(ANIM_CONTROLS_NODE).get_node("v_frames_spin_box").value
|
|
|
|
ClickedTile.x = int(event.position.x / (frame_size.x * zoom_value))
|
|
if ClickedTile.x >= NumHorizFrames:
|
|
return
|
|
ClickedTile.y = int(event.position.y / (frame_size.y * zoom_value))
|
|
if ClickedTile.y >= NumVertFrames:
|
|
return
|
|
|
|
var AbsoluteFrame = ((ClickedTile.y * NumHorizFrames) + ClickedTile.x) + 1
|
|
|
|
if event.button_index == BUTTON_LEFT:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("start_frame").value = AbsoluteFrame
|
|
if event.button_index == BUTTON_RIGHT:
|
|
get_node(ANIM_CONTROLS_NODE).get_node("end_frame").value = AbsoluteFrame
|
|
|
|
|
|
# If the user tries to change animation type without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to save the changes, then will update
|
|
# the interface to select the new direction.
|
|
func unstored_animchange_warning_on_commit_button_pressed() -> void:
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).visible = false
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
var anim_type = return_current_animation_type()
|
|
store_animation("%s_%s" % [anim_type, direction_selected])
|
|
change_animation_type(animation_type_requested)
|
|
reset_arrow_colours()
|
|
|
|
# If the user tries to change animation type without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to lose the changes, then will update
|
|
# the interface to select the new direction.
|
|
func unstored_animchange_warning_on_lose_button_pressed() -> void:
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).visible = false
|
|
get_node(STORE_ANIM_NODE).visible = false
|
|
change_animation_type(animation_type_requested)
|
|
reset_arrow_colours()
|
|
|
|
|
|
# If the user tries to change animation type without commiting first, a window will appear to
|
|
# confirm what they want to do.
|
|
# The button associated with this function chooses to cancel the request to change direction
|
|
# and let the user continue to edit the current animation.
|
|
func unstored_animchange_warning_on_cancel_button_pressed() -> void:
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).visible = false
|
|
|
|
|
|
func change_animation_type(anim_type) -> void:
|
|
if anim_type == "walk":
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = true
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = false
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = false
|
|
animation_type_selected = "walk"
|
|
elif anim_type == "talk":
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = true
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = false
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = false
|
|
animation_type_selected = "talk"
|
|
else: # idle
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = true
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = false
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = false
|
|
animation_type_selected = "idle"
|
|
reset_arrow_colours()
|
|
|
|
|
|
# If the walk button is selected, unselect the other buttons.
|
|
# If this option was already the selected option, reselect it rather than letting the
|
|
# user disable it (which would mean that none of walk/talk/idle were selected.
|
|
func animation_on_walk_checkbox_pressed() -> void:
|
|
# Don't let the checkbox be unselected if it's currently selected
|
|
if animation_type_selected == "walk":
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = true
|
|
return
|
|
if get_node(STORE_ANIM_NODE).visible:
|
|
# Reset the buttons back to how they were
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = false
|
|
if animation_type_selected == "talk":
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = true
|
|
if animation_type_selected == "idle":
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = true
|
|
animation_type_requested="walk"
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).popup()
|
|
else:
|
|
change_animation_type("walk")
|
|
|
|
|
|
# If the talk button is selected, unselect the other buttons.
|
|
# If this option was already the selected option, reselect it rather than letting the
|
|
# user disable it (which would mean that none of walk/talk/idle were selected.
|
|
func animation_on_talk_checkbox_pressed() -> void:
|
|
# Don't let the checkbox be unselected if it's currently selected
|
|
if animation_type_selected == "talk":
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = true
|
|
return
|
|
if get_node(STORE_ANIM_NODE).visible:
|
|
# Reset the buttons back to how they were
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = false
|
|
if animation_type_selected == "idle":
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = true
|
|
if animation_type_selected == "walk":
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = true
|
|
animation_type_requested="talk"
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).popup()
|
|
else:
|
|
change_animation_type("talk")
|
|
|
|
|
|
# If the idle button is selected, unselect the other buttons.
|
|
# If this option was already the selected option, reselect it rather than letting the
|
|
# user disable it (which would mean that none of walk/talk/idle were selected.
|
|
func animation_on_idle_checkbox_pressed() -> void:
|
|
# Don't let the checkbox be unselected if it's currently selected
|
|
if animation_type_selected == "idle":
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = true
|
|
return
|
|
if get_node(STORE_ANIM_NODE).visible:
|
|
# Reset the buttons back to how they were
|
|
get_node(ANIM_TYPE_NODE).get_node("idle_checkbox").pressed = false
|
|
if animation_type_selected == "talk":
|
|
get_node(ANIM_TYPE_NODE).get_node("talk_checkbox").pressed = true
|
|
if animation_type_selected == "walk":
|
|
get_node(ANIM_TYPE_NODE).get_node("walk_checkbox").pressed = true
|
|
animation_type_requested="idle"
|
|
get_node(UNSTORED_ANIMTYPE_CHANGE_NODE).popup()
|
|
else:
|
|
change_animation_type("idle")
|
|
|
|
|
|
# When Auto storage checkbox is checked
|
|
func _on_AutoStoreCheckBox_toggled(button_pressed: bool) -> void:
|
|
autostore = button_pressed
|
|
|
|
|
|
# When player checkbox selected
|
|
func _on_player_pressed():
|
|
# If player button was already selected, don't let it be unselected.
|
|
get_node(CHAR_TYPE_NODE).get_node("player").pressed = true
|
|
get_node(CHAR_TYPE_NODE).get_node("npc").pressed = false
|
|
|
|
|
|
# When NPC checkbox selected
|
|
func _on_npc_pressed():
|
|
# If npc button was already selected, don't let it be unselected.
|
|
get_node(CHAR_TYPE_NODE).get_node("npc").pressed = true
|
|
get_node(CHAR_TYPE_NODE).get_node("player").pressed = false
|