Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » GUI Design and Implementation for Noobs

This thread is locked; no one can reply to it. rss feed Print
GUI Design and Implementation for Noobs
kingnoob
Member #18,984
January 2021

We Noobs tend not to know how to code things like a GUI. That is until we learn how everyone else does it. However, that is when a Noob stops being a Noob. Noobs that join the establishment cannot be King Noobs! King Noobs always forge their own path even if it ends up not being optimal. So if you are a Noob and would like to become a King Noob then this example of noobishness might be for you! :)

First of all King Noobs never imprison themselves in a prepackaged cookie cutter solution that limits their creativity or dulls their excitement about what they have created. Nor do we need more functionality than what will get the job done. And we definitely cannot learn other peoples source code and how to modify it. It is just against our nature. :D

So, now that every Noob reading here knows what it means to be a King Noob we can begin. First we need some code to get events. Even though we are King Noobs we still right noobish code. We plan on cleaning it up later but as long as it works it is not a priority. In the following GetEvent() function at least two very noobish things are done. One, global variables are used that should be placed in a GUI structure. And two, some processing that should be in ProcessEvent() is in GetEvent(). Here is the code for GetEvent().

#SelectExpand
1bool GetEvent() { 2 if (al_get_next_event(queue, &event)) { 3 switch (event.type) { 4 case ALLEGRO_EVENT_KEY_DOWN: 5 switch (event.keyboard.keycode) { 6 case ALLEGRO_KEY_ESCAPE: 7 return false; 8 break; 9 case ALLEGRO_KEY_PAD_MINUS: 10 if (rate < 40) rate++; 11 break; 12 case ALLEGRO_KEY_PAD_PLUS: 13 if (rate > 1) rate--; 14 break; 15 case ALLEGRO_KEY_D: 16 demo = 1 - demo; 17 break; 18 } 19 break; 20 case ALLEGRO_EVENT_MOUSE_AXES: 21 mx = event.mouse.x; 22 my = event.mouse.y; 23 break; 24 case ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: 25 mbu = false; 26 mbd = event.mouse.button; 27 mdx = event.mouse.x; 28 mdy = event.mouse.y; 29 break; 30 case ALLEGRO_EVENT_MOUSE_BUTTON_UP: 31 mbd = false; 32 mbu = event.mouse.button; 33 mux = event.mouse.x; 34 muy = event.mouse.y; 35 break; 36 } 37 } 38 return true; 39}


This code works perfectly in the game that I am writing! And is success for a Noob, even a King Noob! Next is the ProcessEvent() function. It also works perfectly. More success! :)

#SelectExpand
1void ProcessEvent() { 2 switch (gui[ui]) { 3 case NONE: 4 None(); 5 break; 6 case SEND: 7 Send(); 8 break; 9 case STAND: 10 Stand(); 11 break; 12 case GAME: 13 Game(); 14 break; 15 case SELECT: 16 Select(); 17 break; 18 case PAUSED: 19 Paused(); 20 break; 21 } 22}

Also King Noobs are not known for their code commenting skills. You can take the king out of the noob but you can't take the noob out of the king. Don't try to understand that. King Noobs do not always make sense. Looking at the function all it does is proceed to the active GUI element which includes NONE. It is the responsibility of None() to detect the desired activation of a GUI element.

#SelectExpand
1void None() { 2 if (mbu) { 3 org = GetStar(); 4 if (org == numStars) { 5 if (mbu == 2) { 6 paused = true; 7 ui++; 8 gui[ui] = PAUSED; 9 } 10 else if (mbu == 1) { 11 paused = true; 12 ui++; 13 gui[ui] = PAUSED; 14 ui++; 15 gui[ui] = GAME; 16 } 17 } 18 else { 19 ui++; 20 gui[ui] = SELECT; 21 } 22 mbu = false; 23 } 24}


My simple game has only three activations to check for:
1. A right or left click on a star to activate the selector GUI element
2. A right click on "empty space" to pause the game.
3. A left click on empty space to bring up the game menu that also pauses the game
We are going to look at the selector draw code. All it does is draw a line from the center of the origination star to the mouse (mx, my) in green if the destination is in range or red if it is not. Also it prints the distance in green.

