It is currently Tue May 21, 2019 12:12 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 43 posts ]  Go to page 1, 2, 3  Next
Author Message
PostPosted: Tue Mar 26, 2019 1:30 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 91
Location: Gothenburg, Sweden
Hey everyone! I'm curious how you all go about implementing sprite animations in your projects.

Personally I've taken the route of storing states for the entity in question and a timer to determine what frame should be loaded. I then go about writing logic that ultimately stores pointer values to be used by a generic sprite loading routine. A simplified excerpt from my code:
Code:
LoadPlayerSprite:
   
   ; variables:
   ; curr_entity_state_1      ; set before entry representing the player's state
   ; OAM_currSpriteAnimTimer   ; set before entry and later used by the generic routine "LoadSprite"
   
   lda curr_entity_state_1
   and #PLAYER_WALKING
   beq @NotWalking

      lda OAM_currSpriteAnimTimer
      cmp #PLAYERSPRITE_RUN_01_TIME
      bcc @SetFrameToRun01

      cmp #PLAYERSPRITE_RUN_02_TIME
      bcc @SetFrameToRun02

      cmp #PLAYERSPRITE_RUN_03_TIME
      bcc @SetFrameToRun01

      jmp @SetFrameToRun03
      
   @NotWalking:
   
   ....
   
   @SetFrameToRun01:
   lda #<(playersprite1_run_01)
   ldx #>(playersprite1_run_01)
   jmp LoadSprite
   
   @SetFrameToRun02:
   lda #<(playersprite1_run_02)
   ldx #>(playersprite1_run_02)
   jmp LoadSprite
   
   @SetFrameToRun03:
   lda #<(playersprite1_run_03)
   ldx #>(playersprite1_run_03)
   jmp LoadSprite
   
   ....
   
LoadSprite:

   ; loads metasprite data into $0200, using a and x as pointers
I have code like this for all entities.

I tried replacing all that logic with lookup tables instead and got it working but started to question whether it was just making things more complicated.
Now I'm thinking about it again, though. My reasoning being that by having a generic routine for loading a frame for any given entity type and timer, I would be able to write a tool to create animations from a set of metasprites. Doing it that way might alleviate me from having to write most of the animation code (the animation state machine, I guess you would call it) for each individual sprite entity. But by doing so I guess I would also lose full control of the animations.

So, how do you guys and gals go about sprite animations? Do you write all the logic for which frame to display by hand or do take a more tricky route?

Cheers! :beer:


Top
 Profile  
 
PostPosted: Tue Mar 26, 2019 3:36 pm 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
my personal preference: raw oam-format data, optionally with a termination byte or sequence at the end of each cel. table with cel pointers... and the sequence is a base addr, an index into that LUT and a duration for each index. you could keep them in parallel and reuse the x reg, or you could interleave them. Exactly how you read those LUTs is really up to how complex animation sequencer functions you care to write. Eats lots of ROM but won’t give you ”i wish i could have it look like this” type of regrets later on. ROM is cheap! Your designs are not, since they take time.

You may use the MSB in Ypos and Xpos for something if you so wish since it is rare for an object to be that spread out.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Tue Mar 26, 2019 5:07 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
@FrankenGraphics -- I think the OP is asking how one goes about implementing actual sprite animation, i.e. code/logic used for deciding how/when to update the data DMA'd to OAM (thus triggering, visually, an actual change in the sprite), and how to implement (sanely) frame delays on a per-sprite basis -- not so much the internal format used for sprite organisation. The "how complex animation sequencer functions" part of your post is what they're asking about.

This subject is something I imagine every single person/coder struggles with, so it's an excellent question. I'd be curious to read answers from those who have done games (ex. rainwarrior, tepples, others.)


Top
 Profile  
 
PostPosted: Tue Mar 26, 2019 6:15 pm 
Offline
User avatar

