domingo, 3 de junho de 2012

Reverse Engineering and Analyzing the Inner Workings of Game Maker - Part 2


(Este é o segundo artigo de uma série de artigos relacionados ao campo da Engenharia Reversa de software, analisando o Game Maker. This is the second article from a series of articles regarding Reverse Engineering of software, analyzing Game Maker.)


Summing Up The Obvious Structures

In the first article we have seen a very simplified outline of the basic, obvious structures of Game Maker and the settings it allows us to configure in order to create a game. As you must have noticed, there are apparently 11 structures defined by Game Maker for us to manipulate: Sprites, Sounds, Backgrounds, Paths, Scripts, Fonts, Time Lines, Objects, Rooms, Game Information and Global Game Settings.

You could argue that Game Information is just a container for formatted text and that Global Game Settings isn't really a structure but a collection of global variables. But here we are going to assume that they are in fact structures.

What we will do in the next sections is create an object-oriented model of Game Maker as if we were developing it from scratch. But before we do that, we must analyze two of the most important concepts in this program: Events and Actions.

A Quick Look at Events and Actions

All games created in Game Maker (and Game Maker itself) are event-driven. It means that while the game is running, when you press a key, or move the mouse, or one object collides with another, etc. an event is generated and the system handles (i.e. responds to) that event by executing whatever actions were specified. This is a very simplistic view of an event-driven program, but I'd like to concentrate on what exactly are the events and actions in Game Maker and how they are related to each other and to other structures in the system. Let's first take a quick look at all event types and actions that Game Maker provides to the game creator.

Events

Every object can be configured to respond to zero or more events, by associating specific event handlers to them. Every event handler must contain one or more actions which will be executed as soon as the event is handled. The following list briefly describes all available event handlers:

Create - Called when the instance of an object is created
Destroy - Called just before the instance of an object is destroyed
Alarm (0-11) - Called when the specified alarm sets off (when its counter reaches 0)
Step - Called at every game step
Begin Step - Called at the beginning of a game step
End Step - Called at the end of a game step
Collision (object) - Called when two instances collide
Keyboard (key) - Called when a keystroke is detected on the keyboard
Mouse (mouse event) - Called when the mouse generates an event
Draw - Called when the object is drawn
Key Press (key) - Called when a key is pressed down on the keyboard
Key Release (key) - Called when a key is released
Other - Various miscellaneous event handlers, including handlers for 16 user-defined events

Actions

Actions are executed in response to events generated by the system. Almost every action in Game Maker must specify exactly to which object it applies, and there are 3 options: Self, Other and Object.

When an action applies to Self it means that it's applied to the current object (this is the default setting). When an action applies to Other it means that it's applied to the other object involved in a collision with the current object. And when an action applies to Object it means that it's applied to any other object, which must be specified.

Additionally, most actions require one or more parameters in order to be completely specified. Some actions (mostly related to movement and positioning) have an optional Relative (true/false) setting, which indicates that the new position of the object must be relative to its current position.

There are some actions that first check for a condition before executing the next action or group of actions. Those actions have an additional setting named "NOT" which negates the result of the condition check, i.e. it checks for the opposite condition.

The following is a list of all actions and the parameters required to specify them:

* The Self/Other/Object and Relative (true/false) parameters are implied

Movement Actions

Move Fixed (direction, speed)
Move Free (direction, speed)
Move Towards (x, y, speed)
Speed Horizontal (hor. speed)
Speed Vertical (vert. speed)
Set Gravity (direction, gravity)
Reverse Horizontal
Reverse Vertical
Set Friction (friction)
Jump to Position (x, y)
Jump to Start
Jump to Random (snap hor., snap vert.)
Align to Grid (snap hor., snap vert.)
Wrap Screen (horizontal/vertical/both directions)
Move to Contact (direction, maximum, against - solid/all objects)
Bounce (precisely/not precisely, against - solid/all objects)
Set Path (path, speed, at end - stop/continue from start/continue from here/reverse)
End Path
Path Position (position)
Path Speed (speed)
Step Towards (x, y, speed, stop at - solid only/all instances)
Step Avoiding (x, y, speed, stop at - solid only/all instances)

Main Actions - Part 1

Create Instance (object, x, y)
Create Moving (object, x, y, speed, direction)
Create Random (object 1, object 2, object 3, object 4, x, y)
Change Instance (object, perform events - yes/no)
Destroy Instance
Destroy at Position (x, y)
Change Sprite (sprite, subimage, speed)
Play Sound (sound, loop - yes/no)
Stop Sound (sound)
Check Sound (sound)
Previous Room (transition)
Next Room (transition)
Restart Room (transition)
Different Room (new room, transition)
Check Previous Room
Check Next Room

Main Actions - Part 2

Set Alarm (number of steps, alarm number)
Sleep (milliseconds, redraw - true/false)
Set Time Line (time line, position)
Time Line Position (position)
Display Message (message)
Show Info
Restart Game
End Game
Save Game (filename)
Load Game (filename)

