Jump to content
IGNORED

Wen hop? The Search for Planet X


Andrew Davie

Recommended Posts

1 hour ago, Bomberman94 said:

This is so colorful in the video! I think how the colors are with my PAL Atari 2600 as I think you brought us a NTSC version of it?!

Good point. No idea at the moment. Well, sort of no idea. The colours are formed from a tricolour selection - say, red/green/blue and all 8 combinations of those together giving you the colours you see.  So for PAL I find similar colours to that red/green/blue and we get similar colours on PAL.  It will be quite similar, in other words.  There is automatic PAL/NTSC detection in there; that's possibly just not working at the moment. I have been testing on a NTSC machine. I'll get to the PAL stuff after I'm done with work on the engine.

  • Like 1
Link to comment
Share on other sites

1 hour ago, Bomberman94 said:

This is so colorful in the video! I think how the colors are with my PAL Atari 2600 as I think you brought us a NTSC version of it?!

A quick test in Stella with PAL hardwired shows that the default (automatic) conversion of colours will look OK...  I can tweak this later...

 

 

 

 

Edited by Andrew Davie
  • Like 1
Link to comment
Share on other sites

Though not really practical (with scrolling background) on real hardware, the stand-alone sprite system should be able to handle this many bitmap sprites (and more...). Maybe with slowdown. Just putting this video up for posterity.

 

 

 

 

 

Edited by Andrew Davie
  • Like 6
Link to comment
Share on other sites

But... does it run on actual hardware?  Well, yes it does. Mostly!  This is my first test... I can get two of the ships going at the same time with not too many issues. Along with all the scrolling and other background animation/processing. It starts to disintegrate with 3 or more ships, so you see it reset/crash a fair bit. This is fine; the resets are probably the Harmony Cart deciding to gracefully exit when overtime. Still some optimisations in the pipeline, so all good. And the colours on an actual TV - excellent!

 

 

 

 

 

  • Like 8
Link to comment
Share on other sites

I really enjoyed watching the @ZeroPage Homebrew twitch stream today with James and Tanya. They reviewed "Wen Hop?" and it was a hoot. I particularly enjoyed the smirky reactions to the implicit innuendo...  the relevant part starts at the 55 minute mark..

https://www.twitch.tv/videos/1100209904

 

As I wrote to James...  

 

You'll notice I've animated the ship's "balldoges".  Known in the industry as "the ship's balls", for short. These hold the dogecoin you collect, and feed them to the engine by shaking when in flight. SO when you're flying high, your (doge)balls are in action. These are actually implemented as separate sprites (the ship is 4 sprites - the base ship itself, the two balldoges, and the flag).  The balldoges will change in size depending on how much dogecoin they're holding. So you will see your (doge)balls shrinking if you spend too much time goofing around. If your balls shrink to nothing, you're dead as a dodo.  I'm thinking that you don't actually get true credit for the dogecoin you collect, until you actually return to the ship and fill the balldoges with your loot. So it will be a balance between "banking" coin and available time to complete a level. Of course like with everything, too much of a good thing and you might bust your (doge)balls.  Haha.

 

 

Great job James/Tanya - you did justice to the engine/demo... TY

 

 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

So, rocks.  The problem is now that because you can't dig, and you need navigable passages... how are these passages *defined*?  If we allow the rocks to fall, then as soon as a navigable passageway is opened ... the rocks will fall down and close it up.  So, I've been thinking about these things; how to maintain passageways, and how to open up passageways in the first place.  I think I've solved both of these, and found a use for the "alien eggs" in the process. We're going to switch the "eggs" to "soil eating mega-microbes".  You can see them in operation at the start of this video. Basically they eat dirt nearby, but die off pretty quickly. Husk may very well use these microbes as a tool to clear out passageways.  In any case, you'll notice from earlier versions that the rocks clump together in giant conglomerations of rocks. But now, there's quite a bit of behaviour differences; firstly the individual rocks will fall if unsupported. But the conglomerate rocks will be sturdy. How sturdy depends on what's happening; earthquakes and individual rocks can become loose (and you see them shaking before they fall).  Also, conglomerations can just spontaneously start to disintegrate - and I can vary this based on time, or difficulty, etc.  So although the passageways are maintained in esentially the same form, they do slowly disintegrate - but in a way that I consider "fair" and "avoidable" - in that you're not going to have a ton of rocks fall on you unfairly.  In the video you see the mega-microbes at first, clearing out some passageways. Then you see the earthquakes disrupting/destroying the dirt, and also the individual rocks falling as appropriate. Finally, you see the conglomerates slowly disintegrate over time.  I move around to show that there are still clear and distinct passages for most of the entire time.  Pretty happy with this.

 

 