#SelectExpand
1DrawSelect() { 2 int d, dx, i, j, x, y, X, Y; 3 ALLEGRO_COLOR c; 4 char buf[10]; 5 6 i = GetStar(); 7 if (i < numStars) { 8 x = star[i].x; y = star[i].y; 9 } 10 else { 11 x = mx; y = my; 12 } 13 c = RED; 14 inRange = false; 15 for (int j = 0; j < numStars; j++) { 16 if (star[j].owner == 1) { 17 X = star[j].x - x; 18 Y = star[j].y - y; 19 d = sqrt(pow(X, 2) + pow(Y, 2)); 20 if (d <= maxDist) { 21 c = GREEN; 22 inRange = true; 23 break; 24 } 25 } 26 } 27 al_draw_line(star[org].x, star[org].y, x, y, c, 2); 28 X = star[org].x - x; 29 Y = star[org].y - y; 30 d = sqrt(pow(X, 2) + pow(Y, 2)); 31 _itoa_s(d, buf, 10, 10); 32 dx = x + 54; j = ALLEGRO_ALIGN_RIGHT; 33 if (star[org].x > x) { 34 dx = x - 54; 35 j = ALLEGRO_ALIGN_LEFT; 36 } 37 al_draw_text(font15, GREEN, x, y - 32, ALLEGRO_ALIGN_CENTRE, buf); 38}

Since the GUI mode is now in "select" mode the next time ProcessEvent() is called the switch statement will jump to the Select() function which does one of the following.
1. Any click on empty space cancels SELECT mode and returns to NONE or if the game was paused it returns to PAUSED.
2. A left click on a destination star in range puts the GUI in SEND mode and will draw the send ships GUI element.
3. A right click on a star puts the GUI in STAND mode and will draw the standing orders GUI element.
4. A second click on the origin star will cancel its standing order if there is one.

We are going to look at number 3. standing orders. I have not yet displayed the draw GUI selector code. It will be displayed just after Select(). It will look familiar.

#SelectExpand
1void Select() { 2 if (mbu) { 3 dst = GetStar(); 4 if (dst == org) { 5 orders[org].over = 0; 6 gui[ui] = NONE; 7 ui--; 8 } else 9 if (dst == numStars || dst == org) { 10 gui[ui] = NONE; 11 ui--; 12 } 13 else { 14 if (star[org].owner == 1) { 15 if (mbu == 1) { 16 if (star[dst].owner == 1 || InRange(dst)) { 17 gui[ui] = SEND; 18 send = 0; 19 } 20 } 21 else { 22 if (star[dst].owner) { 23 gui[ui] = STAND; 24 active = 0; 25 } 26 } 27 } 28 } 29 mbu = false; 30 } 31}

Draw selection code called from DrawFrame() in the main game loop.

#SelectExpand
1void DrawGui() { 2 switch (gui[ui]) { 3 case NONE: 4 DrawInfo(); 5 break; 6 case SEND: 7 DrawSend(); 8 break; 9 case STAND: 10 DrawStand(); 11 break; 12 case GAME: 13 DrawGame(); 14 break; 15 case SELECT: 16 DrawSelect(); 17 DrawInfo(); 18 break; 19 case PAUSED: 20 DrawInfo(); 21 break; 22 } 23}

Follows is the standing orders draw code. It is drawn every single frame. If I were not such a noob I'd put it in memory so that all that would need done is blit the unchanging parts to be more efficient. However, since the game runs plenty fast enough already, the King Noob way is to remain noobish about it, for now.

