[XP] External Animation Editor

Started by orochii, May 15, 2015, 04:17:25 pm

Previous topic - Next topic

orochii

May 15, 2015, 04:17:25 pm Last Edit: December 05, 2019, 10:56:58 pm by orochii Reason: Updated link, forgot to do it after Dropbox changed how things work (eons ago)
External Animation Editor
Authors: Orochii Zouveleki
Version: 0.2
Type: Game Utility
Key Term: Game Utility

Introduction
Due to life's unexpected happenings (?), I got fed up of certain limitations in battle animations, more specifically the X/Y coordinates. So I started doing this to break those limits. It's still unfinished, barely started maybe. There is a lot to be reimplemented from the default editor to this.
This is probably going to be either made slow, or could even never be completed. I have a lot of assignments in college and/or desires to continue my game (fun fact is, I started this to use it on my own game so... heck).


Features

  • External animation editor without limitations imposed.
  • Uh... can be expanded upon. :^D!

Screenshots



Demo
https://www.dropbox.com/s/gdulaha9fgkjiyv/AnimationEditor.rar?dl=0

Instructions

Copy the Animations.rxdata file from your project to the Data folder of this thing. Then copy the Graphics\Animations folder too.
I'm sorry, maybe one day it will support opening projects.
THEN open the program (Game.exe). Do stuff.
THEN, lastly, copy the Animations.rxdata to your project. Remember to make sure your project isn't open!

RPG::SPRITE ANIMATION MOD SCRIPTLET
This is necessary in case you want to use some extra functions from the editor. These extra functions include by now:
- More than 16 sprites per frame.
More extra functions will be added in the future.

RPG::Sprite Animation Mod
Spoiler: ShowHide

