Anim Editor: added more animation interpolation types, greyed out timeline that isn't part of the animation

This commit is contained in:
Maruno17
2024-03-12 19:11:51 +00:00
parent f0fae4b9ec
commit ae32d59eb9
7 changed files with 117 additions and 32 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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