|
|
Documentation
Contents
1. Initializing and Using GameX
2. Advanced
GameX Initialization
3. About Game Speed and Frame Rate
4. Loading Images - The ImageX class
5. Drawing Images in GameX
6. Special
Effects with Drawing Modes
7. Shading Images and the ColorX class
8. Advanced
Graphics Rendering in GameX
9. 3D Graphics
& CameraX
10. Painting
in and Filtering Images
11. Loading
and Playing Sounds - The SoundX class
12. Music and
the MusicX class
13. Keyboard
Input in GameX
14. Mouse Input
in GameX
15. Loading and Saving Files in GameX
16. Reserved Controls
17.
Things to Remember! (Problems? Look here first!)
18. GameX Performance
1) Initializing
and Using GameX
GameX is designed to be extremely easy to use. A GameX game is just
like any other C++ program except that all GameX programs follow a very
basic main program template. Windows functions (WinProc and WinMain)
have been hidden so you don't need to directly deal with the Windows
API or DirectX.
The main program template is as follows:
#define GAMEX_MAIN
#pragma comment (lib, "gamex.lib")
#include "gamex.hpp"
// #include <...>
void GameInit (void)
{
GameX.Initialize ("My Game", VIDEO_16BIT,
800, 600);
//… {load images/sounds/music/whatever}
…
}
void GameRun (void)
{
//... {motion, behavior, input }
...
}
void GameDraw (void)
{
//... {drawing functions}...
}
|
// Tells GameX that this is your main .cpp file
// Tells your compiler to link with the GameX library
// Includes GameX itself
// (Include whatever else we want to use here)
// Required GameInit( ) function. GameX calls
this once at startup.
// The purpose of GameInit( ) is to set everything up to run the
game.
// Required GameX Initialization. 2nd parameter is for display
mode
// or other options. 800 x 600 is game window size in pixels.
// (Now load or set up whatever else our game needs)
// Required GameRun( ) function. Called repeatedly (speed can
be set).
// Purpose of GameRun( ) is to advance the game's
state by one instant.
// (Calls to implement motion, behavior, input, and sound for
game)
// Make sure not to draw anything here!
// Required GameDraw( ) function.
// Purpose of GameDraw( ) is to draw the game's
current state.
// (Draw background then sprites using GameX
draw functions)
// Make sure not to change or move anything here!
|
This is the minimum length of a GameX game (note that this is *much*
smaller than the minimal Windows application). The WinProc and WinMain
functions which you would normally need for a windows application are
handled automatically by GameX, so all you need to define are the GameInit(
), GameRun( ) and GameDraw( ) functions, which are called separately
by GameX at different times to accomplish different things:
- The GameInit( ) function is called once as the game is starting up.
Thus, you will place all setup code in GameInit( ).
After calling GameX.Initialize( ) here, it's a good idea to load sounds
and images and whatever else your game will need.
- The GameRun( ) function is called repeatedly. Thus, place what would
go inside of the main loop of your game in GameRun( ).
The code can be as complex as necessary to give your game the motion
and behavior you desire. BUT, make sure that it does not call any drawing
functions, directly or indirectly.
- The GameDraw( ) function is also called repeatedly, although possibly
less often. It should do nothing but draw whatever game state has been
set by GameRun( ).
Make sure not to alter the state of the game while you're drawing it;
even appearance-related changes should not be made here unless they
have no effect on any subsequent frames of animation.
Using GameX's built-in functions is very simple. There is a global object
called GameX which you can use to call any GameX function. The GameX
object provides everything you need to make a game - graphics, sound,
and user-input. Its functions can be called from anywhere, including
inside class member functions, as long as the gamex.hpp file is included
in the class header.
Briefly, some of the most essential GameX functions are:
For starting up:
- GameX.Initialize (name, mode, width, height,
[speed]) -- Initializes GameX and sets general game properties.
For graphics:
- GameX.DrawImage (&image, xpos, ypos, [angle,
scale]) -- Draws an image sprite at xpos, ypos (image is an ImageX)
- GameX.DrawRect (color, xleft, ytop, xright, ybottom)
-- Draws a filled rectangle at the given location.
- GameX.DrawTiled (&image, xoffset, yoffset) --
Draws an image tiled as a background.
For sound:
- GameX.PlaySound (&sound, how, [volume, pan, frequency])
-- Plays a sound clip in real-time (sound is a SoundX).
- GameX.PlayMusic (&song, [times, volume, fade_in_time,
tempo, pitch]) -- Starts playing a MIDI music
clip (song is a MusicX).
For keyboard input:
- GameX.IsKeyDown (key) -- Tells you if the given
keyboard key is being pressed by the user.
- GameX.GetKeyPress (key) -- Tells you if the given
keyboard key was just pressed at this instant by the user.
- GameX.GetBufferedKeyPress( ) -- Gets a typed character
from the user, or 0 if nothing was typed
For mouse input:
- GameX.IsMouseDown (button) -- Tells you if the
given mouse button is being pressed.
- GameX.GetMouseClick (button) -- Tells you if the
given mouse button was just clicked this instant.
- GameX.GetMouseX( ) and GameX.GetMouseY( ) -- Gets
the position of the mouse cursor
- GameX.GetMouseDX( ), GetMouseDY( ), GetMouseDZ(
) -- Gets the rate of change of the mouse cursor or wheel.
For general use:
- GameX.GetGlobalCounter(
) -- Gets a value that increases linearly over time.
- GameX.GetOscillatingValue
(min, max, speed, [phase]) -- Gets a value that oscillates sinusoidally
over time.
- GameX.GetRandomInt (min,
max) -- Gets a pseudo-randomly generated integer within the bounds given.
- GameX.GetRandomFloat (min,
max) -- Gets a pseudo-randomly generated floating point value within
the bounds given.
- GameX.DebugOutput (char*
message) -- Adds a log to the "debug.txt" output file located in the
same directory as your game.
2) Advanced
GameX Initialization
The only function you are required to call when initializing
GameX is GameX.Initialize( ). The code above gives you an example of
how to use this function, but let's take a closer look at it:
bool Initialize (char* name, GameXInitFlags options, int xres, int
yres, [int speed = 60]); // speed is optional
Initialize returns true if it succeeds and false otherwise, but don't
worry about this; your code can assume that it always succeeds, and
when it fails, GameX will let you know with an informative popup message
that will give you an idea of how to fix the problem.
The important things to note about this function are the parameters
it takes and what it does with them:
- name is simply a string
enclosed in quotes, such as "My Game" or "Asteroids". It's used only
to let Windows know what the game is called so it can give it the right
titles.
- xres and yres tell GameX the width and height to make your game screen. They
can be almost anything, but standard values include 640x480 or 800x600
(it is best, but not required, to use a 4:3 ratio of width to height).
- speed is optional; if you
don't give a speed parameter, GameX will assume a speed of 60 cycles
per second (cps), which is a fairly typical game speed. If you do give
a speed parameter, that value is the number of times that your GameRun(
) function is called per second. The speed is NOT the frame rate, although
it determines the maximum frame rate your game can reach, along with
the monitor's refresh rate. This parameter must be between 5 and 600.
- options is just an int
value consisting of combinable initialization flags. This required parameter
tells GameX what features to set up for your game. For instance, if
you want the game to only run in "full screen" mode, that's an option
you can specify. The only required part about this parameter is that
you must specify a preferred bit depth (monitor color mode) for your
game. You should almost always use VIDEO_16BIT because 16 bit color
mode is the most widely supported and the fastest mode. There are many
other options though, which you can combine with each other using the
| operator. Here's the list, it's a long one but keep in mind you will
probably only ever use a few of these:
GameX Init Flag:
|
Description of Effect:
|
VIDEO_16BIT
|
Run game in 16-bit color mode -- currently the
fastest, most common, and best-supported mode.
|
VIDEO_32BIT
|
Run game in 32-bit color mode -- beware: on many
computers, this mode is slower and also looks worse than 16-bit
mode. This flag is incompatible with VIDEO_16BIT for obvious reasons.
|
VIDEO_FULLSCREEN
|
Immediately enter full screen mode when the game
begins.
|
VIDEO_WINDOWED
|
Begins game in windowed mode. This flag is incompatible
with VIDEO_FULLSCREEN for obvious reasons. If you don't specify
this flag or the previous flag, then the user is automatically
given a choice at game startup.
|
RUN_USEESCAPEKEY
|
Allows your game to detect the escape key instead
of having ESC automatically quit.
|
RUN_AUTOSHOWINFO
|
Tells GameX to automatically display the following
debugging information in real-time: game speed, frame rate, idle
percent, and mouse position. It will be displayed in the title
bar if the game is in windowed mode, or at the bottom-left corner
of the game screen if the game is in full screen mode. This flag
is for debugging only, so you should remove it when you are done
with your game.
|
RUN_ALWAYSKEEPCURSOR
|
Prevents GameX from hiding the Windows mouse
cursor, even in full screen mode.
|
RUN_ALWAYSHIDECURSOR
|
Allows GameX to hide the cursor whenever it is
over the game screen. (If you want, your game can draw its own
cursor easily.)
|
RUN_NOCONFIRMQUIT
|
Disallows GameX from asking whether the user
is sure about quitting.
|
VIDEO_LOWRES
|
Tells GameX to use low-resolution images to save
memory and speed at the expense of some graphical quality.
|
VIDEO_NORESIZE
|
Prevents the user from being able to resize or
maximize the game window.
|
VIDEO_ALLOWREFRESHSYNC
|
Tells GameX to attempt to automatically synchronize
game drawing updates with the monitor's refresh rate. This is
known as "V-Sync" and it can prevent "tearing" artifacts to improve
the visual quality of your game's animation, but its implementation
is not perfect (sacrifices some accuracy for speed) so it is not
on by default.
|
VIDEO_SLOWDEBUGDRAWING
|
Has GameX draw individual frames very s-l-o-w-l-y
so you can get a visual representation of what order things are
being drawn in. This flag is for temporary debugging purposes
only.
|
VIDEO_SOFTWAREONLY
|
Forces all drawing operations to take place in
software instead of video hardware. Not recommended since it will
make any game run a lot worse; only use this for temporary debugging.
|
AUDIO_DISABLE
|
Disables audio support, which prevents GameX
from playing any sounds or music.
|
RUN_BACKGROUND
|
Allow the game to keep running even in the background
by preventing GameX from automatically pausing the game -- possible
uses are for networked games or cheat prevention, maybe.
|
RUN_NOMOUSEINPUT
|
Prevents GameX from keeping track of mouse movement
and clicks.
|
RUN_NOALTENTERTOGGLE
|
Allows the player to hit alt-enter without toggling
between full screen/windowed mode.
|
So, here's an example of a specialized call to Initialize( ):
GameX.Initialize("My First Person Shooter",
(VIDEO_16BIT
| VIDEO_FULLSCREEN | RUN_USEESCAPEKEY | RUN_ALWAYSHIDECURSOR),
800,
600, 100);
This would initialize a game called "My First Person Shooter" that
runs in 16-bit fullscreen mode, that treats ESC like a regular key for
the game to use, and that always hides the cursor so you can draw your
own cursor (a crosshair for example). It sets the game screen size to
a standard 800x600 pixels, and it tells GameX to call GameRun( ) 100
times every second, which is slightly faster than the usual 60.
3) About
Game Speed and Frame Rate
GameX now incorporates automatic game speed and frame rate management.
Because it is completely automatic, you do not need to worry about it
or write any timing code in order to program your own GameX game. However,
although it is not necessary to know exactly how it works, a general
understanding of it can be educational and will help you better understand
the level of performance of your game, and so it is described in detail
in this section of the documentation, if you wish to learn more about
it.
There are two important terms to know with regard to how GameX does
its automatic timing:
CPS -- Cycles Per Second -- The game's speed -- Technically, the number
of times that GameRun( ) is called per second.
FPS -- Frames Per Second -- The game's smoothness -- Technically, the
number of times that GameDraw( ) is called per second.
If you Initialize GameX with RUN_AUTOSHOWINFO,
you will see values (actual / target) for CPS and FPS; now you know
what they mean.
The CPS value is what determines how fast your game goes. A target
CPS is determined when you Initialize GameX, and you can change this
value later by calling GameX.SetGameSpeed( ), or find out what it is
at run-time by calling GameX.GetGameSpeed( ). The default value is 60
CPS, which is a standard (pretty fast) game speed. A specific value
doesn't necessarily translate to how fast your game seems to play, because
it also matters how fast you tell the individual objects in your game
to move. Whatever the CPS is, what matters is that it remains constant,
because an unpredictably/unintentionally varying CPS is unacceptable.
You may already be familiar with the term "FPS", also known as the
game's "frame rate", in which case you would know that a high FPS is
good because it makes the game animate smoothly, whereas a low FPS makes
the game appear choppy. The FPS your game can obtain is limited by several
factors: The frame rate cannot go higher than the refresh rate of your
monitor, and the FPS also cannot go higher than the CPS of your game.
When you Initialize GameX, it determines a target FPS that is as high
as possible without going over these limits. There are other limiting
factors (for instance, if your computer is too slow to handle the complexity
of your game), but these are not calculable.
The absolute top priority of the GameX timing code is to ensure that
the actual CPS of the game remains exactly fixed at the target CPS value.
It then makes the best attempt possible to keep the actual FPS at or
around the target FPS, but this attempt is a secondary concern. This
means that, if necessary, GameX will skip a few frames of drawing here
and there, lowering the actual FPS (this is known as intentional lag),
in order to keep the game going at the same speed for a consistent playing
experience even if it doesn't appear quite as smooth. It will also wait
between frames when necessary to prevent the game from running too quickly,
which would be just as serious a problem as it going too slowly. Note
that change in keyboard input during lag is ignored, so there is a "lag
limit" which defaults to 12 and can be set with GameX.SetLagLimit(value).
If the control is still imprecise during lag on slow computers, then
you should set the lag limit lower, but don't set it too low. On the
other hand, if your game is a network game, you probably want the lag
limit to be infinitely high, which you can do by calling GameX.SetLagLimit(0),
which GameX interprets to mean no limit at all.
(It is implied in the above that the timing code is able to operate
because the "updating" and the "drawing" code of the game can be called
independently. Thus, it is important that your GameRun and GameDraw
functions really do Run or Draw your game (respectively) as GameX expects
them to.)
Ideally, however, the computer that GameX is running on is capable
of handling the game's visual complexity, in which case the target CPS
and FPS will both be obtained. Which leads to the timing code's third-priority
concern: conservation of CPU cycles. If the target CPS and FPS are both
approximately reached, then GameX will attempt to conserve CPU cycles
by gathering what spare milliseconds it can (between frames) and giving
them back to the system, which increases the "idle time" of the game.
CPU cycles that are not conserved directly translate to energy used
and heat produced by your computer, so the higher the idle time is,
the cooler your system runs and the less energy it uses. This is especially
helpful for laptop computers which have issues with both energy and
cooling. Also, any CPU cycles conserved remain available for background
processes to make use of without disturbing the game.
So how exactly does the above information help a programmer who is
making a GameX game? Here is some advice that it roughly translates
into:
- Make sure that GameDraw( ) only draws your game and doesn't change
anything at all, or the timing code will malfunction.
- If you don't quite understand all of this, don't worry about it; the
timing code is pretty self-sufficient as long as your code is valid.
- Don't set the speed of your game too low, because it limits the game's
frame rate, but don't set it too high, either. 45 to 100 CPS is normal,
60 is the standard value.
- Until you are done with or are presenting your game, use RUN_AUTOSHOWINFO
as a flag for GameX.Initialize, because that way you will know the CPS,
FPS, and Idle% of your game when you're testing it. But don't leave
it in after that, because it can make your game look less professional.
If you want an FPS display in your game, query GameX for the frame rate
and draw it yourself instead.
- If the CPS of your game is not approximately at the requested game
speed, then something serious is wrong.
- If the FPS of your game becomes low, for instance if it says 40/60
or even 10/60, then your game's drawing section is too complex for your
computer to handle it. Make sure you are building your program in "Release"
mode and not "Debug", since this can make a big difference in speed.
If you are running in Release mode, and your computer is decently fast,
and the FPS is still low, that probably means you are doing something
inefficient. Don't blame low FPS on GameX or its timing code; it is
purely the result of your computer and its video card not being powerful
enough to handle the computations that your code is asking them to perform.
- If the Idle% of your game is high (such as 30% to 80%), that is a
good thing, however, it also means that you have a lot of room for expansion.
Idle time translates to unused potential processing power that you could
harness by adding code to make your game look better or act smarter,
without causing any drop in the frame rate. But don't use up *all* of
the idle time if you can avoid it, because as stated above, idle time
is good and ultimately makes your game more compatible with different
computers. If the idle time is 0, and it used to be higher, consider
optimizing your code and/or removing unnecessary features.
Note that GameX allows you to change the game's speed while it is running,
which can be an enormous help when testing or debugging.
Here is a useful block of code you can paste at the end of your GameRun(
) function that makes the + and - keys speed up and slow down time,
respectively:
#ifdef _DEBUG
if(GameX.IsKeyDown(KEY_MINUS))
GameX.SetGameSpeed(GameX.GetGameSpeed( ) - 1);
if(GameX.IsKeyDown(KEY_PLUS)) GameX.SetGameSpeed(GameX.GetGameSpeed( ) + 1);
#endif
One last thing about GameX timing: It is possible to completely disable
GameX's timing and frame rate management by making GAMEX_RUN_UNMANAGED
a preprocessor definition in your game's project settings. This is to
allow custom timing code to be implemented, but be warned that writing
good, flexible timing code is a surprisingly subtle and difficult task.
In any case, when GAMEX_RUN_UNMANAGED is defined, GameX repeatedly calls
a single function GameMain instead of GameRun and GameDraw.
4) Loading
Images - The ImageX class
The graphics of any game are the most important way that the game is
presented to the person playing it. To achieve an acceptable level of
graphical quality, most games draw many pre-made images in various combinations
of ways to represent the game. To allow you to do this, GameX provides
an ImageX class which maintains sprites and textures (images) which
you can use to piece together your game's graphics.
The ImageX class provides features to easily load and manage your images.
You will usually want to pre-load your images in the GameInit( ) part
of the program before anything is drawn (although this is not essential).
Here is an example:
ImageX background, target, ship;
void GameInit (void)
{
background.Load ("back.jpg");
target.Load ("target.tif", true);
ship.Load ("ship.gif", "ship_alpha.gif");
}
|
// Allocate a ship, a target, and a background
sprite
// Required void GameInit (void) function
// Load back.jpg file into the background image
// Load target.tif file with transparency into target image
// Load ship.gif file with transparency from
ship_alpha.gif
|
The Load function loads the specified image file into an ImageX object,
which you can (later) tell GameX to draw. The first parameter of Load(
) is the image file's path, and the second (optional) parameter tells
GameX how/whether to load the image with a transparency layer.
For those who are not familiar with the conventions of filename paths
required to identify Windows files in C++ code:
The first parameter is a C-string that is
the path to the file to load. A path consists of the names of the folders
that the file is inside (each followed by a slash), then the file's
name with its extension. In this case, the first image we load has a
path of "back.jpg", which means that the file is not inside any folders
(it's just sitting in our game's directory), its filename is "back.jpg",
and ".jpg" is the extension, which means that the file is a JPEG file.
The extension tells GameX what type of file it is; GameX can currently
load JPEG, GIF, BMP, and TIFF files.
To load an image that isn't directly in the
game's directory, it must be in a sub-directory of a known name, such
as "Images", then you can call something like:
background.Load("Images\\back.jpg");
NOTE:
The double slash is required, because \\ means \ to your compiler when
inside a string.
These paths are NOT case-sensitive, so "BaCk.JpG" means the same thing
as "back.jpg".
IMPORTANT:
If you are using Windows XP, filenames may appear to have no extension
at all (i.e. "back.jpg" shows up as "back"), but that is only because
the operating system is hiding the extensions from you. To fix this,
go to Start -> Control Panel -> Folder Options-> View, and
scroll down until you see a checkbox next to "Hide extensions for known
file types", which you should uncheck and then click OK.
In any case, once GameX finds the image where you tell it to look,
it will load the image, with or without transparency as mentioned briefly
above. Basically, if you pass in "true" as the second parameter, GameX
will attempt to load the alpha channel in the image as well as its color
channels, and if it does not find an alpha channel, it will instead
use an on/off "mask" where pure black becomes fully transparent. Or,
you can pass in a second filename (instead of a boolean) for the second
parameter to indicate that the alpha channel comes from a different
image file, like so:
ship.Load ("ship_color.gif",
"ship_alpha.gif"); // (from the sample code above)
This allows you to use a separate grayscale image as the alpha/transparency
layer instead of needing the color and alpha merged into the same file.
In this case, the alpha actually comes from the second image's non-alpha
color data, which is converted to grayscale and then to alpha.
You must have called GameX.Initialize( ) before you can load any images.
Your images can be almost any size you want, but computers like squares
and powers of two, so the most efficient image sizes to use are exactly
64x64, 128x128, 256x256, and 512x512. Anything larger will still work,
but should be avoided. Try to conserve memory and speed by using small
images (you can scale them up to make them look as big as you want in
the game) and re-using loaded images whenever possible.
Images can be saved in many different formats. GameX can currently
load 4 of these formats, each of which is best when used in some situations
and not in others. Here is a list of the image types GameX can handle
with its Load function, along with the optimal use of each image format:
Image File Type:
|
File Name Extension:
|
Specific Loading Capabilities:
|
Best Used For:
|
Bitmap File
|
.bmp or .BMP or .dib or .DIB
|
8-bit (Indexed), 16-bit (X1R5G5B5),
24-bit (R8G8B8), or 32-bit (R8G8B8A8)
|
General use, small images that don't need to
be compressed much. 16-bit and sometimes 8-bit BMP files are better.
|
Tag Image File
|
.tif or .TIF or .tiff or .TIFF
|
24-bit (R8G8B8) or 32-bit (R8G8B8A8)
Must be saved with NO compression using IBM byte
order.
|
Small images that don't need to be compressed
at all (or are not in their final form) and would benefit from
having alpha saved directly in them.
|
JPEG File
|
.jpg or .JPG or .jpeg or .JPEG
|
All qualities and compression methods supported.
|
Background images, artwork, screenshots, and
most other large images.
|
Graphic Image File
|
.gif or .GIF
|
Non-animated, without LZW compression or interlacing.
Transparent color, if anything, must be pure black, if not using
a separate file.
|
All images that use or look acceptable with 256
colors or (preferably) less.
|
There are a few things to note about these image formats and the issue
of transparency:
- TIFF and 32-bit BMP are capable
of storing a detailed alpha channel directly inside of them.
- JPEG images must be saved as
two separate files (one for color, one for alpha/transparency) in order
to represent an image with transparency.
- The other image formats can
use pure-black to represent fully-transparent (a mask), OR they can
be saved as two separate files (one for color, one for alpha).
To conserve file memory, you should save each different image in the
format that best suits it according to the uses suggested in the above
"Best Used For" column. Conserving file memory is important, because
when it comes time to distribute your game or even just transfer it
between computers, it will go a lot faster if the files take up less
memory. When saving each image, a good method to use is this: First,
can it be saved and look good as a GIF? If not, try JPEG. If neither
of those are acceptable, then consider 16-bit BMP and finally TIFF.
Besides Load, ImageX has several other methods you may find useful.
Excluding the image painting and filtering methods (which are described
below in section 17, "Painting in Images"), here are the functions that
ImageX provides:
image.Load (char * filename, [bool alpha]); // alpha is optional, defaults
to false
Loads an image from file into
this ImageX. See above in this section for details and example code.
image.Load (char * filename, char * alphafilename);
Loads an image from file,
with alpha from another file, into this ImageX
image.LoadIntoAlpha (char * filename);
Loads a grayscale image and
uses it to replace this image's alpha channel
image.Create (int xSize, int ySize, [bool alpha=false]); // alpha is
optional, defaults to false
Creates a blank image of the
given size.
image.Destroy (void);
Completely unloads this image
from memory.
image.Save (char * filename);
Saves the image to file. The
filename must have a .bmp or .tif extension.
image.CopyTo (ImageX * dest);
Copies this image into a destination
image.
image.CopyToAlpha (ImageX * dest);
Copies the grayscale conversion
of this image's color into the alpha of a destination image.
image.CopyToClipboard (void);
Copies the image to the clipboard,
from where it can be pasted into other programs.
image.GetWidth (void); // returns the width of this image, in pixels
image.GetHeight (void); // returns the height of this image, in pixels
image.Resize (int new_xres, int new_yres);
Resizes this image, preserving and scaling current contents to
the extents of the new size.
5) Drawing Images in GameX
The heart of the GameX engine consists of functions for drawing sprites
and images which you have loaded, and other graphical objects.
All drawing must take place (directly or indirectly) inside the GameDraw(
) function. It is a good idea to not do any other processing in
this section of the program, or at least as little processing as necessary.
Preferably, the drawing section should be used just to call the appropriate
GameX drawing functions (which may be called via member functions of
another class). This drawing happens every single frame, which is often
as many as 60 times per second, to make the game appear continuously
animated.
Drawing an image:
To draw an image in 2D, use the DrawImage( ) function. Here's a sample
call to DrawImage:
GameX.DrawImage(&ship1, 200, 300);
This simple line of code takes a ship image (which must have already
been loaded) and draws it at the position (200,300) pixels from the
top-left corner of the game screen. If the ship image was loaded with
transparency, GameX will automatically take that into account when it
draws it here. And if you want to draw the ship rotated by 180 degrees
and scaled to be 5 times as big, you would simply replace the above
line of code with:
GameX.DrawImage(&ship1, 200, 300, 180.0, 5.0); // 180 is the angle,
5 is the scale
That's how easy it is to do scaling and rotation. You could replace
180.0 with any arbitrary angle and 5.0 with any arbitrary scale and
that's how GameX would draw it; there's no need to create a buffer image
beforehand. Also, note that all clipping is fully automatic.
The general DrawImage function prototype is:
void DrawImage(ImageX* img, int x, int y, [float angle, float scale])
// angle and scale are optional
Note that the angle and scale provided in this function are for convenience,
it's really just shorthand for calling GameX.SetDrawAngle(angle) and
GameX.SetDrawScale(scale) before you draw the image with GameX.DrawImage(img,
x, y). The other drawing functions do not have this shorthand version,
so you will have to use SetDrawScale and SetDrawAngle to change the
angle and scale with them.
Keep in mind that DrawImage takes a pointer to the image you're
drawing. So, if your image variable is called "image", then you would
call DrawImage(&image, x, y) instead of DrawImage(image, x, y)
(which would give a compiler error). If you have an image pointer variable
already (ImageX* imagePointer), then you can call DrawImage(imagePointer,
x, y).
A final note about DrawImage: Normally, the x,y coordinates you give
are used to determine where the top-left corner of the image goes. However,
you will often want to draw an image that is centered
at a particular point, and you don't care where its top-left corner
is. There is now an easy way to center images: Before you make the call
to DrawImage, call GameX.SetDrawMode (DRAWOP_CENTERED), and GameX will
automatically center the image at the x,y coordinates you give. SetDrawMode
is described in further detail later (section 13 of this documentation).
Drawing a background:
There is actually one thing you should do before drawing any images:
draw a background. If you draw the background after drawing the images,
then the background will cover up the images, which is probably not
intended. If you don't draw a background at all, then previous frames
of animation will show through to the current one, which can look neat
but is not usually desirable.
There are several ways of drawing a background:
GameX.ClearScreen( )
Sets the entire screen
to black, to create a black background without requiring any background
art graphics.
GameX.FillScreen (ColorX color )
Sets the entire screen
to the given color, which can be a gradient color (see below for info
on ColorX objects)
. Example: to fill the
screen bright green, you would call GameX.FillScreen (ColorX (0, 255,
0)).
GameX.DrawTiled (&backgroundImage, [x, y]) // x and y are optional
Draws an image automatically tiled multiple
times to make a background image. Here, the (x, y) is an offset value which you can change over
time to make the background scroll smoothly. You should make sure backgroundImage
is NOT loaded with transparency, unless you are drawing the background
in multiple layers. Also, to make the background spin and/or zoom, you
can call SetDrawAngle and/or SetDrawScale beforehand. If spinning it,
you can give two extra coordinates which are rotational offset values
if you want to make the background spin about a specific point: GameX.DrawTiled
(&backgroundImage, x, y, rot_x, rot_y). Finally, note that DrawTiled
(and FillScreen also) are not necessarily limited to being used to draw
the background; you might also use them to overlay the screen with a
transparent and/or tiled layer, for example, to achieve a fog or mist
effect. Transparency of this nature is discussed in further detail below
in section 14 of this documentation, "Shading Images and the ColorX
Class".
Of course, you could also draw your background by piecing it together
out of images using DrawImage, but the above functions are generally
more convenient.
Note about Clipping and Split-Screen
Modes:
When you draw an image that is partway out of the game window, it is
"clipped" so that only the part of it inside your game window is drawn.
Clipping against the edges of the game view is automatic, and when
you draw the background, it normally fills this entire view. However,
sometimes you may want to only use a smaller portion of the window.
For instance, if your game has a split-screen mode, you want the two
halves of the screen to be independent and never overlap each other,
which you can have GameX enforce for you by clipping to each half of
the screen when you draw to it. To change the clipping region, use this
function:
GameX.SetView (int xLeft, int yTop, int xRight,
int yBottom);
When you want to reset the clipping to the way it was before, call
GameX.ResetView( ).
Note about Drawing Order:
In the drawing section, the drawing of sprites and other things normally
proceeds from back to front. If you draw in front-to-back order instead,
then everything will be drawn in the wrong order and covered up by the
background. If you draw two things in the same place, the second one
will always end up on top of (obscuring) the first one. There is one
important exception: You can tell GameX to sort the scene for you so
that it does not matter what order you draw in, but this is an advanced
option (described later) used primarily to simplify 3D drawing.
6) Special Effects with Drawing Modes
GameX allows you to change the "drawing mode" before drawing an image
(or other graphic) in order to achieve a wide variety of special effects.
An example of a common effect is "additive drawing", which is where
the image's color values are added to the color values of whatever it
is drawn on top of, as opposed to replacing them completely like normal.
Additive drawing is the perfect way to achieve special effects such
as fire, lightning, photons, spotlights, explosions, laser beams, etc.
To draw an image additively, simply make the following call before
drawing the image: GameX.SetDrawMode (DRAW_ADD).
There are two types of constants for use with SetDrawMode: "Drawing
Mode" constants (such as DRAW_ADD), and "Draw Option" constants (such
as DRAWOP_CENTERED). When you call SetDrawMode, you can specify one
Drawing Mode constant, and you can also optionally specify any number
of Draw Option constants. This is done by combining constants via the
| operator, for example, GameX.SetDrawMode (DRAW_ADD | DRAWOP_CENTERED).
There are many other constants besides DRAW_ADD and DRAW_CENTERED,
which are as follows:
Image Drawing Mode:
|
Resulting effect:
|
Sample:
|
Drawing Formula:
|
DRAW_NORMAL
|
Image is drawn normally (by replacing the background
with the image). If the image has transparency, it is automatically
drawn with the transparency taken into account. This mode is assumed
by default unless you specify otherwise.
|
|
SOURCE
|
DRAW_ADD
|
Image is drawn additively by adding color brightness
together, in the same way that light works. This useful mode is
great for special effects such as fire, lightning, photons, spotlights,
explosions, laser beams, etc. If the image has alpha (transparency),
it is taken into account, HOWEVER the image should NOT have any
alpha layer, because the effect generates its own transparency
using the image's colors. Darkness in the image automatically
becomes transparency, so there is no need for an alpha layer.
For this and the following modes, you should use images with black
backgrounds that are not loaded with alpha.
|
|
DEST + SOURCE
|
DRAW_SUBTRACT
|
Image is drawn subtractively, which is good for
shadows, shadowy effects, or black text. The brighter the image,
the more it darkens what you draw it over. Also, the darker what
you are drawing over, the more quickly it goes to black. The image
should NOT have any alpha layer (in fact, this and the following
Draw Modes completely ignore any alpha layer that the image may
have).
|
|
DEST - SOURCE
|
DRAW_GHOST
|
Image is drawn using color-valued transparency,
creating an interesting effect that is good for raindrops, some
particle effects, and (of course) ghosts. You can think of this
effect as a sort of combination of DRAW_SUBTRACT and DRAW_ADD
(only very loosely), because it can both darken and lighten what
it is drawn over.
|
|
DEST*(1-SOURCE) + SOURCE*SOURCE
|
DRAW_INVERT
|
Image is drawn by inverting the destination proportionally
to the image colors. Good for either inverting things (creating
a "nuclear-winter" type effect if what you draw is white) or for
drawing images that must be visible against both light and dark
backgrounds.
|
|
DEST + SOURCE - 2*SOURCE*DEST
|
DRAW_BURN
|
Image is drawn by inverting the destination proportionally
to the grayness of the image colors. Very interesting, but of
limited use (perhaps useful for things like smoke, oil, and etchings).
You can think of this effect as a sort of combination of DRAW_INVERT
and DRAW_SUBTRACT, or as a inverted version of DRAW_GHOST.
|
|
SOURCE - SOURCE*SOURCE - DEST*SOURCE
|
DRAW_INTENSIFY
|
Image is drawn additively but also proportionally
to the brightness of the destination. Thus, the destination must
already contain brightness for anything drawn with this mode to
add more. Tends to preserve hues while increasing brightness.
This mode is often useless for drawing a scene, but can be used
to intensify parts of what has already been drawn.
|
|
DEST + 2*SOURCE*DEST
|
DRAW_ADDSOFT
|
Image is drawn additively but also proportionally
to the darkness of the destination. In other words, it's a lot
like DRAW_ADD but tapers off as it approaches full brightness,
preventing the burnout effect that is common to normal additive
drawing, for a softer and more uniform resulting effect.
|
|
DEST + SOURCE - SOURCE*DEST
|
DRAW_ADDSHARP
|
Image is drawn additively but with increased
contrast, often resulting in its colors becoming more prominent.
|
|
DEST + 4*SOURCE*SOURCE
|
DRAW_MULTIPLY
|
Image is drawn by multiplying image colors by
destination colors. This mode can make the destination darker
but not brighter, since it treats white as 1 and black as 0. May
be useful for color filter overlays or certain other effects.
|
|
DEST * SOURCE
|
Image Draw Option:
|
Resulting effect: (can be combined)
|
DRAWOP_BRIGHT
|
Draw image with 2X brightness, causing the brighter
parts of the image to drawn with a level of "overexposure" which
can make for a nice effect. Combine this flag with SetDrawColor
if you want a higher degree of control over image shading and
brightness, such that 127 is normal and 255 is extra-bright. You
can also combine this flag with draw modes like DRAW_ADD, DRAW_SUBTRACT,
etc. to apply the special effect to a greater extent.
|
DRAWOP_INVERTED
|
Temporarily invert the image when drawing it.
White becomes black, black becomes white, blue becomes yellow,
gray stays gray, etc. Do not confuse this with DRAW_INVERT which
inverts the background based on the image; this just inverts the
image and lets you apply it to the background however you want.
|
DRAWOP_NOFILTER
|
Render the image without doing bilinear filtering,
i.e. turn off automatic smoothing of image scaling. This is good
if for some reason you want your images to look blocky. This flag
usually does NOT speed up image drawing.
|
DRAWOP_NODITHER
|
Normally, GameX dithers what you draw to give
the impression that the image is showing more colors than the
monitor can really display. However, certain effects may suffer
from the predictable pattern that dithering follows, in which
case you can specify this flag to avoid the problem. Usually,
it does not make a difference one way or the other, and it does
not affect drawing speed at all.
|
DRAWOP_CENTERED
|
Makes point coordinates specify center instead
of top-left of the image. Very useful for many objects (space
ships, for instance) whose positions are defined in relation to
their center, so you don't have to calculate an offset radius
to center them. Only has an effect on DrawImage, DrawImage3D,
DrawPoint, and DrawPoint3D.
|
DRAWOP_HMIRROR
|
Mirror or flip the image horizontally (sideways)
when drawing.
|
DRAWOP_VMIRROR
|
Mirror or flip the image vertically when drawing.
|
DRAWOP_NOCULL
|
If a polygon is facing away from the camera in
3D, or if an image/rectangle has a negative x and/or y scale in
2D, GameX normally skips drawing it, but this flag prevents GameX
from skipping (culling) it. Using this on too many polygons will
result in decreased performance, however.
|
DRAWOP_NOBLENDALPHA
|
For advanced use only. Tells GameX to treat the
alpha/transparency layer like a normal color layer to be transferred
instead of using it to blend colors. If drawing to the screen
or into an image with no alpha, this means the alpha layer gets
ignored.
|
DRAWOP_ALPHAINTERSECT
|
For advanced use only. If you are drawing an
image in auto-sorted 3D, AND if the image has alpha/transparency,
AND if you want it to be able to intersect in 3D with other images
or polygons, then you should specify this flag. Otherwise, do
not specify this flag because it slows things down. When the flag
is on, parts of the image that are 97% opaque or greater will
be Z-buffered and show up properly over/under partially intersecting
images and polygons.
|
DRAWOP_KEEPSTATES
|
For advanced use only. Preserves image state
settings past the call to the next drawing function. Most drawing
states are reset to default after the next thing you draw if you
don't specify this flag before you draw it.
|
Keep in mind that the above drawing modes and drawing options do NOT
modify the source image, only the way it is drawn during one drawing
operation. Also, remember that you cannot combine two Draw Modes, such
as DRAW_ADD and DRAW_SUBTRACT at the same time; you can only combine one
DRAW_ constant with any number of DRAWOP_ constants.
7) Shading
Images and the ColorX class
Another useful function you can call before drawing an image is GameX.SetDrawShading
(ColorX color). This allows you to shade a particular drawing of an
image without changing the actual image, so you can even use a color
that changes dynamically with time, and the shading operation is very
fast. As a simple example, to draw an image shaded bright red, you would
call GameX.SetDrawShading (ColorX(255, 0, 0)). Shading is capable of
doing much more interesting things, but first it is necessary to know
what a ColorX really is:
An object of class ColorX is intended to hold a color. Specifically,
it holds red, green, and blue values which add to produce a wide range
of possible colors, in fact, every color that a computer monitor is
capable of displaying. If you're not familiar with additive color, then
you'll just have to get used to the fact that Red+Green=Yellow, Red+Blue=Magenta,
Green+Blue=Cyan, and Red+Green+Blue=White. The ColorX class uses the
convention that 0 means no color amount, and 255 means full brightness
of that color amount. So, when (red, green, blue) = (255, 255, 0), that
corresponds with the color yellow. So, to create a yellow ColorX, you
would call the constructor ColorX(255, 255, 0). For some other examples:
pure black is (0, 0, 0), 50% gray is (128,128,128), pure white is (255,
255, 255), a pinkish color is (255,128,128), a deep violet color is
(48, 0, 112). Components that are out-of-bounds of the 0-255 range are
clipped, so for instance (400,300,200) becomes (255,255,200) (pale yellow)
when used.
We now extend the definition of "color" to include an additional concept:
transparency. Transparency is defined via an "alpha" value, where 0
means fully transparent, and 255 means fully opaque. Like color values,
it can be anywhere in-between, for instance, alpha of 128 means it is
exactly halfway transparent. So each color is really defined as (red,
green, blue, alpha), also known as RGBA. To create a ColorX that is
red and with 50% transparency, you would call the constructor ColorX(255,
0, 0, 128). Drawing this color on white would result in pink, on yellow
would result in orange, on black would result in dark-red. When you
call SetDrawShading with a transparent color, the image is drawn with
the given degree of transparency, even if the image itself does not
have transparency in it, and even if you are using one of the special
effect drawing modes.
Finally, a ColorX can encompass one more concept related to color:
The gradient. A color gradient basically means that, instead of one
single color, you can actually have something that fades between multiple
colors (including alpha). In other words, a ColorX can actually hold
multiple ColorX's inside of it, which are automatically used by GameX
to create smooth color gradient shading effects. A ColorX can hold 4
colors which are given in this order: top-left, top-right, bottom-left,
bottom-right. So, to make a ColorX with a gradient, you can call the
constructor ColorX (top_left_color, top_right_color, bottom_left_color,
bottom_right_color). Or, you can give all of the RGBA values at once
and call ColorX (r1,g1,b1,a1, r2,g2,b2,a2, r3,g3,b3,a3, r4,g4,b4,a4).
The ColorX class also contains several operator overloads for convenience,
for instance, you can multiply a ColorX by a number to change its brightness,
or you can add two ColorX objects together and use the result to shade
something.
NOTE: When you use a ColorX in a function that draws a color, for instance
with GameX.FillScreen(ColorX color), the color that the ColorX represents
is exactly what will be drawn to the screen. However, in the case of
shading an image with SetDrawShading, you should know that the shading
color is actually multiplied by the image, so "pure white"
when shading an image means that the image does not get shaded at all,
"50% gray" means that the image is drawn at 50% brightness, and "bright
red" means that the image is drawn with all of its green and blue components
removed. But, a neat trick is to use the drawing option DRAWOP_BRIGHT,
which makes 50% gray mean no shading, pink means intensified red, and
white means doubled brightness, in other words DRAWOP_BRIGHT doubles
the range of possible colors you can shade your image by letting you
make it brighter as well as darker.
Here is an example that draws a ship image that is shaded transparent
at the top-left, bright green at the top right, bright blue at the bottom-left,
and totally black at the bottom-right:
GameX.SetDrawShading (ColorX( ColorX(0,0,0,0),
ColorX(0,255,0), ColorX(0,0,255), ColorX(0,0,0) ));
GameX.DrawImage (&ship_img, x, y)
To save space, you could also change the above SetDrawShading call
to the equivalent:
GameX.SetDrawShading
(ColorX( 0,0,0,0, 0,255,0,255, 0,0,255,255,
0,0,0,255 ));
(Note that you can store a ColorX object as a global or member variable
if you don't want to keep creating it each frame. Also, take advantage
of the fact that you can perform arithmetic with ColorX's.)
Some uses of ColorX and image shading include the following:
- Drawing shapes of a certain color (see the next section of this documentation)
- Making a sprite fade out (draw it using a slowly decreasing alpha
shading).
- Dynamic special effect colors (for instance, keep one white "fire"
effect and shade it red for red flame, or blue for blue flame, etc.)
- Drawing a gradient background (call GameX.FillScreen with a gradient
color, at the beginning of GameDraw)
- Dimming the screen (call GameX.FillScreen with a black but partially
transparent color, at the end of GameDraw).
- Light source shading a sprite (make a gradient color with white-to-black
shading generated sinusoidally toward a light source)
- 3D gouraud-shaded texture lighting effects (add lighting components
to gradient color using polygon normals and vertex positions)
As you can see, you can implement some quite advanced and interesting
features by taking advantage of the flexibility of the ColorX class,
but even simple games with simple graphics can benefit from a little
creative use of image shading.
8) Advanced
Graphics Rendering in GameX
The previous four sections of this documentation tell you how to load,
draw, shade, and create special effects with images, which is everything
you need to know to create graphics for a standard 2D game. However,
some games need to be able to do more than this, even many regular 2D
games. What if you want to draw lines, rectangles, points or polygons
with just colors or with image textures? What about drawing only selected
parts of an image, creating "blur" effects, drawing text, or drawing
in 3D? The answer is that GameX makes all of these things simple to
do: just call the appropriate drawing functions (which are described
here).
Drawing Shapes (In 2D):
Sometimes you will want to draw simple things that are not based on
an image, such as a line or a rectangle. You can use GameX to draw points,
lines, rectangles, and polygons, with the following functions:
(These are all compatible with GameX.SetDrawMode, so you can do special
effects with them.)
GameX.DrawPoint (ColorX clr, float x, float y);
Draws a simple dot of the
given color at the given x,y coordinates.
To set the dot's size, call
GameX.SetDrawScale(float size_in_pixels) beforehand.
To center the dot if it's
larger than 1 pixel, use the DRAWOP_CENTERED draw option flag.
Example: GameX.DrawPoint
(ColorX(255,0,0), 10,20); // draws a red dot at (10,20) pixels from
top-left of screen
GameX.DrawLine (ColorX clr, float x1,float y1, float x2,float y2);
Draws a line segment of the
given color between the two given x,y coordinates.
To set the line's width, call
GameX.SetDrawScale(float width_in_pixels) beforehand.
If clr is a gradient color,
the top-left and top-right are applied to the (x2, y2) side of the line,
the others to the (x1, y1) side.
GameX.DrawRect (ColorX clr, float xLeft, float yTop, float xRight,
float yBottom);
Draws a rectangle filled with
the given color, bounded by the given x and y values.
You can use SetDrawScale and
SetDrawAngle in conjunction with this function.
GameX.DrawPolygon (ColorX clr, float x1,float y1, float x2,float y2, float x3,float y3, float x4,float y4);
Draws a quadrilateral polygon
filled with the given color, with vertices at the given x,y coordinates.
The coordinates must be given
in a roughly "Z" shape.
So, an example of drawing
a green diamond-shaped polygon: GameX.DrawPolygon (ColorX(0,255,0),
0,0, 20,10, 10,20,
30,30)
To draw a triangular polygon,
repeat x3,y3 for x4,y4, like so: GameX.DrawPolygon (ColorX(0,255,0), 0,0, 20,10,
10,20, 10,20);
Mapping Images to Shapes (In
2D):
Often, a color is not enough, and you want to be able to texture-map
an image across one of the above shapes.
(The following are all compatible with GameX.SetDrawMode and GameX.SetDrawShading)
To map an image to a point (a point-sprite):
GameX.DrawImage (ImageX* img, float x, float y)
This has already been described
several sections above this in "Drawing Images in GameX".
To map an image across a line (a line-sprite):
GameX.DrawLineImage (ImageX* img, float x1,float y1, float x2,float y2, [bool pixelUnitScale=false])
Draws the image with its bottom
at x1,y1, and its top at x2,y2.
pixelUnitScale defaults to
false. If false, the line's width is chosen to preserve the image's
proportions, otherwise the width equals the scale amount in pixels.
To alter the line's width,
call GameX.SetDrawScale( ) beforehand.
This function is useful for
things like pendulums, lightning, borders, oriented sprites, or compound
line-based special effects.
To map an image to a rectangle:
GameX.DrawTexturedRect (ImageX* img, float xLeft, float yTop, float
xRight, float yBottom);
You can use SetDrawScale and
SetDrawAngle in conjunction with this function.
If you use the DRAWOP_NOCULL
flag, you can specify a flipped rectangle to flip the image.
To map an image to a polygon:
GameX.DrawTexturedPolygon (ImageX* img, float x1,float y1, float x2,float
y2, float x3,float y3, float x4,float y4);
Works the same as GameX.DrawPolygon, see above
for details.
Drawing Text:
Text is very useful for games to be able to draw, even if all you need
to draw is the score and the title. Currently, the best and fastest way to draw
text is to draw it graphically using an alphabet image (see the GameX downloadable
demos for example graphics and code you can use for this). However, GameX
also provides simple text drawing functions (which are limited to size
12 Windows API font) for when you're just getting started and want to
put some letters or numbers on the screen.
GameX.DrawText (int x, int
y, char* msg, [int r, int g, int b]); // (r,g,b) is optional and defaults
to (255,255,255) white
Or, to make the text more legible if there's anything in the background,
you can instead use:
GameX.DrawOutlinedText (int
x, int y, char* msg, [int r, int g, int b], [int ro, int go, int bo]);
// (r,g,b) defaults to white, (ro,go,bo) defaults to black
Either way, the result is that you output a message string starting
at the given (x,y) coordinates onscreen.
Unfortunately, drawing text
in this way is extremely slow. And if you alternate between drawing
text and drawing any other type of graphics, you will cause even more
slowdown problems. This is why it is better to draw text made out of
images, because it doesn't have this speed limitation, plus it will
allow you to use different text sizes and rotation and drawing modes,
etc.
In any case, here is an example of drawing a textual score (number
of points) at the top-left corner of the screen:
char scoreString [64]; // allocate memory
for a string that can be at most 64 digits
sprintf (scoreString, "%d", points); // fill the string with the number
of points to draw
GameX.DrawOutlinedText(10, 10, scoreString); // draw the score at top-left
of screen
Causing a Blur Effect:
Blurring can actually mean several things (motion-blur, radial-blur,
position-blur, etc.), all of which are rather advanced effects for a
game to perform in real-time.
To perform a position-blur effect on the screen (to make everything that was drawn earlier in this frame
look unfocused), you can use the following functions:
GameX.BlurScreen (float amount)
Blurs the entire screen by
the given amount, amount being a float value between 0.0 and 1.0, where
1.0 blurs it all the way and 0.0 blurs it not at all.
Only changes what has already
been drawn. So, you could draw the graphics you want to blur, then use
this function, then draw the graphics that you don't want to blur.
GameX.BlurRect (float amount, int xLeft, int yTop, int xRight, int
yBottom)
Same as BlurScreen, but only
affects the given rectangular portion of the screen.
This effect is very slow.
Some computers aren't even fast enough to perform this operation at
a reasonable speed, in which case the blur is automatically skipped
to avoid slowing the game to a crawl. Since this means there is a chance
that the blur will be skipped when the game is played on certain computers,
you should not rely on position blur frequently or to obscure information,
but rather to create an aesthetically pleasing (but not strictly necessary)
effect at certain parts of the game.
Advanced Info: Other types of blurring require a little more creativity
to achieve, but they are still possible. To create a pseudo-motion-blur
if your game consists mostly of objects against a background, you can
just draw the background as mostly transparent (shaded with alpha value
32) and with the DRAWOP_NODITHER flag to avoid accumulated dithering
artifacts. To create an actual motion-blur effect, you can create an
image that's the same size as the game screen, and at the end of GameDraw,
draw that image (with DrawTexturedRect) to the screen shaded with a
high alpha value (such as 128, 160, or 192), then draw the screen itself
back into the image (see below for details of how to do this). To create
a radial blur effect, do the same thing as the motion-blur effect but
also use SetDrawScale( ) with a scale somewhat greater than 1 when drawing
the image to the screen, and you can optionally add rotation to make
the blurring spiral out of the center. Needless to say, these effects
can cause a substantial hit to the frame-rate, except for the pseudo-motion-blur
which is relatively fast. To see an example of the radial blur effect
in action, download ParticleDemoX, run it, and press the 5 key once
after it starts going. It requires a substantially better computer
than do almost all other GameX effects.
Advanced Draw States:
We have already covered how to set the Mode and Shading draw states
with GameX.SetDrawMode( ) and GameX.SetDrawShading( ). We have also
briefly mentioned how to use GameX.SetDrawScale( ) and GameX.SetDrawAngle(
). Here is the detailed explanation of these and other draw states:
Drawing
State:
|
Tells
GameX To:
|
Function
Prototypes:
|
Mode
|
Draw the next thing in a special way. See "Special Effects with Drawing Modes" above for details.
|
GameX.SetDrawMode(DrawFlags
mode)
|
Portion
|
Draw only the given rectangular part of the next
image, instead of the entire image. This is useful because you
can store multiple sprites in one image, and use this function
to specify which sprite to draw out of it at a given time.
|
GameX.SetDrawPart (int
x1, int y1, int x2, int y2)
|
Destination
|
Draw the next thing into the given destination
image instead of to the screen. This is the only case where it
is OK to use drawing functions from inside GameRun(). Note: Drawing
into images is currently not hardware-accelerated.
|
GameX.SetDrawDestination
(ImageX* dest)
|
Angle
|
Rotate the next thing drawn by the given angle,
which is in degrees counter-clockwise.
|
GameX.SetDrawAngle
(float angle)
|
Scale
|
Scale the next thing drawn by the given scaling
factor. 2.0 means it's drawn twice as large as normal, 0.5 means
half as large as normal. Negative scaling values result in the
image not being drawn at all, unless you also set the DRAWOP_NOCULL
draw flag.
|
GameX.SetDrawScale
(float scale)
|
Same as above, but allows you to scale the width
and height by separate scaling factors.
|
GameX.SetDrawScale
(float scaleX, float scaleY)
|
Shading
|
Draw the image shaded with the given ColorX.
See "Shading Images with the ColorX class" above for details.
|
GameX.SetDrawShading
(ColorX color)
|
Shading
Effect
|
Apply image shading in a different way. "effect"
must be a non-combined image drawing mode, such as DRAW_ADD (which
would cause any shading to be added to the image). The default
value is DRAW_MULTIPLY, which means that image shading is normally
done multiplicatively. This state is totally unrelated to SetDrawMode,
although it uses some of the same constants.
|
GameX.SetDrawShadingEffect
(DrawFlags effect)
|
Warp
|
Draw with advanced image warping / distortion
/ pinching. (1,1,1,1) is normal, >1 stretches out, <1 pinches
in, at each corner. Only works on sprites/images/textures, and
does not change the outline of the shape they are mapped to.
|
GameX.SetDrawWarp (float
w0, float w1, float w2, float w3)
|
Translation
|
Draw the next thing offset by the given translation
offsets which are in pixels, after any 3D projection. This is
really only useful for certain special cases with the 3D drawing
functions.
|
GameX.SetDrawTranslate
(float transX, float transY)
|
Depth
|
Draw all following 2D graphics at a certain Z-depth
until the depth is set again. Intended for drawing 2D objects
in 3D scenes, or for automatic sorting of 2D scenes. "depth" of
0.0 means as close as possible, 10.0 means further away, etc.
|
GameX.SetDrawDepth
(float depth)
|
Camera
|
Use the given CameraX for all following 3D projections
until another camera is set. This mode must be set with a valid
camera object before you use any 3D drawing functions. See "3D
Graphics and the CameraX class" below for more details.
|
GameX.SetDrawCamera
(CameraX* camera)
|
Keep in mind that not all of these affect
all drawing functions. When you understand what a draw state does, it
should be pretty obvious what functions it does or does not apply to.
However, to avoid any possible confusion, here is a table that shows
exactly which drawing functions are affected by which drawing states,
for your reference:
(It's a very wide table, so it has been split into two parts for
viewing.)
|
Draw
Point
|
Draw
Line
|
Draw
Rect
|
Draw
Polygon
|
Fill
Screen
|
Draw
Image
|
Draw
Line
Image
|
Draw
Textured
Rect
|
Draw
Textured
Polygon
|
Draw
Tiled
|
Mode |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Angle |
Yes
|
No
|
Yes
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Scale |
Yes
|
Yes
|
Yes
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Portion |
No
|
No
|
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Destination |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Shading |
No?
|
No?
|
No?
|
No?
|
No?
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Shading Effect |
No
|
No
|
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Warp |
No
|
No
|
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Translation |
Yes
|
Yes
|
Yes
|
Yes
|
No
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
Depth |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Camera |
No
|
No
|
No
|
No
|
No
|
No
|
No
|
No
|
No
|
No
|
|
Draw
Point
3D
|
Draw
Line
3D
|
Draw
Polygon
3D
|
Draw
Image
3D
|
Draw
Line
Image
3D
|
Draw
Textured
Polygon
3D
|
Clear
Screen
|
Blur
Rect, or
Blur
Screen
|
Draw
Text, or
Draw
Outlined
Text
|
Mode |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Angle |
Yes
|
No
|
No
|
Yes
|
Yes
|
No
|
No
|
No
|
No
|
Scale |
Yes
|
Yes
|
No
|
Yes
|
Yes
|
No
|
No
|
No
|
No
|
Portion |
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Destination |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
Shading |
No?
|
No?
|
No?
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Shading Effect |
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Warp |
No
|
No
|
No
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Translation |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Depth |
No
|
No
|
No
|
No
|
No
|
No
|
Yes
|
No
|
No
|
Camera |
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
Yes
|
No
|
No
|
No
|
Note that "No?"
for Shading means that, while SetDrawShading has no effect, such functions
take a ColorX object directly which basically means they are always shaded anyway.
9) 3D Graphics and the CameraX class
Beginning with GameX version 5, you can now draw 3-dimensional scenes
comprised of polygons, or a combination of polygons and sprites. This
is done in a way analogous to drawing 2-dimensional scenes, because
GameX is primarily a 2D engine, which means two things:
- Drawing 3D graphics with GameX is easy and does not require any advanced
knowledge of the mathematics or established methods of 3D graphics.
- 3D graphics in GameX are not as optimized or as flexible as a real/dedicated
3D engine would allow for.
Despite the limitations, it is easy to make 3D graphics in GameX look
quite good as long as you limit them to a reasonable scope. See below
on the issue of feasibility for details.
In GameX, 3D scenes are drawn in about the same way as 2D scenes, with
the following differences:
- There is an extra, 3rd dimension (obviously). So the 3D drawing functions
are just like the 2D ones, but take x, y, AND z.
- There is a camera which you can position in the 3D scene which determines
how the scene becomes projected onto your 2D screen.
- To make things much easier, you can tell GameX to automatically buffer
and sort the scene for you, so it does not matter what order you draw
objects in.
Viewing the 3D Scene:
Before you can draw anything in 3D, you must decide on a viewpoint
in 3D space. GameX provides the CameraX class for this purpose. You
can think of a CameraX object as a "camera" which has a position in
3D space (where you're looking from) and a target location also in 3D
space (where you're looking at or to). For example, to create an overhead
view you could set your camera's "from" position to be over your scene,
and set its "to" position to be on the ground, so that when you draw
objects in the scene, it looks as if the player is up in the air looking
down at the objects that are on the ground. Of course, you can move
the camera during the game to allow the player to see things from different
angles or locations. If the camera stays fixed at one location or angle
throughout most of the game, it becomes more of a 2D game but with 3D
rendering (which is perfectly okay and a good idea for some types of
games; you can use 3D for a 2D game and treat the 3rd dimension as an
optional element to give you more freedom should the need arise to use
it).
Here is some sample code that creates a CameraX, sets it over the scene
looking down, and does all the other set-up required to do 3D drawing:
CameraX cam;
float sky = 100.0;
float ground = 0.0;
void GameInit (void) {
GameX.Initialize ("CamTest", VIDEO_16BIT,
800, 600);
cam.SetWindow(0,
0, 800, 600);
GameX.SetDrawCam(&cam);
}
void GameRun (void)
{
cam.SetFromPosition(Vector3DF(0,
0, sky));
cam.SetToPosition(Vector3DF(0, 0, ground));
}
void GameDraw (void)
{
GameX.Begin3DScene(
);
... {call functions
to draw 3D polygons and/or sprites} ...
GameX.End3DScene(
);
}
|
// Create a CameraX object
// choose a height (z) value of 100 for the sky
// choose a height (z) value of 0 for the ground
// Initialize GameX
// Tells the camera where to let GameX draw (entire
screen)
// Tells GameX to use this camera for drawing - important!
// Set the camera to be up in the sky...
// Looking down at the ground.
// Tell GameX we're about to draw a 3D scene
// (Draw some 3D stuff here)
// Tell GameX we're done drawing the 3D scene
|
You could also use cam.SetFromAngles( ) instead of cam.SetFromPosition(
) to set the "from" position using 2 angles and a distance, which would
make it easy to have the camera spins around a scene or character, and
zoom in and out. Look in the sample CamDemoX code for an example of
this in action. Other useful camera functions include cam.SetFarPlane
(float dist) which allows you to increase or decrease the maximum drawing
distance, and cam.SetRollAngle (float angle) which lets you easily make
the camera "roll" (for instance, turning a camera upside-down) while
looking in the same direction.
Notice the call to the GameX.SetDrawCam( ) function, which you must
call to tell GameX what camera to use for drawing, otherwise it will
just draw the scene from some arbitrary default viewpoint. Make sure
not to let the CameraX object you specify get deleted while GameX is
using it (i.e. you should make the camera a global variable).
Note also the Begin3DScene and End3DScene functions in the above code.
It is not necessary to use these functions, but they makes things much
easier for you because they allow GameX to sort whatever you draw into
the proper order, which is difficult (and sometimes impossible) to do
properly on your own due to the nature of 3D polygon intersections.
If you already know how to sort a scene (if it's simple and/or mostly
uses DRAW_ADD), then don't tell call Begin3DScene/End3DScene because
it takes time for GameX to sort things. In any case, remember that if
you do begin a 3D scene, you must also remember to end the 3D scene
later during that frame or GameX may never actually draw it. You can
also begin and end multiple scenes in a frame, for instance you could
keep 2 separate cameras and draw the scene twice (once for each camera)
into separate halves of the screen (using cam.SetWindow( ) and GameX.SetView(
)), to create a split-screen mode effect in a 3D game.
Drawing the 3D Scene:
Currently, GameX cannot load 3D models from file or automatically draw
higher-level objects than primitives. You could write/use a custom loader
to load in 3D polygon vertices and then loop through drawing those.
However, as mentioned above, GameX is not well-suited for doing this.
Unless your game has complicated 3D needs, you should instead just choose
where to draw various things with some simple math in way similar to
how 2D drawing often works.
Keep in mind that 3D drawing is not limited to polygons; in addition
to 3D polygons (which may be texture-mapped and/or gouraud-shaded),
you can draw sprites at 3D locations, or you can draw 3D lines (which
may also be texture-mapped). You can even draw all of these types of
things in 3D into the same scene, for instance, you could make a game
with polygonal terrain, regular sprites standing on top of that terrain,
and 3D lines used for special effects.
Instead of having separate x, y, and z variables, you should keep track
of 3D positions using the Vector3DF class that GameX provides. You can
create a 3D vector "on-the-fly" by just calling Vector3DF(x,y,z) with
any float values x,y, and z, or you can create a vector, put it in a
Vector3DF variable, and perform vector calculations (see below) on it
until you're satisfied with the position it represents.
Remember that, since 3D drawing is an extension of 2D drawing in GameX,
all of the previous information about 2D drawing applies to 3D drawing.
For instance, you can still use SetDrawMode( ) for special effects,
etc. Refer to the table in the previous section to see exactly which
state setting functions apply.
Drawing Shapes (In 3D):
GameX.DrawPoint3D (ColorX clr, Vector3DF v);
Draws a simple dot of the
given color at the given vertex v (which contains x,y,z coordinates).
To set the dot's size, call
GameX.SetDrawScale(float size_in_world_units) beforehand.
To center the dot if it's
larger than 1 pixel, use the DRAWOP_CENTERED draw option flag.
Example: GameX.DrawPoint3D
(ColorX(255,0,0), Vector3DF(10,20,30)); // draws a red dot at (10,20,30)
units from the origin
GameX.DrawLine3D (ColorX clr, Vector3DF v1, Vector3DF v2);
Draws a line segment of the
given color between the two given 3D vertices.
To set the line's width, call
GameX.SetDrawScale(float width_in_world_units) beforehand.
If clr is a gradient color,
the top-left and top-right are applied to the v2 side of the line, the
other two to the v1 side.
GameX.DrawPolygon3D (ColorX clr, Vector3DF v1, Vector3DF v2, Vector3DF
v3, Vector3DF v4);
Draws a quadrilateral polygon
filled with the given color, with vertices as given.
The coordinates must be given
in a roughly "Z" shape.
The polygon is 1-sided, so
if viewed form the wrong side to form a "backwards-Z", the polygon is
culled and not drawn at all.
To disable culling for this
polygon to make it two-sided, you can use the DRAWOP_NOCULL flag.
The coordinates should technically
all lie in a plane, but it will still usually draw fine even if they
don't.
So, an example of drawing
a green diamond-shaped polygon: GameX.DrawPolygon (ColorX(0,255,0),
Vector3DF(0,0,0), Vector3DF(20,10,15),
Vector3DF(10,20,15), Vector3DF(30,30,30))
To draw a triangular polygon,
repeat v3 for v4, like so: GameX.DrawPolygon (ColorX(0,255,0), Vector3DF(0,0,0),
Vector3DF(20,10,15), Vector3DF(10,20,15),
Vector3DF(10,20,15))
Mapping Images (Textures)
to Shapes (In 3D):
To map an image to a point (a point-sprite):
GameX.DrawImage3D (ImageX* img, Vector3DF v, [bool worldUnitScale=true])
Draws the given sprite image
with bill-boarding positioned in the 3D scene at the given vertex v.
worldUnitScale defaults to
true. If true, the image is automatically made smaller as it gets further
away; if false, the image is drawn at the size of the 2D sprite.
To alter the size the image
is drawn at, call GameX.SetDrawScale( ) beforehand. You may need to
use a large value to make the sprite big enough to see.
Use this function to draw
2D characters into 3D scenes, and to draw most 3D particle effects.
To map an image across a line (a line-sprite):
GameX.DrawLineImage3D (ImageX* img, Vector3DF v1, Vector3DF v2, [bool worldUnitScale=false])
Draws the image with its bottom
at v1, and its top at v2, in 3D.
worldUnitScale defaults to
false. If false, the line's width is chosen to preserve the image's
proportions, otherwise the width equals the scale amount in world units.
To alter the line's width,
call GameX.SetDrawScale( ) beforehand.
This function is useful for
things like pendulums, lightning, oriented sprites, or compound line-based
3D special effects.
For instance, you could draw
a circle textured to a line from the origin to each particle in a particle
system, using DRAW_ADD and shading, for an impressive effect.
To map an image (texture) to a polygon:
GameX.DrawTexturedPolygon3D (ImageX* img, Vector3DF v1, Vector3DF v2,
Vector3DF v3, Vector3DF v4);
Works about the same as GameX.DrawPolygon3D,
see above for details.
Draws a texture-mapped polygon,
good for piecing together landscapes or structures (or anything).
You can use GameX.SetDrawShading
to apply 3D lighting blended across the polygon's vertices.
If you duplicate v4 to make
a triangular texture-mapped polygon, a diagonal half of the texture
will be ignored because textures are rectangular.
The order that you give the
vectors in determines the orientation of the texture. The top-left of
the "Z" is where the top-left of the image goes.
More about CameraX:
You can do more with CameraX objects than simply tell GameX to use
them for 3D projections. Here is the complete list of functions that
CameraX gives that may be useful:
camera.SetToPosition (Vector3DF to);
Sets the camera to look at
the given position vector. This changes the camera's direction, but
does not change its position.
For this and the following
functions, note that you can either set the position/other properties
each frame, or you can set them just once if you don't want your camera
to move.
camera.SetFromPosition (Vector3DF from);
Sets the camera to be positioned
at the given position vector.
camera.SetFromAngles (Vector3DF angs);
Sets the camera to be positioned
at a given x angle (rotation), y angle (pitch), and z distance (zoom)
away from its current "To" position.
To be used as an alternative
to SetFromPosition, after calling SetToPosition. Great for making the
camera follow or circle around something (or both).
angs.x is the x rotation angle
you want (in degrees), angs.y is the y rotation angle (in degrees),
and angs.z is the distance from the target (in world units).
The y rotation angle you give
MUST be between -89.9 and +89.9 degrees, or you will get strange results.
The x rotation angle, however,
wraps around as you would expect and so it can be any degree value.
camera.SetRollAngle (float angle);
Sets the camera to "roll"
or "twist", i.e. rotate along the axis that it is facing.
This function should be called
before SetFromAngles or SetFromPosition is called.
camera.SetUpVector (Vector3DF up);
Sets which way is "up". The
default is positive Z, but if you want to make the coordinate system
more like 2D,
you can make negative Y "up"
instead by calling camera.SetUpVector(Vector3DF(0,-1,0)).
camera.SetWindow (int x1, int y1, int x2, int y2);
Sets the rectangular region
for this camera to project to, and also sets where 3D graphics will
be clipped to.
camera.GetWindow (int&x1, int&y1, int&x2, int&y2);
Fills the variables you give
with the window that this camera is currently set to.
camera.Project (Vector3DF v, float &x, float &y, float &z);
This is called automatically
by the 3D drawing functions, but you can call it to manually project
a 3D point.
Given the 3D vector v, this
function fills x and y with the 2D screen position that projects to,
and z with the relative z-depth.
camera.GetDirVector( )
Returns a Vector3DF of length
1 that points in the same direction that the camera is facing (pointing
"into" the screen).
This is different depending
on which direction the camera is facing.
camera.GetSideVector( )
Returns a Vector3DF of length
1 that points "right" from the point of view of the camera (points exactly
toward the right side of the screen).
This is different depending
on which direction the camera is facing.
If the camera is rolling,
however, the roll is not taken into account.
camera.GetPixelsPerUnit( );
Returns a float value that
tells you the number of pixels that one world unit is currently equivalent
to.
camera.From( );
Returns a Vector3DF that is
the current location of the camera.
camera.To( )
Returns a Vector3DF that is
where the camera is currently looking at.
More about Vector3DF:
It is possible to just use
the Vector3DF as a holder of x, y, and z coordinates for use with the
3D drawing functions, but the Vector3DF class also allows you to perform
some very useful mathematical operations easily and efficiently.
vector.Cross (const Vector3DF v);
Returns a Vector3DF that is
the cross product between this vector and the vector v.
This function also changes
this vector to be equal to the resulting cross product, although it
does not change the other vector v.
This function is very useful,
essential for things such as 3D lighting, and so is highly optimized
(this applies to the following functions also).
vector.Dot (const Vector3DF v);
Returns a float value that
is the dot product of this vector with the vector v.
This function does not change
either vector, unlike Cross( ).
vector.Normalize( );
Normalizes this vector to
have a length of 1.0 while still facing in the same direction.
vector.Length( );
Returns a float value that
is the Pythagorean length of this vector.
vector.Dist (const Vector3DF v);
Returns a float value that
is the Pythagorean distance between this vector and the vector v.
vector.LengthSq( );
Returns a float value that
is the square of the Pythagorean length of this vector.
This function takes less time
to compute than Length because it doesn't have to take a square root.
vector.DistSq (const Vector3DF v);
Returns a float value that
is the square of the Pythagorean distance between this vector and the
vector v.
This function takes less time
to compute than Dist because it doesn't have to take a square root.
Also, Vector3DF overloads many operators, so you can add and subtract
Vector3DF objects simply by using the + and - operators, or you can
scale a Vector3DF by multiplying or dividing it by a floating point
value using the * or / operators, or you can assign the values of one
Vector3DF to another by simply using the = assignment operator. Other
operators that work are +=, -=, *=, and /=. If you use * or *= to multiply
two Vector3DFs together, it is assumed to mean Dot Product multiplication,
so you must call the Cross function if you want to do a cross product
instead.
As a quick example, here is a good use of the += operator:
position += velocity; // position and velocity are each Vector3DF
objects
This will advance the position vector by a velocity vector, a nice shortcut
for doing: position.x += velocity.x; position.y += velocity.y; position.z
+= velocity.z;
Usually, you would actually make a class that has objects with position
and velocity, and once in GameRun for each object, do something like:
myChar.pos += myChar.vel;
Also, to add a drag to the character's speed so it gradually slows down,
you could also then call:
myChar.vel *= drag; // drag is a float, usually between 0.8 and
0.99
Finally, there are other types of vectors besides 3DF ones. All of
the vector types currently supported are: Vector2DI, Vector2DF, Vector3DI,
Vector3DF, and Vector4DF. The number means the number of dimensions
the vector has, and the letter at the end means the type (Int or Float) used to represent
each coordinate. (A 2D vector only has x and y, a 3D vector has x, y,
and z, and a 4D vector has x, y, z, and w coordinates.) All of these
vector types support all of the above functions, so for instance you
may find Vector2DF very useful in 2D games for making your code simpler
by calculating things like lengths, distances, normalizations, additions,
multiplications, etc. for you.
The Issue of Visibility:
When you draw something in 2D, it's pretty clear whether
it will end up on the screen or not, but in 3D, it all depends on the
camera position and angle. If you try drawing something in 3D (such
as a 3D polygon) and you don't see anything draw, it could be due to
one (or more) of the following reasons:
- You forgot to set the camera with GameX.SetDrawCam.
- The camera is looking at the wrong side of the polygon. If you want
to change which side of a polygon is visible, you have to change the
order that you give the vectors in the draw function.
- The camera is looking away from where the polygon
is, i.e. the polygon is behind you.
- The polygon is too far to the side or above or below
the camera.
- The camera is too far away (in which case try increasing the far plane
distance of the camera).
- If drawing a point or sprite, the scale of your scene may be so large
that you need to scale up your points/sprites by a factor of 10 or 100
just to see them.
- If only half of a polygon draws, then you forgot
to specify the vectors in a "Z" shape, in which case switch around vectors
3 and 4 to fix it.
For the above reasons, it is often easier to begin
with something that works and displays visible results, such as CamDemoX,
and modify or build off of that code for your 3D drawing.
There is currently no easy or efficient way to test
beforehand if the image will be visible before drawing it, so you should
just draw all the images/polygons in your scene and let GameX handle
skipping drawing the ones that are not visible at the time. This applies
to 2D as well, although in both 2D and 3D, don't draw anything that
you know for a fact can't be visible at the moment, since it still takes
some processing time for GameX to figure out that it isn't visible to
avoid drawing it. (Manually testing for 3D visibility is practical if
you use it to eliminate a bunch of drawing calls based on a test of
a single point's visibility, which you can do by calling camera.Project
and comparing the resulting x and y with the game screen's boundary
coordinates, offset by a certain amount because of the approximation
of using a point.)
The Issue
of Feasibility:
You may be tempted to make a fully three-dimensional game using GameX's
3D drawing functions. Not only does it take a lot more creative talent
to make a 3D game than you might realize, but GameX's current support
of 3D graphics is not advanced or optimized enough for a 3D game that
is not relatively simple to be feasible. This is not to discourage you
from making a 3D game, but to advise you to keep the graphics simple.
Do not attempt to draw vast landscapes populated by characters composed
of thousands of polygons each. In fact, if you intend your game to run
smoothly on current-generation computers, a practical limit is between
200 and 2000 total polygons/sprites/lines drawn per frame. (This is
obviously an area of GameX that is of top priority for future improvement.)
Thus, at the moment, here are the 3 situations in which
GameX's 3D support can be
put to practical use:
- 2D games: Believe it or not, a pinch of 3D graphics
are easy to add into a 2D game to make it look a lot better.
- Semi-3D games: For instance, a game with some 3D
terrain and 2D sprite characters standing on it.
- Simple 3D games: Fully-3D games that can maintain a very low polygon
count.
Particle
Effects:
If for nothing else, you are advised to use the 3D
support to draw particle effects, because particle effects in particular
can be made to look extremely impressive with minimal effort. Just make
one "particle" image, such as a blurry white circle on a black background,
then draw it lots of times at slightly varying positions using a special
drawing mode such as DRAW_ADD, and you have the basic groundwork for
good particle effects. This works in 2D also, but is easier to make
look good in 3D because you have an extra dimension to move the particles
in. See the ParticleDemoX demo for a nice example of particle effects
in 3D.
10) Painting in and Filtering Images
Painting or drawing in ImageX objects is useful if you wish to modify
an ImageX object while a game or program is running, or if you wish
to generate an image programmatically. For instance, you could load
in a character image from file, copy that image into another ImageX,
then use a built-in image filter to switch its colors around so you
can use each image for drawing characters with different colors. Or,
you could dynamically generate a wide variety of possible images from
scratch for use in your game, which could not be done simply by loading
in images. In addition to modifying the image, you can use the image-painting
related functions to read the color values from the image, which
lets you do things like loading level data stored inside pictures. You
can also combine reading and writing in a way of your choice to apply
complex custom filters to your images before you draw them, and you
can even save edited GameX images as image files, meaning it's actually
possible to write a GameX "game" which is really a complete image painting
and processing application program (which, no doubt, would be used to
draw and save images for use in another game). Admittedly, most games
don't really need to save images, but regardless, being able to edit,
read, and filter images is extremely useful for a wide variety of features
that many games would (and frequently do) implement, such as color swapping,
active motion blur, and level map loading. (Storing your levels as pictures
is not appropriate for many types of games, however; see (22) "Loading
and Saving Files in GameX" below.)
There are three ways of drawing into images:
1: Using ImageX's built-in functions or filters to change the image
2: Using GameX's pixel getting and setting functions to change the image
with pixel-level control
3: Using GameX's image and shape rendering functions to draw into the
image
These are listed in slowest-to-fastest order, but all three of these
ways of drawing into images are pretty slow because none of them are
accelerated by hardware. Because of this, if you draw into images, you
should try to only do it once or occasionally, not every frame. (Yes,
motion and radial blur normally rely on drawing into an image every
frame, but that is precisely why these effects are so slow.)
Creating Images
Before you draw into an image, you have to either load it from file
(which has already been covered, see "Loading Images" of section 11
above), or you can create a blank image instead without needing to load
in an image file. You can create a blank image (all pixel values start
at 0) with the following method:
image.Create(int xs, int ys, [bool alpha=false]) // alpha is optional
image (the calling object)
is an ImageX that you have already defined but have not yet loaded anything
into.
xs and ys tell GameX
how big to make the image, i.e. the width and height of the image in
pixels. This is necessary only when creating an image, because when
you load an image, GameX gets the image size automatically from the
file it loads.
alpha is for setting whether
the image is to be created with an alpha (transparency) layer. Pass
in "true" to create the image with alpha. See the "Loading Images" section
above for more about what this means.
Once you have an image, either loaded from file or created from scratch,
you can begin drawing into it:
Modifying the ImageX at the
Pixel Level:
Here are the basic ImageX functions you can use for getting or setting
pixels:
image.SetPixel (int x, int y, int r, int g, int b, [int a]) // r, g,
b, a are from 0 to 255
Sets one pixel of the
image to the given RGB color values. If the image has an alpha channel,
you can specify an alpha value as well (if you don't, the alpha is assumed
to be 255).
image.GetPixel (int x, int y, int &r, int &g, int &b, [int
&a]) // gives you r, g, b, a from 0 to 255
Reads in the RGB color
value from a pixel of the image, and copies these values into the variables
you pass in. If the image has an alpha channel, you can also give a
variable that takes the alpha value if you want to know what it is.
image.GetWidth( )
image.GetHeight( )
Returns the width or
height of the image in pixels (as an integer). If you want to use SetPixel
and GetPixel to modify an entire image, you need to iterate through
the entire width and height of the image to change the pixel at each
location.
It should be noted that SetPixel and GetPixel and fairly slow, and
that there is another way to manipulate the pixels of an image which
is somewhat faster, but less friendly:
Basically, just call GameX.AccessPixels(&image) immediately before
you start modifying pixels, replace image.SetPixel(…) with GameX.DrawPixel(…),
replace image.GetPixel(…) with GameX.ReadPixel(…), and call
GameX.EndPixelAccess( ) as soon as you are done modifying the pixels
of "image". You can only access the pixels of one image at a time in
this way, though, and you must not use any other image or drawing functions
or do anything else while the image's pixels are being accessed.
You can also pass in the VIEWPORT constant instead of &image when
you call AccessPixels if you want to draw directly to (or read from)
the screen in this way, but frankly it is almost without exception a
bad idea to draw individual pixels directly to the screen, because
whatever you draw only stays there for one frame and it takes such a
long time to draw it.
Warning: Unlike SetPixel
and GetPixel and the rest of GameX’s functions, DrawPixel and
ReadPixel are not automatically clipped. Thus, your game will probably
crash if you accidentally draw outside of the image boundaries with
these functions. SetPixel( ) and GetPixel( ) are much safer to use if
you think there's any chance you'll try to draw anything out of the
image's boundaries.
Applying Basic Filters to
an ImageX:
The ImageX class has a few methods that you can use to apply standard
modifications to the entire image, such as making it darker or changing
its colors around. Note that you can easily make your own custom image
filters like these by using the basic pixel getting and setting methods
above, although these filters make use of the AccessPixels trick described
above for extra speed. (In the following, "image" is an ImageX object,
of course.)
image.SetFilterRect (int xLeft, int yTop, int xRight, int yBottom)
Tells the following
filters to only filter this part of the image instead of the entire
image. Use image.ResetFilterRect( ) to go back to filtering the whole
image.
image.Fill (int x, int y, int r, int g, int b, [int a]) // r, g, b,
a are from 0 to 255
Fills the image with
the given RGB color (one pixel at a time). If the image has an alpha
channel, you can specify an alpha value to set each pixel to as well,
otherwise the alpha is assumed to be 255. This function always overwrites
the entire (filter rect of the) image, including alpha/transparency.
image.Invert( )
Inverts the RGB color
values of the image, so black becomes white, blue becomes yellow, gray
becomes gray, etc. If the image has an alpha channel, the alpha at each
pixel stays the same as it was before.
image.ChangeBrightness (int brightness, float contrast) // brightness
from -255 to 255 (0 is normal), contrast is a factor (1.0 is normal)
Changes the brightness
and contrast of the image. Can be used to intensify the image, or make
it faded, etc. For instance, image.ChangeBrightness(0,2.0f) will double
the image's contrast, while image.ChangeBrightness(-64,0.5f) will make
the image darker and reduce its contrast. Note that negative contrast
results in image color inversion. This filter will not change the image's
alpha channel.
image.ChangeBrightnesses (int brightness_r, int brightness_g, int brightness_b,
int brightness_a, // from -255 to 255, 0 is normal
float contrast_r, float contrast_g, float contrast_b, float contrast_a)
// factors, 1.0 is normal (no change)
Changes the brightness
and contrast of the image like the previous function, but gives individual
control over each color channel, including the alpha channel, and so
can do more things such as increase only the red contrast, subtract
green from the image, sharpen the alpha channel of the image, invert
an individual color channel, etc. If the image has no alpha channel,
you must still supply values for brightness_a and contrast_a although
these values will be ignored.
image.ConvertToGrayscale( )
Converts the image
to grayscale (red=green=blue) while maintaining perceived luminosity.
This filter will never change the image's alpha channel.
image.ChangeSaturation (float saturation) // saturation is a factor,
1.0 means no change
Changes the saturation
of the image's colors. Saturation basically means "lack of grayness".
For instance, calling image.ChangeSaturation(2.0f) makes the image's
colors stand out more, whereas image.ChangeSaturation(0.5f) makes the
image go halfway to grayscale, and image.ChangeSaturation(0.0f) does
exactly the same thing as calling image.ConvertToGrayscale( ).This filter
will never change the image's alpha channel.
image.ChangeSaturations (float saturation_r, float saturation_g, float
saturation_b) // factors, 1.0 means no change
Changes the saturation
of the image's colors as above, except it merges or divides the color
channels using a separate factor for each channel. You can use this
to do things like intensify the red hues of an image or get rid of the
blue from an image while keeping the image at approximately the same
overall brightness. This filter will never change the image's alpha
channel.
image.SetChannel (int channel, int value) // value is out of 255, channel
is 0 for red, 1 for green, 2 for blue, 3 for alpha
Sets all of one channel
(red, green, blue, or alpha) of the image to one value. For instance,
image.SetChannel(0,255) sets all of the image's red channel to full
brightness, and image.SetChannel(3,128) sets the entire alpha channel
to 50% transparency if the image has an alpha channel.
image.AddNoise (int noise) // noise is a value from 0 to 255, 0 means
no change
Adds random noise to
the image's pixels. Useful if you want to make an image's colors appear
less uniform (rougher/grainier). This filter will never change the image's
alpha channel. If you specify a negative noise value, that means to
only let the noise make the image darker (always negative), otherwise
the noise can be positive or negative.
image.AddNoise (int noise_r, int noise_g, int noise_b, [int noise_a])
// values from 0 to 255, 0 means no change
Adds noise to the image's
pixels, with separate noise intensities for each color channel. This
filter will change the image's alpha channel if a nonzero fourth argument
(noise_a) is given.
image.GaussianBlur (float radius) // radius is blur radius in pixels,
0.0 means no blur, 1.5 means some blur, etc.
Blurs the image smoothly
via a Gaussian blur of all of the image's channels, including alpha
if it has an alpha channel. The operation takes longer as the blur radius
increases and as the image size increases. If the operation would take
too long, the blur radius is reduced to prevent the filter from ever
taking more than a second or two to process, which is more likely to
happen on larger images.
image.GaussianBlur (float red_radius, float green_radius, float blue_radius,
[float alpha_radius]) // alpha_radius is optional
Blurs the image via
a Gaussian blur of each of the image's channels, with a possibly separate
blur radius for each channel. If alpha_radius is not specified, then
the alpha channel of the image is not modified. The time taken by this
operation increases with the maximum of the blur radii given, and also
with the image size, but is limited as with the above function.
image.SwapColors (ColorSwapType type)
Rotates or swaps the
color values of the image. This can be used for a basic "color swapping"
effect on images, which is used by many games to reduce required artwork.
This filter will not change the image's alpha channel. The results of
the filter depend on what colors are initially in the image; color-swapping
a grayscale image will do nothing.
type is a variable of type ColorSwapType, which is really
just an int intended to hold one of the GameX
constants specific to this function, which are:
Type Constant:
|
Effect:
|
COLOR_SWAP_SAME
|
Reds->Reds,
Greens->Greens, Blues->Blues
|
COLOR_SWAP_ROTATE120
|
Reds->Greens,
Greens->Blues, Blues->Reds
|
COLOR_SWAP_ROTATE240
|
Reds->Blues,
Greens->Reds, Blues->Greens
|
COLOR_SWAP_REDGREEN
|
Reds
<-> Greens
|
COLOR_SWAP_REDBLUE
|
Blues
<-> Reds
|
COLOR_SWAP_GREENBLUE
|
Greens
<-> Blues
|
COLOR_SWAP_BA
|
ROYGBIV
-> RVIBGYO
|
COLOR_SWAP_NBA
|
ROYGBIV
-> GBIVROY
|
COLOR_SWAP_BNA
|
ROYGBIV
-> VROYGBI
|
COLOR_SWAP_NBNA
|
ROYGBIV
-> BGYORVI
|
COLOR_SWAP_ANB
|
ROYGBIV
-> VIBGYOR
|
COLOR_SWAP_NAB
|
ROYGBIV
-> GYORVIB
|
COLOR_SWAP_NANB
|
ROYGBIV
-> BIVROYG
|
COLOR_SWAP_BB
|
ROYGBIV
-> RRRRVIB
|
COLOR_SWAP_NBB
|
ROYGBIV
-> GGGGYIV
|
COLOR_SWAP_BNB
|
ROYGBIV
-> VVVVIYG
|
COLOR_SWAP_NBNB
|
ROYGBIV
-> BBBBVIR
|
COLOR_SWAP_AA
|
ROYGBIV
-> RVIBIVR
|
COLOR_SWAP_NAA
|
ROYGBIV
-> GYIVIYG
|
COLOR_SWAP_ANA
|
ROYGBIV
-> VIYGYIV
|
COLOR_SWAP_NANA
|
ROYGBIV
-> BIVRVIB
|
Of course, ROYGBIV means Red, Orange,
Yellow, Green, Blue, Indigo, Violet, in that order. As used in the table
above, each of these letters refers to a general range of colors that
are similar to the given color. The words "Red", "Green", and "Blue",
on the other hand, refer specifically to a color channel of the image.
That's all you need
to know to use SwapColors, just make a call like: shipImage.SwapColors
(COLOR_SWAP_REDBLUE) and the colors will be changed, ready to be drawn
as an image which has the same shape and brightness as the original,
but different coloring. This is different than shading a drawing of
an image with SetDrawShading, because this actually changes what color
the image is, whereas SetDrawShading simply tints the image and does
not change its pixels.
image.ChangeColors(ColorSetType
a_settype, ColorSetType b_settype, [float a_set_mult, float b_set_mult,
float a_app_alpha, float b_app_alpha, ColorSetCondition a_acond, ColorSetCondition
a_bcond, ColorSetCondition b_acond, ColorSetCondition b_bcond]) // parameters
in brackets are optional
Lets you change the colors of an image in
just about any way imaginable. However, SwapColors is simpler to use,
so it is recommended you use that instead if you are a beginner programmer.
If all of the parameters scare you, keep in mind that you are not required
to give most of them, just the first two. Anyway: a_settype is what
to set the red/green range to, b_settype is what to set the blue/yellow
range to. a_app_alpha and b_app_alpha are values between 0.0 and 1.0
for how much to set the given color ranges; it does not affect the image's
alpha, just how strongly the color change operation is applied. a_acond
and a_bcond are the conditions that must apply to the source a and b
in order for the application to the a range to proceed, and b_acond
and b_bcond are the conditions that must apply to the source a and b
in order for the application to the b range to proceed. See the GameX
header file "gamex-defines.hpp" for the list of ColorSetTypes and ColorSetConditions.
This function can do things such as target a specific range of colors,
like a character's hair or a character's shirt, and changing the color
from blonde to green, or green to blue, blue to gold, etc.
Remember that the above image filters permanently change the image
that's in memory. However, none of these image filters (or anything
else GameX normally does) can actually change the image files on your
hard drive.
Rendering into Images
Another useful way to modify images is to treat the image like the
game screen and draw shapes, other images, or whatever else you want
right into the image, using the regular GameX drawing functions. This
method can achieve some effects easily that would be difficult or time-consuming
to do by drawing one pixel at a time.
To render into the image, you simply precede any regular drawing function
with the call:
GameX.SetDrawDestination(&image);
Where "image" is the image that you want to draw into. When you next
call a function like DrawImage or DrawRect, GameX will draw into "image"
instead of the screen. After drawing something, GameX resets to drawing
to the screen, so you have to call SetDrawDestination once for each
graphic you render into an image.
Note that rendering into images is significantly slower than rendering
normally to the screen, although often faster than setting the image's
pixels individually.
One very useful thing to be able to do when rendering images is to
render the game screen itself into an image. To do this, you first call
SetDrawDestination to target the image, then you can call GameX.DrawImage
(VIEWPORT,0,0) to copy the viewport into the image. This is assuming
the image is the same size as the viewport; if not, you can use GameX.DrawTexturedRect
to specify a specific rectangle of the image to copy into, and/or you
can use GameX.SetDrawPart to specify a specific rectangle of the game
screen to copy out of. Effects made possible by rendering from the screen
to an image include: Motion blur, radial blur, rotational fadeout transitions,
and feedback mirror effects. It can also be used for caching of the
image screen for use as a background or other element in another part
of the game (for instance, copying the game screen as a static background
for the menu screen to improve efficiency).
Painting into Images Example:
Here is a example which manipulates generated images using all three
of the above general ways of modifying images. This example paints a
circle into a new image A by drawing individual pixels, then uses the
DrawImage function to copy image A into another image B (although ImageX::CopyTo
could also be used for this step), and finally changes the colors of
image B by applying a filter to it. (After doing this in GameInit, it
simply draws the two images next to each other each frame.)
#define GAMEX_MAIN
#pragma comment (lib, "gamex.lib")
#include "gamex.hpp"
ImageX img_a, img_b;
void GameInit (void) {
GameX.Initialize
("ImgPaintTest", VIDEO_16BIT, 800, 600);
img_a.Create
(100, 100);
img_b.Create
(100, 100);
for
(float ang = 0 ; ang < 360 ; ang ++) {
int
x = 50+(cos(ang*DEGtoRAD)*40.0);
int
y = 50+(sin(ang*DEGtoRAD)*40.0);
img_a.SetPixel(x,
y, 255, 0, 0);
}
GameX.SetDrawDestination(&img_b);
GameX.DrawImage(&img_a,
0,0);
img_b.ChangeColors(COLOR_SWAP_ROTATE120);
}
void GameRun (void) {}
void GameDraw (void)
{
GameX.ClearScreen(
);
GameX.DrawImage
(&img_a, 0, 0);
GameX.DrawImage
(&img_b, 150, 0);
}
|
// Required defines, etc.
// Make images A and B
// Initialize GameX
// Construct a blank 100x100 image
// Construct another blank 100x100 image
// Draw a circle outline into image A:
// For each degree out of 360, draw a pixel
// 50 is the center value, 40.0 is the circle radius
// Circle color is (255, 0, 0) - bright red.
// Now copy image A into image B
// And switch around image B's colors
// Do nothing here; it's just a test demo that
draws two images
// Clear display (draw black background)
// Draw image A at top left of window (should be a red circle)
// Draw image B next to it (should be a green circle).
|
This example is a complete GameX program that really compiles and works,
so feel free to try it out.
11) Loading
and Playing Sounds - The SoundX class
GameX provides sound capability as well as graphics, via a SoundX class
which stores playable WAV sound clips for your games. Sounds are not
anywhere near as necessary as graphics are for most games, but if you
don't add sound and/or music to your game, it will seem unfinished no
matter what else your game does.
Like images, sounds are usually preloaded in the GameInit( ) function,
although they are played in GameRun( ) instead of GameDraw( ) because
you can't draw sounds. Here is an example.
SoundX kick, punch;
int status = 0;
void GameInit (void)
{
GameX.Initialize ("SoundTest", VIDEO_16BIT,
800, 600);
kick.Load ("kick.wav");
punch.Load ("punch.wav");
}
void GameRun (void)
{
... {check input here and set status
accordingly} ...
if (status==1) GameX.PlaySound
(&kick, PLAY_REWIND)
if (status==2) GameX.PlaySound (&punch,
PLAY_REWIND)
}
void GameDraw (void)
{
... {draw graphics
here} ...
}
|
// Create a sound object for 'kick' and another
for 'punch'
// Status of the 'player' or some other character
// Required void GameInit (void) function
// Initialize GameX
// Load the 'kick' sound into memory.
// Load the 'punch' sound into memory.
// Required GameRun function
// At appropriate time, play the 'kick' sound.
// At appropriate time, play the 'punch' sound.
// Required GameDraw function
// Graphics drawing section. (Do not try to
// start sounds inside the graphics section.)
|
Unlike images, which should be drawn each frame for continuous animation,
you will usually begin playing sound effects at certain times, such
as when your character gets hit, and GameX will take care of finishing
playing the sound over multiple frames without you needing to keep track
of the sound's progress. Sounds are played asynchronously, meaning your
game can keep going while any number of sounds are playing; the game
will never pause and wait for a sound to finish unless you tell it to.
As illustrated by the above code, to load a sound from a WAV file you
just make a call like so: sound.Load ("mysound.wav"). The sound file
you load can be a .wav file of any standard PCM format. Please use 8-bit
or 16-bit WAVs. 4-bit WAVs are not a valid PCM format, and 24- or 32-bit
WAVs are a waste of memory.
Once you have loaded a sound, it is ready to play at any time. GameX
provides several functions which let you control the playing of sounds.
These are as follows:
GameX.PlaySound (SoundX* snd, SoundPlayMode mode)
Starts playing the sound object 'snd'. The second parameter 'mode'
determines how the sound will be played. The options for 'mode' are:
Sound Playing Mode:
|
Resulting effect:
|
PLAY_CONTINUE
|
If it's already playing, let the sound continue
playing from where it is.
|
PLAY_REWIND
|
Start the sound over if it's already playing.
|
PLAY_LOOP
|
Set the sound to automatically rewind when it
finishes.
|
PLAY_NOTIFLESSVOL
|
If the sound is currently playing, don't start
over unless it's as loud or louder now.
|
(The most common value to use for 'mode' is PLAY_REWIND.)
Using the above PlaySound function to play sounds, the sound always
plays at maximum volume, normal panning, and normal frequency. If you
want to change these too, you can call the function like this:
GameX.PlaySound (SoundX* snd, SoundPlayMode mode, float volume, float
pan, float frequency)
This is the same as the simpler version above, however, it also provides
volume, pan, and frequency control for the sound.
volume is in the range of
0 .0 to 1.0, with 1 being maximum volume and 0 being total silence.
pan is in the range of -1.0
to 1.0 where -1 plays the sound entirely out of the left speaker, 1
plays the sound entirely out of the right speaker, and 0 plays the sound
the same from both speakers. If outside of this -1 to 1 range, then
the volume starts getting scaled down also.
frequency is a factor in
the range of 0.5 to 2.0, with 0.5 as half pitch and double length, and
2.0 as double pitch and half length, and 1.0 meaning normal frequency.
This basically acts as a multiple of the frequency the sound was recorded
at.
Using these parameters, you can create complex effects even with very
simple sound clips. For instance, you can play a sound in pseudo-3D
by making the volume lower if a sound source is further away, adjusting
the pan depending on which side of the player or the screen the sound
source is on, and even adjusting the pitch depending on the relative
velocity of the sound source (for a Doppler effect). You don't have
to do anything that complicated, of course; you can also use these parameters
if you just want to make a certain sound effect a little quieter or
a little lower pitched. In any case, there are a few more functions
besides PlaySound that you may find useful as well:
GameX.StopSound (SoundX* snd)
Immediately stops playing of the sound 'snd'. If the sound has already
stopped, then this function does nothing.
GameX.RewindSound (SoundX* snd)
Instructs GameX to immediately rewind the sound 'snd' back to the beginning
of the clip. This can be useful if you wish a sound to normally play
continuously, while occasionally rewinding the sound clip based on some
variable or change in the program or game. It basically has the same
effect as playing the sound again with the PLAY_REWIND flag.
GameX.IsSoundPlaying (SoundX* snd)
Returns true if 'snd' is still playing or looping, false otherwise.
Using the above functions, multiple sounds can be started simultaneously,
and will be automatically mixed in real-time by GameX. Although GameX
will take care of the details, it is a good idea to keep in mind the
fact that starting and stopping sounds of different lengths will cause
mixing of sounds to occur even if they are not started simultaneously.
For example, if you have a very long 'rain' sound 10 seconds long, and
a brief 'thunder' sound 1/2 second long, it is possible to start the
rain sound, wait 3 seconds, then start playing thunder. When the thunder
sound starts, both rain and thunder will be present for 1/2 second,
then the rain sound will continue until the full 10 second length of
the clip is finished.
NOTE: If you want a sound to play over itself (if you want two instances
of the same sound playing at once), then you need to load the sound
twice, into two separate SoundX objects, for the sounds to play independently.
12) Music
and the MusicX class
GameX also has a MusicX class which you can use to hold MP3, WMA, or
Standard MIDI music files in the same way that SoundX objects hold WAV
sound files.
MIDI music is useful for many games because
MIDI files are extremely small, thus you can
have lots of background music that can be as long as you want and it
will barely take up any memory at all. The drawback to MIDI
is that it restricts music to using a certain set of instruments, so
MIDI music can't contain custom instruments,
sound effects, or vocals. So, you may choose to use MP3 or WMA music
in your game, formats similar to each other which are capable of holding
compressed digital audio without such limitations, at the expense of
taking longer to load and using a lot more memory than MIDI.
Here is some sample code that loads a MIDI file
and immediately begins playing it:
MusicX music;
void GameInit (void)
{
GameX.Initialize ("MusicTest", VIDEO_16BIT,
800, 600);
music.Load ("musicfile.mid");
GameX.PlayMusic
(&music);
}
void GameRun (void) { ... }
void GameDraw (void) { ... }
|
// Create a MusicX object
// Required void GameInit (void) function
// Initialize GameX
// Load a music file into memory.
// Start playing it immediately
// Required GameRun function (not shown)
// Required GameDraw function (not shown)
|
(Loading an MP3 is exactly the same; just change music.Load ("musicfile.mid")
to music.Load ("musicfile.mp3"), assuming you actually have an MP3 file
named "musicfile.mp3" in the main directory of your game. The same goes
for loading WMA files, just use the ".wma" filename extension.)
The above sample code will cause the music to load and begin playing
as soon as GameX is done initializing, and the music will continue looping
forever while the game is going unless you later tell the music to stop,
or begin playing a different song. You can play sound effects while
the music is going, but you cannot mix two different music files together;
GameX can only play one music file at a time. (In most cases you would
not want to play multiple music files at the same time anyway, so this
is not too much of a limitation.)
The PlayMusic function can take more parameters than is shown in the
above example, if you want greater control over how your music files
are played. Here is the full function:
GameX.PlayMusic (MusicX* music, [int times, float volume, float fade_in_seconds,
float tempo, float pitch]);
This function starts playing
some music, after stopping any other music that was playing. The parameters
are:
music is a pointer to the
MusicX object you want to play (must be already loaded).
(The following parameters
are optional):
times is the number of times
to play the music before stopping; 0 means to never stop looping it,
which is the default.
volume is what volume to
play the song at, 0.0 for silent, 1.0 for full volume (default), 1.27
for maximally amplified volume.
(The following parameters
are optional and have no effect on MP3 or WMA music, they only work
with MIDI):
fade_in_seconds is how long
to "fade in" the music from zero volume so the song doesn't start up
too abruptly.
tempo determines the spacing
between notes. 0.5 makes the song play at half speed, 2.0 for double
speed, etc.
pitch determines the pitch
modulation of each note. 0.5 makes the song play at half pitch, 2.0
for double pitch.
Once you start playing some music, there are several more functions
you can use to monitor and control its playback:
GameX.IsMusicPlaying(MusicX * music);
Returns true if the music
is currently playing or looping.
GameX.StopMusic (MusicX * music, [float seconds]); // seconds is optional
Stops the music from playing,
after smoothly fading out the volume for a certain number of seconds
if given. (Stop is abrupt unless song is MIDI.)
GameX.PauseMusic (void);
Pauses the music and remembers
its location.
GameX.ResumeMusic (void);
Resumes a paused song, if
nothing else music-related was done after it was paused.
GameX.VolumeFadeMusic(MusicX * music, float volume_factor, [float seconds]);
// seconds is optional
Fades the music volume smoothly
up or down to the given volume factor, over a certain number of seconds
if given. (Transition is abrupt unless song is MIDI.)
GameX.PitchBendMusic(MusicX * music, float pitch_factor, [float seconds]);
// seconds is optional
Bends the pitch of the music
smoothly to a given pitch factor, over a certain number of seconds if
given. (Only works with MIDI.)
GameX.SetMusicTempo(float tempo_factor);
Changes the music's tempo
of play. (Only works with MIDI.)
13) Keyboard
Input in GameX
A vital part of any game is the input, without which the game cannot
be interactive.
Naturally, GameX provides functions to easily access user input for
your game. Like all GameX function, these can be used at any point in
your program (or within your classes), by accessing the global GameX
object. Input can be checked for just about anywhere within your game
loop, but the best place to do it is at the beginning of GameRun( ).
(It is usually not a good idea to check for input inside of GameDraw(
).) Keep in mind that all of the input functions are extremely fast,
so you will almost certainly never reduce your game's performance by
checking for input.
The Keyboard Input functions are:
Checking Whether a Key is
Down:
bool IsKeyDown (KeyID k)
IsKeyDown tells you whether or not a specific key is currently being
held down. You pass a KeyID constant to this function which represents
a key on the keyboard, and this function will return true if the given
key is down, or false if the given key is not down. Multiple keys can
be pressed at the same time, so you can check several different keys
at different points in your code as needed. A KeyID is a GameX-defined
constant that represents a physical key on the keyboard, such as KEY_SPACE
or KEY_LEFT or KEY_A or KEY_ESCAPE or KEY_ENTER. These (and many more)
key constants are defined in gamex-defines.hpp (there are too many keys
on the keyboard to list them all here), but usually you won't need to
look them up since they all have common-sense names starting with "KEY_"
and ending with the name of the keyboard key.
KeyIDs are not combinable; to check if the user is holding A and B at
the same time you would have to do:
if(GameX.IsKeyDown(KEY_A)
|| GameX.IsKeyDown(KEY_B))
{ /* insert code here to
react to keys A and B being pressed or held simultaneously */ }
Here is a short example of using IsKeyDown to detect multiple keypresses.
ImageX player_img;
int x = 50, y = 50;
void GameInit (void) {... }
void GameRun (void)
{
if (GameX.IsKeyDown(KEY_UP)) y -=
10;
if (GameX.IsKeyDown(KEY_DOWN)) y
+= 10;
if (GameX.IsKeyDown(KEY_LEFT)) x
-= 10;
if (GameX.IsKeyDown(KEY_RIGHT))
x += 10;
}
void GameDraw (void)
{
GameX.ClearScreen(
);
GameX.DrawImage
(&player_img, x, y);
}
|
// An image for the 'player'
// Initial position of the character
// Required initialization function (not shown)
// Required main loop.
// If key up is pressed, move up.
// If key down is pressed, move down.
// If key left is pressed, move left.
// If key right is pressed, move right.
// Draw black background
// Draw 'player' at the current position
|
Since each key down check is independent, it is possible for the user
to hold 'up' and 'left' simultaneously, causing the character to move
diagonally to the upper left because both x and y will change in the
same time step.
bool IsShiftDown (void)
bool IsAltDown (void)
bool IsCtrlDown (void)
bool IsWinDown (void)
These functions tell you whether a certain modifier key is being held
down. These are useful because, for instance, most keyboards have two
Shift keys, and you probably don't care which shift key is down, in
which case calling IsShiftDown( ) will return true if either or both
Shift keys are down, and false otherwise. Note that your game can make
use of the Windows key just like any other key, although it is not common
practice to do so.
bool IsCapsLockOn (void)
bool IsNumLockOn (void)
bool IsScrollLockOn (void)
These functions tell you the state of the "-Lock" keys like Caps Lock.
This is useful because, if you just do IsKeyDown(KEY_CAPSLOCK), it will
only return true if the user is physically holding the caps lock key
down (which is almost never the intent), whereas if you do IsCapsLockOn(
) it will return true only if Caps Lock is actually activated and the
little Caps Lock light is lit on the keyboard (which is what you normally
want to do). The states of the Num Lock and Scroll Lock can be checked
in the same way with the corresponding IsNumLockOn( ) and IsScrollLockOn(
) functions.
Checking Whether a Key was
Just Pressed:
bool GetKeyPress (KeyID k)
This function is similar to IsKeyDown( ), except it only returns true
if the given key was just
pressed. This is very useful when you want to know if the user pressed
a key but you only want to respond to that key once. For example, suppose
the user is in a menu in your game, and they hit whatever key you decide
is the Cancel key. If you checked for this with IsKeyDown, you're likely
to respond to their keypress many times in a row which will cause the
game to rapidly cancel out of many layers of the menu, which they did
not intend to do with a single press of the cancel key. You could keep
track of what buttons used to be down so as to only respond once, but
GameX already does this for you, so in a case like this, you should
check with the GetKeyPress key. By doing so, your game will respond
to their keypress exactly one time, and it will not respond to it again
until they let go of the key and press it again. Even if the user accidentally
holds down the key for longer than they meant to, it will register only
once with this method. You can combine the use of this function with
IsKeyDown or other Is…Down functions to check if the user pressed
one key while another key was down, for example:
if (GameX.IsCtrlDown( ) &&
GameX.GetKeyPress (KEY_Q))
GameX.Quit( );
This would cause the game to quit when the user holds Control and presses
Q, in other words, when the user hits Ctrl-Q. If the user instead holds
Q and then presses Ctrl, in this case it would not do anything because
the Ctrl key would not be down at the instant that Q was pressed. You
could replace IsCtrlDown( ) with IsKeyDown(…) to check for the
user holding a different key when they pressed Q, and of course you
could replace KEY_Q with any other key constant.
Keep in mind that calling GetKeyPress does not prevent you from calling
it or other key input functions on the same or other keys, even at the
same game instant.
bool AnyKeyPress (void);
This function exists to save you the trouble of calling GetKeyPress
on every single key on the keyboard if you just want to see if the user
pressed a key. The only time you would want to do this is if your game
ever asks the user to "Press any key" to continue. Note that sometimes
the "Press any key" instruction is implicit, for instance you might
automatically pause the game at the end of a mission and wait for the
user to press any key to continue, without needing to actually tell
them to press anything. Here, "any key" is defined as any normal non-modifier
key on the main keyboard or number pad.
Getting Buffered Keyboard
Input:
char GetBufferedKeyPress (void)
GetBufferedKeyPress is used to return buffered keyboard input
from the user. Keys are returned by GetBufferedKeyPress one at-a-time
in the order that they were pressed. This is useful only for getting
sequentially entered data such as high score names, filenames, chat
messages or typed commands.
The 'char' returned is the ASCII character code for the key that was
pressed. For instance, if the user pressed the A key, then GetBufferedKeyPress
would return the character 'a'. This method automatically takes modifier
keys into account, too, so it would actually return either 'a' or 'A'
depending on whether the shift key is down or caps lock is on. This
means that most characters that GetBufferedKeyPress return are exactly
what the user meant to type, so you can usually immediately add it to
a string of what the user has typed so far.
The exception is with certain special keys, for instance, when the user
hits the Backspace key, they mean to delete a character of what they
last typed. Backspace and certain other keys have their own constant
defined by GameX so you don't have to guess what their ASCII code is:
Besides individual characters typed like 'a' for lowercase A, '1' for
the number 1, ' ' for the spacebar, etc., GetBufferedKeyPress( ) may
return the following constants:
BUFFERED_KEY_NOTHING // means no key was pressed
BUFFERED_KEY_BACKSPACE // the Backspace key
BUFFERED_KEY_ENTER // the Enter key
BUFFERED_KEY_TAB // the Tab key
BUFFERED_KEY_ESCAPE // the Esc key
Here is some example code that takes all of this into account to let
the user type a message:
char typedString [64];
int typedNum = 0;
void GameInit (void)
{
GameX.Initialize("BufferedKeyDemo",VIDEO_16BIT,640,480);
}
void GameRun (void)
{
char key = GameX.GetBufferedKeyPress(
);
if (key != BUFFERED_KEY_NOTHING)
{
if (key == BUFFERED_KEY_BACKSPACE)
{
if (typedNum
> 0) typedNum--;
typedString
[typedNum] = (char) 0;
} else {
typedString
[typedNum] = key;
if (typedNum
< 64) typedNum++;
}
}
}
void GameDraw (void)
{
GameX.ClearScreen(
);
GameX.DrawText
(10, 10, typedString);
}
|
// A string of at most 64 typed characters
// How many characters are typed so far
// Required initialization function
// Required main loop.
// Get a pressed key, if there is one
// If something was pressed,
// If it was the backspace key,
// Delete a character
// (Sets to the Null character)
// Otherwise, if it wasn't the backspace key,
// Add what was typed to the string
// And increase the number of chars so far
// Required draw function
// Draw black background
// Draw what the user has typed so far
|
Normally you would also check for and respond to the Enter and Escape
keys, for instance Escape might clear out the whole string by setting
typedNum = 0, and Enter might cause everything in the string to register
as permanently entered.
void ClearKeyBuffer (void)
ClearKeyBuffer is used to simply empty out the key buffer that GetBufferedKeyPress
uses. Call this right before asking the user to type something, otherwise,
all the buttons they pressed before that you haven't responded to from
the buffer will all show up in the key buffer, for example if the player
is moving around with the WASD keys and then you ask them to type a
password, if you don't call ClearKeyBuffer when you ask for the password,
the password box will fill up with garbage like WAAWDSWDWSAWWW before
they even try to type anything into it. The above example code doesn't
use this function only because it starts checking for buffered input
as soon as it starts up, and it constantly processes new input as it
is entered, but if your game switches from not processing buffered input
to processing it, then you should clear the key buffer once each time
the switch happens.
14) Mouse
Input in GameX
Mouse input, like all other aspects of GameX, is also provided via
the GameX object. Mouse input functions give information about the mouse
position, its rate of change, and the status of the mouse buttons. The
Mouse functions are as follows:
Checking the Mouse Buttons:
bool IsMouseDown (MouseButtonID b);
Returns the condition of the given mouse button (true if down, false
if up).
The MouseButtonIDs you can use are MOUSE_LEFT, MOUSE_RIGHT or MOUSE_MIDDLE.
For example: if (GameX.IsMouseDown (MOUSE_LEFT))
{ ... fire lasers}
Keep in mind that not all mice have a middle button to press, and
some (uncommon) don’t even have a right button either.
bool GetMouseClick (MouseButtonID b);
Same as IsMouseDown, but only returns true
ONCE for a given check of a mouse button during one click.
This function is analogous to GetKeyPress (see above) but for the mouse.
For example: if
(GameX. GetMouseClick (MOUSE_RIGHT)) { ... switch weapons}
bool GetMouseDoubleClick (MouseButtonID b);
Returns true if the given mouse button was
just double-clicked on.
Checking for Mouse Movement
or Position:
float GetMouseDX( );
Returns the rate of change (velocity) of the
X coordinate of the mouse. Right is positive.
float GetMouseDY( );
Returns the rate of change (velocity) of the
Y coordinate of the mouse. Down is positive.
float GetMouseDZ( );
Returns the rate of change of the mouse's
scroll wheel. In (up) is positive. Note that not all mice have scroll
wheels.
int GetMouseX( );
Returns the X coordinate of the mouse's current onscreen position.
(In pixels right from the top left of the game window)
int GetMouseY( );
Returns the Y coordinate of the mouse's current onscreen position.
(In pixels down from the top left of the game window)
Mouse coordinates are in pixels measured from the top left of the game
window. Thus the maximum values returned by GetMouseX( ) and GetMouseY(
) are the maximum display coordinates specified in the GameX.Initialize
(…) function when the game starts. For example: GameX.Initialize
("test", VIDEO_16BIT, 800, 400) will create a game window with 800 x
400 pixels, and the range of values returned by GetMouseX( ) and GetMouseY
will be 0 to 800-1 and 0 to 400-1 respectively. If the game is not running
in full-screen mode, however, the user may move the mouse outside of
the game window, in which case GetMouseX and GetMouseY may return values
outside of this range.
void SetMousePosition(int x, int y);
This one is more like "mouse output" than mouse input.
It moves the onscreen (Windows) mouse cursor to a new location.
This can be useful for restricting the cursor's range
or allowing custom cursor behaviour (for instance, keyboard-activated cursor movement).
15) Loading and Saving Files in GameX
Most games (once they reach a certain stage in their production) require
that some form of custom data be loaded. The most obvious example of
this is level data. Suppose you're making a platform-based sidescroller
game. Unless you want the entire game to take place in the same empty
room, you will want to have different levels with different obstacles,
platforms, powerups, enemies, etc. to make the gameplay more interesting.
Technically, there is no need to load this data from file; you could
initialize a giant array with values that represent parts of the level
data, and just use the array. However, if you do this, you'll have to
recompile every time you change the level data, and if you start to
add lots of levels, that's a lot of values all being stored in your
game's code, and all taking up space in your program's global memory
even when it's not being used.
The solution is to save your levels as separate files, and load them
into your game as they're needed. Such files can be as simple as a text
file with a block of numbers that represent the color of that part of
the level, or as complicated as you want or need them to be. They don't
even need to be level files; you could store a list of magic spells
to file, for instance, or a list of physical constants, or a sequence
of commands, or anything at all. And when it starts becoming too complicated
to type all the data into the file by hand, you can easily add code
to your game to let you change the files intuitively from inside the
game, so you get instant visual feedback and don't even have to look
at the files to make or change the levels. The earlier you anticipate
the eventual need you will have for a level editor in your game, the
better such a feature is likely to turn out. Just remember to make it
activated by a keypress code or disable it when you're done, so the
end-user doesn't accidentally start editing your levels while trying
to play the game.
However, this is not really a GameX issue; almost all programs must
load some kind of data from file, whatever they do and whatever they
run on. Since you are using C++, you should use the built-in features
of standard C or C++ file reading functions (fopen( ), fread( ), fscanf(
), etc.) to read and write files from your game. Or: GameX also has
a File class which it uses internally for loading data, and which you
may choose to use for loading and saving your own data (refer to gamex-file.hpp
for the list of functions it provides you). Remember that GameX provides
automatic support for loading standard formats of images, sounds, and
music, and also support for saving image files, so you do not need to
worry about doing these yourself beyond simply calling the Load functions
of the appropriate class (ImageX, SoundX, or MusicX) and telling GameX
where to find the file.
16) Reserved Controls
GameX reserves certain keys and key combinations. When programming
a game's input, it may be useful to know what these are. Also, for general
use when playing any GameX game or debugging your own, it is nice to
know what these reserved keypresses actually do.
Exiting the game:
Alt-F4 (hold either Alt key,
then while holding it, press the F4 function key)
- Exits the game. (Also can be used to exit any other Windows program,
if you didn't know already.)
- Cannot be disabled; this combination always exits the game.
Ctrl-Q (hold either Ctrl key,
then while holding it, press the Q key)
- Exits the game.
- Cannot be disabled.
Esc (press the Escape key)
- Exits the game.
- Can be disabled by initializing the game with the RUN_USEESCAPEKEY
flag, in which case this key is no longer reserved by GameX itself and
may serve another purpose within the game as decided is appropriate
by the game's creators.
(When exiting via any of the above three methods, GameX will ask whether
you really want to quit, and continue the game if you choose "No". "Yes"
is selected by default, so you can choose it by pressing the space bar,
but you can also disable the prompt altogether with the RUN_NOCONFIRMQUIT
flag.)
Switching the screen mode:
Alt-Enter (hold either Alt key,
then while holding it, press the Enter key)
- Toggles the screen mode between the "full-screen" and "windowed" modes.
- May take a short while to switch modes; be patient when using this.
- Can be disabled by initializing the game with the RUN_NOALTENTERTOGGLE
flag (not generally recommended).
Taking a screenshot:
Prnt Scrn (press the Print Screen
button on your keyboard)
- Copies the game's viewport directly to the clipboard, where it can
be pasted as an image into other programs.
- As an added convenience, also saves the viewport to an image file
called "screenshot.bmp" in the game's main directory.
- Screen mode does not affect the resulting screenshot.
- Cannot be disabled.
Pausing the game:
Alt-Tab (hold either Alt key,
then while holding it, press the Tab key)
- Pauses and switches out
of the game without exiting it.
- Cannot be disabled.
Minimize (click on the Minimize
button of the game's window)
- Pauses and minimizes the
game without exiting it.
- Does not work when the game is in full-screen mode.
- Cannot be disabled.
Moving/Resizing the game:
You can move, resize, and maximize the window of the game like you
can with the window of any normal application.
- Does not work when the game is in full-screen mode.
- The game is paused while its window is in the process of being moved
or resized.
- Resizing and maximizing can be disabled by initializing the game with
the VIDEO_NORESIZE flag
(not generally recommended).
17) Things
to Remember! (Problems?
Look here first!)
If you have any trouble programming with GameX, here are several important
things to remember:
If you have problems getting a GameX project to compile, link, or start
up:
- You must have GameInit( ) and GameRun( ) and GameDraw( ) functions
all in your main code file which #defines GAMEX_MAIN in order for your
game code to compile with GameX.
- GameX.Initialize( ) must be the first thing called inside GameInit(
). Otherwise, GameX will generate an error.
- Make sure GameX is linked properly, and that your main .cpp file begins
with: #pragma comment (lib,
"gamex.lib") or, alternatively, you can add gamex.lib in the
Project -> Settings -> Link for your game project.
- If you get strange linker errors, you may need to install the DirectX
SDK and/or tell your compiler where it is.
- If your compiler says it can't find something, and you know you have
that file, that means you have to tell your compiler where it is by
going to Tools -> Options -> Directories and adding its location
under the appropriate section there. Just because you can see the file
in a window doesn't necessarily mean your compiler is smart enough to
find it on its own. On the other hand, if you don't know if you have
the file, do a file search on your computer for it, and if it really
is missing, then download the missing component.
- If your game compiles in Debug mode but not Release, or vice versa,
you probably set something in the Settings window and accidentally applied
it only to the Debug or only to the Release target, when you should
have applied it to All Configurations, in which case you will have to
reapply the changes you made as necessary.
- If your compiler gives lots of warnings, you may want to turn the
warning level down in the Settings window.
If you have problems loading images or sounds:
- TIFF images must be saved in RGB color format, without LZW compression,
and with IBM byte order (using Photoshop, preferably)
- BMP images must NOT be saved in R5G6B5 format (use the standard X1R5G5B5
instead).
- WAV files must be saved without any compression (this is the default
for most programs), and they cannot be 4-bit (use 8-bit or 16-bit instead).
- MIDI files (.mid or .midi) must be saved using
the standard midi format. It must be precisely Standard MIDI or GameX
won't play it.
If your GameX game takes a long time to start up whenever you run it:
- Full-screen mode takes longer to enter than windowed mode, so if
you plan on starting and exiting your game many times when testing it,
start it up in windowed mode each time.
- If your game Initializes GameX with VIDEO_16BIT, keep your monitor
in 16-bit mode while working on the game, that way the monitor won't
have to keep switching each time you start or exit your game. Likewise,
if your game uses VIDEO_32BIT instead (which NOT recommended, by the
way), then keep your monitor in 32-bit mode.
- If you load or process a lot of things in GameInit( ), it is normal
that doing so will cause your game to take longer to start up. Make
sure you aren't loading/processing things that are unnecessary, however.
If nothing happens when you tell GameX to draw:
- Make absolutely sure that the drawing code is being called (directly
or indirectly) inside GameDraw( ).
- Make sure to Load your ImageX objects before drawing them.
- For images you Create, remember to copy/load/draw something into them
before trying to draw them to the screen, otherwise you'll just draw
a blank image.
- If you're drawing in 3D, make sure the CameraX passed to GameX.SetDrawCam(
) is positioned properly
- If 3D sprites aren't showing up, you may be drawing them too small
for your world size -- try using SetDrawScale( ) with a large scale
like 500.
- If 3D polygons aren't showing up, remember that all polygons are one-sided which means they are invisible
if you look at them from the wrong side, and also remember that polygon
vertices should always be listed in a Z shape. The way the Z is flipped
determines which side is visible and which isn't. To force GameX to
draw a polygon as two-sided, use GameX.SetDrawMode(DRAWOP_NOCULL), but
don't use this for all polygons or performance may suffer.
If the game runs strangely or poorly:
- Make sure to only update your game's state in GameRun( ) and only
draw that state in GameDraw( ), or else GameX's automatic timing code
will not function properly.
- If the game run poorly on your computer, either your drawing code
is too complicated and draws too many things for your computer to keep
up, or your computer and/or video card are outdated. Make sure your
computer meets the requirements and recommendations listed above.
- Or, there is a chance that you have loaded too much into memory or
are leaking memory, in which case your computer has to shift lots of
RAM around, which takes time and causes performance to suffer while
it's happening. You should be memory-efficient in the way you use images;
if a level has 200 identical enemies, don't load a separate image for
each one! And if you allocate anything with the "new" operator, you
must de-allocate it later with the "delete" operator or it becomes a
memory leak, which also affects performance.
If the game crashes or freezes:
- If problems arise ONLY in Release mode and not in Debug mode, see
the topic below which is specifically about this problem.
- If the game freezes, then you almost certainly have an infinite loop
somewhere in your code. If your game freezes when it is in full-screen
mode and you have trouble getting out of it, try hitting Alt-Tab, then
(if you're running it from VC++ or .NET) hit Shift-F5 (several times
if necessary) to stop execution. Or better yet, don't run your game
in full-screen mode until your game is mostly bug-free.
- If GameX seems to crash, it is probably due to an oversight in your
code which may or may not interact with GameX. Look out for out-of-bounds
or buffer overwrite errors in particular. Also, if you pass GameX an
uninitialized pointer to an ImageX, it will cause a crash.
- If you get an unexplained memory write error when drawing pixels,
it may be because you are accidentally drawing out of bounds with the
DrawPixel function. For speed reasons, this function (and only this
function) does not do boundary error checking, you have to make sure
it's in-bounds yourself.
- Whenever GameX encounters an error, it makes a log of it in the "debug.txt"
file located in the same directory as the game executable. Should GameX
crash, look in the debug.txt file (scroll to the bottom) and it may
tell you what went wrong.
If you switch your game build to "Release" mode, and suddenly things
go wrong that worked fine in "Debug" mode:
- Resist the temptation to just keep using Debug mode because it works,
because it also performs a lot worse.
- A quick fix that is usually acceptable is to disable individual compiler
optimizations until you find the one or two that cause the problem,
and leave them disabled because they likely weren't helping your game
run better anyway; the optimizations are not the primary reason that
Release mode runs better, so it's OK to disable some if they are "causing"
a problem.
- If that doesn't work (or maybe even if it does), it means you are
depending on the default values of some variables, or you are writing
to out-of-bound memory, somewhere in your code. Usually the culprit
is an array that you define but don't explicitly loop through and set
its values to something, or a variable belonging to a class that you
forget to initialize in the class's constructor, or an uninitialized
variable in a struct. The reason these errors may appear when you switch
to Release mode is that variables start out with different values in
Debug mode than they do in Release mode, and you may have accidentally
relied on some of these values (which is especially easy to do with
the "bool" data type). Also, Debug mode pads the memory a bit which
can make reading/writing to nonsense memory locations sometimes not
cause an error until the padding is removed at the switch to Release
mode.
18) GameX
Performance
This is a continuation and brief summary of the earlier section, "About
Game Speed and Frame Rate", with more of a focus toward the performance
of any given GameX game.
As of version 5, the performance of a GameX game is more comparable
to the performance of a game programmed directly in DirectX or OpenGL.
This does not apply to full-3D games past a certain complexity, because
GameX is not specialized to be a 3D engine and is instead focused on
being flexible and easy to use for both 2D and 3D graphics.
GameX contains automatic time management code which manages the game
speed and frame rate of the games it runs. Basically, it skips a few
frames if the game is going too slowly, and it delays in tiny amounts
between each frame if the game is going too quickly, so that the game
runs at the same speed regardless of how complicated it is. You can
set this speed yourself when you call GameX.Initialize, and you can
change it later with GameX.SetGameSpeed.
The frame rate is managed automatically to be the maximum it can possibly
be at the moment without altering the game's actual speed. When one
refers to the "performance" of a game, they usually mean the game's
frame rate, i.e. the number of times per second that the screen is visually
updated. This number is the FPS (frames per second) of the game, and
the higher it is, the better. If this number is much below 30, the game
will appear to play "choppily." If this number is 60, the game appears
to play quite smoothly. Once the FPS reaches 60, though, the benefit
of going higher than 60 is extremely minimal, because the human eye
can barely tell the difference past that point.
In most cases, GameX games will run at exactly 60 FPS, or higher if
the monitor supports it. However, this rate can become lower due to
several factors.
- The most important factor in determining the frame rate of a game
is the complexity of the game's GameDraw( ) function, specifically,
the number and size of the images and other graphics that the game draws.
Your game's performance will suffer if you try to draw too many sprites
and polygons every frame. One way to look at it is this: 60 FPS means
GameX only has at most one 60th of a second (that's 16.67 milliseconds)
to draw everything in GameDraw each time it is called. Thus, if your game
tries to draw so many things at once that GameDraw takes longer than
16.67 milliseconds to finish, then it becomes impossible to maintain
the maximum frame rate.
- Another extremely important factor is the speed of the computer you
are running the game on and its graphics card, because these affect
the speed of each individual thing you draw. GameX's drawing features
rely on DirectX and your computer's support for the features of Direct3D,
so if your computer is too outdated to play DirectX games at a decent
frame rate, it will also be too outdated to play GameX games at a decent
frame rate, except for very simple games. On the other hand, if your
computer is top-of-the-line, it will be able to play just about any
GameX game at a perfect frame rate. Performance depends heavily on your
processor speed and quality of your graphics card, in addition to the
game's graphical complexity.
- If you set your game speed to lower than the current monitor's refresh
rate, whatever speed value you choose becomes the maximum FPS your game
can run at on that monitor. This is because updating the screen faster
than you update your game would be pointless.
It's important to note that, because of the time management code that
GameX uses, a GameX game is not specialized to any specific computer,
but rather it adapts to best use the available resources of whatever
computer the game is run on. A GameX game is able to run on all computers
that meet the minimum requirements of GameX, but the more complicated
a game is, the worse it will perform on slower computers, so keep this
in mind if you want people with slower computers than yours to be able
to run your game well.
Although the drawing functions you use determine (in large part) the
performance of a game, it is meaningless to discuss the amount of time
that individual drawing functions take because it varies so widely between
computers with different capabilities. However, there are some general
guidelines to give you an idea of how long a function will take, and
the less time your GameDraw function spends on drawing functions, the
better the performance of your game in general.
- If you are running the Debug instead of Release build of your game,
or if you have any debugging or profiling options enabled in your compiler
project settings, then all drawing functions will draw a lot slower,
and in fact everything else your game does will also be more time-inefficient.
Thus it is very important to test your game in both Debug and Release
mode (to make sure they both work) and to use the Release build for
presentations/finished products, and Debug only when you are actually
working on the game.
- When drawing images or other graphics, the more pixels you are changing
at once, the more time it takes. Even more important, though, is the
number of things that you draw. It is much faster to draw one medium-sized
image than it is to draw twenty smaller images that make up the same
thing. This is because every call to a drawing function takes some extra
time for GameX to set up (this extra time is often called the "overhead"
of calling the function), and this overhead is incurred no matter how
big or small the thing is that you are drawing. But you should also
avoid drawing extremely huge images (> 512x512), for the different
reason that some video cards have trouble handling that many pixels
at once.
- Drawing the background is often a major bottleneck, because if you
tile an image that is too small, that means drawing the image lots of
times, especially if the background is rotated. Remember that the number
of times an image is drawn is more important than the number of total
pixels you end up drawing, so make sure that you draw background images
that are not too small or scaled down too much.
- Blurring the screen (with BlurScreen or BlurRect) is a very slow operation,
and so is drawing text with DrawText or DrawOutlinedText, so try not
to use these too often. (If you must use these functions, keep in mind
that they are faster if you "batch" them together instead of alternating
them with other drawing functions)
- The advanced features of GameX are mostly the same as far as performance
is concerned. Except on very old computers, drawing an image with an
alpha layer is only a little slower than drawing an image without an
alpha layer, and (likewise) drawing an image additively or with another
special mode is only a bit slower than drawing it normally. Applying
shading and transparency, even blended across separate corners, does
little to decrease performance, and things like dithering, filtering,
inverting, rotating, and mirroring take no time at all.
- Drawing in 3D is slightly slower than drawing in 2D, because there
are more calculations involved when in 3D, especially if you tell GameX
to Z-Buffer/sort your 3D scene. The speed hit is acceptable considering
how much easier the auto-sorting makes it for you to draw in 3D, unless
you know for a fact or can easily calculate how to draw your scene in
back-to-front order. Either way, any given 3D game will still perform
about as well as a 2D game of comparable complexity, but don't get too
ambitious and start making vast or detailed landscapes or 3D models
with high polygon counts, since GameX can't handle that sort of complexity
yet.
- When drawing in 3D, keep in mind that textures loaded with alpha/transparency
draw a little bit slower than other textures, and anything you draw
with transparent shading or a special Draw Mode (like DRAW_ADD) will
also draw a little slower. Don't let this limit what you do with your
graphics, but keep it in mind when loading textures, setting up 3D scenes,
etc.
- It is possible for something unrelated to drawing to slow down the
game, but much less likely. Drawing operations are (by nature) far more
costly than most ordinary calculations.
|
|
|