Due date: Friday, May 2nd, 2014 at 11:59pm
You will take a game with a traditional client-server architecture and create a working "cloud" version. In the cloud, a server will do the heavy-weight game computations, sending the game screen frame by frame as a sort of "video" to the client. The thin client ("thin" because it does not do game computations) receives and displays the game-video. The client also captures player input (keystrokes) and sends it to the server for processing.
Before undertaking your cloud-game implementation, work through the Saucer Shoot tutorial, making a simple game using Dragonfly. The tutorial helps better understand a game engine by developing a game from a game programmer's perspective, and familiarizes you with some of the knowledge needed for extending Dragonfly to a cloud-based system. Your new game will be a direct port of Saucer Shoot, only to the cloud.
The server starts up the game engine and gets ready for a client to connect by waiting on a well-known port. Once the client connects, the server does all the game computations - keeping track of objects, determining object collisions and even rendering frames on the screen. However, unlike in a traditional game, there is no player viewing the game and providing input where the game is running. Instead, the server "scrapes" the screen every frame it renders, sending the frames down the network socket to the client. Player input, which usually arrives by keyboard where the game is running, arrives by the network socket, sent from the thin client. All input thus received is applied to the game as if they player were at the keyboard of the server.
The server only has to handle one client for one game. When the one game ends, the server closes the network connection and then exits. Subsequent games would restart the server (and client).
The client starts up and immediately connects to the server using the hostname provided by the player and the well-known port. Once connected, the client receives input over the network socket in the form of the game frames to be displayed, displaying them one at a time on the screen. The client also captures keyboard input from the player, sending all key presses to the server.
When the game is over and the network connection closes, the client exits.
You will do the tutorial first, as this will help set up your Dragonfly development environment and understand the relationship between the game you are porting and the Dragonfly game engine. Once done with the tutorial, you can begin your design and implementation. Your client and server must use the pre-compiled Dragonfly game engine, obtained from the Dragonfly Web Page.
You must do your coding in C/C++ in Linux, such that it runs on the WPI CCC machines or in Cygwin in Windows. The CCC machines all run Linux and Cygwin is installed on all the Zoo lab computers.
You will work alone for this project. While you can discuss the project with other students and even help each other debug code, you must write all code yourself.
You will need to include a Makefile that allows your program to be built and a README explaining how to run your game system. See Hand In for details.
There are many ways to design and implement the functionality necessary for this project. However, a strong suggestion is to adopt/use three main elements from Dragonfly: 1) managers, 2) events, and 3) curses.
In Dragonfly, major game engine functionality is done by individual managers (e.g., the WorldManager that handles the game world state, and the GraphicsManager that handles game graphics). These managers all inherit from the base Manager class and implement the singleton pattern (the latter limits the engine to one instance of the class and also provides global access to the manager).
You are encouraged to make a NetworkManager that handles the network activity, akin to the other Dragonfly managers. Like other managers, the NetworkManager inherits from the Manager base class, implementing the singleton design pattern. A possible header file is below:
// // The network manager // #ifndef __NETWORK_MANAGER_H__ #define __NETWORK_MANAGER_H__ #include// Engine includes. #include "Manager.h" #define DRAGONFLY_PORT "9876" // Default port. class NetworkManager : public Manager { private: NetworkManager(); // Private since a singleton. NetworkManager(NetworkManager const&); // Don't allow copy. void operator=(NetworkManager const&); // Don't allow assignment. int sock; // Connected network socket. public: // Get the one and only instance of the NetworkManager. static NetworkManager &getInstance(); // Start up NetworkManager. int startUp(); // Shut down NetworkManager. void shutDown(); // Accept only network events. // Returns false for other engine events. bool isValid(string event_type); // Block, waiting to accept network connection. int accept(string port = DRAGONFLY_PORT); // Make network connection. // Return 0 if success, else -1. int connect(string host, string port = DRAGONFLY_PORT); // Close network connection. // Return 0 if success, else -1. int close(); // Send buffer to connected network. // Return 0 if success, else -1. int send(void *buffer, int bytes); // Receive from connected network (no more than nbytes). // Return number of bytes received, else -1 if error. int receive(void *buffer, int nbytes); // Check if network data. // Return amount of data (0 if no data), -1 if not connected or error. int isData(); // Return true if network connected, else false. bool isConnected(); }; #endif // __NETWORK_MANAGER_H__
Unlike other Managers that are started by
the GameManager
and are typically invoked by the engine in the game loop, your
NetworkManager, not being officially part of the engine, needs to be
started and executed in game programmer code. To do so, you create a
game object (inherited
from Object),
that upon creation, starts up the NetworkManager and registers for
the step
event. Each step, it checks the NetworkManager for network data
received (i.e., calling isData()
), generating network
events when there is data (via onEvent()
).
Objects that express interest in network data register for EventNetwork events with the NetworkManager. EventNetwork is inherited from the base Event class. A possible header file for EventNetwork is below:
// // A "network" event, generated when a network packet arrives. // #ifndef __EVENT_NETWORK_H__ #define __EVENT_NETWORK_H__ #include "Event.h" #define NETWORK_EVENT "__network__" class EventNetwork : public Event { private: int bytes; // number of bytes pending public: // Default constructor. EventNetwork(); // Create EventNetwork with indicated bytes. EventNetwork(int initial_bytes); // Set number of network bytes available. void setBytes(int new_bytes); // Get number of network bytes available. int getBytes(); }; #endif // __EVENT_NETWORK_H__
Note, EventNetwork does not contain the network data itself. Instead, it merely indicates there is network data pending, leaving it up to the interested Objects once notified to pull the data from the NetworkManager.
Graphics in Dragonfly
are managed by the
GraphicsManager
using Curses. Characters drawn to the screen on the server (running
the game) can be "scraped" and sent from the server to the client,
that will display the graphics. To do this, the Curses WINDOW buffer
can be obtained from the GraphicsManager
(see getPreviousBuffer()
and getCurrentBuffer()
) and the whole screen iterated
over using the Curses call mvwinch()
. All data obtained
is then sent to the thin client. The client receives the network
data, uses the GraphicsManager to get the WINDOW buffer for its
display, then writes out the characters on the screen.
For the cloud server, the "scraping" should be done once every game frame, about 30 frames/second. This most easily done from a game object each step event.
On the thin client, Curses can be used to capture keyboard (and mouse) input, too, using the InputManager. This is most easily done by having a game object register for input events, sending any input to the cloud server.
Note, Curses characters and keystrokes are actually all of
type int
, not char
.
Suggestions on the server and client tasks, and breakdown, are as follows:
SERVER (main.cpp) Start GameManager Start NetworkManager Load game resources Start Host Run game Cleanup and exit HOST (a game object) Register for step event Register for network event Each step event Scrape screen Send to client If network input Generate network event Each network event Apply network input to game CLIENT (main.cpp) Start GameManager Start NetworkManager Start Player Cleanup and exit PLAYER (a game object) Register for network event Register for step event Each step event Check network input If network input Generate network event Each network event Receive data Draw frame on screen Each input event Send input to server
Since TCP is a stream-oriented protocol, the client can receive part of a frame even though the server sends only complete frames. It may be easier to only display full sized frames by not taking data from the network socket until an entire frame is available. Note, if the client only plays one frame per step event, this will result in a client falling behind in the visual representation, which to the player will feel like lag. To avoid this, the client may want to read and display all complete frames in a buffer that are available.
Sockets are by default blocking, meaning if a process reads from
them and there is no data available, the process blocks until there is
data. Non-blocking behavior can be achieved with the MSG_DONTWAIT
flag for a
recv()
call.
By default, a recv()
call that retrieves data from a
socket removes it from the OS buffer such that subsequent reads do not
get the same data. The MSG_PEEK
can be used to retrieve
the data but leave it in the buffer for subsequent reads.
The ioctl()
call can be used to see the number of
bytes available in a socket before reading using the FIONREAD
parameter as the second argument (e.g., ioctl(sock, FIONREAD,
&bytes)
).
You must hand in the following:
.cpp
and .h
files.
If you do not have your files on the CCC machines, then copy your
entire working directory to your CCC account. Then, login to the CCC
machines (using slogin
or putty
).
Use tar
with gzip
to archive your files.
For example:
mkdir lastname-project-cloud cp * lastname-project-cloud tar czvf project-cloud-lastname.tgz lastname-project-cloudSubmit your assignment (
project-cloud-lastname.tgz
):
/cs/bin/turnin submit cs4513 project5 project-cloud-lastname.tgz
If you need more information, see Using the turnin Program for additional help with turnin.
A general rubric follows:
100-90. The game system meets all specified requirements. Both client and server are robust in the face of possible errors, such as being unable to connect to the network or network errors in flight (TCP loss or broken connections). Game plays flawlessly with no visual glitches or input problems. All code builds and runs cleanly without errors or warnings. README has sufficient details to easily compile and run programs.
89-80. The game system meets most of the specified requirements. Both client and server have solid communication, but may have occasional problems due to the network or other system conditions. The game plays well and is definitely playable, but may have a few visual glitches. All code builds cleanly and runs. The README has sufficient details to compile and run the programs.
79-70. The game system meets some of the specified requirements. Both client and server can connect and communicate, but may have limited connection abilities. The game can be played, but may feel laggy or have considerable visual glitches. Code builds and runs but may have warnings. The README may be missing some details on how to run the programs.
69-60. The game system meets some of the specified requirements. Both client and server can connect but communication is limited. The game may start but is only somewhat playable, with input difficulties and visual glitches, and/or terminates prematurely. Code builds and runs but may have warnings. The README may be missing some details on how to run the programs.
59-0. The game system does not meet the specified requirements. Both client and server can be started but may fail to connect or stay connected. The game may start but is not displayed at the client and/or the client does not get input back to the server. Code may build, but has warnings or may not even compile. The README may be missing some details on how to run the programs.
Send all questions to the TA mailing list (cs4513-staff at cs.wpi.edu).