| |
knue
Registered: Dec 2012 Posts: 37 |
Layouting tiles for AGSP game
Hi all,
I'm currently trying to write an AGSP-based game engine. The thing that really inspired me on this is Mythos:
Mythos - Battle for Aivanor
Now for the question, I managed to implement the AGSP stuff, fighting the interrupts and everything but what really puzzles me: How the heck do you layout the tiles and manage the redraws???
Like Mythos, I'd like to use 3x2 sized tiles. Let's assume we always scroll 2 pixels at a time. This means you must copy 25% of the next screen in advance for one frame (double the amount if you scroll diagonally). But due to the AGSP there is also this wrap around of the buffers going and everything. How to do that without going insane is a complete mystery to me. |
|
| |
mad Account closed
Registered: Feb 2017 Posts: 6 |
Wow this Mythos Demo is unbelievable! Looks like the dream of game programmers back then :)..
One thing about wraparound.. You only have to do the wraparound if you actually cross a $100 boundary so an
tya
adc #$08
tay
bcs .wraparoundcheckandupdateincrementhibyte
is one easy way to do that. most probably there are other,tablebased?, options too. But it isn't as expensive as it first seems.. At least that's what I think to know :).. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Conceptually, you "just" maintain a pointer to the the top left corner of the viewport, adding one whenever you scroll a character to the right, forty for down, -1 and -40 for up and left. Keep only the low ten bits, and the wrapping takes care of itself.
You can find addresses within screen ram or colour ram by adding that value to your screen ram or colour ram base address, and the address in bitmap by first multiplying by eight.
update column 39 when you're moving right, column -1 when you're moving left, etc.
As for scheduling, when I implemented this for Hysteron Proteron I had the interrupts maintain the screen position, and the mainline code perform the blitting in response to requests from the interrupt code. RMW instructions signalled from mainline back to interrupt so I didn't have to take care to avoid being interrupted between reading and updating a flag. |
| |
knue
Registered: Dec 2012 Posts: 37 |
Well this is more or less clear to me. What really bugs me is the following:
* there are 4 scrolling cases to consider (left, right, top, bottom)
* 3*2 cases for each tile position
* 4 cases for x-scroll (assuming scrolling 2 pixels at once)
* 4 cases for y-scroll
So that's in total 384 cases! Even if I manage to program all that cases with some macro magic to at least share some of the code, I'm afraid that in the end of the day I need to have all 384 code variants in memory, or am I missing something? |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
Shouldn't you only need "update horizontal span" and "update vertical span" routines, which redraw one char-column or one char-row worth of bitmap data at a time, and handle wrapping as they go?
If you check with e.g. ICU64 how Mythos updates the bitmap, it appears to be working like that.
You could compose your tiles from a virtual bitmap "charset" to save memory, instead of always storing unique data for each 3x2 tile. |
| |
knue
Registered: Dec 2012 Posts: 37 |
But updating a complete row is too much to handle in one frame, no? Updating a complete row needs 40*10 reads & writes. (8 for bitmap + 1 for screen ram + 1 for color ram).
I already thought about this virtual char set for compressing the tile data but then the number of cases even explode more. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
It's still less than a char-mode standard hardscroll (1000 bytes for whole screen)
I'd recommend trying how your rastertime fares with that first, as it's simplest.
If your gamelogic can signal the IRQ with the sprite + scroll positions that should be shown on the next frame, while it already calculates ahead, it should be able to take the occasional heavier rastertime hit. |
| |
mad Account closed
Registered: Feb 2017 Posts: 6 |
Perhaps you can prebuild your charlines to append..
So that you only have (bitmapData*8,colorRam1*1,colorRam2*1)*40 into a buffer. Then when you really need to append the data you just do a simple copy loop whilst maintaining the wraparound.
The candidates for prebuilding depend on your (xyScrollPosition & 7)..
If x is bigger or equal 4 you can start prebuilding the buffer for the right. if x is below 4 you can start prebuilding the buffer for the left. The same for y.
On actual appending the prebuild buffers, you can handle the x+y appending cases.
it's just an idea perhaps it doesn't work. Most probably there are better solutions.
However I think it's easier to first inspect the rastertime needed like Cadaver suggested. You somehow have the whole (AGSP) frame to do the appending.. |
| |
mad Account closed
Registered: Feb 2017 Posts: 6 |
Actually that doesn't work! :) You would have to implement some delay on a direct direction switch.. |
| |
knue
Registered: Dec 2012 Posts: 37 |
Any suggestions how to layout the tiles in memory?
Let's say a tile looks like this:
ABC
DEF
The most obvious solution would be this:
A0...A7 B0...B7 C0...C7 D0...D7 E0...E7 F0...F7 Next tile here
But I think address computations can be quite expansive.
Next option:
Tile0_A0...A7 Tile1_A0...A7 ... Tile_7F_A0..A7
Tile0_B0...B7 Tile1_B0...B7 ... Tile_7F_B0..B7
...
Tile0_F0...F7 Tile1_F0...F7 ... Tile_7F_F0..F7
But maybe using a virtual char set isn't that bad after all.
Maybe sth like this:
Tile0_A Tile1_A ... Tile_7F_A
Tile0_B Tile1_B ... Tile_7F_B
...
Tile0_F Tile1_F ... Tile_7F_F
char_00_row0 ... char_00_row7
char_01_row0 ... char_01_row7
...
char_FF_row0 ... char_FF_row7
|
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
You don't need a routine per two pixels; just do one for each row or column of chars. It's allowed to take three or four frames to execute if it's running in mainline instead of on interrupt.
(Or, start it at the end of your VBL interrupt, but CLI before entering the loop. Just make sure you don't trigger a second blit while you already have one in flight). |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
As for memory layout, suspect I'd lean towards a table for A0, then a table for A1, etc - so you can just index each source table by tile ID.
If you've the ram for it, then that's 60 tables, so nearly 16k for 256 tile elements (less if you've fewer tiles).
Could always go for a hierarchy to save memory of course (tiles are made of virtual chars etc). |
| |
knue
Registered: Dec 2012 Posts: 37 |
Quote: You don't need a routine per two pixels; just do one for each row or column of chars. It's allowed to take three or four frames to execute if it's running in mainline instead of on interrupt.
(Or, start it at the end of your VBL interrupt, but CLI before entering the loop. Just make sure you don't trigger a second blit while you already have one in flight).
I don't really get this. Let's say I'm only scrolling left/right. Now the player moves to the right two pixels. So I'm starting the mainline scroll-left routine. But now the players moves back again. What should I do now? I somehow have to notify mainline that it should abort and start the scroll-right routine. I could do the copy for scroll-left from left to right and the copy for the scroll-right from right to left and pray for the best. Is this what you mean? |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
You've got a few options there.
You could indeed abort if the player changes direction, or alternately decouple the scroll position from the player position a little, and don't change scroll direction until you've finished the most recent blit.
It doesn't even have to be blitter dependent - for example if you implement a push scroll where the player can roam anywhere in the middle third of the screen before it starts scrolling, there will be a period of {screenwidth/3}/{player speed} when the screen is stationary. |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
Quote: Conceptually, you "just" maintain a pointer to the the top left corner of the viewport, adding one whenever you scroll a character to the right, forty for down, -1 and -40 for up and left. Keep only the low ten bits, and the wrapping takes care of itself.
You can find addresses within screen ram or colour ram by adding that value to your screen ram or colour ram base address, and the address in bitmap by first multiplying by eight.
update column 39 when you're moving right, column -1 when you're moving left, etc.
As for scheduling, when I implemented this for Hysteron Proteron I had the interrupts maintain the screen position, and the mainline code perform the blitting in response to requests from the interrupt code. RMW instructions signalled from mainline back to interrupt so I didn't have to take care to avoid being interrupted between reading and updating a flag.
wow cj, this is so simple and brilliant at the same time. |
| |
knue
Registered: Dec 2012 Posts: 37 |
Quote: You've got a few options there.
You could indeed abort if the player changes direction, or alternately decouple the scroll position from the player position a little, and don't change scroll direction until you've finished the most recent blit.
It doesn't even have to be blitter dependent - for example if you implement a push scroll where the player can roam anywhere in the middle third of the screen before it starts scrolling, there will be a period of {screenwidth/3}/{player speed} when the screen is stationary.
Looking at Mythos, the player is stuck in the middle of the screen. This is potentially sth I really like. There are way to many games where the screen starts scrolling when the player is in the last third or so and you have hardly time to react to the new stuff that is coming at you. In that regard the best scrolling I've seen so far is in Sam's Journey. There the screen actually overruns you such that you have plenty of space to see what is in front of you. Really cool. Would be interested how exactly this works. |
| |
Copyfault
Registered: Dec 2001 Posts: 478 |
Don't know of what you're planning to have in the upper/Lower border part of screen, but assuming we're talking PAL-system here and the rasterlines in the border area are free (=no sprites to be shown, no other timing tricks for $3fff gfx etc.), you should have 312-200=112 Rasterlines in the upper/lower-border-area alone. This gives 112*63=7056 Cycles.
Now when copying a row, you'd need to do this for 40*10 bytes. Using speedcode (LDA/STA) this means we need 8 Cycles per byte, thus 400*8 = 3200 Cycles (<7056 Cycles -> can be done in one frame!)
Depending on all the other stuff you must calculate to keep your game engine going and depending on how fast you actually want to scroll, I'd go for a speedcode generator that does its job during the non-wrap-around frames and call the speed-code for copying when the wrap-around is due.
It might also help to start with an x-offset of 4 (setting $D016 accordingly) if the overall scrollingspeed allows for it, for example if you manage to do the speed-code calculation within say three frames and the scroll-speed is 1pixel/frame, the offset ensures that you have enough time no matter in which direction the player decides to go. Ofcourse the speedcode generator must take the direction into consideration. If the player decides to change direction before a wrap-around, you'll have enough time to do the calculation for the other direction.
Just some basic ideas that I would try out for agsp'ing.
Cheers-CF
[edit] x-offset is valid if you do left/right scrolling; y-offset is what is needed for up/down-scrolling in which case a row has to be copied upon wrap-around. But I guess you spotted this already, after all it's just a rough concept. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
Quote: Looking at Mythos, the player is stuck in the middle of the screen. This is potentially sth I really like. There are way to many games where the screen starts scrolling when the player is in the last third or so and you have hardly time to react to the new stuff that is coming at you. In that regard the best scrolling I've seen so far is in Sam's Journey. There the screen actually overruns you such that you have plenty of space to see what is in front of you. Really cool. Would be interested how exactly this works.
In SJ, moving horizontally shifts the camera "origin" position to the opposite side, so that you will see more. If you run fast, the shift happens faster until it reaches the maximum. Turning centers the camera again. Vertically it seems to be just keeping the player in the center, but the scrolling is fairly slow, so that a jump does not catch up with you at first. |
| |
cadaver
Registered: Feb 2002 Posts: 1160 |
Btw. before going crazy with speedcode, I recommend checking out how traditional AGSP games like Fred's Back and Maximum Overdrive do the bitmap update. They're actually shockingly inefficient.
Mythos at least unrolls blitting a char onto the bitmap, in the manner of:
lda charrow1,x
sta (bitmap),y
iny
lda charrow2,x
sta (bitmap),y
iny
...
Where X is the char number to blit, and the destination is just a ZP pointer + Y.
Of course you can always go and optimize later, according to how much CPU you need for other things, but don't think you need to immediately break out the big guns just to get a basic scroller running. |
| |
knue
Registered: Dec 2012 Posts: 37 |
Thanks for all the valuable input.
On another note: Are there any pixel editors out there to at least create the tile set? Preferably something that runs on Linux. Or are you guys just using sth like Gimp + some homebrewn scripts to convert it to your desired output format? On another note, I thought about double-buffering the screen ram and swapping them each frame. This is a pretty cheap effect for mixing colors %01 and %10. But this requirement makes it even more difficult to find an appropriate editor.
EDIT: I've just used a little bit of python magic to create a nice palette and tweaked gimp a little bit. So far, it works quite nicely :) |
| |
Golara Account closed
Registered: Jan 2018 Posts: 212 |
Quote: Thanks for all the valuable input.
On another note: Are there any pixel editors out there to at least create the tile set? Preferably something that runs on Linux. Or are you guys just using sth like Gimp + some homebrewn scripts to convert it to your desired output format? On another note, I thought about double-buffering the screen ram and swapping them each frame. This is a pretty cheap effect for mixing colors %01 and %10. But this requirement makes it even more difficult to find an appropriate editor.
EDIT: I've just used a little bit of python magic to create a nice palette and tweaked gimp a little bit. So far, it works quite nicely :)
interlaced game ? lol i'm not sure i'd like to look at that flickering while playing.
As for pixeling, there's lots of programs for drawing c64 graphics, for example charpad, but you can also draw it in whatever graphics program you want and then convert it, it's pretty easy. You can load bmp/png images directly in KickAssembler and convert it to charset/bitmap data there. It has functions like getMulticolorByte which takes a x,y coordinate and automatically makes a byte out of 8 pixels at this position inside the image. |
| |
Hein
Registered: Apr 2004 Posts: 954 |
For bitmap tiles: GFX-Editor V1.4 |