Technical Specifications

OVERVIEW

Development Environment

  • Language : C++
  • Compiler : GCC 3.1(cygwin, Xcode)/VisualStudio.net
  • Version Control : CVS (SourceForge)
  • External Library : SDL for multimedia

Operating System

  • All major operating systems supported by SDL will be supported in this game, but primary target is Win32, Linux, and MacOS X

Video

  • Fullscreen (800 x 600)
  • 2D hardware acceleration (double-buffering)
  • Colors 32 bit

Sound

  • use the SDL_mixer library to ease coding
  • Ability to play sound files as well as CD-Audio
    • 44khz, 16bit - WAV or MP3
    • consider 22khz to improve performance
  • multichannel with individual panning/volume controls
  • no MIDI, all recorded

Game Engine

  • The game engine will be custom written.

Networking

  • Use SDL_net library to ease coding and ensure cross-platform compatibility
  • LAN-style, Clients have built in Server feature (hosting games)
  • For initial connections, Socket TCP connections (handshake) running its own thread to accept connections from game “joins”
  • During gameplay, UDP packets will be used by clients to talk to server, with results broadcasted back via the same methods to clients
  • Chatting uses TCP-IP to guarantee delivery

Physics

  • Friction
  • Gravity ~ -9.8 m/s^2 ~> -200 pixels/s^2
  • Projectiles : described as vectors

Overview of Main Game Loop

ALGORITHM, DATA STRUCTURES AND API

Networking:

There are two ways to create a game for "The Frost Project"
1) Run the dedicated server by itself.
2) From the Main Menu click on Multiplayer and click on Create a Game

The dedicated server is a separate application, unlike the "Create a Game" option, the game
automatically starts when 4 players are connected.
If there is less than 4 players, the game does not start until all players
click on "ready". If all players are "ready" then the game will start in 30 seconds regardless
if anybody joins. If a player joins during the count down or midgame, they will immediately
join the game. Once 4 players are connected, the server will stop accepting connections. However
if one of the players get disconnected, the server will start listening again until 4 players are
connected.

The variables from "Create a Game" option will be done by the GUI.

Networking will be done a class called Network
The server is a separate application that is ran when a person wants to host a game.
All TCP Activity will be on port 10000
All UDP Activity will be on port 10001
There will be two threads one for TCP and one for UDP

To help with lag, each client will keep track of how long it takes for a message to be
received at the server side.
For each message/packet sent, the server will send and acknowledgement.
We will call the time it takes from a packet sent to an acknowledgement / 2 to be lag.
The reasoning behind this is to estimate how long it takes the packet to reach the server
so at that moment we can draw the animation for the action we just took so it synchronizes
with the client.

However the acknowledgement packet contains the time received on the server side
so the client can correct it if it is off.

Client side:

Includes:
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <SDL_net.h>
#include <SDL_thread.h>
#include <projectile.h>

Global Variables:

TCPsocket tcpsock;	// the tcp socket to the server
UDPsocket udpsock;	// the udp socket to the server

Functions

void TCPlisten();	// each of these functions will require a thread	
void UDPlisten();	// they will listen for messages from the server
			// and place messages in a linkedlist for the
			// main game loop to retreive later.

// This is the function that will initiate a connection to the server
// the int value returned will determine the error status
// 1 = ok connected
// 0 = error
int connect(String ip)
{
return connect(ip, 10000);
}
int connect(String ip, int port)
{

}

// sends a message to the server, it will not immediately show up
// since it is order syncrhonized
// all messages sent to the server will be broadcasted back 
//to the client
// in the order it receives through TCP
void sendMessage(char*)
{

}


// this sends the server a projectile
void sendProjectile(projectile, int)
{

}

//returns a linked list of messages, once you retrieve a message, 
// the queue will be deleted once it is called
linkedlist getMessageQueue()
{
}



// sends on acknowledgement on the packet receieved, 
// the acknowledgement
// includes a time and packet number when received 
// so the client can 
//correct his display
// (if needed) when the acknowledgement is received
ack sendAck(int)
{

}


// returns a linkedlist of projectiles, once you 
// retrive a projectile,
// the queue will be deleted once its called.
linkedlist getProjectileQueue()
{

}

