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.