module RPG
  class Sprite < ::Sprite
   
    def animation(animation, hit)
      dispose_animation
      @_animation = animation
      return if @_animation == nil
      @_animation_hit = hit
      @_animation_duration = @_animation.frame_max
      animation_name = @_animation.animation_name
      animation_hue = @_animation.animation_hue
      bitmap = RPG::Cache.animation(animation_name, animation_hue)
      if @@_reference_count.include?(bitmap)
        @@_reference_count[bitmap] += 1
      else
        @@_reference_count[bitmap] = 1
      end
      max_sprite = get_max_sprites(@_animation)
      @_animation_sprites = []
      if @_animation.position != 3 or not @@_animations.include?(animation)
        max_sprite.times {
          sprite = ::Sprite.new(self.viewport)
          sprite.bitmap = bitmap
          sprite.visible = false
          @_animation_sprites.push(sprite)
        }
        unless @@_animations.include?(animation)
          @@_animations.push(animation)
        end
      end
      update_animation
    end
   
    def loop_animation(animation)
      return if animation == @_loop_animation
      dispose_loop_animation
      @_loop_animation = animation
      return if @_loop_animation == nil
      @_loop_animation_index = 0
      animation_name = @_loop_animation.animation_name
      animation_hue = @_loop_animation.animation_hue
      bitmap = RPG::Cache.animation(animation_name, animation_hue)
      if @@_reference_count.include?(bitmap)
        @@_reference_count[bitmap] += 1
      else
        @@_reference_count[bitmap] = 1
      end
      max_sprite = get_max_sprites(@_loop_animation)
      @_loop_animation_sprites = []
      max_sprite.times {
        sprite = ::Sprite.new(self.viewport)
        sprite.bitmap = bitmap
        sprite.visible = false
        @_loop_animation_sprites.push(sprite)
      }
      update_loop_animation
    end
   
    def get_max_sprites(animation)
      max = 0
      animation.frames.each {|frame|
        max = frame.cell_max if max<frame.cell_max
      }
      return max
    end
   
    def dispose_animation
      if @_animation_sprites != nil
        sprite = @_animation_sprites[0]
        if sprite != nil
          @@_reference_count[sprite.bitmap] -= 1
          if @@_reference_count[sprite.bitmap] == 0
            sprite.bitmap.dispose
          end
        end
        for sprite in @_animation_sprites
          sprite.dispose
        end
        @_animation_sprites = nil
        @_animation = nil
      end
    end
   
    def dispose_loop_animation
      if @_loop_animation_sprites != nil
        sprite = @_loop_animation_sprites[0]
        if sprite != nil
          @@_reference_count[sprite.bitmap] -= 1
          if @@_reference_count[sprite.bitmap] == 0
            sprite.bitmap.dispose
          end
        end
        for sprite in @_loop_animation_sprites
          sprite.dispose
        end
        @_loop_animation_sprites = nil
        @_loop_animation = nil
      end
    end
   
    def update_animation
      if @_animation_duration > 0
        frame_index = @_animation.frame_max - @_animation_duration
        cell_data = @_animation.frames[frame_index].cell_data
        position = @_animation.position
        animation_set_sprites(@_animation_sprites, cell_data, position)
        for timing in @_animation.timings
          if timing.frame == frame_index
            animation_process_timing(timing, @_animation_hit)
          end
        end
      else
        dispose_animation
      end
    end
   
    def update_loop_animation
      frame_index = @_loop_animation_index
      cell_data = @_loop_animation.frames[frame_index].cell_data
      position = @_loop_animation.position
      animation_set_sprites(@_loop_animation_sprites, cell_data, position)
      for timing in @_loop_animation.timings
        if timing.frame == frame_index
          animation_process_timing(timing, true)
        end
      end
    end
   
    def animation_set_sprites(sprites, cell_data, position)
      sprites.size.times{ |i|
        sprite = sprites[i]
        pattern = cell_data[i, 0]
        if sprite == nil or pattern == nil or pattern == -1
          sprite.visible = false if sprite != nil
          next
        end
        sprite.visible = true
        sprite.src_rect.set(pattern % 5 * 192, pattern / 5 * 192, 192, 192)
        if position == 3
          if self.viewport != nil
            sprite.x = self.viewport.rect.width / 2
            sprite.y = self.viewport.rect.height - 160
          else
            sprite.x = 320
            sprite.y = 240
          end
        else
          sprite.x = self.x - self.ox + self.src_rect.width / 2
          sprite.y = self.y - self.oy + self.src_rect.height / 2
          sprite.y -= self.src_rect.height / 4 if position == 0
          sprite.y += self.src_rect.height / 4 if position == 2
        end
        sprite.x += cell_data[i, 1]
        sprite.y += cell_data[i, 2]
        sprite.z = 2000
        sprite.ox = 96
        sprite.oy = 96
        sprite.zoom_x = cell_data[i, 3] / 100.0
        sprite.zoom_y = cell_data[i, 3] / 100.0
        sprite.angle = cell_data[i, 4]
        sprite.mirror = (cell_data[i, 5] == 1)
        sprite.opacity = cell_data[i, 6] * self.opacity / 255.0
        sprite.blend_type = cell_data[i, 7]
      }
    end
   
    def animation_process_timing(timing, hit)
      if (timing.condition == 0) or
        (timing.condition == 1 and hit == true) or
        (timing.condition == 2 and hit == false)
        if timing.se.name != ""
          se = timing.se
          Audio.se_play("Audio/SE/" + se.name, se.volume, se.pitch)
        end
        case timing.flash_scope
        when 1
          self.flash(timing.flash_color, timing.flash_duration * 2)
        when 2
          if self.viewport != nil
            self.viewport.flash(timing.flash_color, timing.flash_duration * 2)
          end
        when 3
          self.flash(nil, timing.flash_duration * 2)
        end
      end
    end
   
    def x=(x)
      sx = x - self.x
      if sx != 0
        if @_animation_sprites != nil
          @_animation_sprites.size.times {|i|
            @_animation_sprites[i].x += sx
          }
        end
        if @_loop_animation_sprites != nil
          @_loop_animation_sprites.size.times {|i|
            @_loop_animation_sprites[i].x += sx
          }
        end
      end
      super
    end
   
    def y=(y)
      sy = y - self.y
      if sy != 0
        if @_animation_sprites != nil
          @_animation_sprites.size.times {|i|
            @_animation_sprites[i].y += sy
          }
        end
        if @_loop_animation_sprites != nil
          @_loop_animation_sprites.size.times {|i|
            @_loop_animation_sprites[i].y += sy
          }
        end
      end
      super
    end
   
  end
end



Compatibility
It can be potentially uncompatible with anything that overrides the RPG::Sprite class. Because for several extra functions it needs a small scriptlet that modifies that.


Credits and Thanks

  • My dog.

Author's Notes
Licence is CC0, so make anything you wish with it.

