Prototyping - Gameplay Map Programming
We have to modify the map files and bring them in to GameMaker using text reads for HTML5 compatability. While reading the files, we'll create both the sprite map and a collision map based on the values. We'll test the map drawing both with and without VRAM.
Objects
We can implement the world and view using two objects:
Concept | Object | Notes |
---|---|---|
Map display | obj_map | Loads and holds data for the scene. Spawns child objects as necessary |
Camera | obj_camera | Displays a text-only line in large font centered on the screen |
C Code
We have to convert all of the map files from the original traffic department to a format that's safe for ASCII / UTF8 reads. Since all map files have a maximum byte value of 80, we have plenty of room to move byte values up in to a valid range.
NEWMAP.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 |
/* Shifts the values of the original Traffic Department 2192 MAPs up 0x20 */ #include <stdio.h> #include <string.h> #include <stdint.h> int main(int argc, char* argv[]) { uint8_t FILENAME_LEN; char fname_out[12]; FILE *fin; FILE *fout; uint16_t current_byte; /* Make output file based on input file name */ FILENAME_LEN = strlen(argv[1]); /* Length of Input File Name */ strncpy(fname_out, argv[1], FILENAME_LEN-3); /* Remove extension */ fname_out[FILENAME_LEN-3]='\0'; /* Nullterm it */ strncat(fname_out, "MP2", 3); /* Attach new extension */ /* Open Files */ fin = fopen(argv[1], "rb"); fout = fopen(fname_out, "wb"); /* Process byte stream */ for (current_byte=0; current_byte<40401; current_byte++) { fputc((fgetc(fin)+0x20), fout); fputc('\r', fout); fputc('\n', fout); } /* Close files */ fclose(fin); fclose(fout); return 0; } |
GML Code
We do our GameMaker work in GML for each object and event
obj_map: Create 1
Set a reference to the gamedata object
1 2 |
///Set reference gamedata = instance_find(obj_currentgame,0) |
obj_map: Create 2
Initialize variables for map size, draw trigger, and draw mode
1 2 3 4 5 6 |
///Init vars new_surface = true map_width = 201 map_height = 201 draw_mode = 1 /* 0 = slowest (no VRAM), 1 = fastest (VRAM hog) |
obj_map: Create 3
Create the data structures
1 2 3 4 5 6 7 |
///Create data structures mapdata = ds_grid_create(map_width, map_height) collision_map = ds_grid_create(map_width, map_height) map_surface = 0 if draw_mode == 1 then map_surface = surface_create(map_width*32, map_height*32) |
obj_map: Create 4
Load the map data from our ASCII-safe files. We'll also create the collision map for certain sprites
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 |
///Load map data var file, map_char; file = file_text_open_read("TD.MP2") /* read map in column major */ for (var xx=0; xx < map_width; xx++) { for (var yy=0; yy < map_height; yy++) { map_char = file_text_read_string(file); ds_grid_set(mapdata, xx, yy, ord(map_char)-$20) switch ord(map_char)-$20 { case $31: case $32: case $33: case $38: case $39: case $45: case $3B: case $3C: case $3D: ds_grid_set(collision_map, xx, yy, 0); break; default: ds_grid_set(collision_map, xx, yy, 1); break; } file_text_readln(file) } } file_text_close(file) |
obj_map: Destroy 1
Release all data structures
1 2 3 4 |
///Free data structures ds_grid_destroy(mapdata) ds_grid_destroy(collision_map) surface_free(map_surface) |
obj_map: Draw 1
The RAM only drawing of the map (slow)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
///Draw entire map each frame (slowest) if draw_mode == 0 then { var xx, yy, spr; for (yy=0; yy < map_height; yy++) { for (xx=0; xx < map_width; xx++) { spr = ds_grid_get(mapdata, xx, yy) draw_sprite(BLK1, spr, xx*32, yy*32) } } } |
obj_map: Draw 2
Storing the map in VRAM method (fast)
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 |
///Draw map from VRAM surface (fastest) if draw_mode == 1 { if !surface_exists(map_surface) new_surface = true if new_surface { surface_set_target(map_surface) draw_set_alpha(1) var xx, yy, spr; for (yy=0; yy < map_height; yy++) { for (xx=0; xx < map_width; xx++) { spr = ds_grid_get(mapdata, xx, yy) draw_sprite(BLK1, spr, xx*32, yy*32) } } surface_reset_target() new_surface = false } draw_set_alpha(1) draw_surface(map_surface,0,0) } |
obj_camera: Create 1
Register the keyboard so we can control the camera
1 2 3 4 |
///Register self and references keyboard_register(id) gamedata = instance_find(obj_currentgame,0) |
obj_camera: Create 2
Variables and other properties. The most important is the camera target, which we can lock to anything. For now, we'll lock it to itself
1 2 3 4 5 6 7 8 9 10 11 12 13 |
///Init vars camera_target = id map_x = 0 map_y = 0 //camera properties x = room_width/2 y = room_height/2 view_object[0] = id view_wview[0] = 320 view_hview[0] = 200 view_hborder[0] = view_wview[0]/2 view_vborder[0] = view_hview[0]/2 |
obj_camera: Destroy 1
Remove keyboard
1 2 |
///Deregister keyboard_deregister(id) |
obj_camera: Step 1
Process input commands. Camera only responds to controls when it's not locked to another object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
///Process commands while !ds_stack_empty(command_stack) { var cmd = ds_stack_pop(command_stack) switch cmd { case "hold_left": x-=10; break; case "hold_right": x+=10; break; case "hold_up": y-=10; break; case "hold_down": y+=10; break; case "push_x": { if zoom_out == zoom_in zoom_out = true } break; case "push_z": { if zoom_out == zoom_in zoom_in = true } break; } } |
obj_camera: End Step 1
Lock camera to target
1 2 3 4 5 6 |
///Lock camera to target if instance_exists(camera_target) { x = camera_target.x y = camera_target.y } |
obj_camera: Room Start 1
Find the player's ship
1 2 |
///Lock camera to player camera_target = id |