git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

gbdk-lib/examples/cross-platform/text_advanced_dialogue/src/main.c

      1 #include <gbdk/platform.h>
      2 #include <stdint.h>
      3 #include <string.h>
      4 #include "Font.h"
      5 #include "DialogueBox.h"
      6 
      7 
      8 #define MIN(A,B) ((A)<(B)?(A):(B))
      9 
     10 #define TYPEWRITER_DELAY 2
     11 #define LINE_SKIP_DELAY 7
     12 #define LINE_SKIP 2
     13 #define DIALOG_BOX_HEIGHT 7
     14 #define DIALOG_BOX_INNER_HEIGHT (DIALOG_BOX_HEIGHT-2)
     15 
     16 #if defined(SEGA)
     17     #define BYTES_PER_TILE  2
     18 #else
     19     #define BYTES_PER_TILE  1
     20 #endif
     21 
     22 #if !DEVICE_SUPPORTS_WINDOW
     23 
     24     #define get_winbkg_xy_addr get_bkg_xy_addr
     25     #define set_winbkg_based_tiles set_bkg_based_tiles
     26     #define set_text_tiles set_bkg_tiles
     27     #define fill_winbkg_rect fill_bkg_rect
     28     #define set_winbkg_tile_xy set_bkg_tile_xy
     29     #define DIALOGUE_BOX_Y (DEVICE_SCREEN_HEIGHT-DIALOG_BOX_HEIGHT)
     30 #else
     31     #define get_winbkg_xy_addr get_win_xy_addr
     32     #define set_winbkg_based_tiles set_win_based_tiles
     33     #define fill_winbkg_rect fill_win_rect
     34     #define set_winbkg_tile_xy set_win_tile_xy
     35     #define set_text_tiles set_win_tiles
     36     #define DIALOGUE_BOX_Y 0
     37 #endif
     38 
     39 #define TILE_SIZE_BYTES  (BYTES_PER_TILE*16)
     40 #define INNER_DIALOGUE_BOX_WIDTH (DEVICE_SCREEN_WIDTH-2)
     41 
     42 int16_t windowYPosition=DEVICE_SCREEN_PX_HEIGHT;
     43 uint8_t fontTilesStart=0;
     44 
     45 uint8_t joypadCurrent=0,joypadPrevious=0;
     46 
     47 uint8_t loadedCharacters[Font_TILE_COUNT];
     48 uint8_t loadedCharacterCount=0;
     49 
     50 void MoveWindow(void){
     51     
     52     #if DEVICE_SUPPORTS_WINDOW
     53     
     54     move_win(7,windowYPosition);
     55 
     56     #endif
     57 }
     58 
     59 
     60 uint8_t GetTileForCharacter(char character) {
     61 
     62     uint8_t vramTile=0;
     63 
     64 
     65     // Char's can be interpreted as integers
     66     // We don't need to map every alpha-numeric character
     67     // We can use basic math to simplify A-Z and 0-9
     68     if      (character >= 'a' && character <= 'z') vramTile = (character - 'a') + 1;
     69     else if (character >= 'A' && character <= 'Z') vramTile = (character - 'A') + 1;
     70     else if (character >= '0' && character <= '9') vramTile = (character - '0') + 27;
     71     else {
     72         switch(character) {
     73             case '!': vramTile = 37; break;
     74             case ':': vramTile = 38; break;
     75             case '?': vramTile = 39; break;
     76             case '/': vramTile = 40; break;
     77             case '=': vramTile = 41; break;
     78             case ',': vramTile = 42; break;
     79             case '.': vramTile = 43; break;
     80             case '<': vramTile = 44; break;
     81             case '>': vramTile = 45; break;
     82         }
     83     }
     84 
     85     return vramTile;
     86 
     87 }
     88 
     89 uint8_t IsAlphaNumeric(char character){
     90 
     91     // Return true for a-z,A-Z, and 0-9
     92     if(character>='a'&&character<='z')return TRUE;
     93     else if(character>='A'&&character<='Z')return TRUE;
     94     else if(character>='0'&&character<='9')return TRUE;
     95 
     96     return FALSE;
     97 }
     98 
     99 uint8_t BreakLineEarly(uint16_t index, uint8_t columnSize, char* text){
    100 
    101     char character = text[index++];
    102 
    103     // We can break, if we are at the end of our row
    104     if(columnSize>=INNER_DIALOGUE_BOX_WIDTH)return TRUE;
    105 
    106     // We DO NOT  break on alpha-numeric characters
    107     if(IsAlphaNumeric(character))return FALSE;
    108 
    109     // How many characters are left on the current line
    110     uint8_t spaceLeftOnLine=INNER_DIALOGUE_BOX_WIDTH-columnSize;
    111     uint8_t nextColumnSize=columnSize+1;
    112 
    113     // Loop ahead until we reach the end of the string
    114     while((character=text[index++])!='\0'){
    115 
    116         // Stop when we reach a non alphanumeric character
    117         if(!IsAlphaNumeric(character))break;
    118 
    119         // Increase how many characters we've skipped
    120         nextColumnSize++;
    121     }
    122 
    123     // Return TRUE if the distance to the next non alphanumeric character, is larger than we have left on the line
    124     return nextColumnSize>INNER_DIALOGUE_BOX_WIDTH;
    125 }   
    126 
    127 
    128 void WaitForAButton(void){
    129     while(TRUE){
    130 
    131         joypadPrevious=joypadCurrent;
    132         joypadCurrent = joypad();
    133         
    134         if((joypadCurrent & J_A) && !(joypadPrevious & J_A))break;
    135     }
    136 
    137 }
    138 
    139 void ClearDialogueBox(void){
    140 
    141     // Fill the middle of the dialog box with blank space
    142     fill_winbkg_rect(1,DIALOGUE_BOX_Y+1,DEVICE_SCREEN_WIDTH-2,DIALOG_BOX_HEIGHT-2,0);
    143     
    144     // Top  and Bottom sides
    145     fill_winbkg_rect(1,DIALOGUE_BOX_Y,DEVICE_SCREEN_WIDTH-2,1,2);
    146     fill_winbkg_rect(1,DIALOGUE_BOX_Y+DIALOG_BOX_HEIGHT-1,DEVICE_SCREEN_WIDTH-2,1,2);
    147 
    148     // Left and right sides
    149     fill_winbkg_rect(0,DIALOGUE_BOX_Y+1,1,DIALOG_BOX_HEIGHT-2,4);
    150     fill_winbkg_rect(DEVICE_SCREEN_WIDTH-1,DIALOGUE_BOX_Y+1,1,DIALOG_BOX_HEIGHT-2,4);
    151 
    152     // Top Left and Top Right corners
    153     set_winbkg_tile_xy(0,DIALOGUE_BOX_Y,1);
    154     set_winbkg_tile_xy(DEVICE_SCREEN_WIDTH-1,DIALOGUE_BOX_Y,3);
    155 
    156     // Bottom Left and Bottom Right corners
    157     set_winbkg_tile_xy(0,DIALOGUE_BOX_Y+DIALOG_BOX_HEIGHT-1,5);
    158     set_winbkg_tile_xy(DEVICE_SCREEN_WIDTH-1,DIALOGUE_BOX_Y+DIALOG_BOX_HEIGHT-1,6);
    159 }
    160 
    161 void ShowDialgoueBox(void){
    162 
    163     ClearDialogueBox();
    164 
    165     int16_t desiredWindowPosition = DEVICE_SCREEN_PX_HEIGHT - (DIALOG_BOX_HEIGHT*8);
    166 
    167     while(windowYPosition>desiredWindowPosition){
    168 
    169         windowYPosition-=4;
    170         MoveWindow();
    171         vsync();
    172     }
    173 }
    174 
    175 void HideDialgoueBox(void){
    176 
    177     int16_t desiredWindowPosition = DEVICE_SCREEN_PX_HEIGHT;
    178 
    179     while(windowYPosition<desiredWindowPosition){
    180 
    181         windowYPosition+=4;
    182         MoveWindow();
    183         vsync();
    184     }
    185 }
    186 
    187 void ResetLoadedCharacters(void){
    188 
    189     loadedCharacterCount=1;
    190 
    191     // Reset everything to 255
    192     for(uint8_t i=0;i<45;i++)loadedCharacters[i]=255;
    193 }
    194 
    195 void vsyncMultiple(uint8_t count){
    196      // Wait some frames
    197     // This creates a typewriter effect
    198     for(uint8_t i=0;i<count;i++){
    199 
    200         vsync();
    201     }
    202 }
    203 
    204 void DrawTextAdvanced(char* text){
    205 
    206     uint8_t column=1;
    207     uint8_t row=DIALOGUE_BOX_Y+1;
    208         
    209     ShowDialgoueBox();
    210 
    211     ResetLoadedCharacters();
    212 
    213     uint16_t index=0;
    214     uint8_t columnSize=0;
    215     uint8_t rowCount=0;
    216 
    217     // Increase the first dimension by 1
    218     // So it's easy to slide up rows (the top values will always be 0)
    219     uint8_t copyBuffer[DIALOG_BOX_INNER_HEIGHT+1][INNER_DIALOGUE_BOX_WIDTH];
    220     uint8_t copyBufferCount=0;
    221 
    222     // Clear the copy buffer
    223     for(uint8_t k=0;k<INNER_DIALOGUE_BOX_WIDTH;k++){
    224         for(uint8_t t=0;t<DIALOG_BOX_INNER_HEIGHT+1;t++){
    225             copyBuffer[t][k]=0;
    226         }
    227     }
    228 
    229 
    230     // Get the address of the first tile in the row
    231     uint8_t* vramAddress = get_winbkg_xy_addr(column,row);
    232 
    233     char c;
    234 
    235     while((c=text[index])!='\0'){
    236 
    237         if(c<32) { // Skip non-printable characters instead of printing space for each one
    238             index++;
    239             if(c=='\n')
    240                 goto force_line_break;
    241             continue;
    242         }
    243         uint8_t vramTile = GetTileForCharacter(c);
    244 
    245         // If we haven't loaded this character into VRAM
    246         if(loadedCharacters[vramTile]==255){
    247 
    248             // Save where we place this character in VRAM
    249             loadedCharacters[vramTile]=fontTilesStart+loadedCharacterCount++;
    250 
    251             // Place this character in VRAM
    252             set_native_tile_data(loadedCharacters[vramTile],1,Font_tiles+vramTile*TILE_SIZE_BYTES);
    253 
    254         }
    255 
    256         // Draw our character at the address
    257         // THEN, increment the address
    258         set_vram_byte(vramAddress++,loadedCharacters[vramTile]);
    259 
    260         #if defined(SEGA)
    261         set_vram_byte(vramAddress++,0);
    262         #endif
    263 
    264         // Copy everything to a buffer
    265         // So we can easily slide that row upwards, without having to access VRAM
    266         if(rowCount<DIALOG_BOX_INNER_HEIGHT)copyBuffer[rowCount][copyBufferCount++] = loadedCharacters[vramTile];
    267 
    268         index++;
    269         columnSize++;
    270 
    271          // if we've reached the end of the row
    272         if(BreakLineEarly(index,columnSize,text) ||c=='.'||c=='?'||c=='!'){
    273             force_line_break:
    274 
    275             rowCount+=LINE_SKIP;
    276 
    277             // Reset/Clear the copy buffer
    278             copyBufferCount=0;
    279 
    280             // If we just drew a period or question mark,
    281             // wait for the a button  and afterwards clear the dialogue box.
    282             if(c=='.'||c=='?'||c=='!'){
    283                 WaitForAButton();
    284                 ClearDialogueBox();
    285                 rowCount=0;
    286 
    287                 // Clear the copy buffer
    288                 for(uint8_t k=0;k<INNER_DIALOGUE_BOX_WIDTH;k++){
    289                     for(uint8_t t=0;t<DIALOG_BOX_INNER_HEIGHT+1;t++){
    290                         copyBuffer[t][k]=0;
    291                     }
    292                 }
    293 
    294             }
    295 
    296             // if we've drawn our rows
    297             else if( rowCount>=DIALOG_BOX_INNER_HEIGHT){
    298 
    299                 // We want the most recently drawn line to be at the top of the dialog box
    300                 uint8_t shiftCount = rowCount-LINE_SKIP;
    301 
    302                 for(uint8_t k=0;k<shiftCount;k++){
    303 
    304                     for(uint8_t t=0;t<DIALOG_BOX_INNER_HEIGHT;t++){
    305 
    306                         uint8_t copyBufferIndex = MIN(1+t+k,DIALOG_BOX_INNER_HEIGHT);
    307                         
    308                         set_text_tiles(1,DIALOGUE_BOX_Y+1+t,INNER_DIALOGUE_BOX_WIDTH,1,copyBuffer[copyBufferIndex]);
    309                     }
    310 
    311                     // Wait a little bit
    312                     vsyncMultiple(LINE_SKIP_DELAY);
    313                 }
    314 
    315                 // Clear the copy buffer
    316                 for(uint8_t k=0;k<INNER_DIALOGUE_BOX_WIDTH;k++){
    317                     for(uint8_t t=0;t<DIALOG_BOX_INNER_HEIGHT;t++){
    318                         copyBuffer[t][k]=0;
    319                     }
    320                 }
    321 
    322 
    323                 rowCount=LINE_SKIP;
    324 
    325             }
    326             
    327             // reset for the next row
    328             vramAddress = get_winbkg_xy_addr(column,row+rowCount);
    329             
    330             columnSize=0;
    331 
    332             // If we just started a new line, skip spaces and other non-printable characters
    333             while(text[index]>0 && text[index]<=32){
    334                 index++;
    335             }
    336         }
    337 
    338         vsyncMultiple(TYPEWRITER_DELAY);
    339     }
    340 
    341     HideDialgoueBox();
    342     
    343 }
    344 
    345 
    346 
    347 void ClearScreen(void){
    348     #if DEVICE_SUPPORTS_WINDOW
    349     fill_win_rect(0,0,DEVICE_SCREEN_WIDTH,DEVICE_SCREEN_HEIGHT,0);
    350     #endif
    351     fill_bkg_rect(0,0,DEVICE_SCREEN_WIDTH,DEVICE_SCREEN_HEIGHT,0);
    352 }
    353 
    354 void main(void)
    355 {
    356     DISPLAY_ON;
    357     SHOW_BKG;
    358 
    359     #if DEVICE_SUPPORTS_WINDOW
    360     SHOW_WIN;
    361     #endif
    362 
    363     fontTilesStart = DialogueBox_TILE_COUNT+1;
    364     uint8_t emptyTile[TILE_SIZE_BYTES];
    365     for(uint8_t i=0;i<TILE_SIZE_BYTES;i++)emptyTile[i]=0;
    366 
    367     set_native_tile_data(0,1,emptyTile);
    368 
    369     ClearScreen();
    370 
    371     set_native_tile_data(1,DialogueBox_TILE_COUNT,DialogueBox_tiles);
    372     
    373     while(TRUE){
    374 
    375         // We'll pass in one long string, but the game will present to the player multiple pages.
    376         DrawTextAdvanced("When\n the code reaches a period, exclamation point or question mark, it will pause and wait for you to press the A button for it to continue.\n\n \nExclamation point!Question Mark?\n Afterwards, It will start a new page and continue.  The code will automatically jump to a new line, when it cannot fully draw a word.  When both rows of text are full, the code will slide the current text upwards and continue. For every page, the code will dynamically populate VRAM. Only letters and characters used, will be loaded into VRAM.");
    377         
    378     }
    379 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.