KK20

Holy shit...I was working on something like this too. Granted all I got was like the first couple of windows and defining resolutions, but I did have plans to add a couple other things that the editor didn't have.

Well, seeing as you already have a framework to build upon, I can help out with this. :) Let me take a look at it.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

G_G

How to make it so people can copy it to their projects.

1. Rename everything to AnimEditor. So the executable and the project file.
2. Open the ini and change the scripts file to Data/AnimEditor.rxdata
3. Rename Scripts.rxdata to AnimEditor.rxdata

Then they can just copy it into any project. But other than that, great idea and hella good job for doing this inside RGSS. I seriously would have just created something in C#. xD

KK20

Quote from: gameus on May 15, 2015, 11:30:55 pm
I seriously would have just created something in C#. xD

I would too if I knew how :P
Would've at least made the UI stuff easy.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

finalholylight

Nice!
Oh, I have some ideas, would you mind add these options to your editor ?
1. Zoom only X/ only Y
2. Animation below battlers.

orochii

@KK20: Before anything, excuse me for my horrible mess of code! "orz.
@Gameus: Maybe doing it in C# would had been better. But I... was eager to start, and RMXP was the first thing I had close. And I've never done anything in C#.

I can work on these things, zoom_y and zoom_x by separate, and making some sprites to be below the character is easy too. Only thing it would need is including a small scriptlet that would change the RPG::Sprite::animation_set_sprites method. I did the latter on my game, because I wanted to make certain sprites under the character, but the rest over it.

I want to preserve compatibility with default animations too. So... I guess make something like an AnimExtraData class. That way it will be readable by RMXP.
Only issue I have found regarding to opening the animations from the RMXP editor is that coordinates are automatically readjusted to their default range (-320...320 for X and -160...160 for Y), when you select them from the list.

KK20

Yeah that's kind of a thing with the editor. I tried something like making item descriptions longer via script and went into the database. If I viewed an item that had too many characters for its description, it wouldn't let me close the window, move to another tab, or view a different item until I reduced the description.

So you will have to put some disclaimer that the user should NOT go back into the animation editor.

As for other things I wanted to be able to change with animations:
Give the cell animations the "hit" and "miss" properties, more than 16 sprites, modifying the battler graphic (moving, shaking, zooming, etc), Z-layering, and more than 200 frames.

Things in the editor I always wanted:
More hotkeys (number keys could refer to like what cell you want to modify), layers (like typical art programs), preview loop animations at the same time as playing a normal animation, grid lines toggle and changing the grid interval, and probably a couple other things I'm forgetting.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

Ryex

I no longer keep up with posts in the forum very well. If you have a question or comment, about my work, or in general I welcome PM's. if you make a post in one of my threads and I don't reply with in a day or two feel free to PM me and point it out to me.<br /><br />DropBox, the best free file syncing service there is.<br />

orochii


orochii

May 19, 2015, 10:44:39 pm #9 Last Edit: May 20, 2015, 01:58:57 pm by orochii
UPDATE!

Here is how it looks now.


Right now, these are the most important things to add:
- Support for "timings" (SFX, screen/target flashes, etc)
- Tools (copy/paste frames, autocompletion, cell options, move frames...).
- More hotkeys!

Press F10 to open the help window. ESC closes that window.

Also I've updated the download. Now it is prepared to be copied inside your game folder, just like gameus suggested.

EDIT: Now this is getting interesting. I've added the option to have more than 16 sprites, and... the RMXP editor seems to support them! They don't have a number at the upper left corner but still... they can be moved and edited as any other sprite.

KK20

Still looking good. It seems you're working pretty diligently on this; perhaps I'll wait until it's either finished or stopped before I work on it myself.

While that is interesting the built-in editor allows more than 16 sprites, it's kind of a fruitless discovery. The point is to not allow the user to ever have to rely on the built-in editor for making animations. Besides, most of the things you're changing break the RMXP standard and are not compatible anyways.

I'll also move this.

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

orochii

Thanks KK20 for moving the topic. By the way, I think I'll be doing the three necessary things I've mentioned earlier, then I will leave it be some time, because I need to continue with my game. I wanted to make a demo release this June!

Yeah, that discovery was more something I found interesting, I think I was more worried that it could explode or something (?).

And by the way, I had an idea for fixing the issue with the default editor overriding the X/Y coordinates out of range. What I was thinking is that the custom editor worked on a copy of the Animations.rxdata (I called it "Animations2.rxdata). The custom animation editor makes sure there is the same amount of animations on both files (because skills and other editor stuff need to be able to know there are more animations, and these only read from the default file).

It's time to make the timings!

ForeverZer0

Could just rewrite how animations work with a script, but then you would still need to load from a separate file. It would allow for things out of range, but add some clunkiness to the whwhole thing. Depends on how important it is to use more than 16.
I am done scripting for RMXP. I will likely not offer support for even my own scripts anymore, but feel free to ask on the forum, there are plenty of other talented scripters that can help you.

KK20

Rewriting the animation methods in RPG::Sprite is going to be inevitable at this point--there are a lot of things I'd like to see done with animations that can't be done without doing so.

And yeah, making a second file might help prevent the overwriting, but it'll probably cause more complications depending on how much you want the default editor to be compatible with your custom editor (to which I say "who cares about compatibility?").

Other Projects
RPG Maker XP Ace  Upgrade RMXP to RMVXA performance!
XPA Tilemap  Tilemap rewrite with many features, including custom resolution!

Nintendo Switch Friend Code: 8310-1917-5318
Discord: KK20 Tyler#8901

Join the CP Discord Server!

orochii

The only things I care in terms of compatibility are that the animations show up in RMXP for being selected on skills, items, Show animation command and that stuff. After that, my concerns are not making the editor explode, because that can cause potential data loss/corruption, and that, if someone opens the default animation editor by mistake, they don't lose anything either, because that can be frustrating too.

I've tweaked some of the RPG::Sprite animation-related methods. Others are there in case I need them or as references.
Actually, I need to append that to the post! It's a necessary scriptlet.

RPG::Sprite Animation Mod
Spoiler: ShowHide

module RPG
  class Sprite < ::Sprite
   
    def animation(animation, hit)
      dispose_animation
      @_animation = animation
      return if @_animation == nil
      @_animation_hit = hit
      @_animation_duration = @_animation.frame_max
      animation_name = @_animation.animation_name
      animation_hue = @_animation.animation_hue
      bitmap = RPG::Cache.animation(animation_name, animation_hue)
      if @@_reference_count.include?(bitmap)
        @@_reference_count[bitmap] += 1
      else
        @@_reference_count[bitmap] = 1
      end
      max_sprite = get_max_sprites(@_animation)
      @_animation_sprites = []
      if @_animation.position != 3 or not @@_animations.include?(animation)
        max_sprite.times {
          sprite = ::Sprite.new(self.viewport)
          sprite.bitmap = bitmap
          sprite.visible = false
          @_animation_sprites.push(sprite)
        }
        unless @@_animations.include?(animation)
          @@_animations.push(animation)
        end
      end
      update_animation
    end
   
    def loop_animation(animation)
      return if animation == @_loop_animation
      dispose_loop_animation
      @_loop_animation = animation
      return if @_loop_animation == nil
      @_loop_animation_index = 0
      animation_name = @_loop_animation.animation_name
      animation_hue = @_loop_animation.animation_hue
      bitmap = RPG::Cache.animation(animation_name, animation_hue)
      if @@_reference_count.include?(bitmap)
        @@_reference_count[bitmap] += 1
      else
        @@_reference_count[bitmap] = 1
      end
      max_sprite = get_max_sprites(@_loop_animation)
      @_loop_animation_sprites = []
      max_sprite.times {
        sprite = ::Sprite.new(self.viewport)
        sprite.bitmap = bitmap
        sprite.visible = false
        @_loop_animation_sprites.push(sprite)
      }
      update_loop_animation
    end
   
    def get_max_sprites(animation)
      max = 0
      animation.frames.each {|frame|
        max = frame.cell_max if max<frame.cell_max
      }
      return max
    end
   
    def dispose_animation
      if @_animation_sprites != nil
        sprite = @_animation_sprites[0]
        if sprite != nil
          @@_reference_count[sprite.bitmap] -= 1
          if @@_reference_count[sprite.bitmap] == 0
            sprite.bitmap.dispose
          end
        end
        for sprite in @_animation_sprites
          sprite.dispose
        end
        @_animation_sprites = nil
        @_animation = nil
      end
    end
   
    def dispose_loop_animation
      if @_loop_animation_sprites != nil
        sprite = @_loop_animation_sprites[0]
        if sprite != nil
          @@_reference_count[sprite.bitmap] -= 1
          if @@_reference_count[sprite.bitmap] == 0
            sprite.bitmap.dispose
          end
        end
        for sprite in @_loop_animation_sprites
          sprite.dispose
        end
        @_loop_animation_sprites = nil
        @_loop_animation = nil
      end
    end
   
    def update_animation
      if @_animation_duration > 0
        frame_index = @_animation.frame_max - @_animation_duration
        cell_data = @_animation.frames[frame_index].cell_data
        position = @_animation.position
        animation_set_sprites(@_animation_sprites, cell_data, position)
        for timing in @_animation.timings
          if timing.frame == frame_index
            animation_process_timing(timing, @_animation_hit)
          end
        end
      else
        dispose_animation
      end
    end
   
    def update_loop_animation
      frame_index = @_loop_animation_index
      cell_data = @_loop_animation.frames[frame_index].cell_data
      position = @_loop_animation.position
      animation_set_sprites(@_loop_animation_sprites, cell_data, position)
      for timing in @_loop_animation.timings
        if timing.frame == frame_index
          animation_process_timing(timing, true)
        end
      end
    end
   
    def animation_set_sprites(sprites, cell_data, position)
      sprites.size.times{ |i|
        sprite = sprites[i]
        pattern = cell_data[i, 0]
        if sprite == nil or pattern == nil or pattern == -1
          sprite.visible = false if sprite != nil
          next
        end
        sprite.visible = true
        sprite.src_rect.set(pattern % 5 * 192, pattern / 5 * 192, 192, 192)
        if position == 3
          if self.viewport != nil
            sprite.x = self.viewport.rect.width / 2
            sprite.y = self.viewport.rect.height - 160
          else
            sprite.x = 320
            sprite.y = 240
          end
        else
          sprite.x = self.x - self.ox + self.src_rect.width / 2
          sprite.y = self.y - self.oy + self.src_rect.height / 2
          sprite.y -= self.src_rect.height / 4 if position == 0
          sprite.y += self.src_rect.height / 4 if position == 2
        end
        sprite.x += cell_data[i, 1]
        sprite.y += cell_data[i, 2]
        sprite.z = 2000
        sprite.ox = 96
        sprite.oy = 96
        sprite.zoom_x = cell_data[i, 3] / 100.0
        sprite.zoom_y = cell_data[i, 3] / 100.0
        sprite.angle = cell_data[i, 4]
        sprite.mirror = (cell_data[i, 5] == 1)
        sprite.opacity = cell_data[i, 6] * self.opacity / 255.0
        sprite.blend_type = cell_data[i, 7]
      }
    end
   
    def animation_process_timing(timing, hit)
      if (timing.condition == 0) or
         (timing.condition == 1 and hit == true) or
         (timing.condition == 2 and hit == false)
        if timing.se.name != ""
          se = timing.se
          Audio.se_play("Audio/SE/" + se.name, se.volume, se.pitch)
        end
        case timing.flash_scope
        when 1
          self.flash(timing.flash_color, timing.flash_duration * 2)
        when 2
          if self.viewport != nil
            self.viewport.flash(timing.flash_color, timing.flash_duration * 2)
          end
        when 3
          self.flash(nil, timing.flash_duration * 2)
        end
      end
    end
   
    def x=(x)
      sx = x - self.x
      if sx != 0
        if @_animation_sprites != nil
          @_animation_sprites.size.times {|i|
            @_animation_sprites[i].x += sx
          }
        end
        if @_loop_animation_sprites != nil
          @_loop_animation_sprites.size.times {|i|
            @_loop_animation_sprites[i].x += sx
          }
        end
      end
      super
    end
   
    def y=(y)
      sy = y - self.y
      if sy != 0
        if @_animation_sprites != nil
          @_animation_sprites.size.times {|i|
            @_animation_sprites[i].y += sy
          }
        end
        if @_loop_animation_sprites != nil
          @_loop_animation_sprites.size.times {|i|
            @_loop_animation_sprites[i].y += sy
          }
        end
      end
      super
    end
   
  end
end


So yeah, that's going to the OP.

PrinceEndymion88

I love it! I hope to see, one day, the feature to insert animation under the battler :D