From ae32d59eb943c76d2ba24b49622e68a8fdf83511 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Tue, 12 Mar 2024 19:11:51 +0000 Subject: [PATCH] Anim Editor: added more animation interpolation types, greyed out timeline that isn't part of the animation --- .../901_Anim utilities/001_Anim utilities.rb | 44 ++++++++++++++++--- .../902_Anim GameData/001_Animation.rb | 2 - .../904_Anim Editor/001_AnimationEditor.rb | 11 +++-- .../002_AnimationEditor_popups.rb | 41 ++++++++++++----- .../904_Anim Editor/901_ParticleDataHelper.rb | 18 +++++++- .../Anim Editor elements/001_Canvas.rb | 7 ++- .../Anim Editor elements/003_ParticleList.rb | 26 +++++++++-- 7 files changed, 117 insertions(+), 32 deletions(-) diff --git a/Data/Scripts/901_Anim utilities/001_Anim utilities.rb b/Data/Scripts/901_Anim utilities/001_Anim utilities.rb index b64095f19..83072c9ef 100644 --- a/Data/Scripts/901_Anim utilities/001_Anim utilities.rb +++ b/Data/Scripts/901_Anim utilities/001_Anim utilities.rb @@ -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 diff --git a/Data/Scripts/902_Anim GameData/001_Animation.rb b/Data/Scripts/902_Anim GameData/001_Animation.rb index c83991760..c29f8790a 100644 --- a/Data/Scripts/902_Anim GameData/001_Animation.rb +++ b/Data/Scripts/902_Anim GameData/001_Animation.rb @@ -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. diff --git a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb index 49487c746..f75bb3f92 100644 --- a/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb +++ b/Data/Scripts/904_Anim Editor/001_AnimationEditor.rb @@ -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" } diff --git a/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb b/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb index 2912f2302..903e432d4 100644 --- a/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb +++ b/Data/Scripts/904_Anim Editor/002_AnimationEditor_popups.rb @@ -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 diff --git a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb index 87fc8292d..df84037e1 100644 --- a/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb +++ b/Data/Scripts/904_Anim Editor/901_ParticleDataHelper.rb @@ -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 diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb index 84b69755b..1da9b6048 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/001_Canvas.rb @@ -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 diff --git a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb index 6ece125ee..f8aebb6e8 100644 --- a/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb +++ b/Data/Scripts/904_Anim Editor/Anim Editor elements/003_ParticleList.rb @@ -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