Sunday, May 21, 2017

Nothing actually causes slowdown on the SNES except for the programmer himself.

The 2.68/3.58 Mhz 65816 doesn't cause slowdown.  Having an 8-bit bus doesn't cause slowdown.  Having 128 sprites onscreen doesn't cause slowdown.  Using 512x448 doesn't cause slowdown.

Then what actually causes slowdown?

People being told that all this stuff causes slowdown, AND being told that lowering the frame rate and scroll speed PREVENTS slowdown, when it's really what CAUSES it.

When you slow a game down, it's not going to speed up the CPU.  You're just causing your game to slowdown.

So what should you do?

Always run your game at 60fps.  Even when doing stuff you've been told is impossible.  In fact everything the SNES is capable of doing is stuff people think is impossible.

Saturday, February 25, 2017

Okay, just rearranged collision detection code!

Last post I explained how I got stuck into spaghetti code, and I said I'll try to fix it, and I did.  Now I'll explain what I did

Instead of having the BG collision detection routine do all the sprite movement itself, I can now move sprites outside of the collision routine, just as long as I save the old coordinates before I moved them.  This cut my multijointed boss joint code in half, since I can share a much larger subroutine between non-collision and collision joints.

Friday, February 24, 2017

Learning from programming mistakes, the hard way.

If there is ever something that is really hard to do is finding the easy way of doing things.  Every single time I try programming something the "easy way" it ends up making it harder in the long run.  Obviously the smart thing to do is "think ahead," but there is always that one thing you didn't initially think of when you first started.

When I started I had this not-so-wonderful idea of using 8.8 bit signed velocities and 16.8 bit coordinates.  I was thinking, hey I don't need 32-bit math to do subpixel physics, all I need is to calculate the 8.8 bit velocity, and then use a routine to add that to the 16.8 bit coordinates.  I thought this would fix all my subpixel math problems but man was I wrong.

