| |
TBH
Registered: Mar 2010 Posts: 21 |
Dynamic Character Set
For some years I've been curious about the potential for dynamic character set definitions in scrolling games. It's not a new idea and there are many limited implementations (Bounder, Katakis, Hawkeye, Golden Axe, etc).
But, so far I haven't seen a transition-less scrolling game that was not limited to 256 char definitions. My idea is to treat the viewable screen as two edges - one incoming, one outgoing.
The incoming edge is 16-bit to hold the character definition indices. I'll call these Tiles.
The outgoing edge is 8-bit to hold the outgoing indices of the character definitions.
A section of char IDs can be set static so that Tile IDs fall straight through to the screen instead of undergoing transformation.
LIMITATIONS
1) No more than the number of characters allocated to use by the routine can appear on screen. If all 256 chars are set to be dynamic, then the screen cannot display more characters than that. Maps have to be designed with that in mind.
2) CPU Cycles.
Assuming a 75-byte edge (for diagonals), it's likely no more than 10 new char definitions will be introduced in one hit. YMMV.
No new chars takes about 2500 cycles. 10 new chars about 4500 cycles.
It's a lot, but my prototype code isn't optimised.
TECHNIQUE
Incoming Edge
1) Copy the incoming Tile indices to the Incoming Edge table. For example, if scrolling to the left, these images will appear in the rightmost column.
2) Check each Tile index to see if it is currently assigned a Char ID.
2A) If so, increment the char's counter of onscreen-occurrence and push the Char ID onto the screen or a buffer.
2B) If not, assign an unused character and copy the Tile image to the char's definition.
Outgoing Edge
1) Copy the outgoing char indices to the outgoing Edge table. These are the chars that are scrolling off the screen.
2) Use the char as an index to the table holding the counter for its onscreen occurrences and decrement that value. If there no further occurrences of that char onscreen then place it back into the pool of unused char Ids and mark the corresponding Tile ID as having no associated char.
OTHER USES
This could also work for regions of a non-scrolling screen. For example, a Bard's Tale style RPG where lots of character images need to be plotted and animated. For any region of the screen that needs to use dynamic chars applied, just "swipe" the origin of the edges down each row of that region.
BACK TO REALITY
Anyway, I wrote a prototype routine which works as expected, but that CPU hit is probably why no game seems to have adopted a similar approach. |
|
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
why not just use a character screen that emulates bitmap mode (5 charsets one every 6 lines), thus "character" usage is totally free. there are 25 entering chars, but everything can be optimised to unrolled loops, because of the simplicity. |
| |
Stone
Registered: Oct 2006 Posts: 172 |
When creating Disney's Pocahontas for the SEGA MegaDrive, we (Funcom) used oversized tilesets, but instead of dynamically allocating Char IDs, we used a stochastic method to determine a somewhat optimal set of static "zones" where characters were updated from precomputed tables. This worked out quite well and gave us large levels with varied backgrounds. |
| |
Digger
Registered: Mar 2005 Posts: 437 |
Monroe 6569 uses charmode with the technique described by Oswald. It could probably be good for a pinball implementation. |
| |
Fresh
Registered: Jan 2005 Posts: 101 |
@Oswald
One year ago I released a simple scroller based on the same technique. For stuff like that it works great, but in a game the main problem is that you're going to lose many char based effects (ie bullet). |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
Quote: @Oswald
One year ago I released a simple scroller based on the same technique. For stuff like that it works great, but in a game the main problem is that you're going to lose many char based effects (ie bullet).
you can have 16 free characters even in the most tight setup for bullets water animation, etc., or you can have just 5 rows per character set and have 46 free characters :)
charset switch can be done via nmi, so sprite multiplex will stay (more or less) unhurt.
scrolling will be a bit more complicated this way tho, but on the plus side these special chars dont need to be redrawn. |
| |
Fresh
Registered: Jan 2005 Posts: 101 |
Ok, sorry, I misunderstood the gfx mode you were talking about. You're talking about having normal badlines and changing d018 every 5 or 6 row while I meant having just one badline every 6 row: that means *fast* scrolling but limited char effects.
So yes, using normal badlines and changing every 5 rows gives enough freedom but scrolling the whole screen and updating X chars sounds heavy... |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
25 chars. thats 200 bytes, and even that can be spread over 2 frames if your max scroll speed is 4 pixel per frame.
so extra work is copying 100 bytes per frame. (lda (),y sta abs,x iny .. * 8)
~20-25 rasterlines time.
and as we scroll only horizontally even VSP may be used. at the cost of heaps of ram flimbo style parallax could be done with > 256 charset :)
but looking at sam's journey, or mayhem I dont think a game really needs more than 256. rather a skilled gfx artist. |
| |
Repose
Registered: Oct 2010 Posts: 225 |
I thought of something like this many years ago, for a very real purpose: 9600 baud scrolling 80 column terminal. You can't use too much time at once because you need to read the incoming bits in real-time. |
| |
Martin Piper
Registered: Nov 2007 Posts: 722 |
Flying Shark does this IIRC. |
| |
Spinball
Registered: Sep 2002 Posts: 88 |
Quote: I thought of something like this many years ago, for a very real purpose: 9600 baud scrolling 80 column terminal. You can't use too much time at once because you need to read the incoming bits in real-time.
The Diskmag Relax featured a fullscreen proportional font upscroller. I think it was also done with charsets like this. |
| |
Cruzer
Registered: Dec 2001 Posts: 1048 |
Oswald aka MicroPenis' technique would seriously limit the number of sprite frames you could have in a bank. Almost as much as my work in progress FLI game, so I would not recommend that, unless you think it's easier to dynamically allocate them all the time. :) |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Hybrid TBH/μPenis approach: two charsets for upper/lower screen, for up to 512 definitions at once \O/
(of course, scrolling vertically would be.. interesting.. Perhaps keep the split lines static relative to the map, so they scroll with the screen? Would be useful artwise as you could ensure style changes aligned with charset changes) |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
TBH:
I assume you mean 65 new chars for diagonals? (40+25). You could split the load across screens by usually staying 8-15 pixels ahead on horizontal movement, and deferring the vertical edge by a frame whenever both vertical and horizontal edges are crossed.
I'm wondering if it would be worth dividing the map into regions that used 8 bit indices internally, to avoid all the 16 bit index use in the management routines. Each region would have its own mapping table, and count table for number of onscreen occurrences of each id.
Not much to be gained from large area splits compared to just changing charset when you get to a new area, but perhaps the regions could be (eg) per 16x16 character megatile, or two regions alternating for odd/even rows of chars on the map? |
| |
lft
Registered: Jul 2007 Posts: 369 |
With a static map, much of this work can be done offline. For instance, you could have 128 globally defined chars, and any number of "logical" chars that are mapped to the remaining 128 "physical" chars.
The map editor should perform the mapping, and ensure that it is always possible to assign the same physical char to any given logical char. That is, if two logical chars can ever appear simultaneously on the screen (at some scroll offset), they must be mapped to different physical chars. This turns into a large constraint problem that can be simplified if the map is divided into sub-areas. The map editor would also emit tables/code to update the font when a new sub-area scrolls into view. |
| |
Repose
Registered: Oct 2010 Posts: 225 |
I remembered how I made my full screen no-cpu scroller.
Each charset covers 6 rows, so there's 4 of them. The memory areas are split with 4 rasters. The screens shows 0-239 in order. There's 6 predefined screen mems. Each one shows the chars scrolled one more up.
Scrolling has 6 phases, where you change the screen mem pointer and move the rasters up one row. The new row comes from the first charset, and you have to plot into charset definitions. That's 320 bytes to write.
At 9600 baud, I think there's several chars you can plot per frame, so update in chunks which is a bit more efficient.
You can go even beyond this if you don't mind some flicker; you can use two banks and have each 4x8 area with it's own colour to support ANSI screen codes.
Blank spaces can use one of the remaining 6 chars definitions then you only need to write that char into the row of screen mem, so each line of update doesn't have to plot a full row into the charset definitions. |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Quoting lftThe map editor should perform the mapping, and ensure that it is always possible to assign the same physical char to any given logical char.
That would certainly cut down the runtime significantly, but it's also a somewhat less flexible system. It's easy enough to synthesise a case that fails to satisfy that constraint that TBH's system would nonetheless be fine with (eg include the first 256 logical chars on one screen, have a second screen that includes char 257 and the first 128 chars, and a third screen that includes char 257 and the second 128 chars).
No idea whether that would be an issue in practice, mind :) |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Nice work, Repose.
You could save a few KB by using linecrunch to scroll up a row or five, but then you'd still need the first few rows of another screen to switch to at some raster line or other if you wanted to maintain the 'zero cpu' constraint. |
| |
Repose
Registered: Oct 2010 Posts: 225 |
You can linecrunch but what happens when you've scrolled a whole screen? |
| |
Oswald
Registered: Apr 2002 Posts: 5094 |
Quote: You can linecrunch but what happens when you've scrolled a whole screen?
the universe will collapse. |
| |
chatGPZ
Registered: Dec 2001 Posts: 11386 |
Quote:of course, scrolling vertically would be.. interesting.. Perhaps keep the split lines static relative to the map, so they scroll with the screen?
the growing plant effect in artphosis works exactly like this :) |
| |
ChristopherJam
Registered: Aug 2004 Posts: 1409 |
Quote: You can linecrunch but what happens when you've scrolled a whole screen?
The idea was to combine linecrunch with your six screens method - so after you linecrunch by five rows, the amount of crunch is reset back to zero again. |
| |
TBH
Registered: Mar 2010 Posts: 21 |
Some interesting points have been made that illustrate quite well the various trade-offs between CPU time and memory space for when resources are limited.
I like Oswald's suggestion for a bitmap-like characters display. It's appealing for reasons of convenience and speed:
a) No restriction on the number of unique visible char definitions.
and
b) Hard scroll is fast because it can be done with a CPU index register used as both loop counter and character value, instead of the more common technique of shifting arbitrary character values around screen memory.
But it is not so appealing when memory is sparse since it uses the same amount as a bitmap screen; at least 9K in one video bank unless you split that across two or more.
@ChristopherJam re "I assume you mean 65 new chars for diagonals?"
Yes, 65. Addition was never my strong point. Neither is programming, I suspect.
The various ideas about using regions and generating the char map optimally is something I investigated earlier:
It seemed that for a uni/bi-directional scroller, it would be easy to have character redefinitions flagged only on the incoming rows or columns when necessary. So, a char map designed to never have more than the allowed definitions visible could be used. The actual dynamic redefinitions would be stored in a separate lookup table which would redefine a char when a new definition was incoming on a column (or row). IOW, only the differences would be stored and handled.
But as I see it, only a uni/bi-directional scrolling map can guarantee (in a reasonable amount of memory) that the char map will match such an only-if-a-redefinition-is-needed lookup table. In such a case the lookup table is a one-dimensional array (for a bi-directional scroller it is reflected for the opposite direction).
But a multi-directional scroller would require a potentially massive two-dimensional table that handled four directions separately in relation to the previous XY coordinate (effectively an Acyclic Graph)- which is why I resorted to handling individual characters as described in my prototype routine.
For multidirectional scrolling, the idea by lft and others about regionalising the map elements is a nice one. But, wouldn't the map's variety be reduced due to potential conflict between char definitions occurring in all directions (greater areas of overlap), not just left/right or up/down? However, an extra onscreen charset would help with that, as mentioned. I can imagine it working well with, say, a top-down RPG like Times Of Lore with different geographical regions with clearly defined graphical differences - you are walking in a pine forest, then slowly emerge into farmland, then a few screens later enter a city.
Anyway, the above is just a bundle of thoughts this conversation thread has inspired so far. At least I now have a clearer idea of what other options are available for the things I'm attempting. |