This document provides an overview and reference for QE, a 3D game engine. It describes the development environment including DirectX, OpenGL and Visual Studio. It outlines the engine's directory structure and key files. It also summarizes input handling, adding functions and classes to the engine loop, and deriving from the qeUpdateBase class. The document provides examples of initializing input, remapping keys, and reading the keyboard buffer. It references important QE API functions.
1. QE Reference
Joe Linhoff
Overview
QE stands for the quarter engine.
Development Environment
DirectX & OpenGL
QE uses OpenGL for graphics and DirectX for input, sounds, and other operations on
Windows. For best results, update your OpenGL drivers (from your video card manufacturer's
website) and install the latest DirectX (search for dxwebsetup.exe on Microsoft's site).
Microsoft Visual Studio C++ Express Edition 2008
All the solutions are built using Express Edition 2008.
Directories
● (solution root) -- your application
● starter -- starter kit with hello world
Directories in the tree (and some of the files in those directories)
● lib -- library files
● bin -- executables
○ qeblue.dll -- qe
○ freeglut.dll -- glut-like gl utility toolkit
● inc -- qe include files
○ qe.h -- the main interface file for the engine
○ qec.h -- engine class definitions for C++
○ qebase.h -- base types
Files
config.h -- a user created file that defines how the solution builds
●
qeStartup.c -- startup file for environment, not a user file
●
.h files are added from the inc directory as needed
●
Hello World
// Copyright (C) 2007 Joe Linhoff, All Rights Reserved
// e_hello.c
#include quot;config.hquot;
#if BUILD_HELLO // compile this app
#include quot;qe.hquot;
// qeMain()
// JFL 05 Jan 07
int qeMain(int argc,chr *argv[])
2. {
// print hello world & version information
qePrintf(quot;%s / %s / %snquot;,__FILE__,glGetString(GL_VERSION),qeVersion());
qePrintf(quot;Hello Worldnquot;);
// turn control over to the engine until the user closes the program
qeForever();
return 0;
} // qeMain()
#endif // compile this app
// EOF
Console and Log File
The function qePrintf() follows most of the same formatting rules from printf(). In addition,
all the messages printed are added to the log file. The log file is qelog.txt in your project's
root directory. Use qeLogf() to send messages to the log file and not to the screen.
Reference
QE_API int qeForever(void); // does not return until closed
QE_API chr *qeVersion(void); // get version
QE_API int qePrintf(chr *fmt,...); ///< print a message
QE_API int qeLogf(chr *fmt,...); ///< log a message
Base Types, Definitions, Macros
Base Types
The file qebase.h contains the base type definitions for the engine.
base type definition and use
bit smallest unit available, use 1 or 0
chr 8 bit character for ASCII strings and internal naming
char environment defined character
int8 8 bit signed integer, MIN_INT8..MAX_INT8
uns8 8 bit unsigned integer, 0..MAX_UNS8
int16 16 bit signed integer, MIN_INT16..MAX_INT16
uns16 16 bit unsigned integer, 0..MAX_UNS16
int32 32 bit signed integer, MIN_INT32..MAX_INT32
uns32 32 bit unsigned integer, 0..MAX_UNS32
int environment defined integer, MIN_INT..MAX_INT
uns unsigned version of environmentally defined integer, 0..MAX_UNS
flt32 32 bit float
flt64 64 bit float
float environment defined float
3. int64 64 bit signed integer
uns64 64 bit unsigned integer
Defines
define definition notes, usage
PI 3.1415..
TWOPI (2.0*PI)
EPSILON 0.00001
NUL 0L, a null pointer
Macros
define definition notes, usage
BRK() debugger breakpoint when DEBUG is defined during compile
QE_DEGTORAD(_deg_) convert degree to radians
QE_RADTODEG(_rad_) convert radians to degrees
NUM(_a_) number of elements in a static array
PTR_ADD(_p_,_i_) add _i_ bytes to pointer _p_
PTR_SUB(_p_,_i_) subtract _i_ bytes from pointer _p_
C1(_a_) combine a single 8 bit value
C2(_a_,_b_) combine two 8 bit values, used for character-based constants
C3(_a_,_b_,_c_) combine three 8 bit values, used for character-based
constants
C4(_a_,_b_,_c_,_d_) combine four 8 bit values, used for character-based
constants
SET2(_d_,_s1_,_s2_) set array, i.e. _d_[0]=_s1_, _d_[1]=_s2_
SET3(_d_,_s1_,_s2_,_s3_) set array, i.e. _d_[0]=_s1_, _d_[1]=_s2_
SET4(_d_,_s1_,_s2_,_s3_,_s4_) set array, i.e. _d_[0]=_s1_, _d_[1]=_s2_
CPY2(_d_,_s_) copy array entries from source _s_ to destination _d_
CPY3(_d_,_s_) copy array entries from source _s_ to destination _d_
CPY4(_d_,_s_) copy array entries from source _s_ to destination _d_
Function Pointers
Typedefs for function pointers are defined such that the first letter signals the return type and
the next letters signal the parameter types: v = void, i = int, u = uns, p = pointer, a =
variable argument. The partial table below shows a few examples.
typedefs definition notes, usage
fnc_vv void function(void)
fnc_iv int function(void)
fnc_ii int function(int)
4. fnc_ipi int function(pointer,int)
fnc_ipa int function(pointer,...)
Control
QE presents a single list to the user. In qeMain(), the application setups a list of functions
and objects and then passes control to QE with qeForever(). Each game loop, QE performs
various internal operations, and then begins processing the application-created list. Because
the engine processes this list, it is called the engine list.
Function Objects
The function qeObjAddFnc() adds a C function with the signature int function(void) to the
engine object list. This function will be called when it's node is processed. If the function
returns a negative number, the node is removed from the engine's list and the function is not
called again.
// Copyright (C) 2006-2008 Joe Linhoff, All Rights Reserved
// e_simplefunction.c
#include quot;config.hquot; // include the 'config' file first
#if BUILD_SIMPLEFUNCTION // compile this app
#include quot;qe.hquot;
// JFL 12 Aug 06
int fncEveryLoop(void)
{
qePrintf(quot;Frame %d time %fnquot;,qeLoopFrame(),qeTimeFrame());
return 0; // function must return a value, 0 indicates success
} // fncEveryLoop()
// qeMain()
// JFL 05 Aug 06
int qeMain(int argc,chr *argv[])
{
qePrintf(quot;%s / %s / %snquot;,
__FILE__,glGetString(GL_VERSION),qeVersion());
// add a function to the engine's object loop
if(!qeObjAddFnc(fncEveryLoop))
BRK();
// turn control over to qe until the user closes the program
qeForever();
return 0;
} // qeMain()
#endif // compile this app
// EOF
Extended Operations
Many functions have a variable argument interface which starts with a string parameter. The
5. string parameter specifies the remaining arguments. For example, to specify the name for
the function node added above, use qeObjAddFncSP() as follows.
// add a simple C function, static name, not copied
if(!qeObjAddFncSP(fncEveryLoop,quot;Nquot;,quot;f-everyLoopquot;))
BRK();
// add a simple C function, dynamic name, copied
if(!qeObjAddFncSP(fncEveryLoop,quot;nquot;,name))
BRK();
F8 Console
QE has a built in console. Press F8 to toggle the console input on/off. Type the command
and hit enter. Press F9 to toggle the background color and clear the console screen.
Commands
● list -- lists the engine objects
● step -- single step the engine, forward & backward arrows to step
● run -- exit single step mode
Break Macro
When DEBUG is defined, the BRK() macro expands to a debugger breakpoint. When DEBUG
is not defined, or defined to be zero, the BRK() macro expands into a null statement.
Names
Internally, names are truncated to 11 characters and are case sensitive.
QE Update Base Class
The preferred way to add a C++ object to the engine list is by deriving from the base class
qeUpdateBase. This base class is defined in qec.h. Objects inheriting from qeUpdateBase
are linked into the engine list and sorted against each other using the sort value. The gameid
must be greater than zero and should be unique among all engine objects. Id value 1 is
usually reserved for underdefined objects.
// Copyright (C) 2006-2008 Joe Linhoff, All Rights Reserved
// e_simpleclass.c
#include quot;config.hquot; // include the 'config' file first
#if BUILD_SIMPLECLASS // compile this app
#include quot;qec.hquot;
///////////////////////////////////////////////////////////////////////////////
// Plane
class Plane : public qeUpdateBase {
public:
Plane::Plane(chr *name);
int draw(void); // override virtual
void final(void); // override virtual
}; // class Plane
6. // JFL 11 Nov 07
Plane::Plane(chr *name) : qeUpdateBase(name,0,1) // name, sort, gameid
{
} // Plane::Plane()
// JFL 11 Nov 07
int Plane::draw(void)
{
qefnDrawGrid(10,1);
qefnDrawAxes(1);
return 0;
} // Plane::draw()
// JFL 11 Nov 07
void Plane::final(void)
{
delete this;
} // Plane::final()
// qeMain()
// JFL 05 Aug 06
int qeMain(int argc,chr *argv[])
{
qePrintf(quot;%s / %s / %snquot;,
__FILE__,glGetString(GL_VERSION),qeVersion());
new Plane(quot;plane1quot;);
// turn control over to qe until the user closes the program
qeForever();
return 0;
} // qeMain()
#endif // compile this app
// EOF
Deriving from qeUpdateBase defines and adds nodes for:
● control() -- application definable
● update() -- application definable
● draw() -- application definable
● final() -- must delete this;
QE base class
The qe base class overrides the new and delete operators to provide integration with the
engine without adding memory overhead to your objects.
Reference
QE_API int qeForever(void); // does not return until closed
QE_API uns qeLoopFrame(void); ///< loop count since reset
QE_API float qeTimeFrame(void); ///< game time for the frame
QE_API qeObj* qeObjAddFnc(fnc_iv fnc); // add C function obj
QE_API qeObj* qeObjAddFncSP(ptr adr,chr *sp,...); // add C func w/sp
7. BRK();
class qeUpdateBase
Input
Button Input
Button counts are used to report the current state of each button. Odd button counts signal
the button is currently down. The keyboard is initially setup as buttons.
// JFL 21 Sep 06
int fncEveryLoop(void)
{
uns upCount;
// read the button count for the up button
upCount=qeInpButton(QEINPBUTTON_UP);
// print all the values
qePrintf(quot;upCount=%dnquot;,upCount);
return 0; // function must return a value, 0 indicates success
} // fncEveryLoop()
Default Key to Button Mappings
Z - QEINPUTBUTTON_A up - QEINPUTBUTTON_UP pageup - QEINPUTBUTTON_UP2
X - QEINPUTBUTTON_B down - QEINPUTBUTTON_DOWN pagedown - QEINPUTBUTTON_DOWN2
C - QEINPUTBUTTON_X left - QEINPUTBUTTON_LEFT
V - QEINPUTBUTTON_Y right - QEINPUTBUTTON_RIGHT
Remapping The Keyboard Buttons
To remap the keys to different buttons, or to map multiple keys to the same button, use
qeInpOp(QEINPOP_SETKEYMAP,gameKeymap) where the gameKeymap is the remapping
table. This mapping table replaces the default table.
int gameKeymap[] = {
// remap QEINPBUTTON_ to keys
QEINPBUTTON_UP,'W',0,
QEINPBUTTON_LEFT,'A',0,
QEINPBUTTON_RIGHT,'D',0,
QEINPBUTTON_DOWN,'S',0,
QEINPBUTTON_A,'Z',0,
QEINPBUTTON_B,'X',0,
QEINPBUTTON_X,'C',0,
QEINPBUTTON_Y,'V',0,
QEINPBUTTON_START,'1','r',0, // multiple mappings
GAMEINPBUTTON_SPECIAL,'G','H','8',0, // multiple mappings
0 // term
}; // gameKeymap[]
.. remap the keys in the setup ..
8. // remap the keys -- gameKeymap must remain valid while it is mapped
qeInpOp(QEINPOP_SETKEYMAP,gameKeymap);
The Keyboard As A Keyboard
Instead of treating the keyboard as a set of buttons, keys can also be captured into a buffer.
Setup a buffer for QE to fill with keystrokes between frames, and then empty that buffer each
frame.
// Copyright (C) 2006-2008 Joe Linhoff, All Rights Reserved
// e_keyboard.cc
#include quot;config.hquot;
#if BUILD_KEYBOARD // compile this app
#include quot;qe.hquot;
struct { // create local storage record
uns keyBuf[32];
} game;
// JFL 12 Aug 06
// JFL 24 Sep 06
int fncEveryLoop(void)
{
chr buf[32];
// copy the chrs typed out of the keyBuf and into 'buf'
buf[0]=0; // force a terminating 0 -- will be replaced if chrs are copied
qeInpOp(QEINPOP_CPYCHRBUF,buf,sizeof(buf));
// print all the values captured this loop
qePrintf(quot;'%s'nquot;,buf);
return 0; // function must return a value, 0 indicates success
} // fncEveryLoop()
// qeMain()
// JFL 05 Aug 06
int qeMain(int argc,chr *argv[])
{
MEMZ(game); // zero structure
// print information
qePrintf(quot;%s / %s / %snquot;,__FILE__,qeVersion(),glGetString(GL_VERSION));
// add a function to the engine's object loop
if(!qeObjAddFnc(fncEveryLoop))
qePrintf(quot;** qeObjAddFnc failednquot;);
// setup the keyBuf to record keys as they are typed -- must remain valid
qeInpKeyBuf(game.keyBuf,sizeof(game.keyBuf));
// turn control over to engine until the user closes the program
qeForever();
return 0;
} // qeMain()
#endif // compile this app
9. // EOF
Turn off keyboard button mappings with qeInpOp(QEINPOP_KEYBOARDMAPPINGS_OFF).
And turn them back on with qeInpOp(QEINPOP_KEYBOARDMAPPINGS_ON).
Joysticks
QE supports DirectX joysticks, Xbox360 joysticks, and Wii-motes. Use qeInpJoyNum() to find
the current number of joysticks attached. Buttons and axes are then read using
qeInpJoyButton() and qeInpJoyAxis(). The axis value ranges from QEINP_JOYAXIS_MIN to
QEINP_JOYAXIS_MAX.
int fncEveryLoop(void)
{
uns b1,b2,b3,b4;
int xyLeft[2];
int xyRight[2];
// read joystick buttons and axes
b1=qeInpJoyButton(0,QEJOYBUTTON_A);
b2=qeInpJoyButton(0,QEJOYBUTTON_X);
b3=qeInpJoyButton(0,QEJOYBUTTON_LSHOULDER);
b4=qeInpJoyButton(0,QEJOYBUTTON_LTHUMB);
xyLeft[0]=qeInpJoyAxis(0,QEJOYAXIS_LEFT_X);
xyLeft[1]=qeInpJoyAxis(0,QEJOYAXIS_LEFT_Y);
xyRight[0]=qeInpJoyAxis(0,QEJOYAXIS_RIGHT_X);
xyRight[1]=qeInpJoyAxis(0,QEJOYAXIS_RIGHT_Y);
// print the values
qePrintf(quot;number of joysticks=%d A=%d X=%d LS=%d LT=%d (%d, %d) (%d, %d)nquot;,
qeInpJoyNum(),b1,b2,b3,b4,xyLeft[0],xyLeft[1],xyRight[0],xyRight[1]);
return 0; // function must return a value, 0 indicates success
} // fncEveryLoop()
Reference
QE_API uns qeInpButton(uns inpb); // QEINPBUTTON_
QE_API int qeInpOp(uns op,...); // QEINPOP_
QE_API uns qeInpJoyNum(void); // joysticks attached -- valid after qeOpen()
QE_API int qeInpJoyAxis(uns joy,uns axis); // get, use QEJOYAXIS_
QE_API uns qeInpJoyButton(uns joy,uns button); // get, use QEJOYBUTTON_
Sounds
Supported Formats
A limited number of .wav formats are currently supported.
Channels
Sounds play on software defined channels. There are currently 8 channels, which means you
may play up to 8 sounds at the same time. A sound can range from a short pop to an
10. endlessly looping song. The game engine manages the mixing of the currently playing
channels.
Loading And Playing Sounds
Register sounds with qeSndNew(). Play a sound with qeSndPlay(). To pre-load a sounds, use
qeSndLoad().
QE_API int qeSndNew(chr *name,uns flags,uns tProtect,chr *path);
QE_API int qeSndPlay(chr *name); // returns play id on success
The name parameter is the name of the sound and will be used to play or manipulate the
loaded sound. The flags parameter contains track and priority information as well as other
information. The tProtect parameter is a millisecond time parameter. And path is the file
path to the sound file.
Channel Assignment Algorithm
When qeSndPlay() is called, the engine:
1. Looks through all the channels indicated in the channel mask for an empty
(no sound currently playing) channel. If there is an empty channel, the engine
starts the sound on that channel.
2. If there is not an empty channel, the engine looks through the channels
indicated in the channel mask for a channel with a sound that it can stop
playing and replace with the new sound.
If the new sound's priority is higher than a currently playing sound, the oldest,
currently playing sound is replaced.
If there are no open channels, and the new sound's priority is lower than all the
currently playing sounds, the new sound will not be played.
When the new sound's priority matches currently playing sounds, the oldest
sound that is not protected will be replaced.
Age of a sound is determined by the time after a sound's protection expires.
A Typical Channel Plan
Every game has its own sound requirements. You must design a channel plan for how each
game will use channels. Here is a simple example:
ch0 ch1 ch2 ch3 ch4 ch5 ch6 ch7
background announcer, world player weapons,
11. music music effects, critical reactions,
extras reactions effects
Reference
QE_API int qeSndNew(chr *name,uns flags,uns tProtect,chr *path);
QE_API int qeSndPlay(chr *name); // returns play id on success
Options
Turning Options On And Off
QE is intended to be configurable. Use qeOptionTurnOff() or qeOptionTurnOn() to turn
options on or off. Set options before qeForever() or qeOpen().
#define M_QEOPTION_CLEARSCREEN 0x000001 // clear screen every loop
#define M_QEOPTION_AUTOCAM 0x000002 // add first (arcball) camera
#define M_QEOPTION_DRAWCONSOLE 0x000100 // draw console
#define M_QEOPTION_INITGL 0x000200 // init OpenGL
#define M_QEOPTION_DRAWMODE 0x000400 // black on white
#define M_QEOPTION_FIRSTLIGHT 0x000800 // add first light
#define M_QEOPTION_LOGPRINTF 0x002000 // qePrintf() output also to log
#define M_QEOPTION_CHECKLOGSIZE 0x004000 // check & delete log if too big
#define M_QEOPTION_INERNALSLEEP 0x008000 // sleep inside the engine loop
#define M_QEOPTION_CLEARSTENCIL 0x010000 // clear stencil buffer every loop
#define M_QEOPTION_PAGEFLIP 0x010000 // flip pages
For example, to request the engine to no add the default camera or first light, and to clear
the stencil buffer when it clears the screen:
// qeMain()
// JFL 05 Jan 07
int qeMain(int argc,chr *argv[])
{
qeOptionTurnOff(M_QEOPTION_AUTOCAM|M_QEOPTION_FIRSTLIGHT);
qeOptionTurnOn(M_QEOPTION_CLEARSTENCIL);
qePrintf(quot;%s / %s / %snquot;,__FILE__,glGetString(GL_VERSION),qeVersion());
...
Window Size, Full Screen
Use qeWindowSize() to change the window size. Use qeGetWindowSize() to get the current
window size.
float w,h;
if(qeGetWindowSize(&w,&h)>=0)