Server side:
The dedicated server and the "GUI" server are very similar in function. The server accepts TCP and UDP
packets, applies the physics model and collision detection and returns the result to the client. The
server will probably use vector timestamping help synchronize the client and the server. If there is no
activity between the server and the client for 5 seconds, the server sends a PING packet to see if the
client is alive, if no packet is returned within 10 seconds, the client is considered disconnected and
is immediately dropped from the game. The server uses SDL threads to listen and relay packets back to
the clients.

No activity between the server and client is defined to be no acknowlegement packets received from
the client.

Thread usage:
A thread is used to accept UDP connections. This thread is terminated when 4 players are connected
and is restarted when there is less than 4 players.

A thread is used to listen for UDP connections and reply with an acknowledgement.
An acknowledgement consists of the packet number and the time it was received.

A thread is used to accept TCP connections. This thread, like the udp accept thread, is terminated
when there is 4 players connected and starts up when there is less than 4 players.

A thread is used to listen for TCP connections and reply with an acknoweldgement.

A thread is used to keep track of all locations of projectiles and characters. This thread is
considered the main game loop since it also detects collisions and keeps track of projectile
locations. If there is a collision, this thread calls

Includes:

#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <SDL_net.h>
#include <SDL_thread.h>
#include <projectile.h>

Global Variables:

// the 4 players' tcp sockets
TCPsocket tcpClients[4];

// the variable to see how many players are connected

int tcpClientCount = 0;  

// the 4 players' udp sockets

UDPsocket udpClients[4]; 

// the variable to see how many udp sockets there are

int udpClientCount = 0;	 

// the server tcp socket it listens on
TCPsocket tcpsock;	
// the server udp socket it listens on
UDPsocket udpsock;	

// The following is a acknowledge struct used to 
typedef struct moo3
{
	double time;
	int packetno;
} ack;

Functions:
// the function that the thread calls to 
// accept up to 4 udp connections
void acceptUDP();	
			// the sockets accepted are stored in the 
   // global variable udpClients
			// it also increments udpClientCount

// the thread to relay back all udp activity to all the UDPsocket(s)

void relayUDP();	
void acceptTCP();	// same as UDP but for TCP
void relayTCP();	// same as UDP but for TCP

// the main game loop that keeps track of all 
//characters and projectiles
void gameLoop();	

// sends on acknowledgement on the packet receieved, 
// the acknowledgement
// includes a time and packet number when received
// so the client can correct his display
// (if needed) when the acknowledgement is received
ack sendAck(int)
{

}

Damage sendDamage();	// this is the function called 
// when a projectile collides with a character
			// the Damage struct defines what character is 
   // damaged by how much.
			
Projectile sendProjectile();	// this is the function 
// called when a new projectile is created
				// this is created when a projectile
    // collides with a person
				// this person becomes a projectile

Game Physics:
a) Projectiles
Projectiles act the same way they do in real life.
Each projectile has a velocity and direction associated with it.
Velocity is determined by how long the player holds the fire button.
Direction is determined by the aiming tickmark which is controlled by the mouse.
This game will feature dynamic wind, which changes all the time.
Wind affects all projectiles' directions.
In this version, projectiles are not affected of the wind, however if time permits, we will implement this feature.

To keep track of projectiles we will use a linked list of projectiles.
We will create functions to describe its current location based upon a function of time.
When a projectile is fired, we will store its initial velocity and angle with respect to the horizontal x axis. We will call this angle theta The hight at time t is given by v * t * sin theta - 0.5 * g * t^2 v = initial velocity g = gravitational acceleration (which is -9.8 meters squared) t = time

The distance is just
v * t * cos theta

These functions will be in the class Physics

 typedef struct moo2 {
   double x;
   double y;
 } point;
typedef struct moo { double time; point p; double velocity; } projectile; void addProjectile(Projectile); // adds a projectile to the list of projectiles // linkedlist getProjectiles(); // returns the list of all projectiles point calculateProjectile(Projectile, double); // pass in a projectile and the current time and it // returns the projectile's new location

If a character is in the air, s/he is able to be hit by projectiles. If this happens the collision detection would detect a collision and with that data a new projectile will be created.
The new path of the character would be the vector of the weapon added on to its current trajectory.

