mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
Animation editor now uses proper animation data, misc other code tweaks to animation editor
This commit is contained in:
@@ -19,7 +19,7 @@ module GameData
|
||||
"Common" => :common, "OppCommon" => :opp_common}],
|
||||
"Name" => [:name, "s"],
|
||||
# TODO: Target (Screen, User, UserAndTarget, etc. Determines which focuses
|
||||
# a particle can be given).
|
||||
# a particle can be given and whether "Target" particle exists).
|
||||
# TODO: DamageFrame (keyframe at which the battle continues, i.e. damage
|
||||
# animations start playing).
|
||||
"Flags" => [:flags, "*s"],
|
||||
@@ -56,6 +56,13 @@ module GameData
|
||||
"MoveZoomY" => [:zoom_y, "^uuu"],
|
||||
"SetAngle" => [:angle, "^ui"],
|
||||
"MoveAngle" => [:angle, "^uui"],
|
||||
# TODO: Remember that :visible defaults to false at the beginning for a
|
||||
# particle, and becomes true automatically when the first command
|
||||
# happens for that particle. For "User" and "Target", it defaults to
|
||||
# true at the beginning instead. This affects the display of the
|
||||
# particle's timeline and canvas sprite in the editor, as well as
|
||||
# the animation player.
|
||||
"SetVisible" => [:visible, "^ub"],
|
||||
"SetOpacity" => [:opacity, "^uu"],
|
||||
"MoveOpacity" => [:opacity, "^uuu"]
|
||||
# TODO: SetPriority should be an enum. There should also be a property
|
||||
@@ -69,6 +76,23 @@ module GameData
|
||||
# validate_compiled_animation like the "SE" particle does with the
|
||||
# "Play"-type commands.
|
||||
}
|
||||
PARTICLE_DEFAULT_VALUES = {
|
||||
# :name => "",
|
||||
:focus => :screen
|
||||
}
|
||||
PARTICLE_KEYFRAME_DEFAULT_VALUES = {
|
||||
:graphic => nil,
|
||||
:frame => 0,
|
||||
:blending => 0,
|
||||
:flip => false,
|
||||
:x => 0,
|
||||
:y => 0,
|
||||
:zoom_x => 100,
|
||||
:zoom_y => 100,
|
||||
:angle => 0,
|
||||
:visible => false,
|
||||
:opacity => 255
|
||||
}
|
||||
|
||||
@@cmd_to_pbs_name = nil # USed for writing animation PBS files
|
||||
|
||||
@@ -133,15 +157,17 @@ module GameData
|
||||
new_p = {}
|
||||
particle.each_pair do |key, val|
|
||||
if val.is_a?(Array)
|
||||
new_p[val] = []
|
||||
val.each { |cmd| new_p[val].push(cmd.clone) }
|
||||
new_p[key] = []
|
||||
val.each { |cmd| new_p[key].push(cmd.clone) }
|
||||
else
|
||||
new_p[key] = val
|
||||
end
|
||||
end
|
||||
ret[:particles].push(new_p)
|
||||
end
|
||||
ret[:flags] = @flags.clone
|
||||
ret[:pbs_path] = @pbs_path
|
||||
return ret
|
||||
end
|
||||
|
||||
def move_animation?
|
||||
@@ -197,7 +223,6 @@ module GameData
|
||||
next if !val.is_a?(Array)
|
||||
val.each do |cmd|
|
||||
new_cmd = cmd.clone
|
||||
new_cmd.insert(1, 0) if @@cmd_to_pbs_name[key].length == 1 # "SetXYZ" only
|
||||
if new_cmd[1] > 0
|
||||
ret.push([@@cmd_to_pbs_name[key][1]] + new_cmd) # ["MoveXYZ", keyframe, duration, value]
|
||||
else
|
||||
|
||||
@@ -96,34 +96,22 @@ module Compiler
|
||||
hash[:type] = hash[:id][0]
|
||||
hash[:move] = hash[:id][1]
|
||||
hash[:version] = hash[:id][2] || 0
|
||||
# TODO: raise if "Target" particle exists but animation's target doesn't
|
||||
# involve a target battler.
|
||||
# Create "User" and "SE" particles if they don't exist
|
||||
if hash[:particles].none? { |particle| particle[:name] == "User" }
|
||||
hash[:particles].push({:name => "User"})
|
||||
end
|
||||
if hash[:particles].none? { |particle| particle[:name] == "SE" }
|
||||
hash[:particles].push({:name => "SE"})
|
||||
end
|
||||
# TODO: Create "Target" particle if it doesn't exist and animation's target
|
||||
# involves a target battler.
|
||||
# Go through each particle in turn
|
||||
hash[:particles].each do |particle|
|
||||
# Convert all "SetXYZ" particle commands to "MoveXYZ" by giving them a
|
||||
# duration of 0
|
||||
[:frame, :x, :y, :zoom_x, :zoom_y, :angle, :opacity].each do |prop|
|
||||
next if !particle[prop]
|
||||
particle[prop].each do |cmd|
|
||||
cmd.insert(1, 0) if cmd.length == 2
|
||||
end
|
||||
end
|
||||
# Sort each particle's commands by their keyframe and duration
|
||||
particle.keys.each do |key|
|
||||
next if !particle[key].is_a?(Array)
|
||||
particle[key].sort! { |a, b| a[0] == b[0] ? a[1] == b[1] ? 0 : a[1] <=> b[1] : a[0] <=> b[0] }
|
||||
# TODO: Find any overlapping particle commands and raise an error.
|
||||
end
|
||||
# Ensure valid values for "SetBlending" commands
|
||||
if particle[:blending]
|
||||
particle[:blending].each do |blend|
|
||||
next if blend[1] <= 2
|
||||
raise _INTL("Invalid blend value: {1} (must be 0, 1 or 2).\n{2}",
|
||||
blend[1], FileLineData.linereport)
|
||||
end
|
||||
end
|
||||
# TODO: Ensure "Play", "PlayUserCry", "PlayTargetCry" are exclusively used
|
||||
# by the particle "SE", and that the "SE" particle can only use
|
||||
# those commands. Raise if problems found.
|
||||
|
||||
# Ensure all particles have a default focus if not given
|
||||
if !particle[:focus]
|
||||
if particle[:name] == "User"
|
||||
@@ -134,10 +122,47 @@ module Compiler
|
||||
particle[:focus] = :screen
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Depending on hash[:target], ensure all particles have an
|
||||
# appropriate focus (i.e. can't be :user_and_target if hash[:target]
|
||||
# doesn't include a target). Raise if problems found.
|
||||
|
||||
# Convert all "SetXYZ" particle commands to "MoveXYZ" by giving them a
|
||||
# duration of 0 (even ones that can't have a "MoveXYZ" command)
|
||||
GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.keys.each do |prop|
|
||||
next if !particle[prop]
|
||||
particle[prop].each do |cmd|
|
||||
cmd.insert(1, 0) if cmd.length == 2
|
||||
end
|
||||
end
|
||||
# Sort each particle's commands by their keyframe and duration
|
||||
particle.keys.each do |key|
|
||||
next if !particle[key].is_a?(Array)
|
||||
particle[key].sort! { |a, b| a[0] == b[0] ? a[1] == b[1] ? 0 : a[1] <=> b[1] : a[0] <=> b[0] }
|
||||
# Check for any overlapping particle commands
|
||||
last_frame = -1
|
||||
last_set_frame = -1
|
||||
particle[key].each do |cmd|
|
||||
if last_frame > cmd[0]
|
||||
raise _INTL("Animation has overlapping commands for the {1} property.\n{2}",
|
||||
key.to_s.capitalize, FileLineData.linereport)
|
||||
end
|
||||
if particle[:name] != "SE" && cmd[1] == 0 && last_set_frame >= cmd[0]
|
||||
raise _INTL("Animation has multiple \"Set\" commands in the same keyframe for the {1} property.\n{2}",
|
||||
key.to_s.capitalize, FileLineData.linereport)
|
||||
end
|
||||
last_frame = cmd[0] + cmd[1]
|
||||
last_set_frame = cmd[0] if cmd[1] == 0
|
||||
end
|
||||
end
|
||||
# Ensure valid values for "SetBlending" commands
|
||||
if particle[:blending]
|
||||
particle[:blending].each do |blend|
|
||||
next if blend[2] <= 2
|
||||
raise _INTL("Invalid blend value: {1} (must be 0, 1 or 2).\n{2}",
|
||||
blend[2], FileLineData.linereport)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -134,7 +134,6 @@ class UIControls::BaseControl < BitmapSprite
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether this control has been properly decaptured.
|
||||
def on_mouse_release
|
||||
@captured_area = nil
|
||||
invalidate
|
||||
@@ -167,6 +166,7 @@ class UIControls::BaseControl < BitmapSprite
|
||||
|
||||
# Updates the logic on the control, invalidating it if necessary.
|
||||
def update
|
||||
return if !self.visible
|
||||
# TODO: Disabled control stuff.
|
||||
# return if self.disabled
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ class UIControls::Checkbox < UIControls::BaseControl
|
||||
@value = value
|
||||
end
|
||||
|
||||
def value=(val)
|
||||
return if @value == val
|
||||
@value = val
|
||||
def value=(new_value)
|
||||
return if @value == new_value
|
||||
@value = new_value
|
||||
invalidate
|
||||
end
|
||||
|
||||
|
||||
@@ -267,6 +267,7 @@ class UIControls::TextBox < UIControls::BaseControl
|
||||
end
|
||||
|
||||
def update
|
||||
return if !self.visible
|
||||
super
|
||||
# TODO: Disabled control stuff.
|
||||
# return if self.disabled
|
||||
|
||||
@@ -106,6 +106,7 @@ class UIControls::Slider < UIControls::BaseControl
|
||||
end
|
||||
|
||||
def update
|
||||
return if !self.visible
|
||||
super
|
||||
# TODO: Disabled control stuff.
|
||||
# return if self.disabled
|
||||
|
||||
@@ -122,6 +122,7 @@ class UIControls::ValueBox < UIControls::TextBox
|
||||
end
|
||||
|
||||
def update
|
||||
return if !self.visible
|
||||
super
|
||||
case @captured_area
|
||||
when :minus
|
||||
|
||||
@@ -29,6 +29,16 @@ class UIControls::Button < UIControls::BaseControl
|
||||
}
|
||||
end
|
||||
|
||||
def set_changed
|
||||
@value = true
|
||||
super
|
||||
end
|
||||
|
||||
def clear_changed
|
||||
@value = false
|
||||
super
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def refresh
|
||||
|
||||
@@ -33,6 +33,7 @@ class UIControls::List < UIControls::BaseControl
|
||||
else
|
||||
self.top_row = 0
|
||||
end
|
||||
self.selected = -1 if @selected >= @values.length
|
||||
invalidate
|
||||
end
|
||||
|
||||
@@ -202,6 +203,7 @@ class UIControls::List < UIControls::BaseControl
|
||||
end
|
||||
|
||||
def update
|
||||
return if !self.visible
|
||||
super
|
||||
# TODO: Disabled control stuff.
|
||||
# return if self.disabled
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
class UIControls::ControlsContainer
|
||||
attr_reader :x, :y
|
||||
attr_reader :controls
|
||||
attr_reader :values
|
||||
attr_reader :visible
|
||||
|
||||
LINE_SPACING = 32
|
||||
OFFSET_FROM_LABEL_X = 80
|
||||
@@ -32,6 +34,7 @@ class UIControls::ControlsContainer
|
||||
@control_rects = []
|
||||
@row_count = 0
|
||||
@captured = nil
|
||||
@visible = true
|
||||
end
|
||||
|
||||
def dispose
|
||||
@@ -40,6 +43,20 @@ class UIControls::ControlsContainer
|
||||
@viewport.dispose
|
||||
end
|
||||
|
||||
def changed?
|
||||
return !@values.nil?
|
||||
end
|
||||
|
||||
def clear_changed
|
||||
@values = nil
|
||||
end
|
||||
|
||||
def visible=(value)
|
||||
@visible = value
|
||||
@controls.each { |c| c[1].visible = value }
|
||||
repaint if @visible
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def add_label(id, label, has_label = false)
|
||||
@@ -110,6 +127,7 @@ class UIControls::ControlsContainer
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def update
|
||||
return if !@visible
|
||||
# Update controls
|
||||
if @captured
|
||||
# TODO: Ideally all controls will be updated here, if only to redraw
|
||||
@@ -128,8 +146,8 @@ class UIControls::ControlsContainer
|
||||
# Check for updated controls
|
||||
@controls.each do |ctrl|
|
||||
next if !ctrl[1].changed?
|
||||
# TODO: Get the new value from ctrl and put it in a hash for the main
|
||||
# editor class to notice and use.
|
||||
@values ||= {}
|
||||
@values[ctrl[0]] = ctrl[1].value
|
||||
ctrl[1].clear_changed
|
||||
end
|
||||
# Redraw controls if needed
|
||||
|
||||
@@ -29,14 +29,22 @@ class AnimationEditorLoadScreen
|
||||
@viewport.dispose
|
||||
end
|
||||
|
||||
# TODO: Make separate arrays for move and common animations. Group animations
|
||||
# for the same move/common animation together somehow to be listed in
|
||||
# the main list - individual animations are shown in the secondary list.
|
||||
# The display names will need improving accordingly. Usage of
|
||||
# @animations in this class will need redoing.
|
||||
def generate_list
|
||||
@animations = []
|
||||
# TODO: Look through GameData to populate @animations; below is temporary.
|
||||
# There will be separate arrays for move animations, common animations
|
||||
# and overworld animations. The move animations one will primarily be
|
||||
# a list of moves that have any animations, with the actual GameData
|
||||
# animations being in a sub-array for each move.
|
||||
67.times { |i| @animations.push([i, "Animation #{i + 1}"]) }
|
||||
GameData::Animation.keys.each do |id|
|
||||
anim = GameData::Animation.get(id)
|
||||
if anim.version > 0
|
||||
name = "#{anim.type}: #{anim.move} (#{anim.version}) - #{anim.name}"
|
||||
else
|
||||
name = "#{anim.type}: #{anim.move} - #{anim.name}"
|
||||
end
|
||||
@animations.push([id, name])
|
||||
end
|
||||
end
|
||||
|
||||
def draw_editor_background
|
||||
@@ -53,7 +61,8 @@ class AnimationEditorLoadScreen
|
||||
@screen_bitmap.bitmap.outline_rect(area[0] - 2, area[1] - 2, area[2] + 4, area[3] + 4, Color.black)
|
||||
@screen_bitmap.bitmap.outline_rect(area[0] - 1, area[1] - 1, area[2] + 2, area[3] + 2, Color.white)
|
||||
# Fill the area with white
|
||||
# @screen_bitmap.bitmap.fill_rect(area[0], area[1], area[2], area[3], Color.white)
|
||||
# TODO: This line was quoted out previously, and I'm not sure why.
|
||||
@screen_bitmap.bitmap.fill_rect(area[0], area[1], area[2], area[3], Color.white)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -74,7 +83,7 @@ class AnimationEditorLoadScreen
|
||||
# change to the text box's value. Perhaps it should only do so after
|
||||
# 0.5 seconds of non-typing. What exactly should the filter be applied
|
||||
# to? Animation's name, move's name (if there is one), what else?
|
||||
# TODO: Filter dropdown list to pick a type? Other filter options?
|
||||
# TODO: Filter dropdown list to pick a move type? Other filter options?
|
||||
# "Load animation" button
|
||||
@load_button = UIControls::Button.new(LOAD_BUTTON_WIDTH, LOAD_BUTTON_HEIGHT, @viewport, "Load animation")
|
||||
@load_button.x = LOAD_BUTTON_X
|
||||
@@ -118,21 +127,22 @@ class AnimationEditorLoadScreen
|
||||
Graphics.update
|
||||
Input.update
|
||||
update
|
||||
# Open editor with animation
|
||||
if @load_animation_id
|
||||
# Open editor with animation
|
||||
# TODO: Add animation to be edited as an argument. This will be
|
||||
# GameData::Animation.get(@load_animation_id).to_hash.
|
||||
echoln "Anim number #{@load_animation_id}: #{@animations[@load_animation_id][1]}"
|
||||
screen = AnimationEditor.new
|
||||
screen = AnimationEditor.new(@load_animation_id, GameData::Animation.get(@load_animation_id).clone_as_hash)
|
||||
screen.run
|
||||
@load_animation_id = nil
|
||||
# TODO: Regenerate @animations in case the edited animation changed its
|
||||
# name/move/version. Reapply @animations to @list and the sublist
|
||||
# (this should invalidate them).
|
||||
# Refresh list of animations, in case the edited one changed its type,
|
||||
# move, version or name
|
||||
generate_list
|
||||
@list.values = @animations
|
||||
repaint
|
||||
elsif !inputting_text
|
||||
break if Input.trigger?(Input::BACK)
|
||||
next
|
||||
end
|
||||
# Typing text into a text box; don't want key presses to trigger anything
|
||||
next if inputting_text
|
||||
# Inputs
|
||||
break if Input.trigger?(Input::BACK)
|
||||
end
|
||||
dispose
|
||||
end
|
||||
|
||||
@@ -9,24 +9,37 @@
|
||||
# TODO: Remove the particle named "Target" if the animation's focus is changed
|
||||
# to one that doesn't include a target, and vice versa. Do the same for
|
||||
# "User".
|
||||
# TODO: Things that need pop-up windows (draws a semi-transparent grey over the
|
||||
# whole screen behind the window):
|
||||
# - graphic picker
|
||||
# - SE file picker
|
||||
# - animation properties (Move/OppMove/Common/OppCommon, move, version,
|
||||
# extra name, target, filepath, flags, etc.)
|
||||
# - editor settings (theme, canvas BG graphics, user/target graphics,
|
||||
# display of canvas particle boxes, etc.)
|
||||
# TODO: While playing the animation, draw a semi-transparent grey over the
|
||||
# screen except for the canvas and playback controls. Can't edit anything
|
||||
# while it's playing.
|
||||
#===============================================================================
|
||||
class AnimationEditor
|
||||
WINDOW_WIDTH = AnimationEditorLoadScreen::WINDOW_WIDTH
|
||||
WINDOW_HEIGHT = AnimationEditorLoadScreen::WINDOW_HEIGHT
|
||||
|
||||
CANVAS_X = 4
|
||||
CANVAS_Y = 32 + 4
|
||||
CANVAS_WIDTH = Settings::SCREEN_WIDTH
|
||||
CANVAS_HEIGHT = Settings::SCREEN_HEIGHT
|
||||
SIDE_PANEL_X = CANVAS_X + CANVAS_WIDTH + 4 + 4
|
||||
SIDE_PANEL_Y = CANVAS_Y
|
||||
SIDE_PANEL_WIDTH = WINDOW_WIDTH - SIDE_PANEL_X - 4
|
||||
SIDE_PANEL_HEIGHT = CANVAS_HEIGHT + (32 * 2)
|
||||
BORDER_THICKNESS = 4
|
||||
CANVAS_X = BORDER_THICKNESS
|
||||
CANVAS_Y = 32 + BORDER_THICKNESS
|
||||
CANVAS_WIDTH = Settings::SCREEN_WIDTH
|
||||
CANVAS_HEIGHT = Settings::SCREEN_HEIGHT
|
||||
SIDE_PANE_X = CANVAS_X + CANVAS_WIDTH + (BORDER_THICKNESS * 2)
|
||||
SIDE_PANE_Y = CANVAS_Y
|
||||
SIDE_PANE_WIDTH = WINDOW_WIDTH - SIDE_PANE_X - BORDER_THICKNESS
|
||||
SIDE_PANE_HEIGHT = CANVAS_HEIGHT + (32 * 2)
|
||||
|
||||
# TODO: Add a parameter which is the animation to be edited, and also a
|
||||
# parameter for that animation's ID in GameData (just for the sake of
|
||||
# saving changes over the same GameData slot).
|
||||
def initialize
|
||||
def initialize(anim_id, anim)
|
||||
@anim_id = anim_id
|
||||
@anim = anim
|
||||
@keyframe = 0
|
||||
@particle = -1
|
||||
@viewport = Viewport.new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
|
||||
@viewport.z = 99999
|
||||
@screen_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @viewport)
|
||||
@@ -36,18 +49,75 @@ class AnimationEditor
|
||||
@canvas.x = CANVAS_X
|
||||
@canvas.y = CANVAS_Y
|
||||
@canvas.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", "field_bg")
|
||||
# Side pane
|
||||
@side_pane = ControlPane.new(SIDE_PANEL_X, SIDE_PANEL_Y, SIDE_PANEL_WIDTH, SIDE_PANEL_HEIGHT)
|
||||
set_side_panel_contents
|
||||
# Side panes
|
||||
@keyframe_particle_pane = ControlPane.new(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT)
|
||||
# TODO: Make more side panes for:
|
||||
# - colour/tone editor (accessed from keyframe_particle_pane via a
|
||||
# button; has Apply/Cancel buttons to only apply all its values at
|
||||
# the end of editing them, although canvas will be updated in real
|
||||
# time to show the changes)
|
||||
# - particle properties (that don't change during the animation; name,
|
||||
# focus...)
|
||||
# - SE particle properties (depends on keyframe)
|
||||
# - effects particle properties (depends on keyframe; for screen
|
||||
# shake, etc.)
|
||||
# - keyframe properties (shift all later particle commands forward/
|
||||
# backward).
|
||||
set_side_panes_contents
|
||||
refresh
|
||||
end
|
||||
|
||||
def dispose
|
||||
@screen_bitmap.dispose
|
||||
@canvas.dispose
|
||||
@side_pane.dispose
|
||||
@keyframe_particle_pane.dispose
|
||||
@viewport.dispose
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def set_keyframe_particle_pane_contents
|
||||
# TODO: Move these properties to a new side pane for particle properties
|
||||
# (ones that don't change during the animation).
|
||||
@keyframe_particle_pane.add_labelled_text_box(:name, "Name", "Untitled")
|
||||
# @keyframe_particle_pane.add_labelled_dropdown_list(:focus, "Focus", {
|
||||
# :user => "User",
|
||||
# :target => "Target",
|
||||
# :user_and_target => "User and target",
|
||||
# :screen => "Screen"
|
||||
# }, :user)
|
||||
|
||||
# TODO: Make sure the IDs for these controls all match up to particle
|
||||
# properties that can change during the animation.
|
||||
@keyframe_particle_pane.add_labelled_value_box(:x, "X", -128, CANVAS_WIDTH + 128, 64)
|
||||
@keyframe_particle_pane.add_labelled_value_box(:y, "Y", -128, CANVAS_HEIGHT + 128, 96)
|
||||
@keyframe_particle_pane.add_labelled_value_box(:zoom_x, "Zoom X", 0, 1000, 100)
|
||||
@keyframe_particle_pane.add_labelled_value_box(:zoom_y, "Zoom Y", 0, 1000, 100)
|
||||
@keyframe_particle_pane.add_labelled_checkbox(:visible, "Visible", true)
|
||||
@keyframe_particle_pane.add_labelled_slider(:opacity, "Opacity", 0, 255, 255)
|
||||
@keyframe_particle_pane.add_labelled_value_box(:angle, "Angle", -1080, 1080, 0)
|
||||
@keyframe_particle_pane.add_labelled_checkbox(:flip, "Flip", false)
|
||||
@keyframe_particle_pane.add_labelled_dropdown_list(:priority, "Priority", { # TODO: Include sub-priority.
|
||||
:behind_all => "Behind all",
|
||||
:behind_user => "Behind user",
|
||||
:above_user => "In front of user",
|
||||
:above_all => "In front of everything"
|
||||
}, :above_user)
|
||||
@keyframe_particle_pane.add_labelled_button(:color, "Color", "Edit")
|
||||
@keyframe_particle_pane.add_labelled_button(:tone, "Tone", "Edit")
|
||||
@keyframe_particle_pane.add_labelled_button(:graphic, "Graphic", "Change")
|
||||
# :frame (related to graphic)
|
||||
# :blending
|
||||
# TODO: Add buttons that shift all commands from the current keyframe and
|
||||
# later forwards/backwards in time?
|
||||
end
|
||||
|
||||
def set_side_panes_contents
|
||||
set_keyframe_particle_pane_contents
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def draw_editor_background
|
||||
# Fill the whole screen with black
|
||||
@screen_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.black)
|
||||
@@ -55,50 +125,121 @@ class AnimationEditor
|
||||
@screen_bitmap.bitmap.outline_rect(CANVAS_X - 3, CANVAS_Y - 3, CANVAS_WIDTH + 6, CANVAS_HEIGHT + 6, Color.white)
|
||||
@screen_bitmap.bitmap.outline_rect(CANVAS_X - 2, CANVAS_Y - 2, CANVAS_WIDTH + 4, CANVAS_HEIGHT + 4, Color.black)
|
||||
@screen_bitmap.bitmap.outline_rect(CANVAS_X - 1, CANVAS_Y - 1, CANVAS_WIDTH + 2, CANVAS_HEIGHT + 2, Color.white)
|
||||
# Outline around side panel
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANEL_X - 3, SIDE_PANEL_Y - 3, SIDE_PANEL_WIDTH + 6, SIDE_PANEL_HEIGHT + 6, Color.white)
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANEL_X - 2, SIDE_PANEL_Y - 2, SIDE_PANEL_WIDTH + 4, SIDE_PANEL_HEIGHT + 4, Color.black)
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANEL_X - 1, SIDE_PANEL_Y - 1, SIDE_PANEL_WIDTH + 2, SIDE_PANEL_HEIGHT + 2, Color.white)
|
||||
# Fill the side panel with white
|
||||
@screen_bitmap.bitmap.fill_rect(SIDE_PANEL_X, SIDE_PANEL_Y, SIDE_PANEL_WIDTH, SIDE_PANEL_HEIGHT, Color.white)
|
||||
# Outline around side pane
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANE_X - 3, SIDE_PANE_Y - 3, SIDE_PANE_WIDTH + 6, SIDE_PANE_HEIGHT + 6, Color.white)
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANE_X - 2, SIDE_PANE_Y - 2, SIDE_PANE_WIDTH + 4, SIDE_PANE_HEIGHT + 4, Color.black)
|
||||
@screen_bitmap.bitmap.outline_rect(SIDE_PANE_X - 1, SIDE_PANE_Y - 1, SIDE_PANE_WIDTH + 2, SIDE_PANE_HEIGHT + 2, Color.white)
|
||||
# Fill the side pane with white
|
||||
@screen_bitmap.bitmap.fill_rect(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT, Color.white)
|
||||
end
|
||||
|
||||
def set_side_panel_contents
|
||||
@side_pane.add_labelled_text_box(:name, "Name", "Untitled")
|
||||
@side_pane.add_labelled_value_box(:x, "X", -128, CANVAS_WIDTH + 128, 64)
|
||||
@side_pane.add_labelled_value_box(:y, "Y", -128, CANVAS_HEIGHT + 128, 96)
|
||||
@side_pane.add_labelled_value_box(:zoom_x, "Zoom X", 0, 1000, 100)
|
||||
@side_pane.add_labelled_value_box(:zoom_y, "Zoom Y", 0, 1000, 100)
|
||||
@side_pane.add_labelled_value_box(:angle, "Angle", -1080, 1080, 0)
|
||||
@side_pane.add_labelled_checkbox(:visible, "Visible", true)
|
||||
@side_pane.add_labelled_slider(:opacity, "Opacity", 0, 255, 255)
|
||||
@side_pane.add_labelled_checkbox(:flip, "Flip", false)
|
||||
@side_pane.add_labelled_dropdown_list(:priority, "Priority", { # TODO: Include sub-priority.
|
||||
:behind_all => "Behind all",
|
||||
:behind_user => "Behind user",
|
||||
:above_user => "In front of user",
|
||||
:above_all => "In front of everything"
|
||||
}, :above_user)
|
||||
# @side_pane.add_labelled_dropdown_list(:focus, "Focus", {
|
||||
# :user => "User",
|
||||
# :target => "Target",
|
||||
# :user_and_target => "User and target",
|
||||
# :screen => "Screen"
|
||||
# }, :user)
|
||||
@side_pane.add_labelled_button(:color, "Color/tone", "Edit")
|
||||
@side_pane.add_labelled_button(:graphic, "Graphic", "Change")
|
||||
def get_keyframe_particle_value(particle, frame, property)
|
||||
if !GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.include?(property)
|
||||
raise _INTL("Couldn't get default value for property {1} for particle {2}.",
|
||||
property, particle[:name])
|
||||
end
|
||||
ret = [GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES[property], false]
|
||||
if particle[property]
|
||||
# NOTE: The commands are already in keyframe order, so we can just run
|
||||
# through them in order, applying their changes until we reach
|
||||
# frame.
|
||||
particle[property].each do |cmd|
|
||||
break if cmd[0] > frame # Command is in the future; no more is needed
|
||||
break if cmd[0] == frame && cmd[1] > 0 # Start of a "MoveXYZ" command; won't have changed yet
|
||||
if cmd[0] + cmd[1] <= frame # Command has finished; use its end value
|
||||
ret[0] = cmd[2]
|
||||
next
|
||||
end
|
||||
# In a "MoveXYZ" command; need to interpolate
|
||||
ret[0] = lerp(ret[0], cmd[2], cmd[1], cmd[0], frame).to_i
|
||||
ret[1] = true # Interpolating
|
||||
break
|
||||
end
|
||||
end
|
||||
# NOTE: Particles are assumed to be not visible at the start of the
|
||||
# animation, and automatically become visible when the particle has
|
||||
# its first command. This does not apply to the "User" and "Target"
|
||||
# particles, which start the animation visible.
|
||||
if property == :visible
|
||||
first_cmd = (["User", "Target"].include?(particle[:name])) ? 0 : -1
|
||||
first_visible_cmd = -1
|
||||
if first_cmd < 0
|
||||
particle.each_pair do |prop, value|
|
||||
next if !value.is_a?(Array) || value.length == 0
|
||||
first_cmd = value[0][0] if first_cmd < 0 || first_cmd > value[0][0]
|
||||
first_visible_cmd = value[0][0] if prop == :visible && (first_visible_cmd < 0 || first_visible_cmd > value[0][0])
|
||||
end
|
||||
end
|
||||
ret[0] = true if first_cmd >= 0 && first_cmd <= frame &&
|
||||
(first_visible_cmd < 0 || frame < first_visible_cmd)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def get_all_keyframe_particle_values(particle, frame)
|
||||
ret = {}
|
||||
GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.each_pair do |prop, default|
|
||||
ret[prop] = get_keyframe_particle_value(particle, frame, prop)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
def refresh_keyframe_particle_pane
|
||||
if @particle < 0 || !@anim[:particles][@particle]
|
||||
@keyframe_particle_pane.visible = false
|
||||
else
|
||||
@keyframe_particle_pane.visible = true
|
||||
new_vals = get_all_keyframe_particle_values(@anim[:particles][@particle], @keyframe)
|
||||
# TODO: Need to do something special for :color, :tone and :graphic/:frame
|
||||
# which all have button controls.
|
||||
@keyframe_particle_pane.controls.each do |ctrl|
|
||||
next if !new_vals.include?(ctrl[0])
|
||||
ctrl.value = new_vals[ctrl[0]][0]
|
||||
# TODO: new_vals[ctrl[0]][1] is whether the value is being interpolated,
|
||||
# which should be indicated somehow in ctrl.
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def refresh
|
||||
# Set all side pane controls to values from animation
|
||||
refresh_keyframe_particle_pane
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def update_canvas
|
||||
@canvas.update
|
||||
# TODO: Detect and apply changes made in canvas, e.g. moving particle,
|
||||
# double-clicking to add particle, deleting particle.
|
||||
end
|
||||
|
||||
def update_keyframe_particle_pane
|
||||
@keyframe_particle_pane.update
|
||||
if @keyframe_particle_pane.changed?
|
||||
# TODO: Make undo/redo snapshot.
|
||||
values = @keyframe_particle_pane.values
|
||||
# TODO: Apply vals to the animation data, unless the changed control is a
|
||||
# button (its value will be true), in which case run some special
|
||||
# code. Maybe this special code should be passed to/run in the
|
||||
# control as a proc instead, and the button control can be given a
|
||||
# value like any other control? Probably not.
|
||||
echoln values
|
||||
if values[:color]
|
||||
elsif values[:tone]
|
||||
elsif values[:graphic]
|
||||
end
|
||||
@keyframe_particle_pane.clear_changed
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@canvas.update
|
||||
@side_pane.update
|
||||
# TODO: Check @side_pane for whether it's changed. Note that it includes
|
||||
# buttons which won't themselves have a value but will flag themselves
|
||||
# as changed when clicked; code here should determine what happens if
|
||||
# a button is pressed (unless I put said code in a proc passed to the
|
||||
# button control; said code will be lengthy).
|
||||
update_canvas
|
||||
update_keyframe_particle_pane
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
def run
|
||||
Input.text_input = false
|
||||
loop do
|
||||
|
||||
Reference in New Issue
Block a user