Edit: it's pretty clear Husk is going to need a grappling hook, so let's chalk that in.

 

Edited by Andrew Davie
  • Like 1
Link to comment
Share on other sites

1 hour ago, Andrew Davie said:

Husk may very well use these microbes as a tool to clear out passageways

thus, beginneth the minecrafting of the 2600

 

1 hour ago, Andrew Davie said:

it's pretty clear Husk is going to need a grappling hook, so let's chalk that in.

maybe a shovel and some stakes to shore up the walls of the tunnels?

 

will the mega-microbes (megobes?) eat the dogecoins?  do you have to mine a clean space and then race to pick up the dogecoins before the megobes get them?

Link to comment
Share on other sites

On 7/26/2021 at 9:33 AM, Andrew Davie said:

I hope one or two notice/appreciate that circular wipe at about 16s!

Fully appreciated. Until you pointed it out though, I assumed you'd been adding them in editing! 

 

This is really spectacular. Playfield graphics rule!

  • Like 1
Link to comment
Share on other sites

1 hour ago, Nathan Strum said:

Fully appreciated. Until you pointed it out though, I assumed you'd been adding them in editing! 

 

This is really spectacular. Playfield graphics rule!

 

Here's the thing in slow-mo - noting the real-time calculation of the circular mask, so that gameplay can happen during the wipe/transition...

 

 

 

 

  • Like 4
Link to comment
Share on other sites

15 hours ago, Andrew Davie said:

I really enjoyed watching the @ZeroPage Homebrew twitch stream today with James and Tanya. They reviewed "Wen Hop?" and it was a hoot. I particularly enjoyed the smirky reactions to the implicit innuendo...  the relevant part starts at the 55 minute mark..

All the memes and innuendos are hilarious, loving every minute of it Andrew! Hahah!

15 hours ago, Andrew Davie said:

Great job James/Tanya - you did justice to the engine/demo... TY

Thank you so much for letting us debut your incredible game engine, the power and capabilities of playfield graphics are astounding! Alongside Turbo Arcade and Zeviouz you guys have really ushered in a new era of full screen graphics display for the Atari 2600!

 

- James

Link to comment
Share on other sites

This is fantastic!  At first the rocket was the 2nd sprite, then you added 4 more that is as smooth as the first rocket.  I really like how much detail is shown on screen.  The smooth dripping animation and the rock splashing when landing.  Also the parallax scrolling look really cool!

Link to comment
Share on other sites

5 hours ago, ZeroPage Homebrew said:

Alongside Turbo Arcade and Zeviouz you guys have really ushered in a new era of full screen graphics display for the Atari 2600!

All ARM-based games, AFAIK. But don't forget Boulder Dash which did full-screen coloured scrolling graphics a decade or two back.

  • Like 2
Link to comment
Share on other sites

The engine has an "overview" mode. This is a complete view of the whole playing area (not just what the screen shows) implemented as a one pixel per "character" display. In other words, it's challenging to make it look reasonable.  So in this video the overview mode is starting to look OK; most of the elements (but not the spaceship, yet) are shown in the overview... and most (after a bit of familarity) are distinct enough to be able to identify. In theory, you should be able to play the game in overview mode...

 

 

  • Like 4
Link to comment
Share on other sites

Improving...

 

overview.thumb.jpg.f30f7f388c8ccb9a1d1c1a0df3f41b32.jpg

 

So a bit of a "stock take", so to speak, and it appears I have about 8K left to make this a complete game.  

 

I'm thinking - why not throw in a bit of "lunar lander" here, too... if you have to pilot the spaceship off the planet, why shouldn't you be responsible for landing on the planet, too.  See the above image with a bit of terrain, so to speak. So let's have the start of the level where you're in your ship, coming down to land...  then you jump out and collect doge coins.

 

 

 

  • Like 2
Link to comment
Share on other sites

Here are some notes on how it all works, from a technical standpoint...

 

Firstly, at the most fundamental level is the 6507 kernel.  All this kernel does is "stream" bytes from the ARM and write them to hardware registers. These streams consist of PF0 PF1 PF2 (left), PF0 PF1 PF2 (right), P0, P1, COLUPF, COLUBK.  There's just enough time do do immediate load/store for all these registers on a single scanline, so that's exactly what the kernel does.

 

 