The first problem I had with this technique was BG collision detection (which is a million times more complicated than object-object collision, for some reason people think it's the other way around).  If the playfield is made out of square blocks like Super Mario Bros, it's not that big of a deal, and you don't really need to know what direction a sprite is coming from.  If one side hit's a wall, push it back from whatever side hit it.  However if you want pass-through tiles, slope tiles, and even pass-through slope tiles, you have to know where you're sprite is coming from.  I honestly think I was so worried about glitches, I made my routine a little bit too physically accurate.  I think most games did more physical cheats when it came to this, such as snapping a sprite to a pass-through platform when the sprite is close enough to it.

So what I did was integrate the vertical and horizontal movement into my collision routine.  Move vertically first, detect any ceilings or floors.  Then move horizontally, first detecting any sloped platforms it comes in contact to, move the sprite up or down if it does, and then detect if it hits a wall.

I was able to get rid of the sprite movement code for sprites that had BG collision applied, which worked nice to some sprites.  However, other sprites got MORE convoluted, such as multi-jointed sprites, where velocity didn't matter that much, because sprites were controlled by the angle in which they are connected from the sprite they are connected to.  However, I had to back-track to figure out the velocity vectors, which made my code longer and slower.  I had to have 2 almost entirely different routines for multi-jointed sprites.  One for collision sprites, and one for collision-free sprites.

To make matters worse, I had problems when a multi-jointed boss changed directions.  A sprite joint might move more than 128 pixels in either direction when that happens, which 8.8 bit values don't allow.  I had to write more code to flip the sprite around first, (when the boss changes directions), so that the sprite joints don't break off when they move more than 128 pixels.

Now, as you can probably tell, my code looks like an overly complicated, poorly written, unmaintainable shit-fest, and it would seem like I made it complicated in the first place.  You might say "This only happened because you programmed you're game in ASM!" and in this place you're right.  Eventually I will start programming in C, I just hope I can get it running fast enough for the types of games I want to make.

Next post I'll start talking about how I plan on cleaning up this mess.

Wednesday, August 3, 2016

Now I am done with updating my animation engine!

Here is the almost unreadable code for it:

no_animation_slot:
jsr clear_vram_slot
stz {frame_id}
rts

animation:
lda {animation_update}
bne +
lda {metasprite_request}
cmp {metasprite}
bne +
rts
+;
lda #$0000
ldy {metasprite_request}
beq no_animation_slot
lda $000c,y

sta {vram_size}
clc
adc {total_dma_legnth}
cmp #$0080
beq yes_metasprite_pattern
bcc yes_metasprite_pattern
lda {first_object_to_dma}
bne +
tdc
sta {first_object_to_dma}
+;
no_metasprite_pattern:
rts
yes_metasprite_pattern:


jsr clear_vram_slot
ldy {metasprite_request}
lda $000a,y
asl #5
sta {vram_width}


dynamic_animation:

lda {rotation_disable}
bne +
stz {180_degrees_flip}
lda {angle}
cmp #$8000
bcc +
lda #$c000
sta {180_degrees_flip} //this is so rotation of 180 degrees doesn't happen until
+; //the frame gets updated
stz {animation_update}


lda {metasprite_request}
sta {metasprite}
tay


lda $000e,y
clc
adc {animation_index}
inc #2
sta {frame_id}
tax
lda {animation_copies},x
beq +
inc
sta {animation_copies},x
rts
+;
inc
sta {animation_copies},x



lda $0006,y
clc
adc {graphics_address}
sta {temp3}
lda $0008,y
sta {temp2}
lda $0010,y
bpl +
tya
clc
adc {animation_index}
tay
lda $0010,y
tay
+;

-;
lda $0010,y
bne +
lda #$ffff
sta {animation_chr},x
rts
+;
lda $0018,y
sta {temp}
jsr find_vram_slot
sta {animation_chr},x
cmp #$ffff
bne +
tya
clc
adc #$000a
tay
bra -
+;
tax
-;


lda $0010,y
sta {sprite_size},x
lda $0012,y
sta {sprite_x},x
lda $0014,y
sta {sprite_y},x
lda $0016,y
sta {sprite_attributes},x
tya
clc
adc #$000a
tay
lda $0018,y
sta {temp}
lda $0010,y
beq +
jsr find_vram_slot
sta {sprite_name},x
cmp #$ffff
beq -
tax
bra -
+;
lda #$ffff
sta {sprite_name},x

lda {vram_size}
clc
adc {total_dma_legnth}
sta {total_dma_legnth}
rts


find_vram_slot:
phx
lda $0010,y
cmp #$0002
beq large_slot
jmp small_slot
large_slot:
ldx {large_slot_stack_index}
dex
dex
bpl +
lda #$ffff
plx
rts
+;

lda {large_slot_stack},x
stx {large_slot_stack_index}

tax
phy
ldy {dma_updates}


lda {temp}
clc
adc {temp3}
sta {dma_address},y
adc {vram_width}
sta {dma_address}+2,y
adc {vram_width}
sta {dma_address}+4,y
adc {vram_width}
sta {dma_address}+6,y
sep #$20
lda {temp2}
sta {dma_bank},y
sta {dma_bank}+2,y
sta {dma_bank}+4,y
sta {dma_bank}+6,y
lda #$80
sta {dma_legnth},y
sta {dma_legnth}+2,y
sta {dma_legnth}+4,y
sta {dma_legnth}+6,y
rep #$20
txa
and #$01ff
asl #4
sta {dma_destination},y
adc #$0100
sta {dma_destination}+2,y
adc #$0100
sta {dma_destination}+4,y
adc #$0100
sta {dma_destination}+6,y


tya
adc #$0008
tay
lda #$0000
sta {dma_legnth},y
sty {dma_updates}

ply
txa
plx
rts


small_slot:

ldx {small_slot_stack_index}
dex
dex
bpl +
lda #$ffff
plx
rts
+;
lda {small_slot_stack},x
stx {small_slot_stack_index}

tax
phy
ldy {dma_updates}



lda {temp}
clc
adc {temp3}
sta {dma_address},y
adc {vram_width}
sta {dma_address}+2,y

sep #$20
lda {temp2}
sta {dma_bank},y
sta {dma_bank}+2,y
lda #$40
sta {dma_legnth},y
sta {dma_legnth}+2,y
rep #$20
txa
and #$01ff
asl #4
sta {dma_destination},y
adc #$0100
sta {dma_destination}+2,y


iny #4

lda #$0000
sta {dma_legnth},y
sty {dma_updates}

ply
txa
plx
rts

no_slot_to_clear:
rts

clear_vram_slot:

ldy {metasprite}
beq no_slot_to_clear

ldx {frame_id}
beq no_slot_to_clear
lda {animation_copies},x
beq +
dec
sta {animation_copies},x
bne no_slot_to_clear
+;


lda {animation_chr},x
tax

-;

cpx #$ffff
beq no_slot_to_clear
lda {sprite_size},x
beq no_slot_to_clear
tay
phx
txa
cpy #$0002
beq +
ldx {small_slot_stack_index}
sta {small_slot_stack},x
inx
inx
stx {small_slot_stack_index}

bra ++
+;
ldx {large_slot_stack_index}
sta {large_slot_stack},x
inx
inx
stx {large_slot_stack_index}

+;

plx
lda #$0000
sta {sprite_size},x
lda {sprite_name},x
tax
jmp -
 Basically what it does, is compares the previous metasprite with the current one, or checks if the animation update flag is set, to see if there is a change in animation frame.  When there is, it looks at the size of the metasprite to keep track of DMA usage.  If it spills over 4kB in one frame, it doesn't update it until the next frame.  If there is enough DMA time, it clears the previous animation frame from the tables (see below).

Then it looks at the metasprite's frame number, uses that as an index to a super long list holding both the CHR bits for the first sprite, and the amount of copies that frame has.  If there were no copies of that frame already being used, it creates a linked list table for it.  First it finds an unused CHR name for the first sprite (0-127 are for 16x16 sprites, 128-511 are for 32x32 sprites), then it uses that number as the entry point for the vram linked sprite table, where every CHR number has a cooresponding X-displacement, Y-displacement, attributes word, and the CHR number for the next sprite in the frame/metasprite.

Clearing the animation frame is basically getting all the sprites off the linked list, and putting them back on the list of empty vram slots.  Displaying an animation frame, involves going through the linked list, adding the X and Y coordinates, and putting it on the OAM.

If you're reading this, I've probably lost you, and that is the problem with programming.  I share my codes with people so they don't have to reinvent the wheel, but when I do, it confuses the shit out of them.

Friday, July 29, 2016

...and it only took 7 years for me to get around the Super Nintendo's limited VRAM!

Hello, I'm Andy Koenigs.  You may know me as Aaendi or psycopathicteen.  I used to have an SNES development blog, until Google took over blogger.com, and now every time I try logging into my old blog it logs me into my Google account instead.

Anyway, when I get done with my SNES game, I want to try a new system.  Possibly the Sega Genesis just because the system looks a lot easier to develop for.  I also want to learn C, so I no longer have to go a snail's pace.  The only reason I'm still doing SNES development is that I've already got so far with it that I don't want to give up all my hard work.  Plus, I want to see that one final blow to the myth where the SNES can't handle action games without slowdown, or handle multijointed bosses and such.

What I'm glad about is that I finally have an animation engine that:

1)Doesn't limit sprite size

2)Doesn't require letterboxing

3)Doesn't have a fixed framerate

4)Doesn't limit the number of sprites onscreen

and

5)Doesn't required special case exceptions

I still need to convert a couple animated sprites to my new animation scheme, and then get rid of the vestigial/artifact code that I no longer need anymore.  It's still not as processor efficient as it could be, but I have a plan to fix that.  I'll post my code just so other people can use it for their projects if they don't want to start one from scratch.