| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Sprite multiplexing *with* priorities
I'm willing to port a game on the C64 but for sure I'll need more than 8 sprites per screen. The game uses a top-down perspective so sprites that are southern on the screen must show above the sprites that are nothern, else it will look weird.
Unfortunately, most sprite multiplexing tehniques I've seen arround the net seems to kill any priorities, and that's not good.
I guess I can come up with a solution, but I'm not sure how good it is :
- The screen is sepearated into 10 areas of 20 pixel tall
- There is a raster interrupt each 20 pixels that will handle the multiplexing, and the interrupt at line 250 will also be used for the other usual stuff (screen updates, sound code, etc...)
- Each sprite always crosses 2 areas no matter where they are positionned (this assumes Y expanding is never used)
Now during the frame :
In the first area (lines 0-20), the sprites are mapped to hardware sprites with regards to their sorted priorities. If there is more than 8 then the lowest pirority is discarded.
In the second area, the sprites that were already used in the first area cannot be used, so I map the sprites that are still unused to the sprites starting in the second area, regarding to their priorities.
In the third area, all sprites that were used in the first are done being displayed and I can re-map them to sprites of the third area according to their priorities, etc....
Now the priorities are not matched between areas, but at least they are inside each area. Also the number of interrupts is constant, and everything can be pre-calculed during the non-display time, so that interrupts just do a few writes each 20 scanlines which is not too bothersome and allow it to be mixed with other raster split without too much trouble.
I know Cadavers said it was a bad idea to split the screen with areas, but I can't come with anything else. I could use the generic method in a way so that top sprites are lowest priorities than bottom sprites, and hoping it won't look too weird. |
|
| |
Richard
Registered: Dec 2001 Posts: 621 |
Why not try http://codebase64.org/doku.php?id=base:sprites
There's plenty of information about sprite multiplexing, which should hopefully help you :) |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
"sprites that are southern on the screen must show above the sprites that are nothern, else it will look weird."
so display the sprites in the right order for that (8->1)? at every 8th sprite it will bug, but it comes for free.
edit: and read cadaver's sprite multiplexing tutorial. zone split with fixed irq places for this is a bad approach. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
If you want some extra insurance against the wrap-bug you could reset the physical-sprite assignment index back to 8 (= least priority), whenever you have no overlapping sprites for a while. Never done this in practice, but theoretically it should work?? |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
I have read Cadaver's tutorial before posting this, don't worry. But it doesn't cover the priority part.
I know he said the area split is a bad idea, but why ? He said it worked for Metal Warriors 2/3 (is that right ?) and I like the idea of having fixed IRQ at fixed places which sounds easier than to have an IRQ for each new sprite which means a lot of IRQs and more likely to get glitches.
Yes I could make it so that sprites goes from 8 to 1 and then reset to 8 so that the priority would look mostly good. If I can somoehow detect that there is no overlapping sprites for a while, I could reset the index to 8. I guess I could try that as well and see how it turns. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
Actually Metal Warrior 1-3 never used zone splitted or fixed IRQ's, but an inaccurate bucket sorting method, where Y was simply divided by 8, and that's what the tutorial was referring to if I remember right.
|
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
resetting to sprite nr 8: all you have to do is to search for a virtual sprite which is atleast 21 lines away from the "currently" used sprite 8. so if you hit sprite 8, then look up the next 8 virtual sprites and check if one of them is farther than 21 lines if it is it can be resetted to physical nr 8. dynamic irqs are ESSENTIAL for this as they can pack sprites more densely and no further problems arise like caring about the fixed irq location. fixed irqs issue heavy limitations, if sprite nr 8 is used in one of them you have to wait 40 lines (worst case) for the next chance to use it. compare this to the constant 21 lines of dynamic irqs.
check the link, and spend your time understanding those multiplexers. its really not rocket science, also we're here to explain. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:Actually Metal Warrior 1-3 never used zone splitted or fixed IRQ's, but an inaccurate bucket sorting method, where Y was simply divided by 8, and that's what the tutorial was referring to if I remember right.
OK this clear things up thank you for pointing that.
In my current version of the game (on the NES) I use a modified bubble sort on all object so that their sprites are done in an ascending order (southern object with top priorities), and also uses a divide-by-8 method. If both coordinates divided by 8 are equal, I exchange them on odd frames only so I get nice flickering (something necessary on the NES if there is more than 8 sprites on the same line) and it works fine.
I guess chances are that you experienced guys are right about that area split method is bad, but I'd like to know why exactly. It does sound like in some case it would be bad, for example if there is 8 sprites in an area and 2 in the area below it, the latter 2 won't show at all if I do it in the way I describe it above. Is it why it is bad ?
So I guess I'm listening you and am going to use dynamic IRQs. Fortunately I wasn't planning to do any raster effecs (other than sprite multiplexing) during gameplay, but I plan to do some outside of gameplay. In that cases it would be a headache to try to have both dynamic IRQs and static IRQs for BG background effects, so I would be limited to 8 sprites here. This isn't too much bothering, but I'd then have to have 2 sprite engines and I don't like that but it doesn't matter I'll deal with that. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
you can use the CIA timers for 2 more interrupt sources, and more effects on the screen.
"if there is 8 sprites in an area and 2 in the area below it, the latter 2 won't show at all if I do it in the way I describe it above"
thats why its bad. dynamic irqs can show all 10 sprites in that case. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
OK I've been working on it and it's really insane to get something working. But I'm close to it I guess, but I have problems with how the interrupt fires. I do the following :
I always fire an interrupt at line 255, that I call the "VBlank" interrupt.
At the start of the interrupt, the sprites for the frame to come are sorted, and everything is pre-calculated and copied into buffers for the following frame, so that the main code can maze new sprites while the old are displayed.
A couple of lines before any sprite, an interrupt is triggered and the data for that particual sprite is copied to the actual VIC registers. After that it checks if the following interrupt is very close and if so it loops automatically without having a second interrupt to happen. If the next interrupt is the VBlank and is very close I jump to it automatically too.
The problem I have is that right after I do a "cli" instruction in the VBlank interrupt (after sorting all sprites, assigned them a hardware number and initied the next interrupt), the first interrupt fires straight away even if it's not time yet. Also the VBlank interrupt was already acknownledged before I do that cli so this shouldn't be the problem. This happen on scanline 270 or so, and the interrupt is supposed to happen 4 lines before the first sprite.
I will post the code that causes me problems (I can't post the whole programm because it would be too long). This is what happen after all sprites are sorted, their hardware positions are determined and the IRQ lines that will happen are decided. In case of the variable "SpriteRasterIndex" is here to determine what causes the next IRQ, if it's a positive number it's the number of the sprite that caused the IRQ, if $ff it's for VBlank interrupt.
- ldy SortOrder,X ;Copy Y pos, X posL, Color and Tile Number to sorted buffers
lda SpritePosY.w,Y
sta SpriteSortedPosY.w,X
lda SpritePosXL.w,Y ;So that they are accedded faster during IRQ
sta SpriteSortedPosXL.w,X
lda SpriteFlags.w,Y ;And can be written again during the frame by the main programm
and #$0f
sta SpriteSortedFlags.w,X
lda SpriteTileNmr.w,Y
sta SpriteSortedTileNmr.w,X
dex
bpl -
inx
stx SpriteReadyFlag ;Clear the pending flag
OldSprites
ldy #$00
- lda InterruptLine.w,Y ;Load the first valid interrupt (nonzero)
bne +
iny
cpy #SpriteAmount
bne - ;If no valid interrupt
lda #$ff ;...we set $ff for VBlank interrupt
+ sta $d012 ;Initislise the first interrupt
cmp #$ff
beq +
tya
+ sta SpriteRasterIndex ;The next IRQ is a sprite IRQ if line is not $ff, else it's the VBlank IRQ
stx $d015 ;No sprite visible before the first interrupt
cli ;Enable the interrupts
rts
Here at the CLI the first interrupt occurs immediately.
PS : Where can I found info about the CIA timers ? I can't find any in Commodore's doccumentation nor on the Wiki. I only found how to disable them (and I do that). Are they precise enough to get some screen effects ? Can they be routed on the NMI instead, because here I already have two IRQ possiblilities (sprite/VBlank), if there were 3 it would become really a headache. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
you should show the interrupt handling code instead of the sprite messing. maybe there should be an rti instead of the rts? and if so you dont restore (&save) the registers. also you dont need to do cli. rti will clear the I flag.
furthermore, do you realsie that the raster counter is 9 bit wide, including the topmost bit in d011 ? if you write d011 it can set the 9th bit of the raster counter to high, and the irq will occuer at >255 line. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Yeah I write something to $d011 with bit 7 clear so that I always trigger interrupts in the visible part of the screen (or on the bottom of the screen). Do I have to re-write $d011 regularly to prevent interrupts to be triggered during VBlank ? And yes I do a cli instruction so that after the sprites are sorted and ready to be displayed, the VBlank IRQ can continue and be interrupted by sprite IRQ if it needs so. Without the cli, the sprite IRQ would be blocked as long as the VBlank IRQ does not return.
I can post the IRQ handler if you want :
IRQ inc $d019 ;Acknownledge the raster IRQ
pha
txa
pha
tya
pha ;Check if the IRQ is caused by the sprite multiplexing
ldx SpriteRasterIndex
bpl SpriteIRQ ;Or by the end of the frame (raster position $ff)
MainIRQ
jsr SpriteMultiplexer
jsr InitSprites
jsr SetSprites
; jsr MoveStars
jsr MoveCenter
lda FrameCtr
eor #$01
sta FrameCtr
pla
tay
pla
tax
pla
NMI rti
SpriteIRQ
ldy SpriteHWAssign.w,X
lda SpriteSortedFlags.w,X
sta $d027,Y ;Color
lda SpriteSortedTileNmr.w,X
sta $7f8,Y ;Tile number
tya
asl A
tay
inc $d020
lda SpriteSortedPosY.w,X
sta $d001,Y
lda SpritePosXL.w,X
sta $d000,Y
lda D017Buffer.w,X
sta $d017
lda D01dBuffer.w,X
sta $d01d
lda D01bBuffer.w,X ;Copy all flags
sta $d01b
lda D01cBuffer.w,X
sta $d01c
lda D015Buffer.w,X
sta $d015
lda D010Buffer.w,X
sta $d010
dec $d020
- inx
cpx #SpriteAmount ;Last sprite ?
bcs _lastInt
lda InterruptLine.w,X
beq - ;If raster line is 0, we should ignore it
stx SpriteRasterIndex ;Copy valid index and rasterline
sta $d012
cmp #$ff
beq _lastInt ;If value is $ff, then it's the last interrupt of the frame
sec
sbc #$02 ;If a new interrupt should occur in less than 2 scanlines
cmp $d012 ;Do it straight away
bcs +
jmp SpriteIRQ
-- lda #$ff
cmp $d012
bne _lastInt
- jmp MainIRQ
_lastInt
bit $d011
bmi - ;If we somehow missed the main IRQ make sure we jump to it right away
lda #$fd
cmp $d012 ;If rasterline is $fe or $ff go to main IRQ after a small delay (I don't want to miss it)
bcc --
lda #$ff
sta $d012
sta SpriteRasterIndex
+ pla
tay
pla
tax
pla
rti
|
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
d011 works like d012 when it comes to the raster counter, so once you cleared the topmost bit its fine.
looks like the bug is caused by the complicated irq handling. I'd remove all the logics which differentiates vblank and makes sure it happens. instead simply fire the vblank irq at a position where it will surely not interfere with a sprite irq (bottom border+some lines).
hmm after you set the d012 for the next sprite irq why do you jump back to mainirq (vblank?) you should just restore regs and rti I guess.
maybe you forget that you can change the irq vector inside irqs, so after the last sprite irq you can simply point it to the vblank irq to happen. you could also have 8 irqs chained for writing the 8 hw sprites instead of indexing the vic spr regs.
CIA interrupts:
http://unusedino.de/ec64/technical/aay/c64/ciamain.htm
you have two timers A&B in each CIA. they countdown 1 with each clock cycle (if you havent set them to count the underflow of nother timer). you can use the control registers to start stop the timers & the irq register to ask cia to generate IRQ when a timer underflows.
CIA at dc00 will generate IRQS the one at $DD00 will generate NMIS.
by careful timing you can use cia#2 as a 2nd raster interrupt source. |
| |
Cruzer
Registered: Dec 2001 Posts: 1048 |
You could also mask the unwanted gfx away software-wise from the sprites with wrong priority.
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:
looks like the bug is caused by the complicated irq handling. I'd remove all the logics which differentiates vblank and makes sure it happens. instead simply fire the vblank irq at a position where it will surely not interfere with a sprite irq (bottom border+some lines).
Well the logic is a little complicated as you say but it's cadaver who said in his doc that you must make sure not to be late from the next IRQ else the screen will flicker and I belive he's right. If two sprites are on the exact same line the programm will try to do a second IRQ on the same line, which will probably not happen correctly and it will wait the IRQ of the next frame, and this is really bad.
However you are right that the logic that detects if I'm late from the VBlank interrupt and jump to it is probably not necessary, as the lower position possible for sprites is $f9 (so the IRQ should happen arround $f5), and the NMI is at $ff. Even if there is like 8 sprites at $f9 this shouldn't happen, so I might as well remove it. Maybe I messed something up and this added bugs. I will try that right away and edit that post to say the results. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Oh it seems it is impossible to edit posts. This is a shame. Anyway, I've removed the useless logic to simplfiy things a little, but it changes nothing, there is still something wrong. But you were right that I had to remove that. Here you are the new code :
ldy SpriteHWAssign.w,X
lda SpriteSortedFlags.w,X
sta $d027,Y ;Color
lda SpriteSortedTileNmr.w,X
sta $7f8,Y ;Tile number
tya
asl A
tay
inc $d020
lda SpriteSortedPosY.w,X
sta $d001,Y
lda SpritePosXL.w,X
sta $d000,Y
lda D017Buffer.w,X
sta $d017
lda D01dBuffer.w,X
sta $d01d
lda D01bBuffer.w,X ;Copy all flags
sta $d01b
lda D01cBuffer.w,X
sta $d01c
lda D015Buffer.w,X
sta $d015
lda D010Buffer.w,X
sta $d010
dec $d020
- inx
cpx #SpriteAmount ;Last sprite ?
bcs _lastInt
lda InterruptLine.w,X
beq - ;If raster line is 0, we should ignore it
cmp #$ff
beq _lastInt ;If value is $ff, then it's the last interrupt of the frame
stx SpriteRasterIndex ;Copy valid index and rasterline
sta $d012
sec
sbc #$02 ;If a new interrupt should occur in less than 2 scanlines
cmp $d012 ;Do it straight away
bcc SpriteIRQ
bcs +
_lastInt
lda #$ff
sta $d012
sta SpriteRasterIndex
+ pla
tay
pla
tax
pla
rti
I'd like to know a bit more about acknownledging. Do you have to wait that the raster moves to the next scanline to acknownledge, or else the IRQ will remain low because it it still to the same line as the compare value ? If so it would explain all my bugs.
The CIA timer 2 seems good for raster effects, so that I could have the sprite multiplexer (if I ever get it working) always enabled, and NMIs would be used for other possible effects (and take the higest priority). Not putting sprites right after the split point is the responsability of the main programm (else they won't be displayed because the IRQ will be fired too late).
About changing the IRQ vector in the IRQ routine, yeah this is possible but I don't know what I'd really gain from it. I could possibly make something more reliable with that method, I'll think about it if I ever found my bug. It would not change the logic but just move it to another place.
Writing a different piece of code for each hardware sprite is possible, but I think it's really a bad idea. It would make the code much bigger to load into RAM and you gain nothing from it. LDA is the same number of cycles when indexed or not as long as no page boundary is crossed. STA is one more cycle when indexed, but I only have 4 indexed STA in the IRQ code. It would save 4 cycles, but you'd have to change an jump instruction or the IRQ vector each time, which would take more than 4 cycles so you'd end up with a bigger *and* slower code !
For the priorities I guess I came with a good algorithm. The first sprite is always #7, and for the following one, I detect if it overlaps with the previous (with some margin of about 4 pixels). If it doesn't overlap, it's index it #7. If it does overlap, then the index is one less than the previous index, ANDded with #7. Then I detect if it overlaps with the prveious sprite using the same idex. If it does, the sprite is rejected and not displayed (#0 is written for it's IRQ line to say it's not a valid number). |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
Acknowledging raster IRQs is done by setting bit 0 of $D019. The clean way would be LDA $D019 STA $D019, but on 6502 you can also use INC $D019.
Acknowledging can be done anywhere in the IRQ, but you might kill some raster IRQs if you do it late. The routine you show up there already eats ~3 rasterlines alone, so if you have "an IRQ the next rasterline" and acknowledge it at the end of that IRQ, the next-rasterline IRQ will never happen. If you do it early, the IRQ will happen but atleast 2 rasterlines too late due to RTI being so late.
You can allow IRQs within another IRQ by clearing the IRQ-flag with CLI. Sometimes that might be useful, but in your case it will probably lead to a number of side effects.
|
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
I see no possible bugs now. the next candidate is the routine setting up InterruptLine.w. I'd set up sprites at fixed positions and check the code at strategic breakpoints if it sets up d012 correctly, if its running on the rasterline its supposed to be, spriterasterindex is crucial aswell. a peek at $d011 if topmost bit is cleared could be done aswell. hm, also cpx #lastsprite is a suspect, usually you dont have a constant nr of sprites displayed in a multiplexer.
I'd still prefer 'chained' irqs as it makes the flow of your code more understandable: no check for vblank at each irq, just change vector to vblank after the last sprite, should spare some cycles if enough sprite irqs are present.
hmm also checking both sprite nr and interruptline.w for last sprite looks to be too much, one of them should be enough. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:
Acknowledging can be done anywhere in the IRQ, but you might kill some raster IRQs if you do it late. The routine you show up there already eats ~3 rasterlines alone, so if you have "an IRQ the next rasterline" and acknowledge it at the end of that IRQ, the next-rasterline IRQ will never happen. If you do it early, the IRQ will happen but atleast 2 rasterlines too late due to RTI being so late.
Oh my god I just realised this was sadly true. The C64 is really slugglish slow !
So yeah I had to modify my IRQ so that it becomes as fast as possible. I save the reigsters to Z-Page variables instead of the stack, and instead of doing double-comparaison for the last IRQ, I ended up increasing the size of InterruptLine by one and make sure the last element is always $ff, so that if all sprites are used it detects the last sprites the same way as usual. I could always move some more stuff to zeropage, but it will end up eating too much of it and that's not good.
I also change the IRQ vector so that it points to either routine I need and this works great. I now only have VBlank interrupts at SL 255 which is great (I had false VBlanks before).
Unfortunately it's not working as I'd like it to. Some sprites seems completely misplaced on the X axis. It could be a bug in the main programm, but I'm pretty sure it worked fine with only 8 sprites, so there is no reason the number of sprites would add bugs. Maybe I write to the registers in the wrong order ? I haven't tested to individually play with the flags such as multicolor or X/Y expansion, but I plan to.
Here you are the latest version, with two possible IRQ entry points :
IRQ inc $d019 ;Acknownledge the raster IRQ
pha
txa
pha
tya
pha
jsr SpriteMultiplexer
jsr InitSprites
jsr SetSprites
jsr MoveStars
jsr MoveCenter
lda FrameCtr
eor #$01
sta FrameCtr
pla
tay
pla
tax
pla
NMI rti
SpriteIRQ
inc $d019
sta SpriteStoreA
stx SpriteStoreX
sty SpriteStoreY
ldx SpriteRasterIndex
_loop
ldy SpriteHWAssign.w,X
lda SpriteSortedFlags.w,X
sta $d027,Y ;Color
lda SpriteSortedTileNmr.w,X
sta $7f8,Y ;Tile number
tya
asl A
tay
lda D017Buffer.w,X
sta $d017
lda D01dBuffer.w,X
sta $d01d
lda D01bBuffer.w,X ;Copy all flags
sta $d01b
lda D01cBuffer.w,X
sta $d01c
lda D015Buffer.w,X
sta $d015
lda D010Buffer.w,X
sta $d010
lda SpritePosXL.w,X
sta $d000,Y
lda SpriteSortedPosY.w,X
sta $d001,Y
- inx
lda InterruptLine,X
beq - ;If raster line is 0, we should ignore it
sta $d012
cmp #$ff
beq _lastInt ;If value is $ff, then it's the last interrupt of the frame
stx SpriteRasterIndex ;Copy valid index and rasterline
sec
sbc #$03 ;If a new interrupt should occur in less than 2 scanlines
cmp $d012 ;Do it straight away
bcc _loop
bcs +
_lastInt
lda #<IRQ
sta $fffe
lda #>IRQ
sta $ffff
+ lda SpriteStoreA
ldx SpriteStoreX
ldy SpriteStoreY
rti |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
You should limit the IRQs to something like every 8th rasterline. That way you have enough time to finish the IRQ before the next one will start and avoid a lot of problems. And yes, the C64 is very slow :)
|
| |
JackAsser
Registered: Jun 2002 Posts: 2014 |
Completely unrelated to sprite multiplexing, but saving clobbered registers to the ZP is kinda wasting ZP-space. Simply selfmod the code instead:
sta areg+1
stx xreg+1
xty yreg+1
...
areg: lda #$00
xreg: ldx #$00
yreg: ldy #$00
rti
Also, a HCL 'classic' is to just update $fffe if you know the two interrupt service routines fit into the same page.
Also since you catch the BEQ case after the CMP #$ff you know that the carry will always be clear. Simply remove the SEC and adjust the SBC-value accordingly.
|
| |
HCL
Registered: Feb 2003 Posts: 728 |
@JA: Oh i got a 'classic' for that ;). And while we're off topic: storing registers to zp saves 3 bytes of code for each interrupt!!
(Sorry for interrupting this interesting thread.)
|
| |
Ninja
Registered: Jan 2002 Posts: 411 |
Jack: There is no definitive way to store away registers in the beginning of an interrupt. Depending on the routines, I use this or that option, sometimes another one...
And LOL about the 'HCL-classic' :D I have to admit I prefer to use the 'Crossbow classic', who uses the accumulator to work with 8-bit values ;)
|
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
JA: been using that "classic" and not knowing HCL exists.
Bregelad: I dont see the irq vector re-routing in the vblank. The X coord bug must come from the main prg, or bad d010 handling. you can save time by setting less sprite attributes, sprite pointer/x/y/color is enough for a general case. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:You should limit the IRQs to something like every 8th rasterline. That way you have enough time to finish the IRQ before the next one will start and avoid a lot of problems. And yes, the C64 is very slow :)
How I am supposed to do that ? AND the IRQ line with #$f8 or something that way ? Would that remove bugs ?
@Jack asser : This is really awesome, I did all those mod to my programm so that it is faster.
@Oswald : You don't see the IRQ being changed to sprite IRQ because this is done inside the "SpriteMultiplexer" routine.
I guess the bugs could be the main programm but I doubt that, the 8-sprite version of it is working fine, and the main programm is identical (exept it writes to arrays instead of registers). Even if I restrict the number of stars to 8 so that the programm is exactly the same as before I still get bugs.
I tried to write to registers in many orders but it doesn't make any differences. Should be my $d010 handling, but I double checked it and it seems ok.
Also how many scanlines am I supposed to set my IRQ before the sprite ? I guess 3 is the bare minimum, but in the worst case where I have to rewrinte all 8 sprites on the same line, it would need about 16-24 scanlines before so that I have time to rewrite all 8 ! This sounds like an enormous amount. Having a big margin like that may remove some bugs when many sprites are on the same line, but add other bugs as sprites are much more likely to overlap and to be rejected as there is more than 8 on a line.
When it comes to rewrite less flags, I don't know... If it doesn't allow to set all flags individually for each sprite, it's not a true general multiplexer. Almost all multiplexer examples I've seen does not allow Y-expanding for example. On the other hand I've just figured that those IRQ really needs to be fast, so I don't know.
The latest version so far...
SpriteIRQ
inc $d019
sta _areg+1.w
stx _xreg+1.w
sty _yreg+1.w
ldx SpriteRasterIndex
_loop
ldy SpriteHWAssign.w,X
lda SpriteSortedFlags.w,X
sta $d027,Y ;Color
lda SpriteSortedTileNmr.w,X
sta $7f8,Y ;Tile number
tya
asl A
tay
lda SpritePosXL.w,X
sta $d000,Y
lda SpriteSortedPosY.w,X
sta $d001,Y
lda D010Buffer.w,X
sta $d010
lda D017Buffer.w,X
sta $d017
lda D01dBuffer.w,X
sta $d01d
lda D01bBuffer.w,X ;Copy all flags
sta $d01b
lda D01cBuffer.w,X
sta $d01c
lda D015Buffer.w,X
sta $d015
- inx
lda InterruptLine,X
beq - ;If raster line is 0, we should ignore it
sta $d012
cmp #$ff
beq _lastInt ;If value is $ff, then it's the last interrupt of the frame
sbc #$02 ;If a new interrupt should occur in less than 3 scanlines
cmp $d012 ;Do it straight away
bcc _loop
stx SpriteRasterIndex ;Copy valid index and rasterline
bcs +
_lastInt
lda #<IRQ
sta $fffe
; lda #>IRQ
; sta $ffff
+
_areg lda #$00
_xreg ldx #$00
_yreg ldy #$00
rti |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Oh my I found the X position bug and it is *SOOOO* stupid.
In my IRQ I put "lda SpritePosXL.w,X" instad of "lda SpriteSortedPosXL.w,X", and that was causing all the mess. Now it works perfectly. |
| |
Ninja
Registered: Jan 2002 Posts: 411 |
Two minor hints:
1) This reduces the need to modify the code by hand (check with your assembler syntax):
if (>IRQ != >SpriteIRQ)
lda #>IRQ
sta $ffff
endif
2) If you abuse a preset carry, I usually write the following command like this (for your case):
sbc #3-1
instead of sbc #2. So, I easily see which value I really want to use and that I might need to make adjustments if the code around this instruction changes...
|
| |
JackAsser
Registered: Jun 2002 Posts: 2014 |
Quote: Jack: There is no definitive way to store away registers in the beginning of an interrupt. Depending on the routines, I use this or that option, sometimes another one...
And LOL about the 'HCL-classic' :D I have to admit I prefer to use the 'Crossbow classic', who uses the accumulator to work with 8-bit values ;)
Offtopic: "And LOL about the 'HCL-classic' :D I have to admit I prefer to use the 'Crossbow classic', who uses the accumulator to work with 8-bit values ;)"
Please elaborate on the 'Crossbow classic' because for me the accumulator has always worked with 8-bit values... :) |
| |
WVL
Registered: Mar 2002 Posts: 902 |
Quote: Offtopic: "And LOL about the 'HCL-classic' :D I have to admit I prefer to use the 'Crossbow classic', who uses the accumulator to work with 8-bit values ;)"
Please elaborate on the 'Crossbow classic' because for me the accumulator has always worked with 8-bit values... :)
He means everybody does it ;) |
| |
doynax Account closed
Registered: Oct 2004 Posts: 212 |
Quote:Also how many scanlines am I supposed to set my IRQ before the sprite ? I guess 3 is the bare minimum, but in the worst case where I have to rewrinte all 8 sprites on the same line, it would need about 16-24 scanlines before so that I have time to rewrite all 8 ! This sounds like an enormous amount. Having a big margin like that may remove some bugs when many sprites are on the same line, but add other bugs as sprites are much more likely to overlap and to be rejected as there is more than 8 on a line. Or you could look at the whole timing problem from a slightly different perspective. That is you might opt to program the sprites as early as possible rather than as late as possible, e.g. right after the previous shape has finished with the sprite channel you're attempting to reuse. That way there's no need to estimate raster timing at all, with the drawback that some sprite which would otherwise be displayed imperfectly simply won't show up at all.
As for speeding up the interrupt handler itself about the best you can do is to unroll it completely and keep a separate interrupt handler per-sprite. That way you can poke sprite values directly into LDA immediates and STA to absolute addresses instead of indexing anything. Whether or not the cycle savings are worth the space depends on your game and how tightly you'll want to pack the sprites, but I've found it a useful method.
Oh, and you might want to clear the decimal flag before doing any arithmetic in an interrupt handler. Or not. Just beware of the issue as it can lead to some nasty bugs if you're not careful. |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
Nobody uses the decimal mode so no need to clear the decimal flag.
|
| |
doynax Account closed
Registered: Oct 2004 Posts: 212 |
Quote: Nobody uses the decimal mode so no need to clear the decimal flag.
What.. No one has figured out a way to save cycles by using BCD arithmetic in a bithack?
Anyway, I managed to get bitten by this myself a while back.
Once in a blue moon my game would glitch for no apparent reason. Eventually it turned out (after *many* hours of debugging) to be caused by an interrupt being triggered during the dozen or so cycles it took to update the player's score counter. Making things worse was the fact that the collision detection logic would normally have finished running long before the visible display area, so it only ever happened during the the busiest screens. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Well that decimal flag is really evil but I do never use it for now, and I belive the Kernal clears it for me.
Quote:Or you could look at the whole timing problem from a slightly different perspective. That is you might opt to program the sprites as early as possible rather than as late as possible, e.g. right after the previous shape has finished with the sprite channel you're attempting to reuse. That way there's no need to estimate raster timing at all, with the drawback that some sprite which would otherwise be displayed imperfectly simply won't show up at all.
I've considered that, but it only works if sprites are never Y-expanded. If some sprites are Y-expanded and some aren't, the place where each sprite ends could not be in the same order as the order where each sprite starts. As the result you'll have to ressort to 2 sorting routine, one to sort the start coordinates as usual, and a second to sort the end coordinate which are the same plus 21/42. So it would become significantly more CPU intensive during VBlank, and more complicated logic overall. Altough you'd save the mess with $d015, as all sprites could be always enabled, and write the first 8 active sprites during VBlank.
Quote:
As for speeding up the interrupt handler itself about the best you can do is to unroll it completely and keep a separate interrupt handler per-sprite. That way you can poke sprite values directly into LDA immediates and STA to absolute addresses instead of indexing anything. Whether or not the cycle savings are worth the space depends on your game and how tightly you'll want to pack the sprites, but I've found it a useful method.
I don't plan to pack up sprites a lot, but I'd just like that if that happen the programm does not crash or slow down, just graphic glitches are acceptable.
I don't know how you could "unroll" anything since there is no loop. The only "loop" there is is finding the next valid IRQ position, which normally is the next one unless more than 8 sprites tries to coexist on one line and were rejected.
Again I don't see how I could have one separate IRQ per sprite and have any advantage in doing that. You'll have to show me some example code. Having one separate code for the 8 hardware sprites would only save 1 cycles when storing the frame number, color, X_Lo pos and Y pos, making a grand total of 4 cycles. The time lost in changing the IRQ vector for each sprite would probably be much greater (about 12-20 cycles) so I really don't see the point. And this would eat up so much space that you'll have to change $ffff each time as well.
Having one IRQ per software sprite could possibly save some time, as you could do lda immediate *but* not only you'd have to change the IRQ vector each time or end up using a jump table or something like that and it would also slow things down. Not to mention the arguemnt of lda immediate would be complicated to change inside the VBlank, and you'll have to have the almost exactly same code about 16-20 times in RAM, and I think it should be a waste to reserve up to 2-3 kb of memory for IRQ code only to save a couple of cycles. |
| |
doynax Account closed
Registered: Oct 2004 Posts: 212 |
Quote:I've considered that, but it only works if sprites are never Y-expanded. If some sprites are Y-expanded and some aren't, the place where each sprite ends could not be in the same order as the order where each sprite starts. As the result you'll have to ressort to 2 sorting routine, one to sort the start coordinates as usual, and a second to sort the end coordinate which are the same plus 21/42. So it would become significantly more CPU intensive during VBlank, and more complicated logic overall. Altough you'd save the mess with $d015, as all sprites could be always enabled, and write the first 8 active sprites during VBlank. General-purpose multiplexers rarely bother with any of the bit-packed attributes other than the x bits.
To handle y-expansion effectively I think you'll need to sort by both the top y coordinate and the bottom y coordinate anyway regardless of the interrupt method. At any rate the hardware sprites won't be reprogrammed in strict sequential order anymore.
Quote:Having one IRQ per software sprite could possibly save some time, as you could do lda immediate *but* not only you'd have to change the IRQ vector each time or end up using a jump table or something like that and it would also slow things down. Not to mention the arguemnt of lda immediate would be complicated to change inside the VBlank, and you'll have to have the almost exactly same code about 16-20 times in RAM, and I think it should be a waste to reserve up to 2-3 kb of memory for IRQ code only to save a couple of cycles. I meant using one interrupt handler per software sprite. Otherwise it might be hard to use immediate LDAs ;)
In my current project the multiplexer interrupts essentially look like this:
;roughly 75 cycles and 50 bytes per IRQ
irq0 sta irq_save_a
mux_x0 lda #$00
sta $d000
mux_c0 lda #$00
sta $d027
mux_p0 lda #$00
sta $07f8
mux_m0 lda #$00
sta $d01d
mux_y0 lda #$00
sta $d001
asl $d019
mux_l0 lda #$00
sta $d012
cmp $d012
bcc irq1+2
beq irq1+2
lda #<irq1
sta $fffe
; inc $ffff ;every fifth time or so..
lda irq_save_a
rti
irq1 sta irq_save_a
mux_x1 lda #$00
sta $d002
mux_c1 lda #$00
sta $d028
.
.
. On top of that there's a matching (unrolled) writer "loop" which goes through the sorted sprite list and pokes the sprite attributes into the immediate constants, compiles the most-significant x bytes, and a few other things. The nice thing is that it doesn't really cost me any extra cycles since I had to preserve the sprite data somewhere anyway.
Having lots of sprites flying around happens to be a core aspect of my game so it's easily worth the cost, but I can see how your project might be different. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:General-purpose multiplexers rarely bother with any of the bit-packed attributes other than the x bits.
Then I guess I made one of the first true general multiplexer. How could you call it a general multiplexer if it imposes some restrictions to your flags ? I have to admit that in-game sprites will mostly be multicolor, will mostly never be X or Y expanded and will mostly appear on the front of the background, but it's nice to have an engine that isn't that restricting, especially since I plan to have it enabled at all times.
PS : Commodore were really stupid they should have implemented X and Y *inversion* instead of expansion, it would be MUCH more usefull for in-game use. However I guess I can do inversion by software without having to store the graphics twice.
Quote:
To handle y-expansion effectively I think you'll need to sort by both the top y coordinate and the bottom y coordinate anyway regardless of the interrupt method. At any rate the hardware sprites won't be reprogrammed in strict sequential order anymore.
I don't see the problem as long as I make the interrupt before the sprite, since the starting coordinate is the same regardless if the sprite is Y-expanded. I only regard whether Y-expansion is enabled when computing if sprites overlap, and if there is more than 8 on a scanline.
Quote:On top of that there's a matching (unrolled) writer "loop" which goes through the sorted sprite list and pokes the sprite attributes into the immediate constants, compiles the most-significant x bytes, and a few other things. The nice thing is that it doesn't really cost me any extra cycles since I had to preserve the sprite data somewhere anyway.
Oh I see this is really fast. For now I guess I'll stick with the current version for a while, if I start to have problems because it's too slow I may consider unrolling things and/or not rewrite the flags that don't need to. |
| |
doynax Account closed
Registered: Oct 2004 Posts: 212 |
Quote:I don't see the problem as long as I make the interrupt before the sprite, since the starting coordinate is the same regardless if the sprite is Y-expanded. I only regard whether Y-expansion is enabled when computing if sprites overlap, and if there is more than 8 on a scanline Imagine that you put one y-expanded sprite on the first scanline. Then starting one line below that you add seven non-expanded sprites. Finally lets add yet another sprite 24 lines below that.
Now, the point here is that if you simply sorted by the topmost y coordinate and tried to use the sprites in sequential order then the multiplexer would try to use the same hardware sprite for both the topmost (y-expanded) software sprite and the final bottommost sprite. Clearly not optimal.
This might not be a problem if you only rarely use y-expansion, but then sorting by the bottom y coordinate and triggering interrupts according to that ought to give you much the same results as for the reuse-as-early-as-possible IRQ scheme. |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
Quoting BregaladThen I guess I made one of the first true general multiplexer. How could you call it a general multiplexer if it imposes some restrictions to your flags ? I have to admit that in-game sprites will mostly be multicolor, will mostly never be X or Y expanded and will mostly appear on the front of the background, but it's nice to have an engine that isn't that restricting, especially since I plan to have it enabled at all times.
On C64 you can hardly afford the luxury of "general purpose".
Quote:PS : Commodore were really stupid they should have implemented X and Y *inversion* instead of expansion, it would be MUCH more usefull for in-game use.
Without the expansion registers: sprite stretching, all border sprites, a lot of gfx modes etc etc wouldn't have been possible. X/Y inversion on the other hand only saves a bit of memory. I agree that expansion isn't often used on game sprites, but that doesn't make it useless.
Quote:However I guess I can do inversion by software without having to store the graphics twice.
I guess the routine is longer than those extra sprite gfx.
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:Imagine that you put one y-expanded sprite on the first scanline. Then starting one line below that you add seven non-expanded sprites. Finally lets add yet another sprite 24 lines below that.
Now, the point here is that if you simply sorted by the topmost y coordinate and tried to use the sprites in sequential order then the multiplexer would try to use the same hardware sprite for both the topmost (y-expanded) software sprite and the final bottommost sprite. Clearly not optimal.
Well, in the following case my algorithm will work that way :
- The expanded sprite is the first so it is assigned to sprite #7
- The following 7 sprites are overlapping with their respective previous sprites so they are assigned to sprite #6 down to #0
- The next srite 24 lines later does not overlap with the previous so it is assigned to sprite #7
- It checks if it overlaps with the previously used sprite #7, the answer is yes so this sprite is rejected.
So the sprite is rejected when it would in fact have been possible to draw it, but no graphic glitches should happen.
But I'm pretty sure I will rarely use Y or X expansion anyways, but I see no reason to forbid myself to use it.
Quote:
I guess the routine is longer than those extra sprite gfx.
Certainly not, in fact in most games most object can be seen in 2 directions, and one is the mirror of the other. It's a complete waste to store the same thing twice, only bit-inverted. You could use a 256-byte table if you want to be fast, and this is the same data as 4 inverted sprites, when you'll probably need many dozens. A routine that manually shifts the data would take probably about 30 bytes which is less than one single sprite.
In my case I will have 4 directions, Up, Down, Left, Right and only Left&Right are a mirror of eachother so not all graphics will need to be mirroered, but quite some. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
Quote:However I guess I can do inversion by software without having to store the graphics twice.
You keep forgetting c64 is not deamon of speed.
Usually having precalculated graphics, using some extra memory is the way to go. Oftentimes it makes a difference between running your game smoothly, or not. Or should i say running your game at all or not at all.
Manipulating your graphics data in realtime is something you want to avoid at all costs.
Unrolled routine to flip the sprite would take about (if my memory serves me right - havent been coding for years) 13*63 bytes, that's 13 full rasterlines (in the border, few more on the screen area, especially with sprites turned on)! You cannot afford that! Plus unrolled code would be much longer than 64 bytes, or if it wasn't unrolled it would take much more time. Plus there is 256 byte table you need to store somewhere.
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
If can be pretty fast that way :
ldy Data,X
lda HInversionTbl,Y
sta Data2+2,X
ldy Data+1,X
lda HInversionTbl,Y
sta Data2+1,X
ldy Data+2,X
lda HInversionTbl,Y
sta Data2,X
HInversionTbl
.db $00, $80, $40, $c0, $20, etc...
The HInversion Tbl could be created by software when the game starts to not have to load it from the disc. |
| |
Frantic
Registered: Mar 2003 Posts: 1648 |
This code is bigger than the data for a sprite anyway and would be required to be duplicated for each sprite since the adresses are hard coded. Perhaps you meant to use something like lda (source_zp),y instead? I have to agree with others here, that you seem to underestimate how slow the C64 really is, assuming that you are supposed to do this stuff at the same time as a lot of other game logic and graphics stuff is running.
I'd say, either you have time for this stuff, and in that case you would probably do a loop anyway, to save RAM, or you don't have time, and in that case you would use pre-flipped sprite data and just change sprite pointers instead of doing the flipping in real time. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
You can do evil things like realtime flipping, you just have to measure the CPU load as early as possible to figure how much of that you can actually do!
Certainly wouldn't use it for every sprite, but in MW4 I did realtime depacking & flipping for all small sprites: bullets / weapons / items etc. It actually working somewhat acceptably rested on the assumption that every small sprite wouldn't change its animation each screen frame :) |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
well, if he has the enemys coming not totally randomly he ofcourse can gain a lot with realtime spritemirroring with generating the phases for the upcoming attacks. another possibility to save vic bank memory is to have only 1-2 buffer sprites for the main character, and copy its animation frames there from another place for each frame. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
Quote: well, if he has the enemys coming not totally randomly he ofcourse can gain a lot with realtime spritemirroring with generating the phases for the upcoming attacks. another possibility to save vic bank memory is to have only 1-2 buffer sprites for the main character, and copy its animation frames there from another place for each frame.
Copying one sprite data is over 9 full rasterlines. IMHO should be avoided if possible.
@Bregalad: how many sprite cells do you need for your character? How many for enemies per level? there is 16 explosions, right?
What game are you trying to port? It might be easier to give us hints if we know what do you want to do exactly.
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
I'm planning to port a NES game I'm making (that is not finished yet, but the engine is finished and fully fuctionnal).
I know the graphics will have to be a little worsened to fit the C64's limitations. I normally can deal with 207 separate sprites loaded at a time, which is not bad (the NES can have 256, but they are 8x8 instead of 24x21 which is VERY different), and I have to half that number because of the mirroring issue.
I can have up to 8 enemies, 1 player and 8 exlosions at a time, but normally there is rarely so much. Many enemies are originally 16x16 or 16x24 pixels large, but this will have to change on the C64. Either I will squeeze the same graphics removing 4 columns so that they fit respectively 1 or 2 multicolor sprites, or I will keep the pixel art intact and make them looks stretched horizontally, and be 2/4 multicolor sprites. Or I could stack up hires sprites, but they would look tiny and that would do too many sprites.
The player is 16x26 pixels on the NES version so I'll either squeeze it in 2 multicolor sprites (degrading pixel art) or enlarge it to use 4 (will look streched and a big part of the sprites will be empty). At any rate, I'll see how many sprites it takes up, and if I have not enough space I could rewrite the player's sprite each time it changes (not every frame), and have all other sprites pre-loaded, this should work fine, since the players typically takes up a LOT of sprites. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
You could also try using hires sprites over multicolor sprites. You can get quite nice effects with that, but you would have to repaint all the sprites for that mode, and effectively you would have only 4 sprites.
Main hero in this game is using that thing, enemies are normal multicolor sprites: http://www.lemon64.com/?game_id=560 |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
Quote: Copying one sprite data is over 9 full rasterlines. IMHO should be avoided if possible.
@Bregalad: how many sprite cells do you need for your character? How many for enemies per level? there is 16 explosions, right?
What game are you trying to port? It might be easier to give us hints if we know what do you want to do exactly.
9 rasterlines is not much if you need to squeeze in more sprite data. |
| |
Iapetus/Algarbi/Wood
Registered: Dec 2004 Posts: 71 |
Quote: You could also try using hires sprites over multicolor sprites. You can get quite nice effects with that, but you would have to repaint all the sprites for that mode, and effectively you would have only 4 sprites.
Main hero in this game is using that thing, enemies are normal multicolor sprites: http://www.lemon64.com/?game_id=560
Were you thinking of Mayhem in monsterland perhaps?
http://www.lemon64.com/?game_id=560 |
| |
Playboy
Registered: Feb 2005 Posts: 20 |
Quote: Were you thinking of Mayhem in monsterland perhaps?
http://www.lemon64.com/?game_id=560
He meant this link:
http://www.lemon64.com/games/details.php?ID=1625[/url]
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:This code is bigger than the data for a sprite anyway and would be required to be duplicated for each sprite since the adresses are hard coded. Perhaps you meant to use something like lda (source_zp),y instead? I have to agree with others here, that you seem to underestimate how slow the C64 really is, assuming that you are supposed to do this stuff at the same time as a lot of other game logic and graphics stuff is running.
I don't want to dublicate one sprite, but almost all enemy sprites. And you could use self-modifying code to modify the arguement of lda $xxxx,X to simulate indirect adressing.
Quote:
well, if he has the enemys coming not totally randomly he ofcourse can gain a lot with realtime spritemirroring with generating the phases for the upcoming attacks. another possibility to save vic bank memory is to have only 1-2 buffer sprites for the main character, and copy its animation frames there from another place for each frame.
Yeah for the main charater this sounds a good idea. Too bad I don't know how much sprite it will do.
I don't think stretching the artwork from 16 pixel wide to 12 "multicolor-dash-pixels" would look very well, unless I can somehow make it looks good. Preserving the pixel art would be a good thing, but I can only stack up 3 hires sprites (won't look wide enough), or make multicolour sprites that are wider (will look too wide). In either case, it will become a waste, 8 pixel used in sprites that allow 12. On the good side this could speed the inversion up and make less graphics to load as the third byte would alway be zero...
Almost all characters have black outlines, it would be an idea to make the black outline hires and the colored stuff multi-color, and if the pixels of the colored stuff are slighly off it really shouldn't matter. Also since the borders are almost alway black outlines I could cheat a little with that. I guess I have to try different things and see how well it works. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
Quote:I don't want to dublicate one sprite, but almost all enemy sprites. And you could use self-modifying code to modify the arguement of lda $xxxx,X to simulate indirect adressing.
If there is 8 sprites and a hero using 2 sprites, and it happens that you need to flip them all at once, it would take 9*10 = 90 raster lines, that not counting code to modyfy flipping routine. You need a few interupts going, doing sorting, sprite multiplexing, playing music and running game logic. Are you sure there will be enough cycles left to do that all?
As for sprite graphics - i think the best way would be to repaint them for c64 with c64 restrictions in mind. Any other method would make them look bad.
You told us how do you intent to convert data, but my question was how many cells of animation you have for enemies and hero. And how many different enemies will there be per level? |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Quote:
As for sprite graphics - i think the best way would be to repaint them for c64 with c64 restrictions in mind. Any other method would make them look bad.
Yes you are entierely right. I'll do that, it would look bad to have a lot of sprites that aren't entierely filled. Altough in some way I'd still have to "port" the graphics at some point. Too bad so many graphics editor are for the 64 and not for a modern PC, and that many PC graphic editor does not allow 64 multicolor format (or use full-screen bitmapped-FLI stuff). Hires 1BP format is no match, but not very practical for in-game use.
Quote:
If there is 8 sprites and a hero using 2 sprites, and it happens that you need to flip them all at once, it would take 9*10 = 90 raster lines, that not counting code to modyfy flipping routine. You need a few interupts going, doing sorting, sprite multiplexing, playing music and running game logic. Are you sure there will be enough cycles left to do that all?
Well, I said only the hero's sprite would be rewritten in real time, so that's only 2 or 3 sprite, that wouldn't even be rewritten every frame. Enemy's sprite should be pre-loaded and flipped by software before the level begin (not in real time). That way I don't store the graphics twice, but this doesn't slow anything down.
I guess there would be about 4-6 different enemies per level, altough I haven't put a definite number on that.
Some palette swapping to get more different enemies is planned too. |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
Quoting BregaladAlmost all characters have black outlines, it would be an idea to make the black outline hires and the colored stuff multi-color, and if the pixels of the colored stuff are slighly off it really shouldn't matter. Also since the borders are almost alway black outlines I could cheat a little with that. I guess I have to try different things and see how well it works.
Hires outlines work quite well. A lot of games do that:
Stormlord
Target Renegade
The Untouchables
Nebulus
Platoon
Batman - The Movie
Firefly
Microprose Soccer
etc...
|
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
Quote:Too bad so many graphics editor are for the 64 and not for a modern PC, and that many PC graphic editor does not allow 64 multicolor format (or use full-screen bitmapped-FLI stuff). Hires 1BP format is no match, but not very practical for in-game use.
full-screen bitmapped-FLI stuff is not really usefull for games, except maybe title screen, but you can edit that format on PC easily with Timanthes Timanthes
There were some sprite editors released too but i don't remember them, as i never used them.
64 multicolor is no use on c64 as it only has 16 predefined colors.
There are a few good bitmap editors on pc, that are particularly good for making sprites and game graphics, Pro Motion is one of them, the other one is especially good if you do games for nintendo and nintendo ds, but i don't remember the name - it has $1000++ price tag so it wouldn't be of much use anyway.
-
If only hero sprite is going to be manipulated in realtime it might work. |
| |
WVL
Registered: Mar 2002 Posts: 902 |
For designing levels, you can also take a look at Charpad (it's in the csdb database) and Spritepad. |
| |
Peiselulli
Registered: Oct 2006 Posts: 81 |
Remembers me the time I programmed "Grand Monster Slam" ...
-Multiplexer with priorities ...
-Hires outlined sprites ...
-top-down perspective ...
-shortage of the 16K VIC because of sprites (I copied the dwarf animation every time)
Ahh, good old days ...
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Haha, the good old days.
The xxx-pad programms looks quite great. Thanks for pointing me to them.
Hires outlines seems a good technique but as long as some sprites use black and you do not want to use hires outline for them, you'll have to set a multi-color constant to black, so then it's kind of a "waste" to have one black hires outines. I'll think about that another day tough. |
| |
Graham Account closed
Registered: Dec 2002 Posts: 990 |
You can also set one of the sprite colors to black.
|
| |
WVL
Registered: Mar 2002 Posts: 902 |
Quote: You can also set one of the sprite colors to black.
Yes, but then the two other multicolor colors (d025 and d026) are the same for every sprite => all enemies same colors.. (assuming you dont want to change the black color).
It's best to set d025 to black or something, but this means that your main sprite will have one less color.. (black in d025, and again black in the hires sprite) |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
That's what I were saying. Probably the best color candidates for multicolor constants are $d025 = $0 (black) and $d026 = $7 (yellow), to handle outlines and highlights respectively.
Altough you could use dark-brown ($2) outlines for enemies and have black only for the protagonist, using the dark brown to another escient, but it would make it looks much darker and that may look weird. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
Or you could change sprite color when you change sprite pointer. That is only one more lda/sta pair. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
I'm pretty sure it will change it on all sprites globally, so any sprite that would be draw before the hero's would change it's color in the middle of the sprite, and that's not what you'd want to happen. |
| |
MagerValp
Registered: Dec 2001 Posts: 1078 |
Why don't you post some of the NES sprites here, so some of our experienced C64 graphicians can give you some suggestions? |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
I guess I have a good idea that should work in my case to get more deatail& 4 colors. Have the skin, hair and black outines with 2 vertically arranged multicolor sprites, and have the clothes color on another layer from a third hires sprite, so that I can get more detail on the shape the clothes takes, and the face part does not need that color so only 1 sprite will do. The main drawback is that there is no way to guarantee the priorities of the sprites, and that I can't have much detail on the face.
You can see some graphics from the NES version on my website here. |
| |
Jetboy
Registered: Jul 2006 Posts: 337 |
You have 1 color that can be set per sprite, and 2 that are common for all sprites and used in multicolor mode.
So good practice is to set common colors to dark gray and light gray/white so you can add higligts shadows. Then you use color that is changable per sprite, to give color to your multicolor sprites, then you use overlaid hires sprite that is black to give outlines.
Characters fitting in 1 sprite should be perfectly fine for such kind of game. So you could use 2 sprites per enemy in the same mode, or just have 6 independent sprites.
You could then multiplex those 2 sprites for hero and make her 2x as high
That could complicate it a bit but you could try to make enemies try to avoid being in the same lines (can be a little bit costly depending on implementation), but would make avoiding graphics glitches more easily. |
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
I didn't understand everything.
I'm planning to use light yellow ($7) for highlight as it goes well with pretty much all colors in existance (with red it makes fire-like things, with blue metal-like things, and with green or brown normal highlight, etc...), and can be used for skin color for human characters (that makes them look Chineese, but well, the C64 does not really have any accurate skin color, the only second closer is $a, but too greyish and too dark under some settings.
Small monsters can definitely fit only one sprite, but larger ones and the hero will be 2, because 21 pixels isn't really high. Hires outlines could be good, but having them on the player and every enemy could be too much ? If I do like you say I'd end up with 4 colors per sprites, but for the NES I designed them with 3 and I don't know how to use the last.
Also since my multiplexing engine has no way to guarantee the priorities between sprites (it can only make them very likely to get some priority), hires outlines would randomly be pasted below the colors, and that would make some ugly effects. Again, there will be some ugly effects when too much sprites show up anyway, so I don't know how acceptable this would be. If this ends up not acceptable, I could just stuck with 3 color sprites. No hires for characters, but this may not look that much worse : Since all pixels are wider than tall, the eye won't really notice it. If I make some places very detailed with hires and some places very clunky with multi-color, the multi-color places will look less detailed. If everything is multi-color then it won't be as noticeable. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
if you're going to use 2 sprite high objects, overlay is a big no no imho, as 2 of such objects will use up all 8 sprites already, and I guess you'll want to display more than that. |
| |
MagerValp
Registered: Dec 2001 Posts: 1078 |
I don't see why those sprites couldn't be redrawn to fit in 24x21. With a bit of skill you should even be able to add details, especially when animated.
|
| |
Bregalad Account closed
Registered: Jul 2008 Posts: 42 |
Yeah I probably won't be using hires outlines for that game, or, as you say, only for small sprites.
Some monsters will be able to be redrawn for 24x21, but not all, some will be too large. Human or humanoïd characters are supposed to be taller than larger, in some video games it may be okay if they look square, but if will definitely be horrible if they are larger than taller. So the small ones fit 24x21 (not using all the 24 width), but the larger ones will be something like 24x42 (not using all the 42 height).
Not using the whole height for the protagonist will be benefical as it implies less stuff to rewritre when the animation frame is changed. And I don't care to use more than 8 sprites cause I have a general multiplexing engine ready to use. Altough it would be nice if not many glitches shows up on screen. |
| |
Hein
Registered: Apr 2004 Posts: 954 |
Hires overlay without priority sorting isn't possible, is it?
If you read Dokk's interview at www.c64.com, some wise advice was given to him, long long ago, when the protagonists were still square blobs: interpret the graphics, don't try to do exact converts. C64 is a different medium, so it requires a 'fresh' approach to what you have allready for NES. I wouldn't spend painfull energy on hires graphics, rather spend it on expressive animations and cool gameplay.
2 cents in the basket, soon enough you'll be rich. |
| |
MagerValp
Registered: Dec 2001 Posts: 1078 |
Well a quick fix would be to reserve sprite 0 for the main character hires overlay, and only multiplex sprites 1-7. |
| |
Radiant
Registered: Sep 2004 Posts: 639 |
IMO overlaying looks pretty awkward if it's only done for the protagonist. It's certainly possible to create good normal multicolour sprites without resorting to trickery. |