Goplanes

Announce (tech) demos or games releases

Moderator: Mask of Destiny

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Mon May 07, 2012 11:07 am

Chilly Willy wrote:Nifty way to use the "extra" bits. :D It's little tricks like this that make me interested in reading code and following development threads/blogs.
Thanks! I'll also be able to use that technique for other types of blocks.
Stef wrote:Very nice work :) It's really looks better and better at each version !
You can even animate those tile coins as the sprite ones just by modifying the 4 tiles pattern in vram :)
Oh yes, definitely.

Once again I'm busting vblank time. Now that I have to check the tile type before I draw it, my tile drawing code is getting too slow. I'm going to have to speed that up somehow. It's currently horrendous spaghetti.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Fri May 11, 2012 1:39 pm

I hate you so much, vblank. *shakes fist*

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Fri May 11, 2012 2:00 pm

Try to delegate whatever is possible in non Vblank period: prepare your next data frame, do precalculation if required in main ram and normally the only needed part in vblank are the final data trasfert to vram.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Fri May 11, 2012 2:27 pm

Stef wrote:Try to delegate whatever is possible in non Vblank period: prepare your next data frame, do precalculation if required in main ram and normally the only needed part in vblank are the final data trasfert to vram.
That's what I've been trying to do.

My main loop looks something like:

Code: Select all

while (1)
{
ProcessGameAI()
ProcessPlayer()
ProcessWorldObjects() // also sets up lists of sprites to be drawn later.

WaitForVBlank() // after this point, call all the VDP functions
UpdateBackgroundLayerScrolling() // updates screen edges
SetupSpritesForDrawing() // sets up hardware sprites
}
I'm obviously missing something. I must have stray calls to VDP in my game/player/world code.

I had noticed last night that I was setting up v/h scroll values way too early in the frame and definitely not in vblank. There was noticeable tearing at the top of the screen when there was a lot of sprites along the top. So I moved it to during vblank. The tearing is now gone, but I still have flickery/garbage-y sprites. I was sure setting the scroll values early was my problem, so I got frustrated when other problems still appeared.

Shiru
Very interested
Posts: 786
Joined: Sat Apr 07, 2007 3:11 am
Location: Russia, Moscow
Contact:

Post by Shiru » Fri May 11, 2012 6:36 pm

Could you show these functions?
UpdateBackgroundLayerScrolling() // updates screen edges
SetupSpritesForDrawing() // sets up hardware sprites
I have a feeling that you have something extra there that could be moved out of these.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sat May 12, 2012 1:38 am

Actual main loop looks like this:

Code: Select all

        do
	    {
            JOY_update();
            UpdateLevel();
            UpdateObjects();

            // wait for vsync and then draw
            VDP_waitVSync();
            VDP_resetSprites();
            DrawLevel();
            DrawObjects();
	    VDP_updateSprites();
        } while(levelRunning);


UpdateLevel looks like:

Code: Select all

void UpdateLevel()
{
    updateLevel(); // function pointer to level specific update function. currently doesn't do anything.
    UpdateScroll(); // updates the logical scroll position of the map. used to calculate the scroll values for the vdp later.
}

UpdateObjects() goes through the list of objects, updates them, determines which are visible and sticks them in an fixed-sized "ObjectStruct* objectsToDraw[]" array.
Each game object is represented by a struct with data and function pointers that manipulate that data. Data like world position, health points, ai state, etc, etc.

DrawLevel() looks like this:

Code: Select all

void DrawLevel()
{
    // homemade function that sets the same scroll value for every horizontal line.
    VDP_setAllHorizontalScrollLines(APLAN, scrollData.scrollx_vdp); 
    // homemade function that sets unique hscroll value to every horizontal line.
    VDP_setHorizontalScrollLines(BPLAN, backgroundLineScrollValues + (scrollData.scrolly_vdp >> 2), scrollData.scrollx_vdp >> 2);
   
    // Commenting out the homemade functions had no effect.


    VDP_setVerticalScroll(APLAN, 0, scrollData.scrolly_vdp);
    VDP_setVerticalScroll(BPLAN, 0,  scrollData.scrolly_vdp >> 2);

    updateForeground();
}


DrawObjects() looks like this:

Code: Select all


void DrawObjects()
{
    spriteDrawIndex = 0;

    playerObject->drawFunction(playerObject);

    u16 loop = numObjectsToDraw; 
    ObjectStruct** object = (ObjectStruct**)&objectsToDraw;

    while(loop--)
    {
        (*object)->drawFunction(*object);
        object++;
    }

    DrawHUD(); // commenting out the hud had no effect

    // sets the last used sprite's link to 0, 
    VDP_setSprite(spriteDrawIndex, 0, 0, 0, 0, 0);
}

Almost all object draw functions look like this:

Code: Select all