Control Actions

Check Empty (x, y, objects - only solid/all)
Check Collision (x, y, objects - only solid/all)
Check Object (object, x, y)
Test Instance Count (object, number, operation - equals/smaller/larger)
Test Chance (sides)
Check Question (question)
Test Expression (expression)
Check Mouse (button - none/left/right/middle)
Check Grid (snap hor., snap vert.)
Start Block
End Block
Else
Exit Event
Repeat (times)
Call Parent Event
Execute Code (code)
Execute Script (script, arg. 0, arg. 1, arg. 2, arg. 3, arg. 4)
Comment
Set Variable (variable, value)
Test Variable (variable, value, operation - equals/smaller/larger)
Draw Variable (variable, x, y)

Score Actions

Set Score (score)
Test Score (value, operation - equals/smaller/larger)
Draw Score (x, y, caption)
Show Highscore (background, border - show/hide, new color, old color, font)
Clear Highscore
Set Lives (lives)
Test Lives (value, operation - equals/smaller/larger)
Draw Lives (x, y, caption)
Draw Life Images (x, y, sprite)
Set Health (value)
Test Health (value, operation - equals/smaller/larger)
Draw Health (x1, y1, x2, y2, back color, bar color)
Score Caption (show score - yes/no, score caption, show lives - yes/no, lives caption, show health - yes/no, health caption)

Drawing Actions

Draw Sprite (sprite, x, y, subimage)
Draw Background (background, x, y, tiled - true/false)
Draw Text (text, x, y)
Draw Rectangle (x1, y1, x2, y2, filled - yes/no)
Draw Ellipse (x1, y1, x2, y2, filled - yes/no)
Draw Line (x1, y1, x2, y2)
Draw Arrow (x1, y1, x2, y2, tip size)
Set Color (color)
Set Font (font, align - left/center/right)
Set Screen Mode (switch/window/fullscreen)

Creating an Object-Oriented Model in C++ of the Visible Structures

Now we are going to create an object-oriented model in the C++ language of the visible structures that we have described previously. Rembember that Game Maker was in fact developed in Delphi, not C++. But I'm going to use C++ because this is the standard in the game development industry today.

Based on the analysis we did on the visible structures of Game Maker in the previous article, we will initially write C++ classes that encapsulate the same data that Game Maker allows us to edit in those structures. The following classes comprise the initial object-oriented model that I've come up with. They are merely prototypes and contain mostly data, and aren't supposed to be compiled or anything, they are meant for illustration purposes only:

* The template class List<T> referenced in some classes is actually a placeholder for any container. The model for actions and events will be described in the next articles.

//===============
// BASIC CLASSES
//===============

class String
{
 char* contents;
};

class File
{
 String path;
};

class Color
{
 int rgba;
};

class Image
{
 char* data;
};

class Action
{
 // Will be described later!
};

class Event
{
 // Will be described later!
};

//====================
// VISIBLE STRUCTURES
//====================

enum BoundingBoxType
{
 BOUNDING_BOX_AUTOMATIC, BOUNDING_BOX_MANUAL,
 BOUNDING_BOX_FULL_IMAGE
};

enum BoundingBox
{
 BoundingBoxType type;
 int left_corner;
 int right_corner;
 int top_corner;
 int bottom_corner;
};

class Sprite
{
 String name;
 Image* image;
 bool transparency;
 bool precise_collision_checking;
 bool smooth_edges;
 bool preload_texture;
 int origin_x;
 int origin_y;
 BoundingBox* bounding_box;
};

enum SoundKind
{
 SOUND_NORMAL, SOUND_BACKGROUND,
 SOUND_3D, SOUND_USE_MULTIMEDIA_PLAYER
};

class SoundData
{
 char* data;
};

class SoundEffects
{
 bool chorus;
 bool echo;
 bool flanger;
 bool reverb;
 bool gargle;
};

class Sound
{
 String name;
 SoundData* data;
 SoundKind kind;
 SoundEffects* effects;
 int volume;
 int pan;
 bool preload;
};

class Background
{
 String name;
 Image* image;
 bool transparency;
 bool smooth_edges;
 bool preload_texture;
 bool use_as_tile_set;
};

class Point
{
 int x;
 int y;
};

class PathPoint
{
 Point* point;
 int speed;
};

enum PathConnectionKind
{
 PATH_STRAIGHT_LINES, PATH_SMOOTH_CURVES
};

class Path
{
 String name;
 List<PathPoint*> points;
 PathConnectionKind connection_kind;
 bool closed;
 int precision;
 int snap_x;
 int snap_y;
};

class Script
{
 String name;
 String code;
};

class Font
{
 String name;
 String face_name;
 int size;
 bool bold;
 bool italic;
 int character_range_start;
 int character_range_end;
};

