Prototyping - Programming the HUD
We'll divert from the world and enemies this time in order to bring more information to the player. We'll program up our HUD design from part 23 including all the widgets. We'll rely on the GameMaker GUI abstraction, which is just a quick way to manipulate view space directly (as opposed to world space).
Then we need to update a few of our older objects to make sure they're sending or updated the HUD as necessary
However, I forgot to include the HUD files in our asset extraction part, so I'll run through the process here. We'll focus on three files: HUD.WIN, TD.FAC, and TD.WEA. We'll rename the files in sequence to HUD.HUD, FAC.FAC, and WEA.WEA.
C Code
We run the files through the following code block to get our bitmap assets.
HUDTOBMP.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
/* Convert Traffic Department 2192 HUD assets to bitmaps * argv[1] is the input file */ #include <stdio.h> /* Opening Files */ #include <string.h> /* Manipulating Strings */ #include <stdint.h> /* Fixed-width data types (C99) */ #include <SDL.h> /* Using SDL data structure */ int main(int argc, char* argv[]) { FILE *fin, *fpal; uint16_t IMAGE_WIDTH; uint16_t IMAGE_HEIGHT; uint8_t IMAGE_COUNT; uint16_t SURFACE_WIDTH; SDL_Surface *surface; uint8_t *dst_byte; uint16_t current_byte; uint8_t current_image; uint8_t palette[768]; uint8_t src_byte; uint8_t red_p, green_p, blue_p; uint16_t image_row, image_col; uint32_t surface_offset; uint8_t FILENAME_LEN; char fout[12]; /* Calculate file metrics */ if (!strcmp(argv[1],"HUD.HUD")) { IMAGE_WIDTH = 320; IMAGE_HEIGHT = 32; IMAGE_COUNT = 1; } if (!strcmp(argv[1],"FAC.FAC")) { IMAGE_WIDTH = 28; IMAGE_HEIGHT = 28; IMAGE_COUNT = 5; } if (!strcmp(argv[1],"WEA.WEA")) { IMAGE_WIDTH = 36; IMAGE_HEIGHT = 18; IMAGE_COUNT = 6; } /* Open BLK and PAL file */ fin = fopen(argv[1],"rb"); fpal = fopen("TD.PAL","rb"); /* Make surface strip and pixel data pointer. */ SURFACE_WIDTH = IMAGE_COUNT * IMAGE_WIDTH; surface = SDL_CreateRGBSurface(0, SURFACE_WIDTH, IMAGE_HEIGHT, 32, 0, 0, 0 ,0); dst_byte = surface->pixels; /* Extract image palette - 768 bytes */ for (current_byte=0; current_byte<768; current_byte++) palette[current_byte] = fgetc(fpal) << 2; /* Reranging values 63->252 */ fclose(fpal); /* Process each image in file sequentially */ for (current_image=0; current_image < IMAGE_COUNT; current_image++) { /* Extract 1k of image data, match to palette, write to surface */ for (current_byte=0; current_byte<(IMAGE_WIDTH*IMAGE_HEIGHT); current_byte++) { src_byte = fgetc(fin); /* Read current byte from file */ red_p = palette[src_byte*3]; /* Palette Red Channel */ green_p = palette[src_byte*3+1];/* Palette Green Channel */ blue_p = palette[src_byte*3+2]; /* Palette Blue Channel */ image_row = current_byte / IMAGE_WIDTH; /* Int type clips fractions */ image_col = current_byte % IMAGE_WIDTH; surface_offset = (current_image*IMAGE_WIDTH + image_row*SURFACE_WIDTH +image_col)*4; dst_byte[surface_offset] = blue_p; /* Set Blue Channel pixel */ dst_byte[surface_offset+1]= green_p; /* Set Green channel pixel */ dst_byte[surface_offset+2]= red_p; /* Set Red channel pixel */ dst_byte[surface_offset+3]=0xff; /* Set alpha channel pixel */ } } /* Output filename with .BMP extension */ FILENAME_LEN = strlen(argv[1]); /* Length of input file name */ strncpy(fout, argv[1], FILENAME_LEN-3); /* Chop off Extension */ fout[FILENAME_LEN-3]='\0'; /* Nullterm it */ strncat(fout, "BMP", 3); /* Attach BMP extension */ /* Save BMP */ SDL_SaveBMP(surface, fout); return 0; } |
Objects
We'll use two new objects in this part. First is an AI specific object that ties to ships. Second is a controller-like object that will have several duties by the end of the prototype
Concept | Object | Notes |
---|---|---|
Heads-Up Display | obj_hud | Each ship gets an associated AI object (even the player ship). This object will pass control commands to the ship like the player does with the keyboard. We'll only make the passive path-following AI for now. |
GML Code
Most of our GameMaker work will be in the new object.
obj_hud: Create 1
Variables for our HUD object and each of its widgets. This also includes creating the two minimap surfaces.
obj_hud: Destroy 1
Free the two minimap surfaces
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
///Init Vars fade_out = false bg_alpha = 0 /* GUI Display vars */ scale = view_hport[0]/view_hview[0] y_bottom = display_get_gui_height() /* Weapon statistics */ shield_count = 0 armor_count = 0 current_weapon = WEAPON.gun_normal missile_color = c_red /* Targets Box Widget */ box_color[0] = c_black box_color[1] = c_black box_color[2] = c_black box_color[3] = c_black box_color[4] = c_black box_color[5] = c_black box_color[6] = c_black box_color[7] = c_black /* Mission Ticker */ ticker = "" message_banner = "" message = "" ticker_timer = 0 /* Minimap Structures */ minimap_surface = surface_create(201,201) minimap_overlay = surface_create(201,201) minimap_create = true |
obj_hud: Room Start 1
Set the many references we need to tie all the game information together
1 2 3 4 5 6 |
///Set references gamedata = instance_find(obj_currentgame,0) mission = instance_find(obj_hq,0) map = instance_find(obj_map,0) player = find_player_ship() camera = instance_find(obj_camera,0) |
obj_hud: Step 1
We start each step by resetting and collecting all the HUD data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
///Get HUD Data /* Reset shields and armor data for player */ shield_count = 0 armor_count = 0 missile_count = 0 if instance_exists(player) { shield_count = floor((player.shields/player.shields_max)*12) armor_count = floor((player.armor/player.armor_max)*12) missile_count = player.missile_count current_weapon = player.weapon[player.active_weapon] /* Target box color update */ for (var i=0; i<8; i++) { if player.targets_box[i] > 0 box_color[i] = make_color_rgb(min((player.targets_box[i] mod 32)*75, 255), 0, floor(player.targets_box[i]/32)*255) else box_color[i] = c_black } /* Missile Color */ if current_weapon == WEAPON.missile_normal || current_weapon == WEAPON.missile_weak || current_weapon == WEAPON.missile_strong missile_color = make_color_rgb(255-player.fire_delay*12,0,0) else missile_color = c_black if missile_count == 0 then missile_color = c_black } /* Minimap Ranges */ map_min_x = camera.map_x-10 map_max_x = camera.map_x+10 map_min_y = camera.map_y-10 map_max_y = camera.map_y+10 |
obj_hud: Step 2
Next we move the message ticker if there's a current message
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
///Update Message Ticker draw_set_font(global.little_font) if ticker_timer > 0 ticker_timer-- /* Scroll message */ if string_length(message_banner) > 0 && ticker_timer < 1 { message_banner = string_copy(message_banner,2,string_length(message_banner)-1) ticker = string_copy(message_banner,1,12) ticker_timer = 10 } /* Init new message */ if string_length(message) > 0 && string_length(message_banner) < 5 { message_banner = string_upper(" "+string(message)) message = "" } |
obj_hud: Step 3
Finally, we fade out. I chose to actually end the scene based on the HUD fading, but this should probably move to obj_hq during a refactor pass.
1 2 3 4 5 6 7 8 9 10 11 12 |
///Fade out if fade_out = true { bg_alpha+=0.05 if bg_alpha > 1 { if mission.mission_dead room_goto(rm_mainmenu) else room_goto(rm_debrief) } } |
obj_hud: Draw GUI 1
Render the HUD and all it's elements
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
///Render HUD /* missile backlight */ draw_set_color(missile_color) draw_rectangle(114*scale,y_bottom-24*scale,134*scale,y_bottom-10*scale,false) /* Main HUD */ draw_sprite_ext(HUD,0,0,y_bottom,scale,scale,0,c_white,1) /* Weapon overlay */ if current_weapon != WEAPON.none draw_sprite_ext(WEA,current_weapon-1,179*scale,y_bottom-24*scale,scale,scale,0,c_white,1) /* Missile Count Overlay */ for (i=0;i<missile_count;i++) { if i mod 2 == 0 draw_set_color(c_white) else draw_set_color(c_red) draw_line_width(100*scale,y_bottom-2*scale-scale*i,104*scale,y_bottom-2*scale-scale*i,scale) } /* Shields and Armor overlay */ for (var i=0; i<13; i++) { col = c_green if i < 6 then col = c_yellow if i < 3 then col = c_red if shield_count >= i draw_sprite_ext(spr_bar,0,7*scale+(i*4*scale), y_bottom-20*scale,scale,scale,0,col,1) if armor_count >= i draw_sprite_ext(spr_bar,0,7*scale+(i*4*scale), y_bottom-8*scale,scale,scale,0,col,1) } /* Draw minimap */ draw_surface_part_ext(minimap_surface,camera.map_x-10,camera.map_y-10,20,20,148*scale,y_bottom-25*scale,scale,scale,c_white,1) draw_surface_part_ext(minimap_overlay,camera.map_x-10,camera.map_y-10,20,20,148*scale,y_bottom-25*scale,scale,scale,c_white,1) /* Draw Targets Box */ /* Start is middle right */ draw_set_color(box_color[0]) draw_rectangle(86*scale,y_bottom-18*scale,91*scale,y_bottom-13*scale,false) draw_set_color(box_color[1]) draw_rectangle(85*scale,y_bottom-23*scale,91*scale,y_bottom-17*scale,false) draw_set_color(box_color[2]) draw_rectangle(80*scale,y_bottom-23*scale,85*scale,y_bottom-18*scale,false) draw_set_color(box_color[3]) draw_rectangle(74*scale,y_bottom-23*scale,80*scale,y_bottom-17*scale,false) draw_set_color(box_color[4]) draw_rectangle(74*scale,y_bottom-17*scale,79*scale,y_bottom-13*scale,false) draw_set_color(box_color[5]) draw_rectangle(74*scale,y_bottom-13*scale,80*scale,y_bottom-7*scale,false) draw_set_color(box_color[6]) draw_rectangle(80*scale,y_bottom-12*scale,86*scale,y_bottom-7*scale,false) draw_set_color(box_color[7]) draw_rectangle(85*scale,y_bottom-13*scale,91*scale,y_bottom-7*scale,false) |
obj_hud: Draw GUI 2
Render the message ticker.
1 2 3 4 5 6 7 8 9 |
///Draw Message Ticker draw_set_font(global.little_font) draw_set_halign(fa_left) draw_set_color(c_black) draw_text_transformed(255*scale,y_bottom-17*scale,ticker,scale,scale,0) draw_set_color(make_color_rgb(255,225,255)) draw_text_transformed(256*scale,y_bottom-18*scale,ticker,scale,scale,0) |
obj_hud: Draw GUI 3
Draw the overall fading effect
1 2 3 4 5 6 7 8 9 |
///Fade out if fade_out { /* black backdrop */ draw_set_alpha(bg_alpha) draw_set_color(c_black) draw_rectangle(0,0,display_get_gui_width(),display_get_gui_height(), false) } |
obj_hud: Draw Begin 1
This is the trigger for creating the minimap. It only runs once during the first frame of the mission
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
///Create Minimap if minimap_create { surface_set_target(minimap_surface) var xpos, ypos, img, tile; draw_set_alpha(1) for (ypos=0; ypos<201; ypos++) { for (xpos=0; xpos<201; xpos++) { var col; img = ds_grid_get(map.collision_map,xpos,ypos) tile = ds_grid_get(map.mapdata,xpos,ypos) if img = 0 then col=c_dkgray if img = 1 then col=c_ltgray if tile == $0F || tile == $10 then col = make_color_rgb(174, 124, 88) draw_sprite_ext(spr_pixel,0,xpos,ypos,1,1,0,col,1) } } surface_reset_target() minimap_create = false } |
obj_hud: Draw Begin 2
Reset the ship overlay. All objects will draw to it. We do this in Draw begin because ships and weapon shots draw their position to the minimap during Draw.
1 2 3 4 |
///Reset overlay surface_set_target(minimap_overlay) draw_clear_alpha(c_white,0) surface_reset_target() |
Script: Hud Message
A function callable from any object in the mission to send a message to the HUD.
1 2 3 |
var hud = instance_find(obj_hud,0); hud.message = string_upper(argument0) |
obj_camera: End Step 2
Update the minimap position relative to the camera.
1 2 3 |
///Lock minimap map_x = map_pos(x) map_y = map_pos(y) |
obj_ship: Draw 3
Draw ships to the minimap
1 2 3 4 5 6 7 8 9 10 11 12 13 |
///Render minimap overlay if map_x > hud.map_min_x && map_x < hud.map_max_x && map_y > hud.map_min_y && map_y < hud.map_max_y { var col = c_red if alignment = ALIGNMENT.ally then col = c_lime if alignment = ALIGNMENT.neutral then col = c_aqua surface_set_target(hud.minimap_overlay) draw_set_color(col) draw_set_alpha(1) draw_point(map_x,map_y) surface_reset_target() } |
obj_fire: Draw 2
Draw weapons fire to the minimap
1 2 3 4 5 6 7 8 |
///Render Minimap if map_x > hud.map_min_x && map_x < hud.map_max_x && map_y > hud.map_min_y && map_y < hud.map_max_y { surface_set_target(hud.minimap_overlay) draw_set_alpha(1) draw_point_color(map_pos(x),map_pos(y),c_white) surface_reset_target() } |