Files
gymkhana-actions/addons/escoria-wizard/CharacterCreator.gd
2023-06-06 19:28:49 +02:00

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