#SelectExpand
1void DrawStand() { 2 int i, x, y; 3 char buf[12]; 4 i = dst; 5 if (i < numStars) { 6 x = star[dst].x; y = star[dst].y; 7 gx = x = (x < sw / 2) ? x + 20 : x - 500; 8 gy = y = (y < 640) ? y : 640; 9 al_draw_filled_rounded_rectangle(x, y, x + 479, y + 398, 10, 10, STEEL); 10 al_draw_filled_rounded_rectangle(x + 435, y + 4, x + 475, y + 44, 10, 10, DARKSTEEL); 11 al_draw_text(font35, STEEL, x + 455, y + 4, ALLEGRO_ALIGN_CENTER, "X"); 12 al_draw_text(font70, BLACK, x + 239, y, ALLEGRO_ALIGN_CENTRE, star[i].name); 13 al_draw_line(x + 10, y + 80, x + 469, y + 80, BLACK, 4.0f); 14 for (i = 0; i < 10; i++) { 15 _itoa_s(i, buf, 12, 10); 16 al_draw_text(font70, BLACK, x + 33 + i * 46, y + 80, ALLEGRO_ALIGN_CENTER, buf); 17 } 18 19 al_draw_line(x + 10, y + 158, x + 469, y + 158, BLACK, 4.0f); 20 21 al_draw_text(font35, LIGHTSTEEL, x + 10, y + 168, 0, "If Over"); 22 al_draw_filled_rectangle(x + 140, y + 168, x + 340, y + 208, LIGHTSTEEL); 23 if (active == 0) al_draw_filled_circle(x + 146, y + 173, 4, DARKSTEEL); 24 _itoa_s(over, buf, 12, 10); 25 al_draw_text(font35, BLACK, x + 240, y + 168, ALLEGRO_ALIGN_CENTER, buf); 26 al_draw_filled_rectangle(x + 350, y + 168, x + 420, y + 208, LIGHTSTEEL); 27 al_draw_text(font25, BLACK, x + 385, y + 173, ALLEGRO_ALIGN_CENTER, "Clear"); 28 al_draw_filled_triangle(x + 430, y + 188, x + 450, y + 168, x + 450, y + 208, LIGHTSTEEL); 29 al_draw_filled_rectangle(x + 450, y + 168, x + 469, y + 208, LIGHTSTEEL); 30 al_draw_text(font35, BLACK, x + 457, y + 168, ALLEGRO_ALIGN_CENTER, "X"); 31 32 al_draw_line(x + 10, y + 218, x + 469, y + 218, BLACK, 4.0f); 33 34 al_draw_text(font35, LIGHTSTEEL, x + 10, y + 228, 0, "Send"); 35 al_draw_filled_rectangle(x + 140, y + 228, x + 340, y + 268, LIGHTSTEEL); 36 if (active == 1) al_draw_filled_circle(x + 146, y + 233, 4, DARKSTEEL); 37 _itoa_s(ships, buf, 12, 10); 38 al_draw_text(font35, BLACK, x + 240, y + 228, ALLEGRO_ALIGN_CENTER, buf); 39 al_draw_filled_rectangle(x + 350, y + 228, x + 469, y + 268, LIGHTSTEEL); 40 al_draw_text(font25, BLACK, x + 410, y + 233, ALLEGRO_ALIGN_CENTER, "Stop All"); 41 42 al_draw_line(x + 10, y + 278, x + 469, y + 278, BLACK, 4.0f); 43 44 al_draw_text(font35, LIGHTSTEEL, x + 10, y + 288, 0, "Bump"); 45 al_draw_filled_rectangle(x + 140, y + 288, x + 340, y + 328, LIGHTSTEEL); 46 if (active == 2) al_draw_filled_circle(x + 146, y + 293, 4, DARKSTEEL); 47 _itoa_s(bump, buf, 12, 10); 48 al_draw_text(font35, BLACK, x + 240, y + 288, ALLEGRO_ALIGN_CENTER, buf); 49 al_draw_filled_rectangle(x + 350, y + 288, x + 469, y + 328, LIGHTSTEEL); 50 al_draw_text(font25, BLACK, x + 410, y + 293, ALLEGRO_ALIGN_CENTER, "Order"); 51 52 al_draw_line(x + 10, y + 338, x + 469, y + 338, BLACK, 4.0f); 53 54 al_draw_filled_rounded_rectangle(x + 10, y + 348, x + 469, y + 388, 10, 10, DARKSTEEL); 55 al_draw_text(font15, LIGHTSTEEL, x + 240, y + 359, ALLEGRO_ALIGN_CENTER, "Reasign ALL Standing Orders To Destination"); 56 } 57}

GOT IT WORKING.
{"name":"612863","src":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/d\/8\/d83163e7053812846608b2d45483252f.png","w":983,"h":631,"tn":"\/\/djungxnpq2nug.cloudfront.net\/image\/cache\/d\/8\/d83163e7053812846608b2d45483252f"}612863
See attachment for what this GUI element looks like as post was too long to embed an image. Now the GUI is in STAND mode and the only input that is accepted is only standing orders valid input. The game still runs in real time though (unless the game is paused) and enemy fleets will capture ones star systems. So don't doddle. Or doodle either. Lives are at risk!

Edgar Reynaldo
Major Reynaldo
May 2007
avatar

I'm sorry, but your nick is well deserved.

If bambams were paying attention, he would mention that global variables are just a bad idea generally frowned upon. Place them in an object, a namespace, etc...

If jmasterx were here, he would say you use too many magic numbers. Too many loose literal const values littered in your code. For goodness sake, use a struct and an extra variable.

If ML was feeling bored he might say you're suffering from spaghetti code, although you've done a nice job separating code into relevant functions so I give you props there.

And I would have to agree with them. That gives you a n00b13 score of 3 / 3.

Seriously, we can be pretty critical here, but I assure you we're doing it out of love. :-*

EDIT
I'm guessing QB64 didn't have things like classes or structs much did it.

Dizzy Egg
Member #10,824
March 2009
avatar

;D. It’s 4am here, I’ve just stopped myself from working after 14 hours straight, desperately trying to program a superyacht upgrade that has to be delivered on Monday. Aside from that, I haven’t seen my family in person for a year, and haven’t spent any time with anyone since last March. I’m knackered, I’m completely alone, I have insane deadlines to meet for the rich and spoiled who don’t acknowledge pandemics or recessions or illness, I’m right on the edge of what I can achieve/tolerate....

....and then there’s you.....dr whatever who told us all to go f ourselves because we didn’t download your game.

Kiss my shell.

----------------------------------------------------
Please check out my songs:
https://soundcloud.com/dont-rob-the-machina

Go to: