mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
Anim Editor: added more animation interpolation types, greyed out timeline that isn't part of the animation
This commit is contained in:
@@ -22,16 +22,14 @@ class Bitmap
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Add more curve types once it's decided which ones they are. See
|
||||
# INTERPOLATION_TYPES.
|
||||
def draw_interpolation_line(x, y, width, height, gradient, type, color)
|
||||
start_x = x
|
||||
end_x = x + width - 1
|
||||
start_y = (gradient) ? y + height - 1 : y
|
||||
end_y = (gradient) ? y : y + height - 1
|
||||
case type
|
||||
when :linear
|
||||
# NOTE: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
|
||||
start_x = x
|
||||
end_x = x + width - 1
|
||||
start_y = (gradient) ? y + height - 1 : y
|
||||
end_y = (gradient) ? y : y + height - 1
|
||||
dx = end_x - start_x
|
||||
dy = -((end_y - start_y).abs)
|
||||
error = dx + dy
|
||||
@@ -52,6 +50,40 @@ class Bitmap
|
||||
draw_y += (gradient) ? -1 : 1
|
||||
end
|
||||
end
|
||||
when :ease_in, :ease_out, :ease_both # Quadratic
|
||||
start_y = y + height - 1
|
||||
end_y = y
|
||||
points = []
|
||||
(width + 1).times do |frame|
|
||||
x = frame / width.to_f
|
||||
case type
|
||||
when :ease_in
|
||||
points[frame] = (end_y - start_y) * x * x
|
||||
when :ease_out
|
||||
points[frame] = (end_y - start_y) * (1 - ((1 - x) * (1 - x)))
|
||||
when :ease_both
|
||||
if x < 0.5
|
||||
points[frame] = (end_y - start_y) * x * x * 2
|
||||
else
|
||||
points[frame] = (end_y - start_y) * (1 - (((-2 * x) + 2) * ((-2 * x) + 2) / 2))
|
||||
end
|
||||
end
|
||||
points[frame] = points[frame].round
|
||||
end
|
||||
width.times do |frame|
|
||||
line_y = points[frame]
|
||||
if frame == 0
|
||||
line_height = 1
|
||||
else
|
||||
line_height = [(points[frame] - points[frame - 1]).abs, 1].max
|
||||
end
|
||||
if !gradient # Going down
|
||||
line_y = -(height - 1) - line_y - line_height + 1
|
||||
end
|
||||
fill_rect(start_x + frame, start_y + line_y, 1, line_height, color)
|
||||
end
|
||||
else
|
||||
raise _INTL("Unknown interpolation type {1}.", type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -72,8 +72,6 @@ module GameData
|
||||
# NOTE: "Name" isn't a property here, because the particle's name comes
|
||||
# from the "Particle" property above.
|
||||
"Graphic" => [:graphic, "s"],
|
||||
# TODO: If more focus types are added, add ones that involve a target to
|
||||
# the Compiler's check relating to "NoTarget".
|
||||
"Focus" => [:focus, "e", FOCUS_TYPES],
|
||||
# TODO: FlipIfFoe, RotateIfFoe kinds of thing.
|
||||
|
||||
|
||||
@@ -215,7 +215,10 @@ class AnimationEditor
|
||||
commands_pane.add_labelled_number_slider(:z, _INTL("Priority"), -50, 50, 0)
|
||||
# TODO: If the graphic is user's sprite/target's sprite, make :frame instead
|
||||
# a choice of front/back/same as the main sprite/opposite of the main
|
||||
# sprite. Will need two controls in the same space.
|
||||
# sprite. Will need two controls in the same space, which is doable.
|
||||
# Will also need to change the graphic chooser to only have "user"/
|
||||
# "target" options rather than all the variants that this control
|
||||
# would manage.
|
||||
commands_pane.add_labelled_number_text_box(:frame, _INTL("Frame"), 0, 99, 0)
|
||||
commands_pane.add_labelled_checkbox(:visible, _INTL("Visible"), true)
|
||||
commands_pane.add_labelled_number_slider(:opacity, _INTL("Opacity"), 0, 255, 255)
|
||||
@@ -317,6 +320,8 @@ class AnimationEditor
|
||||
def set_animation_properties_contents
|
||||
anim_properties = @components[:animation_properties]
|
||||
anim_properties.add_header_label(:header, _INTL("Animation properties"))
|
||||
# Create "usable in battle" control
|
||||
anim_properties.add_labelled_checkbox(:usable, _INTL("Can be used in battle?"), true)
|
||||
# Create animation type control
|
||||
anim_properties.add_labelled_dropdown_list(:type, _INTL("Animation type"), {
|
||||
:move => _INTL("Move"),
|
||||
@@ -341,8 +346,6 @@ class AnimationEditor
|
||||
anim_properties.add_labelled_checkbox(:has_target, _INTL("Involves a target?"), true)
|
||||
# Create flags control
|
||||
# TODO: List, TextBox and some Buttons to add/delete.
|
||||
# Create "usable in battle" control
|
||||
anim_properties.add_labelled_checkbox(:usable, _INTL("Can be used in battle?"), true)
|
||||
anim_properties.add_button(:close, _INTL("Close"))
|
||||
anim_properties.visible = false
|
||||
end
|
||||
@@ -420,6 +423,8 @@ class AnimationEditor
|
||||
# TODO: Ideally be able to independently choose base graphics, which will
|
||||
# be a separate setting here.
|
||||
:canvas_bg => "indoor1",
|
||||
# NOTE: These sprite names are also used in Pokemon.play_cry and so should
|
||||
# be a species ID (being a string is fine).
|
||||
:user_sprite_name => "ARCANINE",
|
||||
:target_sprite_name => "ABOMASNOW"
|
||||
}
|
||||
|
||||
@@ -187,15 +187,28 @@ class AnimationEditor
|
||||
preview_bitmap = nil
|
||||
set_preview_graphic = lambda do |sprite, filename|
|
||||
preview_bitmap&.dispose
|
||||
# TODO: When the canvas works, use the proper user's/target's sprite here.
|
||||
case filename
|
||||
when "USER", "USER_BACK", "TARGET_BACK", "TARGET_OPP"
|
||||
preview_bitmap = AnimatedBitmap.new("Graphics/Pokemon/Back/" + "000")
|
||||
when "TARGET", "TARGET_FRONT", "USER_FRONT", "USER_OPP"
|
||||
preview_bitmap = AnimatedBitmap.new("Graphics/Pokemon/Front/" + "000")
|
||||
else
|
||||
preview_bitmap = AnimatedBitmap.new(sprite_folder + filename)
|
||||
folder = sprite_folder
|
||||
fname = filename
|
||||
if ["USER", "USER_BACK", "USER_FRONT", "USER_OPP",
|
||||
"TARGET", "TARGET_FRONT", "TARGET_BACK", "TARGET_OPP"].include?(filename)
|
||||
chunks = filename.split("_")
|
||||
fname = (chunks[0] == "USER") ? @settings[:user_sprite_name].to_s : @settings[:target_sprite_name].to_s
|
||||
case chunks[1] || ""
|
||||
when "", "OPP"
|
||||
# TODO: "TARGET" and "TARGET_OPP" will not be accurate in cases where
|
||||
# the target is on the same side as the user.
|
||||
if (chunks[0] == "USER") ^ (chunks[1] == "OPP") # xor
|
||||
folder = (@settings[:user_opposes]) ? "Graphics/Pokemon/Front/" : "Graphics/Pokemon/Back/"
|
||||
else
|
||||
folder = (@settings[:user_opposes]) ? "Graphics/Pokemon/Back/" : "Graphics/Pokemon/Front/"
|
||||
end
|
||||
when "FRONT"
|
||||
folder = "Graphics/Pokemon/Front/"
|
||||
when "BACK"
|
||||
folder = "Graphics/Pokemon/Back/"
|
||||
end
|
||||
end
|
||||
preview_bitmap = AnimatedBitmap.new(folder + fname)
|
||||
bg_bitmap.bitmap.fill_rect(BORDER_THICKNESS + list.x + list.width + 10, BORDER_THICKNESS + list.y,
|
||||
GRAPHIC_CHOOSER_PREVIEW_SIZE, GRAPHIC_CHOOSER_PREVIEW_SIZE,
|
||||
Color.white)
|
||||
@@ -294,10 +307,14 @@ class AnimationEditor
|
||||
when :play
|
||||
vol = audio_chooser.get_control(:volume).value
|
||||
ptch = audio_chooser.get_control(:pitch).value
|
||||
# TODO: Play appropriate things if a cry is selected. See which
|
||||
# battlers are defined in the editor's settings, and use their
|
||||
# cries.
|
||||
pbSEPlay(RPG::AudioFile.new("Anim/" + list.value, vol, ptch))
|
||||
case list.value
|
||||
when "USER"
|
||||
Pokemon.play_cry(@settings[:user_sprite_name])
|
||||
when "TARGET"
|
||||
Pokemon.play_cry(@settings[:target_sprite_name])
|
||||
else
|
||||
pbSEPlay(RPG::AudioFile.new("Anim/" + list.value, vol, ptch))
|
||||
end
|
||||
when :stop
|
||||
pbSEStop
|
||||
end
|
||||
|
||||
@@ -37,8 +37,24 @@ module AnimationEditor::ParticleDataHelper
|
||||
case (cmd[3] || :linear)
|
||||
when :linear
|
||||
ret[0] = lerp(ret[0], cmd[2], cmd[1], cmd[0], frame).to_i
|
||||
when :ease_in # Quadratic
|
||||
x = (frame - cmd[0]) / cmd[1].to_f
|
||||
ret[0] += (cmd[2] - ret[0]) * x * x
|
||||
ret[0] = ret[0].round
|
||||
when :ease_out # Quadratic
|
||||
x = (frame - cmd[0]) / cmd[1].to_f
|
||||
ret[0] += (cmd[2] - ret[0]) * (1 - ((1 - x) * (1 - x)))
|
||||
ret[0] = ret[0].round
|
||||
when :ease_both # Quadratic
|
||||
x = (frame - cmd[0]) / cmd[1].to_f
|
||||
if x < 0.5
|
||||
ret[0] += (cmd[2] - ret[0]) * x * x * 2
|
||||
else
|
||||
ret[0] += (cmd[2] - ret[0]) * (1 - (((-2 * x) + 2) * ((-2 * x) + 2) / 2))
|
||||
end
|
||||
ret[0] = ret[0].round
|
||||
else
|
||||
# TODO: Use an appropriate interpolation.
|
||||
raise _INTL("Unknown interpolation method {1}.", cmd[3])
|
||||
end
|
||||
ret[1] = true # Interpolating
|
||||
break
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
|
||||
# TODO: Should the canvas be able to show boxes/faded sprites of particles from
|
||||
# the previous keyframe? I suppose ideally, but don't worry about it.
|
||||
# TODO: Battler/particle sprites should be their own class, which combine a
|
||||
# sprite and a target-dependent coloured frame. Alternatively, have the
|
||||
# frame be a separate sprite but only draw it around the currently
|
||||
# selected particle(s).
|
||||
# TODO: Show a focus-dependent coloured frame around just the currently selected
|
||||
# particle. Only show one frame even if the focus involves a target and
|
||||
# there are multiple targets.
|
||||
# TODO: Ideally refresh the canvas while editing a particle's property in the
|
||||
# :commands_pane component (e.g. moving a number slider but not finalising
|
||||
# it). Refresh a single particle. I don't think any other side pane needs
|
||||
|
||||
@@ -31,7 +31,8 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
:target_side_foreground => Color.new(128, 248, 248), # Cyan
|
||||
:target_side_background => Color.new(128, 248, 248) # Cyan
|
||||
}
|
||||
SE_CONTROL_BG = Color.gray
|
||||
SE_CONTROL_BG_COLOR = Color.gray
|
||||
TIME_AFTER_ANIMATION_COLOR = Color.new(224, 224, 224)
|
||||
|
||||
attr_reader :keyframe # The selected keyframe
|
||||
attr_reader :values
|
||||
@@ -56,7 +57,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
@commands_viewport = Viewport.new(@commands_bg_viewport.rect.x, @commands_bg_viewport.rect.y,
|
||||
@commands_bg_viewport.rect.width, @commands_bg_viewport.rect.height)
|
||||
@commands_viewport.z = self.viewport.z + 3
|
||||
# Create scrollbar
|
||||
# Create scrollbars
|
||||
@list_scrollbar = UIControls::Scrollbar.new(
|
||||
x + width - UIControls::Scrollbar::SLIDER_WIDTH, @commands_bg_viewport.rect.y,
|
||||
@commands_bg_viewport.rect.height, self.viewport, false, true
|
||||
@@ -67,6 +68,13 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
@commands_bg_viewport.rect.width, self.viewport, true, true
|
||||
)
|
||||
@time_scrollbar.set_interactive_rects
|
||||
# Time background bitmap sprite
|
||||
@time_bg_sprite = BitmapSprite.new(
|
||||
@commands_viewport.rect.width,
|
||||
TIMELINE_HEIGHT + VIEWPORT_SPACING + @list_viewport.rect.height, self.viewport
|
||||
)
|
||||
@time_bg_sprite.x = @commands_viewport.rect.x
|
||||
@time_bg_sprite.y = self.y
|
||||
# Timeline bitmap sprite
|
||||
@timeline_sprite = BitmapSprite.new(@commands_viewport.rect.width, TIMELINE_HEIGHT, self.viewport)
|
||||
@timeline_sprite.x = @commands_viewport.rect.x
|
||||
@@ -150,6 +158,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
def dispose
|
||||
@list_scrollbar.dispose
|
||||
@time_scrollbar.dispose
|
||||
@time_bg_sprite.dispose
|
||||
@timeline_sprite.dispose
|
||||
@position_sprite.dispose
|
||||
@particle_line_sprite.dispose
|
||||
@@ -483,7 +492,16 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
end
|
||||
|
||||
def refresh_timeline
|
||||
@time_bg_sprite.bitmap.clear
|
||||
@timeline_sprite.bitmap.clear
|
||||
# Draw grey over the time after the end of the animation
|
||||
dur = duration
|
||||
draw_x = TIMELINE_LEFT_BUFFER + (dur * KEYFRAME_SPACING) - @left_pos
|
||||
greyed_width = @time_bg_sprite.width - draw_x
|
||||
if greyed_width > 0
|
||||
@time_bg_sprite.bitmap.fill_rect(draw_x, 0, greyed_width, @time_bg_sprite.height, TIME_AFTER_ANIMATION_COLOR)
|
||||
@time_bg_sprite.bitmap.fill_rect(draw_x, TIMELINE_HEIGHT, greyed_width, VIEWPORT_SPACING, Color.black)
|
||||
end
|
||||
# Draw hover highlight
|
||||
hover_color = nil
|
||||
if @captured_keyframe && !@captured_row
|
||||
@@ -538,7 +556,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
p_index = (@particle_list[index].is_a?(Array)) ? @particle_list[index][0] : @particle_list[index]
|
||||
particle_data = @particles[p_index]
|
||||
if particle_data[:name] == "SE"
|
||||
bg_color = SE_CONTROL_BG
|
||||
bg_color = SE_CONTROL_BG_COLOR
|
||||
else
|
||||
bg_color = CONTROL_BG_COLORS[@particles[p_index][:focus]] || Color.magenta
|
||||
end
|
||||
@@ -575,7 +593,7 @@ class AnimationEditor::ParticleList < UIControls::BaseControl
|
||||
particle_data = @particles[p_index]
|
||||
# Get the background color
|
||||
if particle_data[:name] == "SE"
|
||||
bg_color = SE_CONTROL_BG
|
||||
bg_color = SE_CONTROL_BG_COLOR
|
||||
else
|
||||
bg_color = CONTROL_BG_COLORS[@particles[p_index][:focus]] || Color.magenta
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user