class TimeLineAction
{
 int moment;
 Action* action;
};

class TimeLine
{
 String name;
 List<TimeLineAction*> actions;
};

class Object
{
 int id;
 String name;
 Sprite sprite;
 bool visible;
 bool solid;
 bool persistent;
 int depth;
 Object* parent;
 Sprite* mask;
 
 // Event handlers will be added later!
};

class RoomBackground
{
 Background* background;
 bool visible_when_room_starts;
 bool foreground_image;
 bool tile_horizontal;
 bool tile_vertical;
 int tile_x;
 int tile_y;
 bool stretch;
 int horizontal_speed;
 int vertical_speed;
};

class RoomView
{
 bool visible_when_room_starts;
 int view_x;
 int view_y;
 int view_width;
 int view_height;
 int port_x;
 int port_y;
 int port_width;
 int port_height;
 Object* object_to_follow;
 int horizontal_border;
 int vertical_border;
 int horizontal_separation;
 int vertical_separation;
};

class Tile
{
 int id;
 Image* image;
};

class TileLayer
{
 int depth;
 List<Tile*> tiles;
};

class RoomTiles
{
 Background* tile_set;
 List<TileLayer*> tile_layers;
};

class Room
{
 String name;
 String caption;
 int width;
 int height;
 int speed;
 bool persistent;
 String creation_code;
 List<Object*> objects;
 int snap_x;
 int snap_y;
 RoomBackground room_backgrounds[8];
 bool draw_background_color;
 Color* background_color;
 bool enable_views;
 RoomView room_views[8];
 RoomTiles* room_tiles;
};

class GameInformation
{
 String formatted_text;
};

//=======================================
// CLASSES ENCAPSULATING GLOBAL SETTINGS
//=======================================

enum ScreenScaling
{
 SCALING_FIXED, SCALING_KEEP_ASPECT_RATIO, SCALING_FULL
};

enum ColorDepth
{
 COLOR_DEPTH_NO_CHANGE, COLOR_DEPTH_16, COLOR_DEPTH_32
};

enum ScreenResolution
{
 RESOLUTION_NO_CHANGE, RESOLUTION_320_240, RESOLUTION_640_480,
 RESOLUTION_800_600, RESOLUTION_1024_768, RESOLUTION_1280_1024,
 RESOLUTION_1600_1200
};

enum ScreenRefresh
{
 REFRESH_NO_CHANGE, REFRESH_60HZ, REFRESH_70HZ, REFRESH_85HZ,
 REFRESH_100HZ, REFRESH_120HZ
};

enum GameProcessPriority
{
 PRIORITY_NORMAL, PRIORITY_HIGH, PRIORITY_HIGHEST
};

class VersionInfo
{
 int major;
 int minor;
 int release;
 int build;
 String company;
 String product;
 String copyright;
 String description;
};

class ScreenSettings
{
 bool start_in_fullscreen_mode;
 ScreenScaling screen_scaling;
 int fixed_scale_percent;
 bool interpolate_colors_between_pixels;
 Color* color_outside_room_region;
 bool allow_player_resize_window;
 bool window_always_on_top;
 bool show_window_border;
 bool show_window_buttons;
 bool display_cursor;
 bool freeze_when_window_unfocused;
 ColorDepth color_depth;
 ScreenResolution screen_resolution;
 ScreenRefresh screen_refresh;
 bool use_synchronization;
};

class ControlSettings
{
 bool let_esc_end_the_game;
 bool treat_close_button_as_esc;
 bool let_f1_show_game_info;
 bool let_f4_switch_screen_modes;
 bool let_f5_and_f6_save_and_load_game;
 bool let_f9_take_screenshot;
};

enum LoadingProgressBar
{
 LOADING_PROGRESS_BAR_NONE, LOADING_PROGRESS_BAR_DEFAULT,
 LOADING_PROGRESS_BAR_OWN
};

class LoadingSettings
{
 bool show_own_loading_image;
 Image* loading_image;
 int loading_image_translucency_alpha;
 LoadingProgressBar loading_progress_bar;
 bool scale_loading_progress_bar;
};

class Constant
{
 String name;
 void* value;
};

class ErrorHandlingSettings
{
 bool display_errors;
 bool write_errors_to_log;
 bool abort_on_all_errors;
 bool treat_uninitialized_variables_as_zero;
};

class GameInfo
{
 String author;
 String version;
 String info;
};

class GlobalGameSettings
{
 ScreenSettings* screen_settings;
 ControlSettings* control_settings;
 GameProcessPriority game_process_priority;
 VersionInfo* version_info;
 LoadingSettings* loading_settings;
 Image* game_icon;
 int game_identifier;
 List<Constant*> constants;
 List<File*> included_files;
 ErrorHandlingSettings* error_handling_settings;
 GameInfo* game_info;
};


Go to Part 1
Go to Part 2 (this is Part 2)
Go to Part 3 (coming soon...)