"Animation engine" idea help.
Moderator: Moderators
Forum rules
- For making cartridges of your Super NES games, see Reproduction.
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
"Animation engine" idea help.
I've heard psychopathicteen talk about how he branches to an "animation engine" when he is done going through the code of all the objects. I'm not exactly sure how much his does, I imagine that every object slot would have information that the animation engine would look at and put on the screen in some way. One idea that I though of is that every object just has one register (16 bits) that would say what animation frame you want to display. This number would offset multiple tables thought the animation engine like the address of where the tiles start and how many tiles the object is using. One thing that's frustrating is that I'm pretty sure vram updates have to be done during vblank, so that's a bit on an inconvenience. I'm not sure how I would store the addresses for all (or a least most) of the tiles in sprite part of vram for it. The animation engine would find empty space in vram to add the new tiles, using the thing I thought of where it looks for 16x16 sized slots, or four of them for a 32x32 sized sprite. About the register in the object slots, it would also offset the table that holds addresses for a metasprite table, so it works for the metasprite creating routine. I have no idea how to translate this all into code... (the animation engine part.) The code also needs to know not to look for empty space in vram for the tiles if the object has already been created and the tiles are in vram.
How does everyone else deal with animation? Like I said, I'm aiming on just doing something like "lda #Player1Walk1" and it appearing on screen.
How does everyone else deal with animation? Like I said, I'm aiming on just doing something like "lda #Player1Walk1" and it appearing on screen.
Re: "Animation engine" idea help.
Personally, at the risk of repeating myself... I based my metasprite routine largely off psycopathicteen's reply to you back in December. I have buildMetasprite in my normal object loop right after I finish each object and I only iterate across all objects once.
When an object is spawned in my system, it's allocated its block in VRAM if it requires one. I'm using the DKC method (so I hear) of having 16 strips of VRAM for one object each, with one or two reserved for common objects. Each object contains a pointer to the address of its current sprite in ROM, if it has a VRAM block allocated. Whenever I change that ROM pointer, I set a bit that tells my vblank routine to DMA the new strip of sprite tiles over to that VRAM block.
So in my system, updating a VRAM-block-allocated object's image on screen really is as simple as storing the new ROM address and metasprite label into the object's internal registers. If it's a common object with no VRAM allocation, you either just change the tile number or write a new tile to that slot during vblank.
I know you could have problems with object-object interactions when drawing as you go through the first time, but I can't imagine them being too hard to just patch around and they'd mostly just be visual errors only visible on one frame, so hardly noticeable, right?
When an object is spawned in my system, it's allocated its block in VRAM if it requires one. I'm using the DKC method (so I hear) of having 16 strips of VRAM for one object each, with one or two reserved for common objects. Each object contains a pointer to the address of its current sprite in ROM, if it has a VRAM block allocated. Whenever I change that ROM pointer, I set a bit that tells my vblank routine to DMA the new strip of sprite tiles over to that VRAM block.
So in my system, updating a VRAM-block-allocated object's image on screen really is as simple as storing the new ROM address and metasprite label into the object's internal registers. If it's a common object with no VRAM allocation, you either just change the tile number or write a new tile to that slot during vblank.
I know you could have problems with object-object interactions when drawing as you go through the first time, but I can't imagine them being too hard to just patch around and they'd mostly just be visual errors only visible on one frame, so hardly noticeable, right?
-
psycopathicteen
- Posts: 3001
- Joined: Wed May 19, 2010 6:12 pm
Re: "Animation engine" idea help.
I do have something similar to a "frame number register." In Alisha's Adventure, I use two registers, one that holds a "metasprite number" and another that holds a "graphics address offset" number. The "graphics address offset" is there, so I can reuse the same metasprite for multiple frames, by just changing where in ROM it's DMAing the patterns from.
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
Why does that matter at all? I already have a metasprite building routine, I just want to have it to where I offset a table with metasprite table addresses so I can also do that with the vram slot finding thing. That's easy to implement.Khaz wrote:Personally, at the risk of repeating myself... I based my metasprite routine largely off psycopathicteen's reply to you back in December. I have buildMetasprite in my normal object loop right after I finish each object and I only iterate across all objects once.
See, you don't have the problem because tile positions in vram are always going to be next to each other.Khaz wrote:When an object is spawned in my system, it's allocated its block in VRAM if it requires one. I'm using the DKC method (so I hear) of having 16 strips of VRAM for one object each, with one or two reserved for common objects. Each object contains a pointer to the address of its current sprite in ROM, if it has a VRAM block allocated. Whenever I change that ROM pointer, I set a bit that tells my vblank routine to DMA the new strip of sprite tiles over to that VRAM block.
Basically, I have a number I store in an object slot (lets call the slot "Joe") that at the start of the vram finding routine, is loaded to x and offsets a table that has the addresses of tables that say position of where the tiles for the frame are. In the metasprite routine, the number from "Joe" is loaded again and also offsets a table that has the addresses of the metasprite tables. Maybe this is just too much going on...
Code: Select all
Animation:
ldx Joe
lda TileAddressTableTable,x
tay
lda TileAddressTable,y
Metasprite:
ldx Joe
lda MetaspriteTablePositionTableTable,x
tay
lda MetaspriteTablePositionTable,y
sta MetaspriteCount
iny
iny
sty MetaspriteOffset
Re: "Animation engine" idea help.
Well correct me if I'm wrong, but it sounds to me like all you're talking about is combining the metasprite number and the tile source in ROM into a single "image ID" value that will represent that exact frame of animation. I just don't see the point in that; it might save you a byte or two in each object slot, but at the expense of more space and time for the lookup tables to convert your "image ID" into a metasprite and a ROM address.
I see what you're saying about trying to dynamically search for room in VRAM to fit new sprites in, but I'm a bit wary of that approach taking up too much processing and just plain being more of a pain. But in terms of how to implement it, it doesn't really seem much different from my approach. You could still probably store every image sequentially in ROM so you only need the start address and load the right number of tiles.
The hard part of that approach is keeping track of where everything is in VRAM. You'll need several bytes inside each object to track that if you're storing one metasprite in non-sequential blocks of space. If you have multiple tile sizes you'll also be prone to having wasted space, though I'm sure that happens no matter what you do. If you have large object slots with lots of spare bytes, I could see doing things that way and maybe squeezing out a bit more VRAM room.
I dunno. I'll just give my opinion and say that it just doesn't seem like you'll get any noticeable benefit out of it for all the work and time and processor load, as opposed to a simpler system.
I see what you're saying about trying to dynamically search for room in VRAM to fit new sprites in, but I'm a bit wary of that approach taking up too much processing and just plain being more of a pain. But in terms of how to implement it, it doesn't really seem much different from my approach. You could still probably store every image sequentially in ROM so you only need the start address and load the right number of tiles.
The hard part of that approach is keeping track of where everything is in VRAM. You'll need several bytes inside each object to track that if you're storing one metasprite in non-sequential blocks of space. If you have multiple tile sizes you'll also be prone to having wasted space, though I'm sure that happens no matter what you do. If you have large object slots with lots of spare bytes, I could see doing things that way and maybe squeezing out a bit more VRAM room.
I dunno. I'll just give my opinion and say that it just doesn't seem like you'll get any noticeable benefit out of it for all the work and time and processor load, as opposed to a simpler system.
Last edited by Khaz on Fri May 08, 2015 9:51 pm, edited 1 time in total.
-
psycopathicteen
- Posts: 3001
- Joined: Wed May 19, 2010 6:12 pm
Re: "Animation engine" idea help.
Just give each object slot a designated tile-name table.
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
Well, I think I've kind of settled on what I want to do. I was originally using a "MetaspriteTableSize" byte in every one of my object slots along with a "MetaspriteTableOffset", but I'm just having it to where the first byte in the metasprite table is what says the size. As you can see below, the performance is pretty much the same, and I save on a word in ram and I also save on an extra load and store, so performance is probably slightly higher. I'm just going to have a separate register for the tile location, so I'll have 2 things instead of 3.
Code: Select all
Original Code:
ldx MetaspriteTableOffset (It actually needs to be loaded into x for the code)
lda MetaspriteTableSize
sta a:MetaspriteCount
New Code:
ldx MetaspriteTableOffset
lda a:Zero,x (but it had proven itself useful here)
sta a:MetaspriteCount-
psycopathicteen
- Posts: 3001
- Joined: Wed May 19, 2010 6:12 pm
Re: "Animation engine" idea help.
My metasprite data format actually contains information on animation, as well as metasprite stuff.
What these numbers mean is this:
ntsc frames per animation loop, unused, ntsc frames per animation frame
graphics ROM address, graphics ROM bank, image width, image height
sprite size, sprite X, sprite Y, attributes and number of tiles offset from ROM address
repeat until sprite size is 0
For rotating sprites, it is similar.
graphics ROM address, graphics ROM bank, size of rotation steps
rotation buffer address, rotation buffer bank, window width, window height
sprite size, sprite X, sprite Y, attributes and number of tiles offset from rotation buffer address
repeat until sprite size is 0
Code: Select all
Alisha_stance:
dw $0018,$0000,$0004
dw $4000,$00c0,$0006,$0008
dw $0002,$0010,$fff0,$3002
dw $0002,$0010,$0010,$301a
dw $0001,$fff8,$ffe8,$3000
dw $0001,$fff8,$fff8,$300c
dw $0001,$fff8,$0008,$3018
dw $0001,$fff8,$0018,$3024
dw $0000
ntsc frames per animation loop, unused, ntsc frames per animation frame
graphics ROM address, graphics ROM bank, image width, image height
sprite size, sprite X, sprite Y, attributes and number of tiles offset from ROM address
repeat until sprite size is 0
For rotating sprites, it is similar.
Code: Select all
head:
dw $8000,$00c1,$0002
dw $0000,$007f,$0004,$0004
dw $0002,$0000,$0000,$3400
dw $0000
rotation buffer address, rotation buffer bank, window width, window height
sprite size, sprite X, sprite Y, attributes and number of tiles offset from rotation buffer address
repeat until sprite size is 0
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
I would have though things like animation loops would have been done in the object's code.
-
psycopathicteen
- Posts: 3001
- Joined: Wed May 19, 2010 6:12 pm
Re: "Animation engine" idea help.
Yes. The looping part is done as part of the of the object code.
BTW, now that I look at it, this routine looks kind've slow. I could probably optimize it a little more.
Code: Select all
animate_object:
ldy {metasprite_request}
cpy {metasprite}
beq +
stz {animation_frame}
+;
cpy #$0000
beq +
sep #$20
lda {animation_frame}
sta $4204
stz $4205
lda $0004,y
sta $4206
nop #8
lda $4214
sta $4202
lda $000a,y
sta $4203
nop
lda $0008,y
beq non_dynamic_animation
lda $4216
sta $4202
lda $000c,y
sta $4203
nop #3
rep #$20
lda $4216
asl #5
sta {graphics_address_request}
-;
lda {animation_frame}
inc
sta {animation_frame}
cmp $0000,y
bne +
stz {animation_frame}
+;
rts
non_dynamic_animation:
rep #$20
lda $4216
asl #4
sta {attributes}
stz {graphics_address_request}
stz {name_offset}
bra -
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
I just thought of something, and I want to see if anyone approves. So you have a table with information on it relating to animation data that you index. The first word of the table says the bank the graphics are in. Everything but the first word will be addresses for tiles, but there will be special values that the animation engine will determine as different things (tile addresses obviously can't be these) The table gets indexed one word higher every fame (there will be an "animation timer" word in each object slot) the object still exists.
#$0000: End of Table Still Frame (The frame is displayed forever, as the animation timer never increases)
#$0001: End of Table Loop (The animation engine goes back to the second word in the table for an animation loop, and the animation timer is changed appropriately)
#$0002: No Animation Update (Makes the animation engine increase the timer by two and skips to the next object, or something like that)
Maybe Possible:
#$0003: Blank Frame (I'm not sure how this would work, but it would somehow mess with the metasprite routine)
I'm not exactly sure how I'm going to make it make sure to not count these values as addresses and how to do the appropriate action with each, but I'll get to it sometime.
Here are two example of how a table would look using this format:
#$0004 (bank 4)
#$916F (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$6C87 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$3B9E (nonsense address)
#$0002 (same frame as before)
#$0001 (loop to the beginning)
#$0007 (bank 7)
#$7D0D (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$1805 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$4B6F (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$F781 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$9A76 (nonsense address)
#$0000 (do nothing but stop)
#$0000: End of Table Still Frame (The frame is displayed forever, as the animation timer never increases)
#$0001: End of Table Loop (The animation engine goes back to the second word in the table for an animation loop, and the animation timer is changed appropriately)
#$0002: No Animation Update (Makes the animation engine increase the timer by two and skips to the next object, or something like that)
Maybe Possible:
#$0003: Blank Frame (I'm not sure how this would work, but it would somehow mess with the metasprite routine)
I'm not exactly sure how I'm going to make it make sure to not count these values as addresses and how to do the appropriate action with each, but I'll get to it sometime.
Here are two example of how a table would look using this format:
#$0004 (bank 4)
#$916F (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$6C87 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$3B9E (nonsense address)
#$0002 (same frame as before)
#$0001 (loop to the beginning)
#$0007 (bank 7)
#$7D0D (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$1805 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$4B6F (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$F781 (nonsense address)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$0002 (same frame as before)
#$9A76 (nonsense address)
#$0000 (do nothing but stop)
-
UnDisbeliever
- Posts: 77
- Joined: Mon Mar 02, 2015 1:11 am
- Location: Australia (PAL)
- Contact:
Re: "Animation engine" idea help.
I like it. It's kinda similar to the format I have designed.Espozo wrote:I just thought of something, and I want to see if anyone approves...
I use a different method to handle the loops. I have a command called DECREMENT_INDEX and it uses a two byte parameter which the index is decremented by. This allows for starting animations which continue into loops (ie, StopWalkingLeftAndBlink). It also saves 2 bytes as I don't need to store the starting address of the current animation.
----
While were all showing off our formats. I'll show you mine.
My format uses a bytecode syntax to prevent the linker from assigning a tileset/frame address <= $0008 (something that is possible with HIROM mapping).
I also don't store the tileset Bank in the animation list. That is set during entity load/Init (limiting me to 64KiB of tiles per entity, but I doubt I'll need more).
The current bytecodes I've designed are:
- LOAD_LEFT_TILES - Load 4 16x16 tiles to the left half of the entity's VRAM tiles.
- LOAD_RIGHT_TILES - Load 4 16x16 tiles to the right half of the entity's VRAM tiles.
- LOAD_PALETTE - Copies 16 colours into CGRAM (I'm not entirely sure how to handle this one)
- SET_METATILE_FRAME - Address of the table of the sprites frame.
- SET_ENTITY_SIZE - Sets the bounding box for collisions (crouching)
- WAIT_ONE_FRAME - Does nothing for one frame.
- DECREMENT_INDEX - Decrements the animation table position by # bytes.
Code: Select all
Animation_Player_WalkLeft:
.byte Animation::LOAD_LEFT_GRAPHICS
.addr Player_HeadTiles
.byte Animation::LOAD_RIGHT_GRAPHICS
.addr Player_WalkTiles
_Animation_Player_WalkLeft_Loop:
.byte Animation::SET_FRAME
.addr MetaSpriteFrame_Player_WalkLeft1
.byte Animation::WAIT_FRAME
.byte 3
.byte Animation::SET_FRAME
.addr MetaSpriteFrame_Player_WalkLeft2
.byte Animation::WAIT_FRAME
.byte 3
.byte Animation::SET_FRAME
.addr MetaSpriteFrame_Player_WalkLeft3
.byte Animation::WAIT_FRAME
.byte 3
.byte Animation::DECREMENT_INDEX
.word * - _Animation_Player_WalkLeft_Loop
Last edited by UnDisbeliever on Sat May 16, 2015 10:57 am, edited 1 time in total.
- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
You mean you're only going to use one bank for all the sprite tiles?UnDisbeliever wrote:I also don't store the tileset Bank in the animation list. That is set during entity load/Init (limiting me to 64KiB of tiles per entity, but I doubt I'll need more).
Wait a minute! I didn't even tell it anything on multiple sprites! Should I have it to where the values in the table are actually loaded to index a table with the addresses of other tables? The table might say the sprite size of each sprite and the tiles for every sprite would be set up a certain way so it always works. You could have it like this maybe: (The code wouldn't have to differentiate these numbers with addresses because it will always alternate)
#$0000: End of Table
#$0001: Small Sprite Size
#$0002: Large Sprite Size
A table would look like this:
#$0001 (Small Sprite)
#$6D89 (Nonsense Address)
#$0002 (Large Sprite Size)
#$F7C4 (Nonsense Address)
#$0001 (Small Sprite)
#$37DE (Nonsense Address)
#$0001 (Small Sprite)
#$5AA7 (Nonsense Address)
#$0001 (End of Table, No Sprites Left)
Maybe this is getting out of hand...
One thing though UnDisbeliever, are you coding in C? I think I remember looking at the source code for one of your games and it didn't look like anything I was used to.
-
UnDisbeliever
- Posts: 77
- Joined: Mon Mar 02, 2015 1:11 am
- Location: Australia (PAL)
- Contact:
Re: "Animation engine" idea help.
No, each entity type (tank, walker, player, stomper) has their own Bank, leaving up to 64KiB tiles (2048 8x8 tiles) per enemy type. I may change this in the future.Espozo wrote:You mean you're only going to use one bank for all the sprite tiles?
I've tried to parse that paragraph a few times and I don't really understand it.Espozo wrote:Wait a minute! I didn't even tell it anything on multiple sprites... You could have it like this maybe
Is this what your trying to explain?
Code: Select all
repeat
vramPos = sprite VRAM position.
command = table[pos]
pos += 2
if command == 0
break loop
else if command == SmallSprite
DelayedCopytoVRAM(bank, table[pos], vramPos, 16)
pos += 2
vramPos += 16 / 2
else if command == LargeSprite
DelayedCopytoVRAM(tile bank, table[pos], vramPos, 32) // top half
DelayedCopytoVRAM(tile bank, table[pos] + 32, vramPos + 16 * 16, 32) // bottom half
pos += 2
vramPos += 32 / 2
if vramPos & $0100
// move to next line so you don't override the large tiles
vramPos += $0100
I think its going to eat quite a bit of ROM space if you do that. I think you should add a size parameter to it. My version 1.0 is going to use a fixed size parameter size of 256 bytes (128 bytes (8 small tiles) to the top half, 128 bytes for the lower half).
---
No, I'm coding using structure macros to bring a bit of High Level Assembly to the 65816.Espozo wrote: One thing though UnDisbeliever, are you coding in C? I think I remember looking at the source code for one of your games and it didn't look like anything I was used to.
These macros turn code like this:
Code: Select all
LDA z:WES::walkLeftOnZero
IF_ZERO
LDA #JOY_LEFT
ELSE
LDA #JOY_RIGHT
ENDIFCode: Select all
LDA z:WES::walkLeftOnZero
BNE __STRUCTURE_001
LDA #JOY_LEFT
BRA __STRUCTURE_002
__STRUCTURE_001:
LDA #JOY_RIGHT
__STRUCTURE_002:- Drew Sebastino
- Formerly Espozo
- Posts: 3496
- Joined: Mon Sep 15, 2014 4:35 pm
- Location: Richmond, Virginia
Re: "Animation engine" idea help.
Basically, each frame of animation in each object consists of multiple sprites. It needs to look through vram to find an empty slot for each sprite in the frame, and the result of where it was finally found is transferred to the character bits in the metasprite routine. The reason you need to tell it if it's big or not is that it has to find a slot that size in vram. Do you get it now?UnDisbeliever wrote:I've tried to parse that paragraph a few times and I don't really understand it.
I don't get that at all.UnDisbeliever wrote:Is this what your trying to explain?
Weird...No, I'm coding using structure macros to bring a bit of High Level Assembly to the 65816.