Scorched Asteroids Project Summary
Game Overview
We are taking pieces of Scorched Earth and the most addictive part
of Asteriods and merging them into a Win32 application. With real time
play, multiple weapons, and networked opponents, we are hoping to
create the most addictive final project with the smoothest sprite
based graphics.
Scorched Earth is a side view, turn based game. Each 'tank' is
allowed to take one shot at the other players on the team. After all
the tanks are dead, each player gets an amount of money based on what
kills they made. And in between each battle is a buying round with
dozens of weapons and special items to buy.
Asteriods, the classic, is viewed from above and played in real
time. The player's ship continuously moves, with a single booster and
a single weapon. The goal of the game is to destroy the asteroids
floating around. The only physics done is the acceleration from the
booster.
Merging Scorched Earth and Asteroids: Play Modes
Scorched Asteroids will have a side view (Scorched Earth) and play in
real time (like Asteroids). There will be multiple weapons (Scorched
Earth) and will be played with momentum (Asteroids) and gravity (kind
of like Scorched Earth) between objects in mind.
To include the best elements of both games, we have broken the battles
up into three different modes.
- Earth Mode
- Much like Scorched Earth, the earth mode will take place on the
surface of a planet. Each ship will have an amiable turret and will be
able to move horizontally. As with all the modes, multiple weapons and
special items are allowed.
- Moon Mode
- This mode will be more like asteroids. It will
take place in space, with a moon in the middle of the screen. This
is the first and simplest of the 'space' modes. The direction of
fire will be the direction of the ship (fixed turret). Maybe with
multiple direction 'jets' for moving in different directions. Gravity
will be in effect and will be toward the center of the moon.
- Free Space Mode
- This mode will be asteroids. Gravity will be in effect for only
the largest asteroids. This is the most complex of the 'Space' modes.
Note On Summary
All of the procedures that have a basis in C are described using C
prototypes. Since we are using MASM's PROTO and
INVOKE to simplify C calls, we believe that it is more
intuitive to describe functions this way than it is to use ASM
declarations.
Data Types
- HRESULT
- A 32 bit number. Usually in index into a table of results specific
for a function or group of functions. Always returned from a DD API
call.
- LPDDSurface
- A pointer to a structure the OS provides as a kind of 'handle' to
a buffer in video memory. Usually used to represent one screen full of
information. Also commonly used to represent one buffered bitmap in
video memory. If the system doesn't have enough video memory, then the
buffer is placed in system memory. This structure is never directly
accessed by programers. It is used as an entry point for all DD calls.
- LPSysSurface
- A pointer to a SysSurface.
- MY_RGB
- A device dependent type of color. Currently we are running in
16bit color (because that's all my computer at home can handle in
1024x768). So MY_RGB would be equivalent to an unsigned short
(WORD). If we where in 8bit color, MY_RGB would be
equivalent to an unsigned byte (BYTE). Our target color depth
is 24bit color, so the final MY_RGB will be a 3-byte
structure.
#if COLORDEPTH == 8
typdef unsigned char MY_RGB;
#elseif COLORDEPTH == 16
typedef unsigned short MY_RGB;
#elseif COLORDEPTH == 24
struct MY_RGB {
unsigned char red;
unsigned char green;
unsigned char blue;
};
- RECT
- Win32 Structure of 4 32bit integers.
typedef struct _RECT { // rc
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT;
- SysSurface
- This structure is not an OS or DD structure -- it is our own
structure used as a pointer to an image buffer in system memory. We
were experimenting to see if there were any speed advantages if we
rendered graphics in system memory instead of video memory (using
triple buffering).
struct SysSurface {
LPVOID pSurface;
LONG width;
LONG height;
};
- Point
- This structure represents a point on the screen.
struct Point {
  int x;
int y;
};
- MenuItem
- This structure is used in the menu routines. It represents a text
item in a menu that can be selected.
struct MenuItem {
char* itemName;
int stringLen;
Point;
};
- MenuItem netMenuStrings[]
- contains all the strings and
their locations required to draw the Network Game menu. This is for
the UI.
- Object
- A structure that encapsulates the various attributes of any game
object, such as a ship or a weapon.
struct Object {
objectType;
int status;
double
xPos,
yPos,
yPos,
xVel,
yVel,
dir,
mass;
int damage;
};
- Object MasterTable[]
- contains information about each
object that is to be drawn to the screen.
- CSocket Connections[]
- This array holds all incoming
connections to the server (i.e., clients). This array will be used to
identify each of the clients and to send data to them.
- int MAXCONNECTIONS
- This variable simply limits the
maximum number of client connections.
Introduction (Opening Sequence)
The introduction sequence for Scorched Asteroids will serve two
important functions: (a) it will be the main entrypoint into the game
and (b) it will acquaint the user with the various features of the
game. The intro must also be attractive to make the game appeal to
users. The introduction sequence will fade into a screen that
displays our project group name. Then, we will display a visual
"water" effect, and finally bring up the Main Menu.
Functions
- void Fade(BOOL fadeIn)
- Depending on the boolean flag
fadeIn, this routine will fade into (increase the brightness
of the screen) or out of (decrease the brightness of the screen) the
screen.
- void RenderWater()
- The RenderWater routine
will render a shimmering water surface onto the screen. This function
will be used to provide an appealing visual effect.
Menuing (User Interface)
After the game's introduction is complete, the game will enter menu
mode. Here, the players will be presented with various options
including game mode, volume levels, and network play. The menu page
will be implemented dynamically, so that additional options can be
added easily if time permits.
Functions
- void buildMenu(MenuItem menuContents[MAXMENUITEMS])
- Purpose: To write all menu items to the screen based on the information in menuContents
- Inputs:
- menuContents - An array of MenuItem structures
- Outputs:
- Draws the menu to the screen
- Assumptions:
- The menuContents array contains valid information including: the Points are within the screen
and the desired text will not go off the screen.
- void drawString(char* string, Point p)
- Purpose: To draw a string to the main surface using non-selected characters
- Inputs:
- string - A pointer to a string to be printed
- p - A Point structure specifying the upper left corner of the first character of the sting
- Outputs:
- Writes the string to the screen
- Assumptions:
- void drawSelectedString(char* string, Point p)
- Purpose: To draw a string to the main surface using selected (bold) characters
- Inputs:
- string - A pointer to a string to be printed
- p - A Point structure specifying the upper left corner of the first character of the sting
- Outputs:
- Writes the string to the screen
- Assumptions:
- menuMode WinMain()
- Purpose: To handle keyboard and mouse events and process them accordingly
- Inputs:
- Mouse events (single click)
- Keyboard events (up arrow, down arrow, enter)
- A pointer to the function to call after a valid event is processed
- Outputs:
- AX - An index into the menuContents array specifying which option was selected
- Calls the function at the passed function pointer
- Assumptions:
Video and Graphics
There are three main challenges to doing video in Win32.
Initialization, acquiring and releasing the hardware, and lastly,
learning to utilize the hardware.
The first part of initialization includes creating our 'window', a
full screen exclusive mode Win32 application. This means that when we
are active, we require the whole screen for ourselves. Next we need to
claim the screen and an off screen buffer as our own, so we can use
them to draw to. Lastly we need to load the images that are going to
be needed for the play mode we are in. But our work is not done, we
can lose our claim to the screen any time the user switches to another
app. We will discuss in detail in the next section.
Because this is in Win32, which is a 32bit preemptive multitasking
environment, we don't have exclusive access the video hardware. As a
result, the graphics becomes much more complicated than it did in
Dos-16 Assembly. Every API call can fail: imagine having to check to
see if a write operation to the video buffer had failed, or a write
operation to a port to change the palette had failed.
Any time the user switches to another application, the current
video buffer is immediately overwritten. So there is a continuous
process of testing and reacquiring any time a resource is lost.
Acquisition and release happens every time a write to the video
buffer is necessary. Because almost every process on the computer
shares the video screen, the OS is very careful about exclusive
access. The Lock/Unlock pair that wraps every write to the video
buffer is the basis for the screenLock() and
screenUnlock() procedures.
Lastly, and most complicated is what every project needs: the
graphics rendering code. Our 2D overhead view is simple. Because we
are in a device independent environment, we do have a few quirks to
worry about, but most of it is done just like it was in MP4. Writes to
a buffer are followed by a call to flip the back buffer with the
primary buffer. If we have time, we might add the ability to run in a
window, which requires adding another buffer and a clipping rectangle
(triple buffering).
- HRESULT restoreAll()
- Called during initialization and when a surface is dropped to
restore the surface. This loads all the bitmaps used in the current
play mode into the video buffer.
- BOOL clearBackSurface()
BOOL clearBackSurfaceAsm()
- These two functions clear the drawing surface.
- HRESULT updateFrame()
- Renders the current scene and flips the video buffers.
- LPDDSurface loadSurface(const char * filename)
- Creates a DD Surface, loads the image file pointed to by filename,
and returns a pointer to the created surface.
- HRESULT destroySurface(LPDDSurface surf)
- Destroys a surface created by loadSurface. This is used to release
video memory between switching of play modes.
- HRESULT fillRect(RECT* pDrawRect, MY_RGB color)
- Fills a rectangular piece of the draw buffer with the specified
color.
- HRESULT drawRect(RECT* pDrawRect, POINT* start, LPDDSurface source)
HRESULT drawRectSys(RECT* pDrawRect, POINT* start, LPSysSurface source)
- Copies the rectangle DrawRect from the source surface to the back
surface.
Note: does no validation of parameters and no clipping of
it's own. It's up to the caller to validate input.
Note: call updateFrame() to flip the surfaces.
Networking Overview
The networking for the project is based on a simple client/server
model. Networking will be implemented using TCP/IP sockets through
the Win32 Winsock API. Initially, we plan to have a dedicated server
that will maintain and update a "master copy" of the object table,
MasterTable. This table will then periodically be broadcast to all
the clients. If time permits, we will attempt to embed the server
portion of the project into the client, so that there is only one
binary a user needs to run.
There are two approaches we can take to implement networking. One
method is to make the server responsible for not only updating the
MasterTable, but also for calculating a sequence of frames to
broadcast to each client. Using this approach, the client is simply
responsible for drawing the screen based on the sequence of positions
the server has told it to draw. However, there is a significant
drawback with this approach: the network traffic will become intense,
and the server would slow down immensely, causing jumpy and lagged
gameplay.
The other approach is to have the server simply update positions
based on the change in a client's velocity. This method would force
the physics modeling to be done by the client of this. While this
approach would be compute-intensive on the client, it would decrease
network traffic and lallow for smoother gameplay.
Since, at this point, we cannot effectively calculate which
approach is better, we propose to try both methods and choose the
better of the two for the final release.
There are also multiple approaches to take when using the Winsock
API. We can either use the C++-based Microsoft Foundation Classes to
abstract low-level threading issues and Winsock calls, or we can use
the straight, C-based Winsock calls. In order to first obtain a
working version of the networking, we will use the MFCs. If time
permits, we will port the code to work using the C-based calls.
Client Functions
- void connectToServer(char* pszServName)
- This
function will connect to the server whose name is pszServName. If the
host resolution succeeded, the player will be connected. Otherwise,
the player will be notified that the connection failed and will be
returned to the main menu.
- void onReceivePacket()
- This function will handle the
updating of the MasterTable once the client has received a new one
from the server.
- void sendPacket()
- This function will send a packet
out over the network to the server and wait for the server's response.
Server Functions
- void serverInit()
- This function will initialize the
Network Server.
- void onReceiveConnection()
- This function will handle
an incoming connection. If there are already MAXCONNECTIONS
clients connected, then the server will simply ignore any incoming
connections; it will be the responsibility of the client to handle a
server timeout.
- void onReceivePacket()
- This function will receive a
packet from a client. It will then update the master copy of the
MasterTable, and will broadcast this back to all connected clients.
Physics Modeling Overview
The kinematics of the objects on the screen is dependent on the level
that the user selects. There are two separate levels that the user can
choose from and below is a brief description of each:
Free Space
The setting for this level is such that the motion of the objects on
the screen is not influenced by the gravity of stars or planets. Although
the gravity of some objects does play a role in determining the trajectories,
it is not as profound as in the terrestrial level (see below). Only objects
with a certain minimum mass can affect the motion of the other objects
on the screen. The positions and velocities of the active objects are updated
using the following sequence of calculations:
Force->Acceleration->Velocity->Position
Force:
The force acting on each object on the screen is calculated using
Newton's universal law of gravitation as follows,
.
The number of objects whose mass is above the minimum threshold is
denoted by 'n' and "current" indicates the object upon which
the calculated force is acting. The summation is necessary so that all
objects above the threshold mass can be taken into consideration for
the calculation. And finally, the separation between the two objects
in question is represented by 'r'.
Acceleration:
Using F = ma, we can calculate the acceleration of any particular object
with mass m:
.
Velocity:
Since
,
we get
.
In order to obtain a numerical value for the velocity we assign an
appropriate value to the infinitesimal time interval 'dt' (such as 1s
or 0.01s) and assume the acceleration calculated above is constant
during this interval. While the program is running, this interval is
essentially the duration of the updatePosition procedure. Thus
the new velocity of the current object is given by
.
Position:
Similarly, we get
.
And once again, we assume that the velocity calculated above is
constant during this infinitesimal time interval. So we get
.
NOTE: Keep in mind that a new value for acceleration is
calculated during each call to updatePosition and that the duration of
this call represents
. The
amount of error that creeps into the model can be minimized by making
.
Level 2 (Terrestrial)
In this level, all objects are on a planetary surface and there is
no gravitational interaction between these objects. As a result, the
acceleration of all objects on the screen is the same - both in terms
of direction (downward) and magnitude (9.8m/s). The equations are the
same as above.
Procedures
- updatePosition
- Purpose: This procedure updates the position and velocity
of every single object on the screen
- Inputs:
- masterTable - An array containing the objects on the
screen. The fields in the objects represent the various object attributes
such as position and mass.
- massiveObjectsTable - An array containing those objects
whose mass and position need to be taken into consideration for calculation
of the gravitational force.
- Outputs:
- masterTable and massiveObjectsTable - The velocity
and position fields of these structures are updated based on the above
calculations.
Sound Overview
Functions
- InitSound
- Purpose: Initializes the Sound Buffer
- Inputs:
- Outputs:
- AddSound
- Purpose: Adds a Sound to the Buffer
- Inputs:
- AX : Sound that is to be loaded
- Outputs:
- BX = Offset in memory of the sound to be played
- Notes:
- 0 - Shot Fired
- 1 - Explosion from a Ship
- 2 - Explosion with Object (Ground)
- 3 - Thrust
- 4 - Intro Sound
- 5 - Exit Sound
- 6 - Winner
- 7 - Loser
- 8 - Test Sound
- PlaySound
- Purpose: To play the sounds in the buffer
- Inputs:
- BX : Location of sound in memory
- Outputs:
- Notes:
- Will most likely be a continuous loop