b) Bouncing/Traction/Friction
When a player gets hit by a projectile or melee weapon, they will follow a trajectory path and then they will bounce once a character hits the terrain. The number of bounces is determined by the height of the trajectory path on the initial path. The trajectory of the bounce is initiated where they land. The character's bounce direction will be determined by the terrain slope.

This is to prevent characters from bouncing up a hill, which is unrealistic. If a character does not have enough height for a bounce, that character will slide along the terrain and that will be determined by the direction he was bouncing and the terrain slope. In our next version we will include Dynamic Friction based off of levels so that a character would slide more on ice, and not slide at all on rubber surfaces.

struct moo9 {
	point start; // where the character starts sliding
	point end; // where the character end sliding
	double velocity; //how fast the character slides

} SlideData;

// the follwing function is used by the server to send a "slide" data to the client
SlideData sendSlideData();

Collision Detection

Collision can occur between two objects and objects within the space they are confined to.

Defined are what objects may collide with each other:
Characters and Characters
Characters and projectiles
Characters and melee weapons
Characters and end of playable area (far-most left, right, up, and defined ground of map/screen) Weapons and end of playable area

Melee weapons may not collide with each other. The same holds true for projectiles.

Characters can collide with each other. This can take place on the ground or in the air. Upon happening on the ground, the characters cannot pass each other. Both characters come to a halt. No character is stronger or more powerful than the other so none has an advantage in forcing another character to retreat upon collision. The same holds true for aerial collision. Upon two characters colliding in the air, both characters will fall towards the earth straight down based on gravity. They will not bounce back from each other.

Upon characters reaching the end of the map/screen, the character will halt and cannot advance any further. If a character collides with the end of the screen while aerial, the character will fall towards the earth based upon gravity.

Character collision against the ground occurs only when a player is falling towards the ground after being airborne.

Characters can collide with weapons. That is, any intrusion into the character pixilated area by a weapon will inflict collision. When a melee weapon enters this space, collision occurs and the event is handled appropriately (such as damage given). The same holds true when a projectile enters this space.

Upon implementation of teams, collision detection will be disabled between team members and any infliction by weapons.

To detect collision, each sprite will have a bitmask. This will define the possible colliding pixels for overlap detection. Basically, this will be a rectangular outline of the object. Upon rectangles overlapping each other, the overlapping parts will be tested for collision. Therefore, the pixels of the actual sprite object overlapping one another will trigger this event.
Severe collision, collision of more than one pixel, has no more affect then a single pixel collision.

Collision detection only takes place on active sprites. That is, any characters or weapons calculate collision detection. Walls, end of screen, and etc. do not need to worry about it for the characters or weapons will detect when collision occurs.

Upon collisions between each sprite, when reported, applicable animations may occur (such as weapons exploding, character reacting to damage, etc.)

//Character class implementation
private const int HEIGHT = <value>
private const int WIDTH  = <value>
private int north;
private int south;
private int east;
private int west;

public int getNorth() {
    return north;
}

public int getSouth() {
    return south;
}


public int getEast() {
    return east;
}

public int getWest() {
    return west;
}

public void setPosition(int x, int y) {
//Check if this is a valid x/y position in regards to
// terrain, set the new x/y if necessary, and continue on


//If bottom of screen collided (this equals death; 
//falling into a pit)

//Check for collision with the top, left, and 
// right of screen

//Check for collision with other characters 
// - loop through all characters on the screen
									
//If no collision took place (excluding pit fall), 
//set the new coordinates

//If pit fall occured, so does death

}

//Weapon class implementation
									
private const int HEIGHT = <value>
									
private const int WIDTH  = <value>

									
public void setPosition(int x, int y) {
									
//Check to see if collision occurs against the terrain
									
//Check for collision with the top, bottom, left, 
// and right of screen
									
//Check for collision with other characters 
// - loop through all characters on the screen
									
//if collision occurs
									
//apply character damage upon collision
									
//Weapon should explode/disappear at this point 
//if it is a projectile type weapon
								
//If no collision took place, set the new coordinates
}
								

Damage Calculation

Upon characters colliding with weapons (reported via collision detection method), damage will be taken. Weapon damage will come from two ways: melee attack collision or projectile collision.

Each individual weapon will have its own defined damage it may inflict.
Projectiles tend to inflict more damage than melee damage infliction.

