mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-08 21:54:58 +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}],
|
"Common" => :common, "OppCommon" => :opp_common}],
|
||||||
"Name" => [:name, "s"],
|
"Name" => [:name, "s"],
|
||||||
# TODO: Target (Screen, User, UserAndTarget, etc. Determines which focuses
|
# 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
|
# TODO: DamageFrame (keyframe at which the battle continues, i.e. damage
|
||||||
# animations start playing).
|
# animations start playing).
|
||||||
"Flags" => [:flags, "*s"],
|
"Flags" => [:flags, "*s"],
|
||||||
@@ -56,6 +56,13 @@ module GameData
|
|||||||
"MoveZoomY" => [:zoom_y, "^uuu"],
|
"MoveZoomY" => [:zoom_y, "^uuu"],
|
||||||
"SetAngle" => [:angle, "^ui"],
|
"SetAngle" => [:angle, "^ui"],
|
||||||
"MoveAngle" => [:angle, "^uui"],
|
"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"],
|
"SetOpacity" => [:opacity, "^uu"],
|
||||||
"MoveOpacity" => [:opacity, "^uuu"]
|
"MoveOpacity" => [:opacity, "^uuu"]
|
||||||
# TODO: SetPriority should be an enum. There should also be a property
|
# 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
|
# validate_compiled_animation like the "SE" particle does with the
|
||||||
# "Play"-type commands.
|
# "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
|
@@cmd_to_pbs_name = nil # USed for writing animation PBS files
|
||||||
|
|
||||||
@@ -133,15 +157,17 @@ module GameData
|
|||||||
new_p = {}
|
new_p = {}
|
||||||
particle.each_pair do |key, val|
|
particle.each_pair do |key, val|
|
||||||
if val.is_a?(Array)
|
if val.is_a?(Array)
|
||||||
new_p[val] = []
|
new_p[key] = []
|
||||||
val.each { |cmd| new_p[val].push(cmd.clone) }
|
val.each { |cmd| new_p[key].push(cmd.clone) }
|
||||||
else
|
else
|
||||||
new_p[key] = val
|
new_p[key] = val
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
ret[:particles].push(new_p)
|
||||||
end
|
end
|
||||||
ret[:flags] = @flags.clone
|
ret[:flags] = @flags.clone
|
||||||
ret[:pbs_path] = @pbs_path
|
ret[:pbs_path] = @pbs_path
|
||||||
|
return ret
|
||||||
end
|
end
|
||||||
|
|
||||||
def move_animation?
|
def move_animation?
|
||||||
@@ -197,7 +223,6 @@ module GameData
|
|||||||
next if !val.is_a?(Array)
|
next if !val.is_a?(Array)
|
||||||
val.each do |cmd|
|
val.each do |cmd|
|
||||||
new_cmd = cmd.clone
|
new_cmd = cmd.clone
|
||||||
new_cmd.insert(1, 0) if @@cmd_to_pbs_name[key].length == 1 # "SetXYZ" only
|
|
||||||
if new_cmd[1] > 0
|
if new_cmd[1] > 0
|
||||||
ret.push([@@cmd_to_pbs_name[key][1]] + new_cmd) # ["MoveXYZ", keyframe, duration, value]
|
ret.push([@@cmd_to_pbs_name[key][1]] + new_cmd) # ["MoveXYZ", keyframe, duration, value]
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -96,34 +96,22 @@ module Compiler
|
|||||||
hash[:type] = hash[:id][0]
|
hash[:type] = hash[:id][0]
|
||||||
hash[:move] = hash[:id][1]
|
hash[:move] = hash[:id][1]
|
||||||
hash[:version] = hash[:id][2] || 0
|
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
|
# Go through each particle in turn
|
||||||
hash[:particles].each do |particle|
|
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
|
# TODO: Ensure "Play", "PlayUserCry", "PlayTargetCry" are exclusively used
|
||||||
# by the particle "SE", and that the "SE" particle can only use
|
# by the particle "SE", and that the "SE" particle can only use
|
||||||
# those commands. Raise if problems found.
|
# those commands. Raise if problems found.
|
||||||
|
|
||||||
# Ensure all particles have a default focus if not given
|
# Ensure all particles have a default focus if not given
|
||||||
if !particle[:focus]
|
if !particle[:focus]
|
||||||
if particle[:name] == "User"
|
if particle[:name] == "User"
|
||||||
@@ -134,10 +122,47 @@ module Compiler
|
|||||||
particle[:focus] = :screen
|
particle[:focus] = :screen
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Depending on hash[:target], ensure all particles have an
|
# TODO: Depending on hash[:target], ensure all particles have an
|
||||||
# appropriate focus (i.e. can't be :user_and_target if hash[:target]
|
# appropriate focus (i.e. can't be :user_and_target if hash[:target]
|
||||||
# doesn't include a target). Raise if problems found.
|
# 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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -134,7 +134,6 @@ class UIControls::BaseControl < BitmapSprite
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns whether this control has been properly decaptured.
|
|
||||||
def on_mouse_release
|
def on_mouse_release
|
||||||
@captured_area = nil
|
@captured_area = nil
|
||||||
invalidate
|
invalidate
|
||||||
@@ -167,6 +166,7 @@ class UIControls::BaseControl < BitmapSprite
|
|||||||
|
|
||||||
# Updates the logic on the control, invalidating it if necessary.
|
# Updates the logic on the control, invalidating it if necessary.
|
||||||
def update
|
def update
|
||||||
|
return if !self.visible
|
||||||
# TODO: Disabled control stuff.
|
# TODO: Disabled control stuff.
|
||||||
# return if self.disabled
|
# return if self.disabled
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ class UIControls::Checkbox < UIControls::BaseControl
|
|||||||
@value = value
|
@value = value
|
||||||
end
|
end
|
||||||
|
|
||||||
def value=(val)
|
def value=(new_value)
|
||||||
return if @value == val
|
return if @value == new_value
|
||||||
@value = val
|
@value = new_value
|
||||||
invalidate
|
invalidate
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ class UIControls::TextBox < UIControls::BaseControl
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return if !self.visible
|
||||||
super
|
super
|
||||||
# TODO: Disabled control stuff.
|
# TODO: Disabled control stuff.
|
||||||
# return if self.disabled
|
# return if self.disabled
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ class UIControls::Slider < UIControls::BaseControl
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return if !self.visible
|
||||||
super
|
super
|
||||||
# TODO: Disabled control stuff.
|
# TODO: Disabled control stuff.
|
||||||
# return if self.disabled
|
# return if self.disabled
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ class UIControls::ValueBox < UIControls::TextBox
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return if !self.visible
|
||||||
super
|
super
|
||||||
case @captured_area
|
case @captured_area
|
||||||
when :minus
|
when :minus
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ class UIControls::Button < UIControls::BaseControl
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_changed
|
||||||
|
@value = true
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_changed
|
||||||
|
@value = false
|
||||||
|
super
|
||||||
|
end
|
||||||
|
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def refresh
|
def refresh
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class UIControls::List < UIControls::BaseControl
|
|||||||
else
|
else
|
||||||
self.top_row = 0
|
self.top_row = 0
|
||||||
end
|
end
|
||||||
|
self.selected = -1 if @selected >= @values.length
|
||||||
invalidate
|
invalidate
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -202,6 +203,7 @@ class UIControls::List < UIControls::BaseControl
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return if !self.visible
|
||||||
super
|
super
|
||||||
# TODO: Disabled control stuff.
|
# TODO: Disabled control stuff.
|
||||||
# return if self.disabled
|
# return if self.disabled
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
class UIControls::ControlsContainer
|
class UIControls::ControlsContainer
|
||||||
attr_reader :x, :y
|
attr_reader :x, :y
|
||||||
attr_reader :controls
|
attr_reader :controls
|
||||||
|
attr_reader :values
|
||||||
|
attr_reader :visible
|
||||||
|
|
||||||
LINE_SPACING = 32
|
LINE_SPACING = 32
|
||||||
OFFSET_FROM_LABEL_X = 80
|
OFFSET_FROM_LABEL_X = 80
|
||||||
@@ -32,6 +34,7 @@ class UIControls::ControlsContainer
|
|||||||
@control_rects = []
|
@control_rects = []
|
||||||
@row_count = 0
|
@row_count = 0
|
||||||
@captured = nil
|
@captured = nil
|
||||||
|
@visible = true
|
||||||
end
|
end
|
||||||
|
|
||||||
def dispose
|
def dispose
|
||||||
@@ -40,6 +43,20 @@ class UIControls::ControlsContainer
|
|||||||
@viewport.dispose
|
@viewport.dispose
|
||||||
end
|
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)
|
def add_label(id, label, has_label = false)
|
||||||
@@ -110,6 +127,7 @@ class UIControls::ControlsContainer
|
|||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
return if !@visible
|
||||||
# Update controls
|
# Update controls
|
||||||
if @captured
|
if @captured
|
||||||
# TODO: Ideally all controls will be updated here, if only to redraw
|
# TODO: Ideally all controls will be updated here, if only to redraw
|
||||||
@@ -128,8 +146,8 @@ class UIControls::ControlsContainer
|
|||||||
# Check for updated controls
|
# Check for updated controls
|
||||||
@controls.each do |ctrl|
|
@controls.each do |ctrl|
|
||||||
next if !ctrl[1].changed?
|
next if !ctrl[1].changed?
|
||||||
# TODO: Get the new value from ctrl and put it in a hash for the main
|
@values ||= {}
|
||||||
# editor class to notice and use.
|
@values[ctrl[0]] = ctrl[1].value
|
||||||
ctrl[1].clear_changed
|
ctrl[1].clear_changed
|
||||||
end
|
end
|
||||||
# Redraw controls if needed
|
# Redraw controls if needed
|
||||||
|
|||||||
@@ -29,14 +29,22 @@ class AnimationEditorLoadScreen
|
|||||||
@viewport.dispose
|
@viewport.dispose
|
||||||
end
|
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
|
def generate_list
|
||||||
@animations = []
|
@animations = []
|
||||||
# TODO: Look through GameData to populate @animations; below is temporary.
|
GameData::Animation.keys.each do |id|
|
||||||
# There will be separate arrays for move animations, common animations
|
anim = GameData::Animation.get(id)
|
||||||
# and overworld animations. The move animations one will primarily be
|
if anim.version > 0
|
||||||
# a list of moves that have any animations, with the actual GameData
|
name = "#{anim.type}: #{anim.move} (#{anim.version}) - #{anim.name}"
|
||||||
# animations being in a sub-array for each move.
|
else
|
||||||
67.times { |i| @animations.push([i, "Animation #{i + 1}"]) }
|
name = "#{anim.type}: #{anim.move} - #{anim.name}"
|
||||||
|
end
|
||||||
|
@animations.push([id, name])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def draw_editor_background
|
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] - 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)
|
@screen_bitmap.bitmap.outline_rect(area[0] - 1, area[1] - 1, area[2] + 2, area[3] + 2, Color.white)
|
||||||
# Fill the area with 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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -74,7 +83,7 @@ class AnimationEditorLoadScreen
|
|||||||
# change to the text box's value. Perhaps it should only do so after
|
# 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
|
# 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?
|
# 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 animation" button
|
||||||
@load_button = UIControls::Button.new(LOAD_BUTTON_WIDTH, LOAD_BUTTON_HEIGHT, @viewport, "Load animation")
|
@load_button = UIControls::Button.new(LOAD_BUTTON_WIDTH, LOAD_BUTTON_HEIGHT, @viewport, "Load animation")
|
||||||
@load_button.x = LOAD_BUTTON_X
|
@load_button.x = LOAD_BUTTON_X
|
||||||
@@ -118,21 +127,22 @@ class AnimationEditorLoadScreen
|
|||||||
Graphics.update
|
Graphics.update
|
||||||
Input.update
|
Input.update
|
||||||
update
|
update
|
||||||
if @load_animation_id
|
|
||||||
# Open editor with animation
|
# Open editor with animation
|
||||||
# TODO: Add animation to be edited as an argument. This will be
|
if @load_animation_id
|
||||||
# GameData::Animation.get(@load_animation_id).to_hash.
|
screen = AnimationEditor.new(@load_animation_id, GameData::Animation.get(@load_animation_id).clone_as_hash)
|
||||||
echoln "Anim number #{@load_animation_id}: #{@animations[@load_animation_id][1]}"
|
|
||||||
screen = AnimationEditor.new
|
|
||||||
screen.run
|
screen.run
|
||||||
@load_animation_id = nil
|
@load_animation_id = nil
|
||||||
# TODO: Regenerate @animations in case the edited animation changed its
|
# Refresh list of animations, in case the edited one changed its type,
|
||||||
# name/move/version. Reapply @animations to @list and the sublist
|
# move, version or name
|
||||||
# (this should invalidate them).
|
generate_list
|
||||||
|
@list.values = @animations
|
||||||
repaint
|
repaint
|
||||||
elsif !inputting_text
|
next
|
||||||
break if Input.trigger?(Input::BACK)
|
|
||||||
end
|
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
|
end
|
||||||
dispose
|
dispose
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,24 +9,37 @@
|
|||||||
# TODO: Remove the particle named "Target" if the animation's focus is changed
|
# 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
|
# to one that doesn't include a target, and vice versa. Do the same for
|
||||||
# "User".
|
# "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
|
class AnimationEditor
|
||||||
WINDOW_WIDTH = AnimationEditorLoadScreen::WINDOW_WIDTH
|
WINDOW_WIDTH = AnimationEditorLoadScreen::WINDOW_WIDTH
|
||||||
WINDOW_HEIGHT = AnimationEditorLoadScreen::WINDOW_HEIGHT
|
WINDOW_HEIGHT = AnimationEditorLoadScreen::WINDOW_HEIGHT
|
||||||
|
|
||||||
CANVAS_X = 4
|
BORDER_THICKNESS = 4
|
||||||
CANVAS_Y = 32 + 4
|
CANVAS_X = BORDER_THICKNESS
|
||||||
|
CANVAS_Y = 32 + BORDER_THICKNESS
|
||||||
CANVAS_WIDTH = Settings::SCREEN_WIDTH
|
CANVAS_WIDTH = Settings::SCREEN_WIDTH
|
||||||
CANVAS_HEIGHT = Settings::SCREEN_HEIGHT
|
CANVAS_HEIGHT = Settings::SCREEN_HEIGHT
|
||||||
SIDE_PANEL_X = CANVAS_X + CANVAS_WIDTH + 4 + 4
|
SIDE_PANE_X = CANVAS_X + CANVAS_WIDTH + (BORDER_THICKNESS * 2)
|
||||||
SIDE_PANEL_Y = CANVAS_Y
|
SIDE_PANE_Y = CANVAS_Y
|
||||||
SIDE_PANEL_WIDTH = WINDOW_WIDTH - SIDE_PANEL_X - 4
|
SIDE_PANE_WIDTH = WINDOW_WIDTH - SIDE_PANE_X - BORDER_THICKNESS
|
||||||
SIDE_PANEL_HEIGHT = CANVAS_HEIGHT + (32 * 2)
|
SIDE_PANE_HEIGHT = CANVAS_HEIGHT + (32 * 2)
|
||||||
|
|
||||||
# TODO: Add a parameter which is the animation to be edited, and also a
|
def initialize(anim_id, anim)
|
||||||
# parameter for that animation's ID in GameData (just for the sake of
|
@anim_id = anim_id
|
||||||
# saving changes over the same GameData slot).
|
@anim = anim
|
||||||
def initialize
|
@keyframe = 0
|
||||||
|
@particle = -1
|
||||||
@viewport = Viewport.new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
|
@viewport = Viewport.new(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT)
|
||||||
@viewport.z = 99999
|
@viewport.z = 99999
|
||||||
@screen_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @viewport)
|
@screen_bitmap = BitmapSprite.new(WINDOW_WIDTH, WINDOW_HEIGHT, @viewport)
|
||||||
@@ -36,18 +49,75 @@ class AnimationEditor
|
|||||||
@canvas.x = CANVAS_X
|
@canvas.x = CANVAS_X
|
||||||
@canvas.y = CANVAS_Y
|
@canvas.y = CANVAS_Y
|
||||||
@canvas.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", "field_bg")
|
@canvas.bitmap = RPG::Cache.load_bitmap("Graphics/Battlebacks/", "field_bg")
|
||||||
# Side pane
|
# Side panes
|
||||||
@side_pane = ControlPane.new(SIDE_PANEL_X, SIDE_PANEL_Y, SIDE_PANEL_WIDTH, SIDE_PANEL_HEIGHT)
|
@keyframe_particle_pane = ControlPane.new(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT)
|
||||||
set_side_panel_contents
|
# 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
|
end
|
||||||
|
|
||||||
def dispose
|
def dispose
|
||||||
@screen_bitmap.dispose
|
@screen_bitmap.dispose
|
||||||
@canvas.dispose
|
@canvas.dispose
|
||||||
@side_pane.dispose
|
@keyframe_particle_pane.dispose
|
||||||
@viewport.dispose
|
@viewport.dispose
|
||||||
end
|
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
|
def draw_editor_background
|
||||||
# Fill the whole screen with black
|
# Fill the whole screen with black
|
||||||
@screen_bitmap.bitmap.fill_rect(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, Color.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 - 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 - 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)
|
@screen_bitmap.bitmap.outline_rect(CANVAS_X - 1, CANVAS_Y - 1, CANVAS_WIDTH + 2, CANVAS_HEIGHT + 2, Color.white)
|
||||||
# Outline around side panel
|
# Outline around side pane
|
||||||
@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_PANE_X - 3, SIDE_PANE_Y - 3, SIDE_PANE_WIDTH + 6, SIDE_PANE_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_PANE_X - 2, SIDE_PANE_Y - 2, SIDE_PANE_WIDTH + 4, SIDE_PANE_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)
|
@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 panel with white
|
# Fill the side pane with white
|
||||||
@screen_bitmap.bitmap.fill_rect(SIDE_PANEL_X, SIDE_PANEL_Y, SIDE_PANEL_WIDTH, SIDE_PANEL_HEIGHT, Color.white)
|
@screen_bitmap.bitmap.fill_rect(SIDE_PANE_X, SIDE_PANE_Y, SIDE_PANE_WIDTH, SIDE_PANE_HEIGHT, Color.white)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_side_panel_contents
|
def get_keyframe_particle_value(particle, frame, property)
|
||||||
@side_pane.add_labelled_text_box(:name, "Name", "Untitled")
|
if !GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES.include?(property)
|
||||||
@side_pane.add_labelled_value_box(:x, "X", -128, CANVAS_WIDTH + 128, 64)
|
raise _INTL("Couldn't get default value for property {1} for particle {2}.",
|
||||||
@side_pane.add_labelled_value_box(:y, "Y", -128, CANVAS_HEIGHT + 128, 96)
|
property, particle[:name])
|
||||||
@side_pane.add_labelled_value_box(:zoom_x, "Zoom X", 0, 1000, 100)
|
end
|
||||||
@side_pane.add_labelled_value_box(:zoom_y, "Zoom Y", 0, 1000, 100)
|
ret = [GameData::Animation::PARTICLE_KEYFRAME_DEFAULT_VALUES[property], false]
|
||||||
@side_pane.add_labelled_value_box(:angle, "Angle", -1080, 1080, 0)
|
if particle[property]
|
||||||
@side_pane.add_labelled_checkbox(:visible, "Visible", true)
|
# NOTE: The commands are already in keyframe order, so we can just run
|
||||||
@side_pane.add_labelled_slider(:opacity, "Opacity", 0, 255, 255)
|
# through them in order, applying their changes until we reach
|
||||||
@side_pane.add_labelled_checkbox(:flip, "Flip", false)
|
# frame.
|
||||||
@side_pane.add_labelled_dropdown_list(:priority, "Priority", { # TODO: Include sub-priority.
|
particle[property].each do |cmd|
|
||||||
:behind_all => "Behind all",
|
break if cmd[0] > frame # Command is in the future; no more is needed
|
||||||
:behind_user => "Behind user",
|
break if cmd[0] == frame && cmd[1] > 0 # Start of a "MoveXYZ" command; won't have changed yet
|
||||||
:above_user => "In front of user",
|
if cmd[0] + cmd[1] <= frame # Command has finished; use its end value
|
||||||
:above_all => "In front of everything"
|
ret[0] = cmd[2]
|
||||||
}, :above_user)
|
next
|
||||||
# @side_pane.add_labelled_dropdown_list(:focus, "Focus", {
|
end
|
||||||
# :user => "User",
|
# In a "MoveXYZ" command; need to interpolate
|
||||||
# :target => "Target",
|
ret[0] = lerp(ret[0], cmd[2], cmd[1], cmd[0], frame).to_i
|
||||||
# :user_and_target => "User and target",
|
ret[1] = true # Interpolating
|
||||||
# :screen => "Screen"
|
break
|
||||||
# }, :user)
|
end
|
||||||
@side_pane.add_labelled_button(:color, "Color/tone", "Edit")
|
end
|
||||||
@side_pane.add_labelled_button(:graphic, "Graphic", "Change")
|
# 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
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@canvas.update
|
update_canvas
|
||||||
@side_pane.update
|
update_keyframe_particle_pane
|
||||||
# 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).
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
def run
|
def run
|
||||||
Input.text_input = false
|
Input.text_input = false
|
||||||
loop do
|
loop do
|
||||||
|
|||||||
Reference in New Issue
Block a user