Prototyping - Introduction Programming
We're going to implement the design from the last part by constructing the primary object obj_introscene. A key feature of this object is that it interprets the intro script language we created last time. First we need to build the script into a data file that we'll include with GameMaker.
Introduction Script
The following data is the contents of introscene.txt, which uses our custom scripting language to storyboard the introduction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
~introscene.txt [cred [vmid [hcenter may 16, 2178 [ssprite16_INTRO0 [caqua [vbottom [hleft far from earth, a distant sun rises over the desert planet seche below, a hoverskid flashes across the desert, on its way home. the pilot, ric velasquez, looks forward to seeing his daughter again after several long months of garrison duty... [oobj_credits [ssprite17_INTRO1 just seconds away from the hangar, disaster strikes. a burst of ion cannon fire tears the ship apart... [ssprite18_INTRO2 [cblack [vmid [hcenter ric velasquez is killed instantly in the fiery explosion... ... and his daughter watches from the hangar as her father dies [e |
Objects
We'll introduce a few more objects in to the prototype.
The Introduction Controller - obj_introscene
This object implements the finite state machine by declaring, defining, and handling the states. This means we'll also be interpreting the script above in the read state, and displaying the scene to the screen in most other states.
The Credits Placeholder - obj_credits
We can't recreate the credits sequence yet because we haven't created the gameplay engine. We'll add a placeholder object that displays the same information for now. While this object is active, the controller object is sleeping.
GML Code
We'll add code to both of our objects under several events. We'll also add a script.
obj_introscene: Create 1
The first thing we'll do is register the intro object with the keyboard handler so that it receives commands. Almost all active objects will do this in their constructor. If GameMaker supported multiple inheretence, we'd want all objects to inherent a keyboard listener prototype rather than explicitly calling the procedure.
1 2 |
///Register with keyboard handler keyboard_register(id) |
obj_introscene: Create 2
Here we define all the states in our finite state machine as an enum type
1 2 3 4 5 6 7 8 9 10 11 12 |
///Define States (enum types) enum INTRO { inactive, read, swap, screen_in, text_in, listen, text_out, screen_out } |
obj_introscene: Create 3
Here we define the applicable variables for the introscene object. Remember that we're building type of front and back buffer, so we'll have to declare a duplicate set of variables for both current screen and next screen that we'll swap later.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
///Init vars intro_state = INTRO.read event_list = ds_list_create() /* Hold event actions from the markup file */ event_list_pos = 0 screen_alpha = 0 text_alpha = 0 current_text = "" current_screen= -1 current_color = -1 current_pos_x = -1 current_pos_y = -1 current_align = -1 next_text = "" next_screen= -1 next_color = -1 next_pos_x = -1 next_pos_y = -1 next_align = fa_left |
obj_introscene: Create 4
We have to read in the introscene.txt file so that we can follow the sequence of events without repeatedly accessing the file.
1 2 3 4 5 6 7 8 9 10 |
///Open intro script file and read to list var file = file_text_open_read("introscene.txt") while (!file_text_eof(file)) { ds_list_add(event_list, file_text_read_string(file)) file_text_readln(file) } file_text_close(file) |
obj_introscene: Destroy 1
The destructor will do two things: remove the keyboard reference and push us to the next room.
1 2 3 4 5 |
///Remove references, data structures, and goto next room keyboard_deregister(id) ds_list_destroy(event_list) room_goto(rm_mainmenu) |
obj_introscene: Step 1
We'll have to process commands from the keyboard handler at the beginning of every step.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
///Process input commands passed from keyboard object if !ds_stack_empty(command_stack) { var cmd = ds_stack_pop(command_stack) if intro_state = INTRO.listen { switch cmd { case "any": intro_state = INTRO.text_out; break; case "escape": instance_destroy(); break; } } } |
obj_introscene: Step 2
This is where the magic happens -- We check which state we're in, do the work of the state, which includes interpreting our script, and then push us to the next state as necessary
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
///Update States switch intro_state { /* interpret intro events */ case INTRO.read: { var line = ds_list_find_value(event_list, event_list_pos) var first_char = string_char_at(line,1) /* IF line is actually a markup command */ if first_char == "[" then { var operator = string_char_at(line,2) var operand = string_copy(line, 3, string_length(line)) switch operator { /* set background screen */ case "s": next_screen = operand; break; /* set text color */ case "c": { switch operand { case "red": next_color = c_red; break; case "black": next_color = c_black; break; case "aqua": next_color = c_aqua; break; } } break; /* set vertical position */ case "v": { switch operand { case "bottom": next_pos_y = round(view_hview[0] * 0.9); break; case "mid": next_pos_y = round(view_hview[0] * 0.45); break; case "top": next_pos_y = round(view_hview[0] * 0.1); break; } } break; /* set horizontal position */ case "h": { switch operand { case "left": next_align = fa_left; break; case "mid": case "center": next_align = fa_center; break; } } break; /* spawn new screen object */ case "o": { var new_object = operand intro_state = INTRO.inactive instance_create(0,0,asset_get_index(new_object)) } break; /* end scene */ case "e": instance_destroy() } } else /* event is actually text, not a command */ { next_text = string_upper(line) intro_state = INTRO.swap } event_list_pos += 1 } break; /* copy buffered state to active state */ case INTRO.swap: { /* swap values if relevant */ if next_text != "" then current_text = next_text if next_screen != -1 then current_screen = asset_get_index(next_screen) if next_color != -1 then current_color = next_color if next_align != -1 then current_align = next_align if next_pos_x != -1 then current_pos_x = next_pos_x if next_pos_y != -1 then current_pos_y = next_pos_y /* clear data buffer */ next_text = "" next_screen= -1 next_color = -1 next_pos_x = -1 next_pos_y = -1 next_align = -1 /* center alignment position is based on current text width - not known until this point */ if current_align == fa_center then current_pos_x = max(view_wview[0]/2 - (string_width(current_text)/2), view_wview[0]*0.05) else current_pos_x = round(view_wview[0]*0.05) /* next state is always screen in */ intro_state = INTRO.screen_in } break; /* screen fade in */ case INTRO.screen_in: { if screen_alpha < 1 then screen_alpha += 0.05 else intro_state = INTRO.text_in } break; /* text fade in */ case INTRO.text_in: { if text_alpha < 1 then text_alpha += 0.05 else intro_state = INTRO.listen } break; /* text fade out */ case INTRO.text_out: { if text_alpha > 0 then text_alpha -= 0.05 else { var peek = ds_list_find_value(event_list, event_list_pos) if string_char_at(peek, 1) == "[" intro_state = INTRO.screen_out else intro_state = INTRO.read } } break; /* screen fade out */ case INTRO.screen_out: { if screen_alpha > 0 then screen_alpha -= 0.05 else intro_state = INTRO.read } break; } |
obj_introscene: Draw 1
We have to render the results of all our work to the screen in this event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
///Render screen if current_screen > -1 draw_sprite_ext(current_screen,0,0,0,1,1,0,c_white,screen_alpha) if intro_state != INTRO.read || intro_state != INTRO.inactive { draw_set_color(current_color) draw_set_alpha(text_alpha) draw_set_font(global.little_font) var line_width = round(view_wview[0]*0.9) var line_height = round(sprite_get_height(LT2)*1.5) draw_text_ext_transformed(current_pos_x, current_pos_y, current_text, line_height, line_width, 1, 1, 0) } |
obj_keyboard: Begin Step 1
Now that we have an object listening to the 'escape' key, we have to add it to th keyboard handler. Add this line:
1 |
++if keyboard_check_pressed(vk_escape) then ds_stack_push(input_stack, "escape") |
Script: keyboard_register
This script registers any object that uses it with the keyboard handler so that it receives input commands.
1 2 3 4 5 6 7 8 9 10 11 12 |
/* Register object as a keyboard listening argument0 = unique object id */ var keyboard = instance_find(obj_keyboard,0) var object_id = argument0 with object_id command_stack = ds_stack_create() ds_list_add(keyboard.target_list, object_id) |
Script: keyboard_deregister
This removes the object as a keyboard listener
1 2 3 4 5 6 7 8 9 10 11 12 |
/* Remove keyboard listening argument0 = unique object id */ var keyboard = instance_find(obj_keyboard,0) var object_id = argument0 with object_id ds_stack_destroy(command_stack) ds_list_delete(keyboard.target_list, ds_list_find_index(keyboard.target_list, object_id)) |
obj_credits: Create 1
We'll register the keyvoard and set references
1 2 3 4 5 |
///Register with keyboard and set references keyboard_register(id) audio = instance_find(obj_music,0) intro = instance_find(obj_introscene,0) |
obj_credits: Create 2
Set the fading variables specific to the credits display
1 2 3 4 |
///Init Vars fade_in = true fade_out = false alpha = 1 |
obj_credits: Create 3
Kickoff the credits by changing the music
1 2 3 |
///Change music for credits audio_stop_sound(audio.current_music) audio.current_music = audio_play_sound(mus_credits,0,false) |
obj_credits: Destroy 1
When the credits object is finished, we have to remove the keyboard, restore the sounds, and tell the intro object to get back to work.
1 2 3 4 5 6 7 |
///Deregister keyboard and change music keyboard_deregister(id) audio_stop_sound(audio.current_music) audio.current_music = audio_play_sound(mus_intro2, 0, true) intro.intro_state = INTRO.read |
obj_credits: Step 1
The credits object must process keyboard commands
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
///Process keyboard messages from keyboard object if !ds_stack_empty(command_stack) { var cmd = ds_stack_pop(command_stack) switch cmd { case "any": { if !fade_in fade_out = true } break; case "escape": instance_destroy(); break; } } |
obj_credits: Step 2
There are a few fading routines which triggers entry and exit of the object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
///Fading if fade_in alpha -= 0.05 if fade_out alpha += 0.05 if alpha < 0 then { fade_in = false alpha = 0 } if alpha > 1 then instance_destroy() |
obj_credits: Draw 1
Finally, the credits object renders static information to the screen, which is harded here
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 |
///Render credits /* draw background */ draw_set_alpha(1) draw_sprite(sprite41_VIDCOM,0,0,0) /* Overlay black text area background */ draw_set_color(c_black) draw_set_alpha(0.8) draw_rectangle(69,38,250,175,false) /* setup drawing text */ draw_set_alpha(1) draw_set_color(c_aqua) draw_set_font(global.little_font) /* Top header */ draw_set_halign(fa_left) draw_text_transformed(75, 40, string_upper("Safari software presents a P2 productions"),1,1,0) /* Title */ draw_set_color(c_black) draw_text(79,56, string_upper("T R A F F I C D E P A R T M E N T")) draw_text(144,66,string_upper("2 1 9 2")) draw_set_color(c_white) draw_text(80,55, string_upper("T R A F F I C D E P A R T M E N T")) draw_text(145,65,string_upper("2 1 9 2")) /* Crew */ draw_set_halign(fa_center) draw_set_color(c_aqua) draw_text(160,85, string_upper("John pallet-plowright")) draw_text(160,95, string_upper("robert a allen")) draw_text(160,105, string_upper("Michael pallet-plowright")) draw_text(160,115, string_upper("Bruce hsu")) draw_text(160,125, string_upper("Michael Taylor")) draw_text(160,135, string_upper("Samuel Goldstein")) draw_text(160,145, string_upper("christopher Perkins")) /* CC attribution */ draw_set_halign(fa_left) draw_set_color(c_yellow) draw_text(80, 165, string_upper("Released under creative commons, BY-ND")) /* Overlay fading black box */ draw_set_alpha(alpha) draw_set_color(c_black) draw_rectangle(0,0, view_wview[0], view_hview[0], false) |