GUI Tutorial

From Uzebox Wiki
Jump to: navigation, search
  • Formatting is horrible I'll correct it later.

Basic Gui Tutorial

This will be a brief tutorial describing some concepts that may be useful to someone. If you have some experience you could probably skip this.


Adding a decent GUI doesn't have to be hard or take much space depending how you go about it. I will cover only some basic aspects, certainly you'd modify it to your own purposes. Some ground rules I'd recommend you go by:

  • Just Make your GUI with blocking functions, unless you have some justifiable reason not to. We aren't making fancy PC games here.
  • Hide all sprites. This isn't necessarily required, but at the very least a bounding box test needs to be done to hide sprites that would be over the window(s).
  • Use ram tile effects if you're looking for something extra(transparency on menu corners, use any colors, gradients?) and don't mind a bit of complication.


Example 1

  • Here's some example code for drawing a menu. Drawing a menu with ram tiles would also be similar and allows us any window or text color for no extra flash space(more on that next time). Not much else to say let's see some code:


  #define uchar unsigned char //save time/readability
  #define MENUSTART GAMETILESEND+1 //this would be whatever tile index your menu starts at. Examples are using the below tiles.

Graphics resource example: Menutiles.png

  void DrawMenu(uchar x, uchar y, uchar w, uchar h){
     SetTile(x+0,y+0,MENUSTART+5);//draw the corners
     SetTile(x+w,y+0,MENUSTART+6);
     SetTile(x+0,y+h,MENUSTART+7);
     SetTile(x+w,y+h,MENUSTART+8);
     for(uchar i=x+1;i<x+w;i++){SetTile(i,y,MENUSTART+0);SetTile(i,y+h,MENUSTART+1);}//draw top and bottom
     for(uchar i=y+1;i<y+h;i++){SetTile(x,i,MENUSTART+2);SetTile(x+w,i,MENUSTART+3);}
     for(uchar i=y+1;i<y+h;i++)
     for(uchar j=x+1;j<x+w;j++)
        SetTile(j,i,MENUSTART+9);

  
  }

-Here is a little added functionality in the form of an opening window, with controllable speed. This is a blocking function. Redesign to your needs.

  void DrawOpeningMenu(uchar x, uchar y, uchar w, uchar h, int speed){ //meh, simple but it works
     uchar mt = 1;
     uchar count = speed;

     while(mt<w+1){//open horizontally
        DrawMenu(x,y,mt,1);

if(speed < 0)

           WaitVsync(abs(speed));

else if(!count){

            WaitVsync(1);
            count=speed;
         }else
            count--;

mt++;

     }
     mt = 1;
     count = speed;
     while(mt<h+1){
        DrawMenu(x,y,w,mt);

if(speed < 0)

           WaitVsync(abs(speed));

else if(!count){

            WaitVsync(1);
            count=speed;
         }
         else
            count--;

}

mt++;

     }
  }


Ok, now we have the ability to draw a menu at any place with variable speed. This is all you need if you are doing a title screen or similar. But let's assume that we would like to create an in game GUI. Let's also say that our game does not redraw the whole screen every frame. It's apparent that when you break out of your menu logic, the menu will still be there in vram. So we just need a way to get it back to it's previous state. Many options for this. Really it would be ideal if the menu system kept track of it's draws, and returned a handle to it so you could call a corresponding CloseMenu(handle). This is specific to your needs for at least 1 reason:

  • You may not have the extra ram to waste on menus you will rarely use. The menu would have to keep a buffer of what tiles were under it before the draw and it would have to be either resizeable or as large as your largest menu!

In some games this might be totally acceptable and eventually I may get around to writing a nice little generic GUI for that. So far my games are gluttons for ram tiles seeking to destroy the stack at any moment. Opening a large restore buffer in a function is a risk I don't need to take! This is why I say just hide all your sprites. Then use that ram tile space for your restore buffers since they're already there and wont be visible. If you want to get fancy with effects our memory may have to do double duty sometimes. Otherwise do it the tidy way.

Example 2

Here's a simple example for these concepts. Replace the game specifics with your own and this is a drop in. Nothing fancy but it works.

  void InGameMenu(){
     //Fill the screen with a background if this isn't an in game menu. You could fill vram with a ram tile index, then fill that ram tile with any color you like   or even strobe it.
     //for(int i=0;i<(SCREEN_TILES_V*SCREEN_TILES_H);i++)//set all vram to point to ram tile 0
     //   vram[i] = 0;
     //for(int i=0;i<64;i++)//fill all of ram tile 0 pixels with some color 0xC4
     //   ram_tiles[i] = 0xC4;
     //Lots more possibilites here

     uchar cursorpos = 0;
     ClearSprites();//make sure there are no ram tile indexes in vram, this function just sets all sprite's x coordinates to 255 so the kernel ignores them
     WaitVsync(1);

     int off = 64; //would need to be greater than 64, if you used more ram tiles for the screen fill described above. This is the actual byte offset into ram tiles obviously.
     for(uchar y=0;y<6;y++)
     for(uchar x=0;x<10;x++)
        ram_tiles[off++] = (vram[((y+9)*SCREEN_TILES_V)+x+11]);//store in ram tiles since we are low on ram and it wont be drawn anyway. you could just use a regular buffer too

     DrawOpeningMenu(11,9,8,5,-2);
     print(12,10,strBack); //I just use a custom print because I don't need full font sets. Use the kernel version(same parameters)
     print(12,11,strRetry);
     print(12,12,strForfeit);
     print(12,13,strQuit);
  
 
     while(true){
        FillPad(); //fill our padstate variable

if(StartDown()){ //if(padstate & BTN_START && !(oldpadstate & BTN_START)){

        //First restore the map

off = 64;

           for(uchar y=0;y<6;y++)
           for(uchar x=0;x<10;x++)
              vram[((y+9)*30)+x+11] = ram_tiles[off++];//we used ram tiles because we are low on ram ^

          if(cursorpos == 0)//back

asm("nop"); else if(cursorpos == 1)//retry KillLoLo();

          else if(cursorpos == 2){//give up

guistate = GCONTINUE; guijuststarted = true;

           }

else{//quit guistate = GMAINMENU; guijuststarted = true; }

           //...etc.         

WaitVsync(1); return; } else if(UpDown() && cursorpos > 0){cursorpos--;} //move our cursor else if(DownDown() && cursorpos < 3){cursorpos++;} for(uchar i=0;i<4;i++){SetTile(11,10+i,MENUSTART+2);}//draw the menu side over the old cursor positions SetTile(11,10+cursorpos,MENUSTART+4); //draw our cursor position

        WaitVsync(1);
     }
  }

Wrap Up

So what have you learned? Maybe nothing! This is all I have right now and I just wanted to lay out some basics for those that it may help(I know there were a few who mentioned they were newer to games). If this is all you needed to know, great! Otherwise, I think the real strength of a good GUI is ease of expandability. We need a layer on top of the whole thing that controls all the windows opening closing and text print out. We also need it to do what we want based on menu choices, and be able to pass it style flags. Some visual details could include having what would be visible text strings drawn partially clipped as the menu opens. Everything higher level incurs overhead, but in nearly all cases I find programmer's time is more valuable than a cpu's. This is where the next tutorial is headed anyways, well whenever I get around to it that is :) Hopefully some newer guys did find this working example useful. Read my Ram Tile Effects Primer otherwise if you are bored/drunk/or on some form of PCP(which is not endorsed by my sponsors).