Pixelate:Issue 14/Object oriented controller input for our games
From Allegro Wiki
| Object oriented controller input for our games | |
| Original author: | Kronoman |
|---|---|
| Website: | |
| zip: | controller_prj_tree.zip |
Contents |
Object oriented controller input for our games
Introduction
Anybody that develops a game will need to take input from player (user), usually through hardware devices.
Allegro provides ways to take input from several hardware devices such as keyboard, joystick, mouse, etc ; but, in games with many players, or with AI players, using diferent input devices for players can be tricky (specially, configuration, and 'fake' devices like a AI controlled).
In this article, we are going to design and implement a controller abstraction layer for our games, using C++ as our object oriented language.
This will let us have a way to let the player control our game using any hardware controller, and let the door open for future reutilization of our code in other games that we made in future, and also, for adding new hardware controllers without changing too much of our previous code.
In short, we will always 'see' a gamepad from our game, no matter if the player is using a keyboard, a joystick, mouse, mind control, or even if the computer is using AI to control this player...
Starting to work on it
Well, first of all, I will assume that you know at least a little about object oriented programming (in short, OOP) and about C++.
We are going to build our magnificent controller using polymorphism and virtual functions.
Polymorphism (implemented in C++ with virtual functions) is the third essential feature of an object-oriented programming language, after data abstraction and inheritance.
It provides improved code organization and readability as well as the creation of extensible programs that can be grown not only during the original creation of the project, but also when new features are desired.
That, for us, are good news, and means that we will have the ability to add new features to our controller without changing too much of other game source code.
First design
Well, we want to 'model' a gamepad, that means, the logic of our game will see that the player (human or not, maybe AI) is using a gamepad to control our hero/ship/whatever.
Our gamepad will we one of six (6) buttons and four (4) axis (up, down, left, right), that can be pressed in any way. We will call this 'controller events'.
Also, we want some other stuff for our controller:
- Redefine input source configuration (if available, like a keyboard, we want to change input keys)
- Save configuration of controller
- Restore configuration of controller
So well, we have what we need about our controller now.
It needs to have methods to get input, redefine input source configuration (interactive with user), save and load configuration of controller.
We now see the methods that we will need and will be the standard interface of our controller class:
CBaseController(); virtual ~CBaseController(); virtual int do_input_poll(); virtual void save_configuration(char *cfg_section); virtual void load_configuration(char *cfg_section); virtual void interactive_configuration();
We defined many of the methods 'virtual' so we can 'morph' them in child classes.
That means, that, in future, we can have, lets say, a CKeyboardController that will 'overwrite' the do_input_poll() method, so it can return keyboard events.
Coding the CBaseController base class
First, I need to clarify wich value will return do_input_poll(). It will return a bitmask that will hold the state of our magic hipotetic gamepad (remember? the 6 buttons, 4 directions gamepad). So, well, the bitmask will be composed of integer values combined with OR, so, for example, 0 will mean 'idle event', 1 will mean 'up, 5 will mean 'up' and 'left' (yes, at same time), etc.
We start to code, and we get our header file for our base class:
Check the zip file that comes with this article for the file(s)
// -----------------------------------------------------------------------
// Base Controller Class
// -----------------------------------------------------------------------
// controlb.h
// -----------------------------------------------------------------------
// This class handles a generic controller for Allegro games.
// Simulates a 8 way gamepad with 6 buttons using standard input methods
// and events.
// In that way, you put a abstraction layer between your game and keyboard,
// joystick,mouse,network,other hardware...
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#ifndef CONTROLB_H
#define CONTROLB_H
// This are the return values of the controller's events, they are returned as a bit mask
// So, all joystick input is DIGITAL
#define KC_NONE 0
#define KC_UP 1
#define KC_DOWN 2
#define KC_LEFT 4
#define KC_RIGHT 8
// buttons (there is room left, for adding more axis/buttons in future)
#define KC_BTN1 256
#define KC_BTN2 512
#define KC_BTN3 1024
#define KC_BTN4 2048
#define KC_BTN5 4096
#define KC_BTN6 8192
class CBaseController
{
public:
CBaseController();
virtual ~CBaseController();
virtual int do_input_poll(); // this is the one that does the input from hardware/whatever plugged
// Configuration stuff -- NOTICE: you have to _previously_ call to allegro's set_config_file to set file to save
virtual void save_configuration(char *cfg_section); // save current configuration in a file (cfg_section = section)
virtual void load_configuration(char *cfg_section); // load configuration from a config file (cfg_section = section)
virtual void interactive_configuration(); // this should be a interactive configuration of controller
void set_controller_id(int iid) { this->controller_id = iid; }
int get_controller_id() { return this->controller_id; } // controller ID
// Static public members (available from the class itself)
static int get_controller_count() { return CBaseController::controller_count; }
private:
// ID of controller
int controller_id; // controller ID: automated (useful for saving configuration, you can have different sections, well, use it for something... )
// static members; available from the class itself
static int controller_count; // to keep track of automated ID
};
#endif
And, we code the implementation of the class; this base class does nothing in his methods, but is useful as 'place holder'.
// -----------------------------------------------------------------------
// Base Controller Class
// -----------------------------------------------------------------------
// controlb.cpp
// -----------------------------------------------------------------------
// This class handles a generic controller for Allegro games.
// Simulates a 8 way gamepad with 6 buttons using standard input methods
// and events.
// In that way, you put a abstraction layer between your game and keyboard,
// joystick,mouse,network,other hardware...
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#include <allegro.h>
#include "controlb.h"
int CBaseController::controller_count = 0;
CBaseController::CBaseController()
{
// automatically set the controller ID
this->controller_id = CBaseController::controller_count;
CBaseController::controller_count++;
}
CBaseController::~CBaseController()
{
CBaseController::controller_count--;
}
// This actually does the input from hardware and returns the bitmask acording to action
int CBaseController::do_input_poll()
{
return KC_NONE; // this base controller does not generate any events
}
// save current configuration in a file (cfg_section = section)
void CBaseController::save_configuration(char *cfg_section)
{
// in the base controller, nothing to be done here
}
// load configuration from a config file (cfg_section = section)
void CBaseController::load_configuration(char *cfg_section)
{
// in the base controller, nothing to be done here
}
// this should be a interactive configuration of controller
void CBaseController::interactive_configuration()
{
// in the base controller, nothing to be done here
}
A derived class to handle keyboard events
Now, we got our base controller class done, but it is not useful as we expected. If we use it, we will not get input from player.
Now, we "rewrite" the virtual methods of our CBaseController, creating a new CKeyboardController that will handle keyboard events.
Look at this source code:
Here is the header file for CKeyboardController
// -----------------------------------------------------------------------
// Keyboard Controller Class
// -----------------------------------------------------------------------
// keybctrl.h
// -----------------------------------------------------------------------
// This class handles a keyboard controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#ifndef KEYBCTRL_H
#define KEYBCTRL_H
#include "controlb.h"
class CKeyboardController : public CBaseController
{
public:
CKeyboardController();
~CKeyboardController();
int do_input_poll();
void save_configuration(char *cfg_section);
void load_configuration(char *cfg_section);
void interactive_configuration();
void set_default_keyboard(); // sets the default keyboard configuration
private:
// keyboard data
int key_val[15]; // keys to input: 0..3= up,down,left,right | 4..7= reserved | 8..13= buttons | 14= reserved (reserved for future 'grow')
};
#endif
Here is the implementation file for CKeyboardController
// -----------------------------------------------------------------------
// Keyboard Controller Class
// -----------------------------------------------------------------------
// keybctrl.cpp
// -----------------------------------------------------------------------
// This class handles a keyboard controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#include <allegro.h>
#include "keybctrl.h"
CKeyboardController::CKeyboardController()
{
this->set_default_keyboard();
}
CKeyboardController::~CKeyboardController()
{
}
// sets the default keyboard configuration
void CKeyboardController::set_default_keyboard()
{
// keys to input: 0..3= up,down,left,right | 4..7= reserved | 8..13= buttons | 14= reserved
key_val[0] = KEY_UP;
key_val[1] = KEY_DOWN;
key_val[2] = KEY_LEFT;
key_val[3] = KEY_RIGHT;
key_val[8] = KEY_A;
key_val[9] = KEY_S;
key_val[10] = KEY_D;
key_val[11] = KEY_Z;
key_val[12] = KEY_X;
key_val[13] = KEY_C;
}
// This actually does the input from hardware and returns the bitmask acording to action
int CKeyboardController::do_input_poll()
{
int ret = KC_NONE;
if (keyboard_needs_poll()) poll_keyboard();
if (key[key_val[0]]) ret |= KC_UP;
if (key[key_val[1]]) ret |= KC_DOWN;
if (key[key_val[2]]) ret |= KC_LEFT;
if (key[key_val[3]]) ret |= KC_RIGHT;
if (key[key_val[8]]) ret |= KC_BTN1;
if (key[key_val[9]]) ret |= KC_BTN2;
if (key[key_val[10]]) ret |= KC_BTN3;
if (key[key_val[11]]) ret |= KC_BTN4;
if (key[key_val[12]]) ret |= KC_BTN5;
if (key[key_val[13]]) ret |= KC_BTN6;
return ret;
}
// save current configuration in a file (cfg_section = section)
void CKeyboardController::save_configuration(char *cfg_section)
{
char str[256];
// save keys
for (int i=0; i < 15; i++)
{
usprintf(str,"key_val_%d",i);
set_config_int(cfg_section, str, this->key_val[i]);
}
}
// load configuration from a config file (cfg_section = section)
void CKeyboardController::load_configuration(char *cfg_section)
{
char str[256];
// get keys
for (int i=0; i < 15; i++)
{
usprintf(str,"key_val_%d",i);
this->key_val[i] = get_config_int(cfg_section, str, this->key_val[i]);
}
}
// interactive configuration of controller (really LAME interface with user... improve it! :P)
void CKeyboardController::interactive_configuration()
{
int y = 0, h = text_height(font);
clear_keybuf();
while (keypressed()) readkey();
textout(screen, font, "Press key for 'UP'", 0, y, makecol(255,255,255));
key_val[0] = readkey() >> 8;
textout(screen, font, "Press key for 'DOWN' ", 0, y+=h, makecol(255,255,255));
key_val[1] = readkey() >> 8;
textout(screen, font, "Press key for 'LEFT' ", 0, y+=h, makecol(255,255,255));
key_val[2] = readkey() >> 8;
textout(screen, font, "Press key for 'RIGHT' ", 0, y+=h, makecol(255,255,255));
key_val[3] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 1' ", 0, y+=h, makecol(255,255,255));
key_val[8] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 2' ", 0, y+=h, makecol(255,255,255));
key_val[9] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 3' ", 0, y+=h, makecol(255,255,255));
key_val[10] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 4' ", 0, y+=h, makecol(255,255,255));
key_val[11] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 5' ", 0, y+=h, makecol(255,255,255));
key_val[12] = readkey() >> 8;
textout(screen, font, "Press key for 'BUTTON 6' ", 0, y+=h, makecol(255,255,255));
key_val[13] = readkey() >> 8;
textout(screen, font, "Done. Press any key... ", 0, y+=h, makecol(255,255,255));
readkey();
// done
}
Now, we have a fully functional keyboard handler... Great! We will push more, and we will make a mouse controller... keep reading!
A controller with mouse as input device
Well, again, we will do a class CMouseController derived from CBaseController, this time, to take input from mouse.
Sadly, most mouses don't have six buttons, at most they have three, and often, only two, so our mouse controller probably will not be able to handle all six button events. I managed to emulate up to five buttons, using a three button mouse and mouse wheel (roll wheel up and down as button 5 and button 6).
In most arcade games you only need two or three buttons, so this will not be a major issue, I hope.
Well, let's go to source code!
Here is the header file:
// -----------------------------------------------------------------------
// Mouse Controller Class
// -----------------------------------------------------------------------
// mousctrl.h
// -----------------------------------------------------------------------
// This class handles a mouse controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#ifndef MOUSCTRL_H
#define MOUSCTRL_H
#include "controlb.h"
class CMouseController : public CBaseController
{
public:
CMouseController();
~CMouseController();
int do_input_poll();
void save_configuration(char *cfg_section);
void load_configuration(char *cfg_section);
void interactive_configuration();
private:
int mouse_sens; // square of 'dead' until mouse movement is detected; (default 5, smaller is more sens)
};
#endif
And here is the implementation:
// -----------------------------------------------------------------------
// Mouse controller Class
// -----------------------------------------------------------------------
// mousctrl.cpp
// -----------------------------------------------------------------------
// This class handles a mouse controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#include <allegro.h>
#include "mousctrl.h"
CMouseController::CMouseController()
{
this->mouse_sens = 5; // square of 'dead' until mouse movement is detected; (default 5)
}
CMouseController::~CMouseController()
{
}
// This actually does the input from hardware and returns the bitmask acording to action
int CMouseController::do_input_poll()
{
int ret = KC_NONE;
static int old_mouse_z = -666; // mouse_z last call, special flag = -666, means dirty
int mickeyx = 0, mickeyy = 0;
if (mouse_needs_poll()) poll_mouse();
get_mouse_mickeys(&mickeyx, &mickeyy);
if (mickeyx < -mouse_sens) ret |= KC_LEFT;
if (mickeyx > mouse_sens) ret |= KC_RIGHT;
if (mickeyy < -mouse_sens) ret |= KC_UP;
if (mickeyy > mouse_sens) ret |= KC_DOWN;
if (mouse_b & 1) ret |= KC_BTN1;
if (mouse_b & 2) ret |= KC_BTN2;
if (mouse_b & 4) ret |= KC_BTN3;
// also mouse_z (mouse wheel) is used, up = btn4, down = btn5, sadly I can't do yet button 6 (ideas please! :D)
if (old_mouse_z != -666)
{
if (mouse_z < old_mouse_z) ret |= KC_BTN4;
if (mouse_z > old_mouse_z) ret |= KC_BTN5;
}
old_mouse_z = mouse_z;
return ret;
}
// save current configuration in a file (cfg_section = section)
void CMouseController::save_configuration(char *cfg_section)
{
set_config_int(cfg_section, "mouse_sens", this->mouse_sens);
}
// load configuration from a config file (cfg_section = section)
void CMouseController::load_configuration(char *cfg_section)
{
this->mouse_sens = get_config_int(cfg_section, "mouse_sens", this->mouse_sens);
}
// interactive configuration of controller (not a lot to configure... improve it please!)
void CMouseController::interactive_configuration()
{
int y = 0, h = text_height(font);
clear_keybuf();
while (keypressed()) readkey();
textout(screen, font, "--Mouse configuration--", 0, y, makecol(255,255,255));
textout(screen, font, "Press a key number from 1..9 to choose mouse sensitiviness (1 = minimum, 9 = maximum)", 0, y+=h, makecol(255,255,255));
textout(screen, font, "Any other key to cancel.", 0, y+=h, makecol(255,255,255));
char ret = (readkey() & 0xff); // take ASCII code
if (ret == '1') mouse_sens = 40;
if (ret == '2') mouse_sens = 30;
if (ret == '3') mouse_sens = 25;
if (ret == '4') mouse_sens = 20;
if (ret == '5') mouse_sens = 15;
if (ret == '6') mouse_sens = 10;
if (ret == '7') mouse_sens = 5;
if (ret == '8') mouse_sens = 1;
if (ret == '9') mouse_sens = 0;
textout(screen, font, "Done. Press any key... ", 0, y+=h, makecol(255,255,255));
readkey();
// done
}
Well, the mouse controller is a little sluggish in this implementation, so if anybody comes up with a better idea, please let me now!
Now, we move forward, and we do a joystick controller.
Joystick as our input device
Well, a joystick or gamepad is a nice gaming device usually, so is natural that we desire to take input from it.
Again, we found the same trouble that with the mouse: not all joysticks has six buttons, so we will probably lose some buttons, and again, this may be a minor trouble in most arcade games (that usually uses only one or two buttons).
Lets see some source code...
Here is the header file:
// -----------------------------------------------------------------------
// Joystick Controller Class
// -----------------------------------------------------------------------
// joyctrl.h
// -----------------------------------------------------------------------
// This class handles a joystick controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#ifndef JOYCTRL_H
#define JOYCTRL_H
#include "controlb.h"
class CJoystickController : public CBaseController
{
public:
CJoystickController();
~CJoystickController();
int do_input_poll();
void save_configuration(char *cfg_section);
void load_configuration(char *cfg_section);
void interactive_configuration();
private:
int joy_num; // wich joystick to use? 0..num_joysticks (default=0)
};
#endif
And here is the implementation:
// -----------------------------------------------------------------------
// Joystick Controller Class
// -----------------------------------------------------------------------
// joyctrl.cpp
// -----------------------------------------------------------------------
// This class handles a joystick controller for Allegro games.
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003,2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#include <allegro.h>
#include "joyctrl.h"
CJoystickController::CJoystickController()
{
this->joy_num = 0;
}
CJoystickController::~CJoystickController()
{
}
// This actually does the input from hardware and returns the bitmask acording to action
int CJoystickController::do_input_poll()
{
int ret = KC_NONE;
poll_joystick(); // needed
// digital joystick input
if (joy[joy_num].stick[0].axis[0].d1) ret |= KC_LEFT;
if (joy[joy_num].stick[0].axis[0].d2) ret |= KC_RIGHT;
if (joy[joy_num].stick[0].axis[1].d1) ret |= KC_UP;
if (joy[joy_num].stick[0].axis[1].d2) ret |= KC_DOWN;
if (joy[joy_num].num_buttons > 0)
if (joy[joy_num].button[0].b) ret |= KC_BTN1;
if (joy[joy_num].num_buttons > 1)
if (joy[joy_num].button[1].b) ret |= KC_BTN2;
if (joy[joy_num].num_buttons > 2)
if (joy[joy_num].button[2].b) ret |= KC_BTN3;
if (joy[joy_num].num_buttons > 3)
if (joy[joy_num].button[3].b) ret |= KC_BTN4;
if (joy[joy_num].num_buttons > 4)
if (joy[joy_num].button[4].b) ret |= KC_BTN5;
if (joy[joy_num].num_buttons > 5)
if (joy[joy_num].button[5].b) ret |= KC_BTN6;
return ret;
}
// save current configuration in a file (cfg_section = section)
void CJoystickController::save_configuration(char *cfg_section)
{
set_config_int(cfg_section, "joy_num", this->joy_num);
}
// load configuration from a config file (cfg_section = section)
void CJoystickController::load_configuration(char *cfg_section)
{
this->joy_num = get_config_int(cfg_section, "joy_num", this->joy_num);
}
// interactive configuration of controller (really LAME interface with user... improve it! :P)
void CJoystickController::interactive_configuration()
{
int y = 0, h = text_height(font);
clear_keybuf();
while (keypressed()) readkey();
if (!num_joysticks)
{
textout(screen, font, "-- Error: Joystick not found! --", 0, y, makecol(255,0,0));
textout(screen, font, "Press any key...", 0, y+=h, makecol(255,255,255));
readkey();
return;
}
textout(screen, font, "-- Joystick configuration --", 0, y, makecol(255,255,255));
if (num_joysticks > 1)
{
textprintf(screen, font, 0, y+=h, makecol(255,255,255), "You have %d joysticks", num_joysticks);
textprintf(screen, font, 0, y+=h, makecol(255,255,255), "Wich one to use? (Press number 1..%d)", num_joysticks);
char tmp = (readkey() & 0xff); // take ASCII code
joy_num = 0; // default
if (tmp == '1') joy_num = 0;
if (tmp == '2') joy_num = 1;
if (tmp == '3') joy_num = 2;
if (tmp == '4') joy_num = 3;
// although Allegro driver currently supports up to 4 controllers, we let room to grow in future
if (tmp == '5') joy_num = 4;
if (tmp == '6') joy_num = 5;
if (tmp == '7') joy_num = 6;
if (tmp == '8') joy_num = 7;
if (tmp == '9') joy_num = 8;
if (joy_num > num_joysticks) joy_num = 0; // default
}
else
{
joy_num = 0;
}
textprintf(screen, font, 0, y+=h, makecol(255,255,255), "We will use joystick number %d", joy_num+1);
// calibrate joystick if needed, although not needed, because we use digital input
AL_CONST char *msg;
while (joy[joy_num].flags & JOYFLAG_CALIBRATE)
{
msg = calibrate_joystick_name(joy_num);
textout(screen, font, msg, 0, y+=h, makecol(255,255,255));
textout(screen, font, "And press any key.", 0, y+=h, makecol(255,255,255));
if ((readkey()&0xFF) == 27) return;
if (calibrate_joystick(0) != 0)
{
textout(screen, font, "Error calibrating joystick!", 0, y+=h, makecol(255,0,0));
textout(screen, font, "Press any key...", 0, y+=h, makecol(255,255,255));
readkey();
return;
}
}
textout(screen, font, "Done. Press any key... ", 0, y+=h, makecol(255,255,255));
readkey();
// done
}
In that way, we get a fully functional joystick controller.
We are almost done, in next, I will show how to use all this stuff that we made.
A job almost done
Well, we have our keyboard, mouse and joystick controller. Great!... but, now, what we do with it? We use it to control a player! :^D
Let's do a simple test program to test and show how this works...
Here is the test program... a little messy code...
// -----------------------------------------------------------------------
// Test program for controller class
// -----------------------------------------------------------------------
// main_test.cpp
// -----------------------------------------------------------------------
// By Kronoman - In loving memory of my father
// Copyright (C) 2003, 2004, Kronoman
// This package is distributed under the Allegro gift-ware license.
// -----------------------------------------------------------------------
#include <allegro.h>
// our controllers
#include "controlb.h"
#include "keybctrl.h"
#include "mousctrl.h"
#include "joyctrl.h"
// counter for timer
volatile int speed_counter = 0;
// Timer callback for the speed counter
void increment_speed_counter()
{
speed_counter ++;
}
END_OF_FUNCTION(increment_speed_counter);
// we create a lame player class that will use a controller as input device
class CLamePlayer
{
public:
CLamePlayer::CLamePlayer() { the_controller = NULL; x = y = sx = sy = 0; c = makecol(255,255,255); }
void render(BITMAP *bmp) { circlefill(bmp, (int)x,(int)y,8,c); }
void update_logic()
{
if (the_controller == NULL) return; // no controller assigned yet :(
int ret = the_controller->do_input_poll();
if (ret & KC_UP) sy-=0.75;
if (ret & KC_DOWN) sy+=0.75;
if (ret & KC_LEFT) sx-=0.75;
if (ret & KC_RIGHT) sx+=0.75;
if (ret & KC_BTN1) c = makecol(255,255,255);
if (ret & KC_BTN2) c = makecol(0,255,255);
if (ret & KC_BTN3) c = makecol(255,0,255);
if (ret & KC_BTN4) c = makecol(255,255,0);
if (ret & KC_BTN5) c = makecol(0,255,0);
if (ret & KC_BTN6) c = makecol(0,0,255);
// move it
y += sy;
x += sx;
if (x < 0) x = SCREEN_W;
if (y < 0) y = SCREEN_H;
if (x > SCREEN_W) x = 0;
if (y > SCREEN_H) y = 0;
// slow it by 10%
sx *= 0.9;
sy *= 0.9;
}
float x, y; // x,y
float sx, sy; // speed x, y
int c; // color
CBaseController *the_controller; // controller to handle the player
};
// --------------------------------------------------------
// main()
// Program entry point
// (yes, sue me, I'm going to do all in here, is just a test, remember!)
// --------------------------------------------------------
int main()
{
int vid_m = GFX_AUTODETECT_FULLSCREEN; // screen desired graphic mode
int game_color_depth = 16; // default color depth
int vid_w = 640; // desired video resolution
int vid_h = 480;
int desk_bpp = 0;
// initialize allegro and other stuff
allegro_init();
desk_bpp = desktop_color_depth(); // using the same color depth as the host will make the game run faster
if (desk_bpp != 0) game_color_depth = desk_bpp;
srand(time(NULL)); /* init random numbers */
install_timer();
install_keyboard();
install_mouse();
install_joystick(JOY_TYPE_AUTODETECT);
// set graphics mode
set_color_depth(game_color_depth);
if ( set_gfx_mode(vid_m, vid_w, vid_h, 0, 0) ) return -1; // oops... crash!
clear_bitmap(screen);
// --------------------- PERFORM THE TEST --------------------------
BITMAP *dbuf = create_bitmap(SCREEN_W, SCREEN_H); // doble buffer
CBaseController *my_control = NULL; // my controller
CLamePlayer player1;
// *** NOTICE THIS LINE ***
my_control = new(CKeyboardController); // HERE CHANGE TO JOYSTICK, MOUSE, KEYBOARD, ETC!!!!
player1.the_controller = my_control; // 'bind' controller to player using a pointer
my_control->load_configuration("ControllerTest");
player1.x = SCREEN_W/2;
player1.y = SCREEN_H/2;
// set the timers
LOCK_VARIABLE(speed_counter);
LOCK_FUNCTION(increment_speed_counter);
install_int_ex(increment_speed_counter, BPS_TO_TIMER(30));
while (!key[KEY_ESC])
{
while (speed_counter > 0)
{
// here update logic
player1.update_logic();
speed_counter--;
if (key[KEY_F1]) // configure controller
{
clear(screen);
my_control->interactive_configuration();
clear(screen);
}
// using 1,2,3 change the controller at runtime O.O
if (key[KEY_1])
{
my_control->save_configuration("ControllerTest");
delete(my_control);
my_control = new(CKeyboardController);
player1.the_controller = my_control; // 'bind' controller to player using a pointer
my_control->load_configuration("ControllerTest");
}
if (key[KEY_2])
{
my_control->save_configuration("ControllerTest");
delete(my_control);
my_control = new(CMouseController);
player1.the_controller = my_control; // 'bind' controller to player using a pointer
my_control->load_configuration("ControllerTest");
}
if (key[KEY_3])
{
my_control->save_configuration("ControllerTest");
delete(my_control);
my_control = new(CJoystickController);
player1.the_controller = my_control; // 'bind' controller to player using a pointer
my_control->load_configuration("ControllerTest");
}
}
// here update screen
clear(dbuf);
player1.render(dbuf);
textout(dbuf, font, "Press F1 to configure controller", 0, 0, makecol(255,255,255));
textout(dbuf, font, "Press 1..3 to CHANGE controller (keyboard,mouse,joystick)", 0, text_height(font), makecol(255,255,255));
blit(dbuf,screen,0,0,0,0,dbuf->w,dbuf->h);
}
my_control->save_configuration("ControllerTest");
// remove timer
remove_int(increment_speed_counter);
return 0; /* normal end of the program */
}
END_OF_MAIN();
Closing words
Well, we are done for now. The main controller classes are finished, and now we can control using keyboard, mouse, or joystick, in a easy way for us; we just 'bind' our player to the controller, and ready to go. In this way, is easy for us to add new controllers for other hardware devices, or why not, reading a script from disk, or using AI to send control events.
Make sure to check the zip file with the complete project tree for MingW32, DJGPP and GNU GCC, and do your own experiments.
With nothing more to say for now...
Kronoman, Buenos Aires, Argentina.