void CoinSparkleDraw(ObjectStruct* coinSparkleObject)
{
    coinSparkleObject->sprite.link  = spriteDrawIndex + 1;
    VDP_setSpriteP(spriteDrawIndex, &coinSparkleObject->sprite);
    spriteDrawIndex++;
}
The rest look like this, using one 16x16 sprite to make a 32x32 one using h/v flipping:

Code: Select all

void LargeExplosionDraw(ObjectStruct* largeExplosionObject)
{
    u16 spriteLink = spriteDrawIndex + 1;
    u16 animationFrame = LARGE_EXPLOSION_TILE_STARTINDEX + ((largeExplosionObject->spriteIndex >> 2 ) << 2);
    s16 x = largeExplosionObject->sprite.posx;
    s16 y = largeExplosionObject->sprite.posy;

    VDP_setSprite(spriteDrawIndex, x, y, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0, 1, 0, 0, animationFrame), spriteLink);
    spriteDrawIndex++;
    spriteLink++;

    VDP_setSprite(spriteDrawIndex, x + 16, y, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0, 1, 0, 1, animationFrame), spriteLink);

    spriteDrawIndex++;
    spriteLink++;

    VDP_setSprite(spriteDrawIndex, x, y + 16, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0, 1, 1, 0, animationFrame), spriteLink);

    spriteDrawIndex++;
    spriteLink++;

    VDP_setSprite(spriteDrawIndex, x + 16, y + 16, SPRITE_SIZE(2,2), TILE_ATTR_FULL(PAL0, 1, 1, 1, animationFrame), spriteLink);

    spriteDrawIndex++;
    spriteLink++;
}

When the glitches appear, there are about eight to ten bullets active and a half dozen small explosions being manually created. So I'd say I have less than 20 objects on screen. I wouldn't say I'm pushing the hardware really hard, but I don't really know what the expected performance should be. The rest of the AI/object/level code has been comfortably fast enough so far.

If you guys have any ideas, I'm all ears.

Shiru
Very interested
Posts: 786
Joined: Sat Apr 07, 2007 3:11 am
Location: Russia, Moscow
Contact:

Post by Shiru » Sat May 12, 2012 4:45 am

I don't use the SDK myself and work with the HW directly, so I don't know what exactly happens in the VDP_setSprite, but I guess it writes into the VRAM without buffering.

What I would do is construct the sprite list in RAM, before the VBlank, then dump it into the VRAM with a single DMA.

Edit: checked the SDK source. Well, it is cached. So you should be able to move the VDP_resetSprites(); and DrawObjects() out of the VBlank, as only VDP_updateSprites() has to be called during the VBlank.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sat May 12, 2012 11:52 am

I did as you suggested and it seems to have improved!

Thanks!

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sun May 13, 2012 1:09 am

Figured out that random crash on hardware. Seems like I was performing collisions between player bullets and garbage memory. Whoops.

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sun May 20, 2012 2:16 am

Goplanes Dev Video 013

http://youtu.be/kRVg4_mE674

From the description:

Added the first new Goplanes gameplay addition in over a decade, a charge attack. In this initial version, it lets the player crash through breakable rocks. It slows down the plane when charging, which also helps with aiming. It's a powerful move, but when the charge attack is unleashed the plane is uncontrollable.

Asides from that new major feature, worked on smaller things. Fixed a random crash bug where I was performing collision detection on random memory (bad programmer, bad).

Fixed an issue that I hadn't noticed before where sprites would "swim" around when the screen scrolls. This was due to not updating their positions relative to the scrolling correctly, and some error caused by my "virtual" coordinate system. The virtual coordinate system has four points for every pixel. This makes flying objects move smoother across the screen when flying at angles.

Fixing the swimming issue also had the bonus of sprites shaking when I apply a shake effect to the screen. The shake happens when the plane unleashes its charge attack.

Also added a toggle for a debug flying mode. Hitting the start button stops the plane and then it can move up/down/left/right by using the dpad. This was necessary for testing the collision detection with the breakable blocks.

The physics/collision code is getting pretty complicated now. But it's at the point where I could apply it for large sprites. So I could, say, have a large zeppelin and have the plane bounce off of it without a lot of modifications to the physics/collision code.

Refactored the drawing code to draw different types of objects in a certain order. UI goes first, then pick ups, then effects, then player/enemies, then bullets/projectiles, and finally breakables.

Switched to SGDK 0.91, which has trimmed off 128k off my binary, which is nice.

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Sun May 20, 2012 3:48 am

That's really cool! I like that charge attack. :D

djcouchycouch
Very interested
Posts: 710
Joined: Sat Feb 18, 2012 2:44 am

Post by djcouchycouch » Sun May 27, 2012 3:22 am

http://youtu.be/5kYjLdKqWHo

From the description:
Added a few more new features.

1. Radar indicator thingie. I've always wanted to give the player an idea of where objects and enemies are outside of the screen, within a certain range. This makes it easier to find and fight them.