So streams are a CDFJ feature that allow you to load an immediate value, and get back the next entry in a "stream" of data. So there are multiple streams operating here, one for each hardware register. On the ARM/C side of things, then we have vertical "strips" of bytes -- the data that gets sent to the stream requestor.  Each strip is the height of the visible screen - say, 198 scanlines.  So that's effectively our screen video memory - vertical strips of bytes which, once it's all setup, automatically get drawn.

 

So now we have a video buffer and have abstracted away the vertical position of sprites, or the playfield pixels.  Or the colours. All I have to do is makes sure that video memory is "drawn" to correctly, and I'll see the result on the screen.

 

Next, we have the "board" -- this is a large chunk of memory representing our "character graphics" screen. The board is 40 x 24 characters in size.  That is, 40 characters across, and 24 characters deep. Each "character" is 4 pixels across and 21 scanlines deep.  So, a 40 x 24 board is actually 160 x 504 pixels in size -- and the screen being 198/21 = 9.4... character lines deep, and 40/4 = 10 characters wide.  So "drawing" a scrolling playfield consists of updating the aforementioned video memory "strips" with appropriate data from that large 160 x 504 pixel area.

 

The draw is done in real-time, every frame.  It's done on a line-by-line basis, first the left side of a line (PF0, PF1, PF2) and then the right side of a line (again, PF0, PF1, PF2).  So to do a half of a line, we're looking at 20 pixels that we need to fill with correct data. A half-line can overlap 20/4 = 5 characters from the board, so we fetch the 5 characters first and use those to determine what the 20 pixels should be.  Adjusting (shifting) by up to 3 pixels based on the horizontal scroll.  So, firstly we've fetched a character NUMBER from the board. This is an index into the character set (the visible pixels). But first we do a neat trick.

 