Joined: Wed Sep 07, 2005 9:55 am
Posts: 364
Location: Phoenix, AZ
I keep three arrays for animation handling:

Object_Animation - the current animation number
Object_Frame - the current frame number in that animation
Object_AnimTimer - the timer for the current frame

The structure of my animations are:

Code:
  [frame id], [timer]
  [frame id], [timer]
  ...
  [control code]


The control codes decide if the animation should hold on the last frame, loop to the beginning, or reverse back to the beginning. Each time the drawing code is called, Object_AnimTimer is decremented and if zero, Object_Frame is incremented and Object_AnimTimer is loaded with the new value.

The objects state handler can then set the animation by calling Func_ChangeAnimation with the animation in the A register and the objects id# in the X register:
Code:
         ; ... code that determines player is standing still

         ldx Object_Direction               ;   set to standing animation
         lda plyrstand_animtbl, X
@change_animation:
         ldx #0
         jsr Func_ChangeAnimation

         ; ... other stuff

_________________
. That's just like, your opinion, man .


Top
 Profile  
 
PostPosted: Tue Mar 26, 2019 7:29 pm 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 606
Location: Central Illinois, USA
koitsu wrote:
This subject is something I imagine every single person/coder struggles with, so it's an excellent question. I'd be curious to read answers from those who have done games (ex. rainwarrior, tepples, others.)


Really, it's so specific to the game requirements that I've done it differently with every game I've done.

never-obsolete wrote:
I keep three arrays for animation handling:

Object_Animation - the current animation number
....
The control codes decide if the animation should hold on the last frame, loop to the beginning, or reverse back to the beginning.


In my current project (which Frakengraphics is making metasprites for) I do almost the exact same thing as this, only I use a pointer to an animation's address instead of a number.

Like your control codes, I also have a control code (followed by an address) for "switch to another animation" so that an animation can start, run a few frames, then jump to a new animation.