2. Rotating objects. The enemy planes now orient themselves towards the player. This will be useful for other things like cannon turrets.

3. Changing levels. Hitting the start button switches to a different level, using different assets and code. You can tell by the changing tile layout and enemy placement. One map I attempted to do a "winter" style theme but totally failed and it looks pretty bad. :) Before I was hardcoding sprites locations into the VDP but now it's much more flexible. There's a common set of tiles to load (player, weapons, effects, hud, etc.) and each level can choose to load their own stuff, like the background and foreground layers.

4. Player lives. Only shown briefly at the end. When the player dies, they restart the level one with less life. And the enemies that have been destroyed stay dead.

5. Enemy planes have their own sprite. Now I won't get questions anymore about why there are many copies of the player on screen :) Enemy objects now have their own dedicated 16 colour palette. The current palette setup is PAL0 for the player and effects, PAL1 for enemy objects, PAL2 for the background layer and PAL3 for the foreground layer.

This part wouldn't fit in the Youtube description:

It took me a while to come up with a solution to do the radar and enemy plane rotations. I went with a look up table of precomputed values of angles in degrees. The table is four times the size of the screen area, but divided by 8 so that it maps to tile space. Only having angle values for 8x8 sections of the map is good enough for me as I don't need pixel perfect values. This gives me a map of 80 x 56 of 16 bit values for a total of 8k. It's a little big but it'll be used for a lot of things so I don't mind too much.

The angle look up table. Every value in the table contains the angle made from the origin to its position. Angles go from 0 to 90, but we're mapping it to 0 to 63 to only take up six bits in the 16 bit value, explained later.

Code: Select all

(0, 0)
--------------------------
|                        | 
|                        | 
|                        | 
|                        | 
------------------------- (79, 55)
This is a typical scene. When the enemy is off screen but within a certain range, we want to show a radar indicator thing.
(P) is the player, in the middle of the screen
(E) is the enemy object, outside of the screen but in range.

Code: Select all

range
--------------------------------------------------
|                                                |
|                                                |
|             _______________________            |
|             |screen               |            |
|             |            (P)      |            |
|             |                     |            |
|             -----------------------            |
|                                     (E)        |
|                                                |
--------------------------------------------------
When computing the angle between two objects, we first translate their positions so that the first object is at the origin. Then we divide their positions by 8, the tile size, so that their new tile-space positions map to the angle look up table.

Code: Select all

(0, 0)
(P)---------------------
|                      | 
|                      | 
|              (E)     |  (50, 30)
|                      | 
------------------------ (79, 55)
So the value at angle_table[50, 30] will be the actual angle between those two objects. Later I'll explain what I do with it.

The area around the player is logically divided into four quadrants, with the player position as the origin. Each quadrant is 80 x 56 in tile space (640x448 in pixel space) which maps to our angle look up table.

The angle look up table really only has values for one quadrant, quadrant 4. But we can derive correct values from it for the other quadrants. This saves us space as a trade off for a bit of computation.

Code: Select all

range
1                                2
----------------------------------------------------
|                          |                       |
|                          |                       |
|                ____________________              |
|                | screen  |        |              |
-----------------|--------(P)-------|---------------
|                |         |        |              |
|                --------------------              |
|                          |        (E)            |
|                          |                       |
----------------------------------------------------
3                                4
Once I've gotten the angle from the table, I use it to make other lookup into other tables. For example, for the radar pointer thing, I use the angle to get the x/y positions from a pre-computed position table. And for the enemy planes, I map the angle to a table of animation frames. Angles are mapped to 0-63 because then I can use bit shifts to map into other tables that are almost always power of two. So while I lose a bit of precision (256 degrees for a circle instead of 360), it makes things a bit easier to work with.

A related problem I was trying to figure out was how to quickly compute distance between two objets. Since we're only using 6 bits out the 16 for every value, there's quite a bit of room left so I thought I'd use the extra 10 bits to store distance as well. Bonus!

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Tue May 29, 2012 8:02 am

Nice trick ! And as you said you can also store the distance in the remaining 10 bits :) Really neat again ! lookup tables are our friends :)

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Tue May 29, 2012 5:16 pm

Stef wrote:Nice trick ! And as you said you can also store the distance in the remaining 10 bits :) Really neat again ! lookup tables are our friends :)
As long as you had the space (rom or ram), lookup tables have long been the savior of lesser processors everywhere. It's not until CPU instruction caches were large enough to hold an algorithm AND the CPU was much faster than the bus that lookup tables started to fade from programming.

Stef
Very interested
Posts: 3131
Joined: Thu Nov 30, 2006 9:46 pm
Location: France - Sevres
Contact:

Post by Stef » Wed May 30, 2012 9:48 am

Indeed.. but it's always nice to find tricks like that, a good lookup which can avoid us lot of calculations :)

Post Reply