When we fetch a character from the board, we use that as an index into a TYPE table. For example, CH_DIRT is TYPE_DIRT.  But there are several different character visuals for dirt (CH_DIRT1, CH_DIRT2, etc.) They all have TYPE_DIRT. This has several advantages, most of all when we are doing creature logic, because we can very quickly determine WHAT a character is by just going "CharToType[*this]" and we know what type of creature is o the board.  Anyway, for drawing purposes, we have fetched the character name (shape #) from the board, first thing we do is convert it to a TYPE (as above) and then we look up an Animate table. This table has one entry for each TYPE (DIRT, ROCK, WATER, LAVA, etc.,) which either has zero (no animation), or points to a short animation program/list for that type.  For example, the entry for TYPE_DOGECOIN looks like this...

 

const char AnimDogeCoin[] = {
    
    CH_DOGE,8,
    CH_DOGE_PULSE_1,6,
    CH_DOGE_PULSE_2,4,
    CH_DOGE_PULSE_3,3,
    CH_DOGE_PULSE_4,3,
    CH_DOGE_PULSE_5,4,
    CH_DOGE,8,
    CH_DOGE_PULSE_5,4,
    CH_DOGE_PULSE_4,3,
    CH_DOGE_PULSE_3,3,
    CH_DOGE_PULSE_2,4,
    CH_DOGE_PULSE_1,6,
    CH_DOGE,8,
    255,
};

 

So, that's a simple "programming language" which is basically saying show the character CH_DOGE for 8 frames, then CH_DOGE_PULSE1 for 6 frames, etc., etc. And at the end that 255 is a "loop" command. So it's a little animation program for the dogecoin that effecively happens automatically for ALL objects of TYPE_DOGE on the screen. I don't have to think about animating; I just put the character in the board, and if it has an animation then it will animate automatically.  Of course ALL objects of that type will have the same frame of animation shown at any time. There are ways around this, but the fundamentals are thus.

 

So with that little bit of indirection - we've fetched the character number from the board, converted it to the type of object, then looked up the correct character to display for this frame for that object type... now we can lookup the actual character shape data for the character we should see. This shape data is 4 pixels x 21 scanlines in the ChronoColour(TM) format (that is, triplets of R/G/B) lines, where R/G/B can be replaced with any colour trio that you like. Because COLUPF is written by the kernel on every scanline, you can in theory change the ChronoPixel R/G/B value on every pixel. This is how I get the gradated dirt colouring on the screen - it's just changing one of the R/G/B values.

 

Here's an actual character shape definition...

const unsigned char CHAR_ROCK1[] = {

    X_  _XX_    // 00 < A
    XX  _XX_    // 01   B
    XX  __X_    // 02   C
    __  __X_    // 03 < A
    XX  XX_X    // 04   B
    __  ___X    // 05   C
    _X  ___X    // 06 < A
    XX  XXXX    // 07   B
    __  ____    // 08   C
        ____    // 09 < A
        XXXX    // 10   B
        ____    // 11   C
        ____    // 12 < A
        XXXX    // 13   B
        ____    // 14   C
        __X_    // 15 < A
        _XXX    // 16   B
        ____    // 17   C
        ____    // 18 < A
        ____    // 19   B
        ____    // 20   C

};

That rather odd-looking bit with the small block at left top, and larger block in the middle are the character definitions for overview screen (1 bit wide character) and the regular screen (4 pixel wide characters). I have a few macros setup so that I can type in character shapes like this. The comments show the A/B/C triplet lines, and the _ means "0" and "X" means "1" as far as shapes go. There are 2 shapes defined for the overview character definitions because I display either in a checkerboard pattern to make the shapes more distinct from each other when side-by-side.  Each line here resolves to just one byte (and there are still bits free), so it's 21 bytes to define each ICC character shape. I currently have about 130 characters, give or take.

 

So after determining which line in the character shape we want (remember, we're drawing into the video buffer strips), then we copy the shape data for the 5 characters we're looking at in the current left/right half of the screen into a character shape "line".  And we shift it based on the scroll (up to 3 pixels, as with 4 or more we'd be looking at the next character ... so only shift up to 3 *inside* this characters shape data we just fetched, and note that it won't overflow.  The appropriate pixels from the character line shape data are then copied into a 20 bit buffer (for speed, one byte per bit) representing the 20 pixels onscreen as we see them (again, left side) in left-to-right order.  Once we have that, we are ready to mangle the bits into the weird PF order. This can be done very quickly, so that's what we do - we want to produce the 3 PF bytes PF0 (mirrored) PF1 PF2 (mirrored) that would be copied to the screen. But remember we're not copying them to the screen, we're just dumping them into the next scanline of the PF strips... which represent our video memory. The kernel will copy them to the screen at the appropriate scanline.  So, here's the code to fetch the 20 bits from our character line shape data and produce the PF register write values...

 


unsigned char *pf0 = arenas[half] + scanline;
for (int y = 0; scanline < SCANLINES && y < PIECE_DEPTH; y++) {

  int p = 0;
  for (int pix = 0, pshift=20; pix < 6; pix++, pshift -= 4)
    p |= ((*(image[pix])++) & 0b1111) << pshift;
  p >>= 4-shift;

  if (lcount >= 0 ) {
    *pf0 = ((BitRev[(p >> 16) & 0b1111]));
    *(pf0 + _ARENA_SCANLINES) = ((((p >> 12) & 0b1111) << 4) | (((p >> 8) & 0b1111)));
    *(pf0 + (_ARENA_SCANLINES << 1)) = ((
      BitRev[p & 0b1111] | (BitRev[(p >> 4) & 0b1111] >> 4)
    ));

    pf0++;

    scanline++;
  }

  lcount++;
}

 

Now I acknowledge that this code is incomprehensible and rather nothing like what I just described - but trust me, it is the same process in operation just highly optimised. This code is retrieving character shape data, shifting it, then unpacking the bits and rearranging into PF format. Since the strips of video memory are consecutive in memory, I use "+ARENA_SCANLINES" to index into the next PF register.  So it's writing PF0, PF1, PF2 for the current scanline, based on the graphics content of the characters retrieved from the character shape number retrieved from the animation program for the character type retrieved from the character number retrieved from the board. Whew!

 

The upshot of this; a smooth scrolling pixel-precision (vertical/horizontal) playfied using ChronoColour(TM) that can be fully drawn in a single frame and thus give 60Hz smooth playfield scrolling.

 

So, this process is running ever frame. But asynchronously to that is the board processor. This packs as much work as it can into spare time -- typically processing many, many board "squares" every frame. It scans the board from top to bottom, left to right... and it either handles each "character" on the board based on the character number (e.g., CH_WATER0), or character type (e.g., CharToType[*this] is TYPE_WATER). There are some times you need the actual character and some times you need the character type. Both are possible/handled, depending on the object and its behaviour.

 

There are an awful lot of potential interactions between the player and objects and objects and objects. Rather than code much of this as conditional code, it's all table driven, based on the character type. I have a bitfield table which indicates exactly what sort of interaction is available for each character type. Here's a subset of that table (in reality I have about 50+ types of objects)...

 


const int Attribute[] = {

// index via ObjectType value

#define _ 0

#define RKF ATT_ROCKFORDYBLANK
#define PSH ATT_PUSH
#define SSP ATT_SEMIBLANK
#define WTR ATT_WATER
#define LAV ATT_LAVA
#define QUI ATT_NOROCKNOISE
#define XIT ATT_EXIT
#define HRD ATT_HARD
#define SQB ATT_SQUASHABLE_TO_BLANKS
#define ACT ATT_ACTIVE
#define BNG ATT_EXPLODES
#define GRB ATT_GRAB
#define SQD ATT_SQUASHABLE_TO_DOGES
#define SPC ATT_BLANK
#define PER ATT_PERMEABLE
#define XPD ATT_EXPLODABLE
#define FLY ATT_KILLS
#define ROL ATT_ROLL
#define DRP ATT_DRIP
#define RND ATT_ROUNDDIRT

//                                         e           k
//                                        s           n               m           e
//           t               k           i           a               a       e   l
//          r       y       n           o           l       s       i       l   b    
//         i       d       a           n           B       e       D       b   a    
//        D       r       l           k           h   e   d       h       a   d    
//       d       e       b   r       c           s   v   o       s   k   e   o   s
//      n   p   y   h   i   e   a   o   t   d   a   i   l   b   a   n   m   l   l   1
//     u   i   a   s   m   t   v   R   i   r   u   t   p   a   u   a   r   p   l   l
//    o   r   l   u   e   a   a   o   x   a   q   c   x   r   q   l   e   x   i   o
//   R   D   P   P   S   W   L   N   E   H   S   A   E   G   S   B   P   E   K   R
//   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
//  RND DRP PKF PSH SSP WTR LAV QUI XIT HRD SQB ACT BNG GRB SQD SPC PER XPD FLY ROL

    RND| _ |PKF| _ | _ |WTR|LAV|QUI| _ | _ | _ | _ | _ | _ | _ |SPC|PER|XPD| _ | _  , // 00 BLANK_PARALLAX
    RND| _ |PKF| _ | _ |WTR|LAV|QUI| _ | _ | _ | _ | _ | _ | _ |SPC|PER|XPD| _ | _  , // 01 BLANK
     _ |DRP| _ | _ | _ |WTR|LAV| _ | _ | _ | _ | _ | _ | _ | _ | _ |PER|XPD| _ | _  , // 02 DIRT
     _ |DRP| _ | _ | _ | _ | _ | _ | _ |HRD| _ | _ |BNG| _ | _ | _ | _ |XPD| _ |ROL , // 03 BRICKWALL  
    RND| _ | _ | _ | _ | _ | _ |QUI| _ | _ | _ |ACT| _ |GRB| _ | _ | _ |XPD| _ | _  , // 09 DOGE  
    RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 10 EXPLODE_SPACE_0  
    RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 15 EXPLODE_DOGE_0
    RND| _ |PKF| _ | _ | _ |LAV| _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER| _ | _ | _  , // 23 DRIP 
    RND| _ |PKF| _ | _ | _ |LAV| _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER| _ | _ | _  , // 24 DRIP_SPLASH
    RND| _ | _ | _ | _ |WTR|LAV| _ | _ | _ | _ | _ | _ | _ | _ | _ |PER|XPD| _ | _  , // 28 RUBBLE
    RND| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC| _ |XPD| _ | _  , // 35 DOGE_GRAB  
    RND| _ |PKF| _ | _ | _ | _ |QUI| _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 37 DUST 
    RND| _ |PKF| _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 47 LAVA 
    RND| _ |PKF| _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ |SPC|PER|XPD| _ | _  , // 48 WATER
    RND| _ | _ | _ | _ | _ | _ | _ | _ | _ |SQB|ACT| _ | _ | _ | _ |PER| _ | _ | _  , // 49 EGG  
     _ | _ | _ |PSH| _ | _ | _ | _ | _ |HRD| _ |ACT|BNG| _ | _ | _ | _ |XPD| _ |ROL , // 52 ROCK  
     _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 53 DRILL
     _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |ACT| _ | _ | _ | _ | _ | _ | _ | _  , // 54 DRILLBODY
     _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 55 BELT
     _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _  , // 56 BELT2

//  RND|DRP PKF PSH SSP WTR LAV QUI XIT HRD SQB ACT BNG GRB SQD SPC PER XPD FLY ROL
//   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |
//   R   D   P   P   S   W   L   N   E   H   S   A   E   G   S   B   P   E   K   R
//    o   r   l   u   e   a   a   o   x   a   q   c   x   r   q   l   e   x   i   o
//     u   i   a   s   m   t   v   R   i   r   u   t   p   a   u   a   r   p   l   l
//      n   p   y   h   i   e   a   o   t   d   a   i   l   b   a   n   m   l   l   1
//       d       e       b   r       c           s   v   o       s   k   e   o   s
//        D       r       l           k           h   e   d       h       a   d
//         i       d       a           n           B       e       D       b   a  
//          r       y       n           o           l       s       i       l   b  
//           t               k           i           a               a       e   l
//                                        s           n               m           e
//                                         e           k

};

 

Each of the acronym entries is a unique bit value (bit 0 - bit31). So, for example...

     _ | _ | _ |PSH| _ | _ | _ | _ | _ |HRD| _ |ACT|BNG| _ | _ | _ | _ |XPD| _ |ROL , // 52 ROCK  

The above entry is for TYPE_ROCK. So as I'm processing the board, I can switch the object on CharToType[*this] == TYPE_ROCK, Let's say I wanted to know if I could push the object (it might not be a rock...) so ALL pushable objects have the PSH bit set. So I go... "if (Attribute[CharToType[*this]] & ATT_PUSH)" and that will be non-zero (i.e., true) if the object -whatever it may be- is pushable. Or, if it should explode during an explosion (XPD), or if objects can roll off the top of it (ROL), etc., etc.  It makes for a very nice system that doesn't require much of any specific conditional handling for the basics. It lets me modify and change the behaviour of many objects in the world just by changing the attributes in the table above.

 

Finally, "doing stuff" -- this is simply a matter of objects writing character numbers into the board. For example, to grab a coin, all the player's code does is something like -- if the object in the new square is grabbable, then blank the new square.  Of course there's a bit more finesse - animations, scoring, etc., but that's the fundamental. The board is scanned. The object there is processed based on character number and/or type. It checks squares around it (if required) and decides what to do. Move there. Blow up. Grab the object... whatver. This is the object behaviour that gives the game its look/feel. Once the decision is made, then modify the approrpiate surrounding squares on the board and exit. The board scanner will then process the next board character/square. If there's time available. If there isn't time, then the scanner will exit, and the code executes the normal overscan/vertical blank, screen draw (as described above), and then once again continue board scanning from where it left off.  So, the board scan is asynchronous occurring over (say) 10 frames. The draw is every frame. Animation is every frame (automatic) or at 10Hz (programmatic). Movement of objects on the board is only at 10Hz, but the player is a special-case which happens at 60Hz.

 

Well that's about all I feel like describing at the moment. It's a very generic system that allows me to switch to new behaviour and visuals very quickly. It is not challenged by lack of memory or processing power at the moment, so i really can see using this system for lots of games/demos. Having an (effective) pixel-resolution (8 colours) bitmapped video buffer opens up a lot of possibilities.

 

 

 

 

 

  • Like 4
Link to comment
Share on other sites

Well sometimes you can come back to heavily optimised code and see simple and obvious changes that optimise it even more. Such was the case with presenting the draw code above; I don't know why it was so complicated, as a bit of thinking and it's now much cleaner and even faster...

 

            unsigned char *pf0 = arenas[half] + scanline;
            unsigned char *pf1 = pf0 + _ARENA_SCANLINES;
            unsigned char *pf2 = pf1 + _ARENA_SCANLINES;

            for (int y = 0; scanline < SCANLINES && y < PIECE_DEPTH; y++) {

                int p =   (*image[0]++ & 0b1111) << 20
                        | (*image[1]++ & 0b1111) << 16
                        | (*image[2]++ & 0b1111) << 12
                        | (*image[3]++ & 0b1111) << 8
                        | (*image[4]++ & 0b1111) << 4
                        | (*image[5]++ & 0b1111);

                p >>= 4 - shift;

                if (lcount++ >= 0 ) {
                    *pf0++ = BitRev[(p >> 16) & 0b1111];
                    *pf1++ = p >> 8;
                    *pf2++ = BitRev[p & 0b1111] | (BitRev[(p >> 4) & 0b1111] >> 4);
                    scanline++;
                }
            }

A fair improvement, and much more readable/understandable - and speed is important in this particular section of the code. Each optimisation has the benefit of 198 times the optimisation amount because the code inside the loop there happens on every scanline. It's pretty neat, though, for fetching data from a character shape and mangling the bits into PF format and drawing into the video memory.

 

Edited by Andrew Davie
  • Like 2
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...