When damage infliction occurs, the characters hit points will be decremented. Each weapon will return the amount of hit points to be decremented. When hit points are decremented, the characters life bar will decrease. Also, death must be detected. When a life bar reaches it's minimum, the player loses the game and assumes death.

public int getDamage() {
/*
This method is within the Weapon class that is inherited
by all weapon types
return number of hit points to decrement */ }
public void decrementHP(int damage) {
//This method is within the Player class
hitPoints -= damage;
updateLifeBar();
detectDeath();
}
public void updateLifeBar() {
/*
This method is within the Player class
Update life bar based upon remaining hit points */ }
public void detectDeath() {
/*
This method is within the Player class
upon hit points reaching zero or below, death occurs */
character.initiateDeathAnimation();
}
public void initiateDeathAnimation() {
/*
This method is within the Character class
Animate characters death
Disable all player control
*/
}
Name Type Damage Notes
Guitar melee 10-100 comes with Rocker
L-pipe melee 10-100 comes with Plumber
Sword melee 10-100 comes with Knight
Bayonette melee 10-100 comes with Soldier
Syringe melee 10-100 comes with Nurse
Platter melee 10-100 comes with Waitress
Sling Shot short range 5-50 50 price, unlimited ammo
Pistol short range 50-200 150 price, 50 for 10 ammo pack
Shotgun short range 100-300 200 price, 100 for 10 ammo pack
Rifle long range 50-150 200 price, 100 for 10 ammo pack
Cannon artillery 100-300 200 price, 200 for 10 ammo pack

 

Input API

Input to the game will be through a keyboard and mouse. The keyboard will be use for movements, while the mouse used for aiming. The character you control will not do anything unless you tell it to; basically the process that controls your character will wait until you give it an input. This will be achieved by

  • declaring a SDL_Event
  • using SDL_WaitEvent to wait for a user action
  • the action to watch for would be SDL_KEYDOWN

Keyboard Input

  • Moving your Character
    • Once a directional key is pressed (left or right), the game will be sent into a state where the character will move in a certain direction until:
      • a) the character runs into an object, or
      • b) character gets propelled into the air by an explosion or by falling off the terrain
      • c) a KEYUP for the same directional key is detected (key is depressed)
    • Part c is important because otherwise we'd have to press and release the right directional key 100 times to move right 100 units. By detecting the KEYUP event, we can just hold the key down to get constant walking motion.
  • Selecting Weapons
    • The numerical keys 1, 2, 3, 4, etc. will control the "category" of weapon to select.

      1 = melee
      2 = short range
      3 = long range
      4 = artillery
      5 = special/items

    • Once a category is selected via the numbers, the topmost weapon in the category is selected. If there are multiple weapons in the same category, the user can press that same number multiple times to select among the weapons in that category.
  • Jumping
    • The jump vector will be determined by whether a directional key is held or not. The distance of a jump will be static. If "jump" is pressed by itself, the character will jump straight up. If jump is pressed while the character is moving right, the character will jump right. Conversely, if jump is pressed while the character is moving left, the character will jump left. Jump is stopped if:
      • a) the character jumps into a wall
      • b) the character is hit by a weapon in mid-air
    • In the former case, the character will just drop straight down.
      In the latter case, the character will react appropriately according to the physics model.
      21:00

Mouse Input

  • Moving the Mouse
    • The mouse will control the in-game cursor. If no button is pressed, the mouse will serve no purpose other than to cosmetically orient the character either left or right (depending on whether the mouse cursor is to the left or right of the character). Since the player is oriented depending on the mouse cursor location, this will give other players a general idea as to where the player's interest is at (depending on whether he is facing left or right).
    • Shooting
      • Once the left mouse button is clicked, the character will "fire" at whatever x-y location the mouse pointer is at. If the weapon can be charged up, such as the bazooka, a "charge meter" will appear. The longer the left mouse button is held, the farther the weapon "shell" will shoot. For charge up weapons, the actual firing of the weapon will commence when the left mouse button is depressed. For normal weapons, as soon as the left mouse button is clicked, the weapon will fire.
    • Selecting Weapons (alternate)
      • Another way to choose weapons is to click the right mouse button. This will open up a "weapons palette" of all available weapons the character has, and the user can now select a weapon by left clicking on a weapon. This may be faster than using the keyboard if you want to select a certain weapon that's in a category shared by many other weapons.