On a completely separate thought, but worth mentioning -- I also (based on a tip from someone here, but unfortunately I can't remember who) found that setting a metasprite's anchor significantly to the left and either above/below the sprites, can make clipping easier. When you need to check each sprite to see if that sprite went offscreen (when the metasprite is partially onscreen), it's easier if each sprite is ALWAYS the same direction from the anchor point.

(In practice, she will design the metasprite with a reasonable anchor point like you'd expect, and I pre-process each one at build time, to adjust the anchor point for this optimization)

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Wed Mar 27, 2019 7:26 am 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
I think implementation and format are kind of inseparable things. For instance, project blue assumes 4 cels per object and animation, and 4 tiles per cel. You may get some variation out of each such set, SMB style, by altering the durations, but if you want it do do something different, you need to switch object ID. If you need bigger entities, you need to construct them from multiple objects.
A bullet is held on its first cel and the wall-collided destruction is kept in the remaining three. Most other objects cycle through their cels.

You could for instance let a duration of 0 imply no forward progression. You could then have a subroutine to forward one step manually to create one-shot animations, since stepping forward and cycling is otherwise implied.

The pro is you can keep it simple. The con is that if you need comlex actors/animations, you'll eat object ID:s quickly.

NESmaker assigns one animation type and one speed to each so-called action step* (of which each object may have up to 8). Since it was written for topdown games primarily, each animation type has up to 8 variations for each direction, which can be exploited for other things in a non-topdowner. Durations are set with each action animation type, or at events which overwrite what action step is due and at what animation speed. You can't vary the duration within an animation sequence easily, except by setting the animation speed high and referring to the same cel multiple times to act as duration.
Again in this engine, if you need more than 8 action steps you need to construct your entity from several object ID:s that replace each other on events. However, this wouldn't be too common.

While the engine assumes cycling/looping animations, for a flower that burts it bud, i worked within the present system and used one action step to cycle a 1-cel animation of it being closed, another action step for a couple of cels of bursting, and at the end of animation i call a 3rd action step that loops another single cel that keeps the flower open.

*An action step is basically an AI unit comprising of an action, an end of action alarm and an optional end of animation alarm. It can be "walk current direction then turn in the general direction of the player" or "wait this long, then increment action step if this condition is fulfilled, else go to the first action step" or something like that. The system is good for rulebound patterns but not too good at reactions and priorities as-is.


I think it's fair to categorize the object complexity needs in three categories.

-main character, maybe bosses - lots of flexibility
-typical goons - medium complexity
-dumb objects, like projectiles, pickups, overlay details, objects that the player/good/bosses use, maybe even NPC:s and stationary goons.

In addition to this, you could have a fourth category labeled inanimate, which would include quite a few of the 'dumb' ones.

It might make sense to simplify AI handling, collision handling AND animation handling for the 3rd category.

The first category may have a handling category of its own, or you may reserve several object ID:s for the same entity to create modes this way. Bosses might have components that work on either level.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Wed Mar 27, 2019 12:11 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 91
Location: Gothenburg, Sweden
Thanks for all the replies, it's very interesting to hear how you all have gone about this task!

It seems so far that I am the odd one out, still sticking to my hard coded animations rather than using lookup tables. As I mentioned before, I have tried switching to using LUTs but abandoned it when I was afraid it would limit my control over animation priority (if the player is running AND shooting, which animation should play?) and fluidity. But I suppose I could always do hard coded overrides if I run into issues like that.

Here's an example of how I formatted those LUTs:
Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Animation_lists ;;;;;;;;

player1_animations:
   @size:   .db 7
   .dw @dying
   .dw @hit   
   .dw @throwing
   .dw @ducking
   .dw @jumping
   .dw @running
   .dw @idle
   
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;; Animations ;;;;;;;;
   
   @running:
      .db 4                  ; animation length in cels.
      .db 2 | ANIMATION_LOOPING   ; here i store a value for speed and a flag for looping.
                           ; the speed is essentially a divider for the animation timer
                           ; used to index the list of frames below. this means the higher the value - the slower the animation
      .dw playersprite1_run_01   ; pointer to metasprite
      .dw playersprite1_run_02
      .dw playersprite1_run_01
      .dw playersprite1_run_03

   
   @dying:
      .db 6
      .db 3
      .dw playersprite1_dying_01
      .dw playersprite1_dying_02
      .dw playersprite1_dying_01
      .dw playersprite1_dying_02
      .dw playersprite1_dying_03
      .dw playersprite1_dying_04
   
   @idle:
      .db 1
      .db 1
      .dw metasprites_player1_0

   @hit:
      .db 1
      .db 1
      .dw playersprite1_hit_01
      
   @ducking:
      .db 1
      .db 1
      .dw playersprite1_duck_01

   @jumping:
      .db 1
      .db 1
      .dw playersprite1_jump_01
      
   @throwing:
      .db 2
      .db 2
      .dw playersprite1_throw_01
      .dw playersprite1_throw_02
So what I did was every frame I decide which animation should be playing depending on what state flags are activated for the player. I then use the players animation timer (which is incremented every frame) divided by the "speed" byte for selected animation as an index of what frame I should render. I thought I was kind of clever to do it that way but it also limits me in that every metasprite of an animation is displayed for an equal amount of time. It also limits me to having animations lasting no longer than 256 frames (VBlanks, not actual animation frames), as the timer is incremented every frame.

FrankenGraphics wrote:
I think implementation and format are kind of inseparable things.
I agree and I think laying out the format is an easier way to explain the system by it's own than it would be to lay out the actual code reading it.
It kinda felt to me like koitsu confused your animation format with actual metasprite data though, but I may very well be wrong.

never-obsolete wrote:
I keep three arrays for animation handling:

Object_Animation - the current animation number
Object_Frame - the current frame number in that animation
Object_AnimTimer - the timer for the current frame
It seems you all seem to go for a frame countdown rather than a timer for the animation as a whole, which I found interesting as it would add another byte in ROM to every cel of animation. Keeping track of which animation and which frame is played for each entity also first seemed kind of wasteful in RAM to me but I guess it speeds up the logic of progressing to the next frame when the countdown reaches 0.

Maybe I'm becoming a little bit too concerned about RAM and ROM usage, though... I guess even if ROM space is cheap I'm worried about having to spread things out in too many banks but maybe I should worry about that when it actually becomes a problem.
I will most likely try my hand at a LUT based animation system again and I think I'll implement some of the design choices you've all laid out. Having a countdown for each frame seems like a sensible way of gaining maximum control of the animation's fluidity without doing hard coded stuff like I am. It would also take away the limitation I have of an animation only being able to last a few seconds - those same seconds would now be able to fill out a single cel if needed.

FrankenGraphics wrote:
For instance, project blue assumes 4 cels per object and animation, and 4 tiles per cel.
So does that mean your metasprites are limited to only 4 tiles unless you combine multiple entities? Is that because you store the metasprites in the actual animation sequence, and if so, would it not be better to instead store two bytes as pointers to a metasprite of any size?
gauauu wrote:
Like your control codes, I also have a control code (followed by an address) for "switch to another animation" so that an animation can start, run a few frames, then jump to a new animation.
I'm guessing this feature is mostly used to chain together multiple animations where 4 cels are not enough? Is there a reason why you have fixed length for the LUTs? Is it so you can store them in parallel? Hmmm.. but even if that would be the reason, you should be able to just have a header for each animation with the length stored. I'm not trying to criticise your choices, I'm just very interested as I've not had the opportunity to discuss this topic with anybody else.

Thank you all for the replies so far! Once again, I find it very interesting and I would love to hear more from as many people as possible.

Beers to that! I'm going to my fridge right now. :beer:


Top
 Profile  
 
PostPosted: Wed Mar 27, 2019 12:56 pm 
Offline
User avatar

Joined: Mon Mar 13, 2017 5:21 pm
Posts: 58
I have a table of 'cells' which are single frames of an animation sequence, stored as raw OAM data, 4 bytes per 8*8 tile (I like the flexibility of not having to align all the sprite's tiles to a perfect grid--many of my sprites have tiles that overlap). Then I have an animation table where each animation sequence is a series of Cell indices, how many cells are in that animation sequence, how many frames each cell should last, and attribute data for that cell such as flipping and palette changes. each object has a byte of RAM for the current animation sequence, the current cell index, and an animation timer that counts down to 0. when it reaches 0, it loads the next cell and resets the timer for that cell's length. when it reaches the last cell, it loops.

As far as how I determine which animation sequence an object should be displaying at any given time based on its actions, I don't use look up tables directly for that. Instead, I use the animation index as a sort of quasi 'state' variable. for example, if the player object is jumping, I have a logic script that determines that and assigns the jumping animation to it. if I need to check later on whether it's jumping or not, I can look at the animation sequence byte and know based on which index it's using. It's convoluted, but it works well enough.


Top
 Profile  
 
PostPosted: Wed Mar 27, 2019 1:37 pm 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
pwnskar wrote:
So does that mean your metasprites are limited to only 4 tiles unless you combine multiple entities? Is that because you store the metasprites in the actual animation sequence, and if so, would it not be better to instead store two bytes as pointers to a metasprite of any size?

Basically yes to all.

The con i mentioned is under the circumstance that an entity switches object ID to show another animation and have other physics properties (such as climbing or jumping/falling). That's not too bad, or rather it would depend on how big a table of object ID:s you can have, and how much of it is shared vs specific to levels.

The bigger, unmentioned con is that for the bosses (which are bigger than 2x2 tiles) eat up quite a bit of cpu time each frame since each 2x2 piece is being handled separately. You also need to take some care with both AI and boss room layout since they're subject to vs. background collisions individually. This is not recommended.

It can be convenient for some games to have a very rulebound distribution of sprites, though. Project Blue just happened to grow outside its original scope considerably. I'm largely at fault for exploding the goalpost and delaying the final game. Toggle_switch is the one responsible for reminders that that the game ought to be released at some point. :wink:

The PrBlue engine is also able to put the tiles in other configurations too, not just 2 by 2. For the upward air streams in maps 2 and 3 they're stacked in 1x4 columns. But the max is 4 sprites per metasprite.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
PostPosted: Wed Mar 27, 2019 9:49 pm 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 606
Location: Central Illinois, USA
pwnskar wrote:
It seems so far that I am the odd one out, still sticking to my hard coded animations rather than using lookup tables.


I've done that also in some cases. (which is why I mentioned that it varied so much with specific game requirements) It really depends on the game and situation. In my current game, I have a bit of hard coded animation for pieces that work better that way. In my previous two years' nesdev compo game, I hard-coded all the animations because there weren't very many unique animations, and it was easier that way. But for my current game with a lot of enemies, having data-driven metasprites makes it a lot easier for an artist (frankengraphics) to create, edit, and test animations without us having recode things.

pwnskar wrote:
I'm guessing this feature is mostly used to chain together multiple animations where 4 cels are not enough?

It's more useful for animations that always flow into another one -- things like "landing from a jump into standing idle", where "standing idle" is already an animation. But it can also be useful for chaining when I need more cels, see below.

Quote:
Is there a reason why you have fixed length for the LUTs?

I don't have a limit of 4 cels (that was Frankengraphics' other game), but I do have a limit of 64 sprites per animation definition (ie 256 bytes). This way, for each entity, I store the address of the animation, and the offset into the frame data, and can use an indirect indexed lookup to quickly get each byte of the animation. (lda (ptr),y). It's fast, and 64 sprites is enough for most things.

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Fri Mar 29, 2019 12:28 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 91
Location: Gothenburg, Sweden
gauauu wrote:
I've done that also in some cases. (which is why I mentioned that it varied so much with specific game requirements) It really depends on the game and situation. In my current game, I have a bit of hard coded animation for pieces that work better that way. In my previous two years' nesdev compo game, I hard-coded all the animations because there weren't very many unique animations, and it was easier that way. But for my current game with a lot of enemies, having data-driven metasprites makes it a lot easier for an artist (frankengraphics) to create, edit, and test animations without us having recode things.

Yeah, I'm thinking it will make things easier for me too, even though I'm doing both code and art myself. I used to write in my metasprites by hand, thinking it would go much faster to keep doing it like that rather than writing a tool to convert msb's from NESST to the format used by my engine. But once I actually wrote some scripts to help with that stuff I'm now much more prone to do iterations on my graphics. So I think doing the same for my animations will make that process more enjoyable as well.

I took a liking to the approach you all share of storing a variable for the current frame/cel index in each entity and using a countdown for when to proceed to the next one. I'm still a bit cheap on RAM though, so I've opted to not store any value for current animation and instead will be doing that every frame based on other variables until I find a need to do otherwise. Got it working on a test sprite and will update with a code snippet once it's cleaned up a bit. Maybe it could be useful for someone new to animation on the NES or other retro platforms, or helpful to me in case someone notices something that could be done better in the code.

Now I kind of feel like I have to find/create a tool to make the animation sequences. I've tried googling a bit but didn't find much. Maybe someone here would know if such a tool has already been done and shared?
If not, it would be fun to brush up on my C# skills to make something that loads an msb file and let's you arrange metasprites in sequence with a countdown in between.

I've seen some gifs of mind blowing tools by kasumi but I don't know if he/she has shared anything related to animation sequences of existing CHR and metasprite data.


Top
 Profile  
 
PostPosted: Fri Mar 29, 2019 1:15 pm 
Offline
User avatar

Joined: Sat Jan 09, 2016 9:21 pm
Posts: 606
Location: Central Illinois, USA
pwnskar wrote:
But once I actually wrote some scripts to help with that stuff I'm now much more prone to do iterations on my graphics. So I think doing the same for my animations will make that process more enjoyable as well.

Then, if your build step can automatically run those scripts, you can just work with your graphics in whatever format makes you happy, save them, then re-run the build. No fiddling about with exports, or remembering to convert things. It makes iterative design and testing a lot easier.

Quote:
Now I kind of feel like I have to find/create a tool to make the animation sequences. I've tried googling a bit but didn't find much.

People seems to really like Aseprite, although I've never really used it, so I have no idea how well it fits with the NES way of doing things.

_________________
My games: http://www.bitethechili.com


Top
 Profile  
 
PostPosted: Fri Mar 29, 2019 1:58 pm 
Offline

Joined: Tue Oct 16, 2018 5:46 am
Posts: 91
Location: Gothenburg, Sweden
gauauu wrote:
Then, if your build step can automatically run those scripts, you can just work with your graphics in whatever format makes you happy, save them, then re-run the build. No fiddling about with exports, or remembering to convert things. It makes iterative design and testing a lot easier.
Yes, already doing that. :)

gauauu wrote:
People seems to really like Aseprite, although I've never really used it, so I have no idea how well it fits with the NES way of doing things.
That looks like an interesting piece of software but it doesn't appear to have any native support to import NES CHR files or work with tiles at all?


Top
 Profile  
 
PostPosted: Fri Mar 29, 2019 7:47 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4071
Location: A world gone mad
pwnskar wrote:
That looks like an interesting piece of software but it doesn't appear to have any native support to import NES CHR files or work with tiles at all?

Correct, it doesn't. It's a pixel-focused image editor with "enhancements", but mainly intended for image formats like the following: BMP, FLC, FLI, GIF, ICO, JPG, PCX, PCC, PNG, TGA, and WEBP. You have to rely on external third-party conversion tools for the image conversion process for NES stuff. The one potential exception seems to be the Export Sprite Sheet option, which is horribly documented; one of the options you can choose there is to export JSON data, if that's at all helpful -- the JSON format itself isn't documented either, so you have to sift through it and look carefully (it contains a nested set of hashes/dicts and arrays). It has support for "extensions" but there doesn't seem to be any documentation on it, nor is there any extension that adds NES-specific features.

Overall, Aesprite is intended for commonplace PC graphic formats and not so much for video game console data formats -- don't let anyone tell you otherwise. (FYI: I'm an Aesprite owner. It's a "useful" utility, intuitive in some ways and completely non-intuitive or downright weird in others; the file-oriented dialogs are terrible, and some of its region-select features are weird/bizarre in behaviour compared to other editors. Off-topic so I'll save my comments for some other time.)

You might consider using NES Screen Tool by Shiru here on the forum instead. I haven't used it myself, but have seen infiniteneslives use it on his live streams.


Top
 Profile  
 
PostPosted: Fri Mar 29, 2019 8:12 pm 
Offline
Formerly WheelInventor
User avatar

Joined: Thu Apr 14, 2016 2:55 am
Posts: 2018
Location: Gothenburg, Sweden
I use NESST for pretty much everything except 1) sprite overlays on still pictures such as title screens and 2) animation previews. For the latter i just screen grab and paste my NESST-made metasprites in photoshop and make gif animations; 2 frames equals 1 NES frame (on PAL, NTSC would be slightly quicker). I can still step through metapsrites to kind of review animations in NESST.

You need to be a bit careful when saving, which has its quirks, and when editing metasprites, but otherwise it's really great.

_________________
http://www.frankengraphics.com - personal NES blog


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 43 posts ]  Go to page 1, 2, 3  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: kikutano and 4 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group