Character Class
class Character
{
public static const int TOTAL_NUMBER_OF_WEAPONS = 6;
public static const int MELEE = 0;
public static const int SLINGSHOT = 1;
public static const int PISTOL = 2;
public static const int SHOTGUN = 3;
public static const int RIFLE = 4;
public static const int CANNON = 5;
public static const int NO_WEAPON = -2;
public static const int UNLIMITED_AMMO = -1;
private int health;
private int money;
private int score;
private int x;
private int y;
private int weapon[TOTAL_NUMBER_OF_WEAPONS]; 
// the array will contain NO_WEAPON, UNLIMITED_AMMO, 
// or the number of ammo currently owned
public void setHealth( int newHealth );
public void setMoney( int newMoney );
public void setScore( int newScore );
public void setLocation( int x, int y );
// sets the x, y location for use by sound and drawing
//Sound API
void playSound( int action )
// it will play the appropriate sound for the 
// particular character with the appropriate panning for the x/y
//Graphic API
void draw( int pose );
// it will accept the Character Animation Sequence 
// Constants, along with the current state
// of the animation sequence in the character class 
// instance fields and then draw the correct sprite
// bitmap at the x, y location where the x and y are the 
// lower left corner, as well as a health bar
// floating above the character
//Character Animation/Sound Sequence Constants
//The drawCharacter method will get the constants 
//defined below to draw appropriate sprites onto the //screen
// standing still (1 frame, repeated ) no sound defined
public static const int FACE_LEFT
public static const int FACE_RIGHT
// walking (5 frames, repeated) no sound defined
public static const int WALK_LEFT
public static const int WALK_RIGHT
// jumping (~ 10 frame 5 reversed )
public static const int JUMP_LEFT
public static const int JUMP_RIGHT
public static const int JUMP_VERTICALLEFT
public static const int JUMP_VERTICALRIGHT
// "baseball bat" ( 10 frames )
public static const int MELEE_LEFT
public static const int MELEE_RIGHT
// Short/Long Range Projectile ( 20 frames )
public static const int SHOOT_LEFT
public static const int SHOOT_RIGHT
// Artillery ( 10 frames ... rest is
public static const int LAUNCH_LEFT
public static const int LAUNCH_RIGHT
// damaged from "baseball bat"
public static const int MELEED_LEFT
public static const int MELEED_RIGHT
// damaged from shooting/artillery
public static const int SHOT_LEFT
public static const int SHOT_RIGHT
// death sequence ( 20 frames )
public static const int DEATH

Music API in the main program
background music will be playing in a separate thread --hopefully, and music will be queued up for a certain stage, and is looped

void playMusic( enum stage );

Graphic API in the main program

void addMessage( char* message );
// this function will add the message into a queue which 
// will be displayed when drawMessages is called
void drawMessages( void );
// draws the global 'chat'
void drawPlayerStats( void );
// draws points and money of the player in the top part 
// of the screen

Level (stage) Terrain File Format

The level will be pixel format which will be loaded into memory, which then will reflect terrain damage. To make loading simple, we will use a primitive pixel format called PPMA. The advantage is that it is an ASCII format, which will allow easy loading of the files.

PPMA is the ASCII version of the portable pixel map PPM format. It is a simple RGB color image description. The definition is as follows:
• A "magic number" for identifying the file type. A PPM file's magic number is the two characters "P3".
• Whitespace (blanks, TABs, CRs, LFs).
• A width, formatted as ASCII characters in decimal.
• Whitespace.
• A height, again in ASCII decimal.
• Whitespace.
• The maximum color-component value, again in ASCII decimal.
• Whitespace.
• Width * height pixels, each three ASCII decimal values between 0 and the specified maximum value, starting at the top-left corner of the pixmap, proceeding in normal English reading order. The three values for each pixel represent red, green, and blue, respectively; a value of 0 means that color is off, and the maximum value means that color is maxed out.
Characters from a "#" to the next end-of-line are ignored (comments). No line should be longer than 70 characters.

EXAMPLE: this ppm example is a diagonal black line
        P3
        # example.ppma
        4 4
        15
         0  0  0  0  0  0   0  0  0   15 15 15
         0  0  0  0  0  0  15 15 15    0  0  0
         0  0  0 15 15  0   0  0  0    0  0  0
        15 15 15  0  0  0   0  0  0    0  0  0