Due date: Sunday, September 29th, 11:59pm
The goal of this project is to leverage the code and skills acquired in Project 1 (Serialize) and Project 2 (Yak) to design and implement
Overview | Details | Tips | Submission | Grading
Details: Synch | Messages | Game Objects | Server | Client
For functionality,
In general, there are a variety of ways that the game objects can be modified and synchronized between server and clients, especially player-controlled objects. Usually, only "important" objects and associated events are synchronized. For example, in Fruit Ninja, a fruit being sliced is an important (perhaps, the most important) event and should be synchronized across all computers. Explosions when a Fruit is sliced, on the other hand, only provide decorations and, as such, do not need to be synchronized.
For this project, a client must only gather player input and send it to the server but not act upon the input. This means the client gets mouse movements and sends the data (i.e., the (x,y)
coordinates) to the server. The server then moves the mouse and syncs the new location with the clients. Given this restriction - only the server moves Swords in response to mouse input - the recommended design is to have the server determine when a Fruit is sliced.
Note, in theory, having the same random seeds on clients and the server could mean that random events (controlled by rand()
) such as the spawning of Fruit do not have to be synchronized, but in practice, this requires events to take place at specific game clock times. Such timed delivery of events is not currently supported by Dragonfly. So, the suggested implementation has only the server spawn Fruit (via the Grocer), with the newly-created game objects then synchronized with the clients.
Details: Synch | Messages | Game Objects | Server | Client
A message format and protocol (a core component of any client-server communication) should be designed for both server-to-client communication and client-to-server communication. The client may only communicate player input (e.g., mouse positions) to the server, but the server needs to communicate at least several types of messages (e.g., sync object, destroy object, game over). And both client-to-server and server-to-client options should include an exit message if they disconnect. Note, message types can be setup as an enum class
.
A suggested format is to include a base header for all messages:
size
: the entire message size, in bytes (an int
)message_type
: the message type (an enum
, effectively an int
)Additional elements depend upon the message type.
When the message type is to synchronize an Object, the body is then:
id
: the Object id (an int
)object_type_len
: length of the Object type C-string (an int
)object_type
: the Object type (a C-string)data
: the rest of the message bytes are the serialized attributesWhen the message type is to delete an Object, the body is:
id
: the Object id (an int
)When the message type is game over or exit, there is no body.
For client-to-server communication, when the message type is mouse movement the body is:
mouse_x
: the x position of the mouse (a float
)mouse_y
: the y position of the mouse (a float
)After pulling the message from the socket (via the NetworkManager receive()
), the server or client can subsequently check the message type and take appropriate action.
The server takes actions for the incoming message type:
mouse movement - update Sword position and synchronize with all clients.
exit - remove client connection from the game, including all allocated Sword and Points objects for that client.
The client actions for the incoming message type:
syncrhonize - if the client does not find an Object with the indicated id, it first creates (new
) an Object of the appropriate type. Either way, the client then updates the attributes (i.e., calling deserialize()
).
delete - find the Object with the indicated id, then mark it for deletion (WM.markForDelete()
).
game over - create a GameOver
object and when that animation has completed, shut down the game engine.
exit - close the network connection to server and shut down the game engine.
Details: Synch | Messages | Game Objects | Server | Client
Some suggestions for modifications to the base Fruit Ninja objects in order to support multiplayer gameplay.
Sword
Unlike in the single player game, a game can have multiple Swords. The server needs to keep track of which Sword is for which client. The suggestion is to keep the client socket index with the sword. This attribute (the socket index) only matters for the server, however, and does not need to be synchronized with the clients.
Each Sword should have an individualized color for each player, where the color of the sword character (+
) should match the trail. The suggestion is to assign colors at the server based on the socket index of the client connection.
Swords no longer change position with mouse movement. Instead, if the mouse moves at the client, it transmits a mouse movement message to the server whereupon the server then moves the appropriate Sword according to the received mouse position. If the mouse moves at the server, it is ignored.
When a Sword changes positions, the clients do not determine if it slices a Fruit. That functionality (determining slicing) is handled only by the server.
The server does not need to create Sword trails. Instead, the client creates trails when it moves a Sword upon receiving a sync message from the server and updating the position.
Grocer
Only the server has a Grocer object. Thus, the Grocer does not need to be serialized/deserliaized or otherwise synchronized between clients and server.
The Grocer sends a game over message to all clients when the game is over (i.e., m_wave == NUM_WAVES+1
).
Fruit
The Fruit movement can be handled by the server and all clients in parallel (i.e., their position changes do not need to be synchronized).
Fruit on the client does not need to handle collisions (e.g., when sliced by a Sword). That functionality - slice determination and collisions with a Sword - is done only by the server.
Similarly, Fruit on the client does not need to handle out of bounds events. That check is done only by the server, sending a delete message to the clients when a Fruit goes out of bounds.
The server does not need to create an explosion (i.e., in the Fruit destructor) when a Fruit is destroyed. That only needs to happen on the client.
Points
Points need to be modified to be at a different location on the screen for each client. The suggestion is to have the location (such as lower left, lower middle, lower right, center middle, center right) determined at the server based on the socket index of the client connection. All Points can white, or they could take on a color based on the corresponding Sword color.
The Points should have a different string based on the client. e.g., "Player1: " and "Player2: ". That way, they can respond to a points event (EventPoints
) based on that player's Sword slicing. The suggestion is to use socket index at the server to customize this string for each corresponding Points object.
Timer
Kudos
The Kudos objects are determined and created only at the server.
An individual Kudos does not need to be synchronized across all clients - only for the client that earned the Kudos.
GameOver
The GameOver object does not need to be synched. It can be created independently on the client and server.
When the GameOver animation is complete on either a client or server, all network connections are closed and the game engine shuts down.
Details: Synch | Messages | Game Objects | Server | Client
Server-only functionality is most easily handled by a Server game object.
Upon creation, the Server object sets the NetworkManager to server mode (setServer(true)
).
The Server handles network events and step events. For network events:
Upon accept, the Server should create a Sword of the appropriate color.
Upon data, the Server should determine if it is an exit message or a mouse movement message. If the former (or if the NetworkManager determines a socket has been closed by the client) the Server should close the network connection and destroy any associated Objects with that client (i.e., Sword and Points). If the latter, it updates the mouse position for the corresponding sword.
For step events, the Server primarily looks to see if any Objects need to be synchronized with the clients.
Swords - are synchronized with all client when created and when their positions change.
Fruit - are synchronized on all clients only when created.
Timer - is synchronized on all clients only when created,
Points - are synchronized on all clients when created and when their values are updated.
Kudos - are synchronized on only the client that earned them and only when created.
For ease of Object management, the Server should keep track of all Swords and Points with a list (e.g., an array or a std::vector
) for each.
Note, when creating the initial Sword on the server upon connection and then synchronizing with the clients, care must be taken not to have an Object id be the same as another Object id on a client. The suggestion is to set the id manually on the Server when it is created iniitally, using an offset of a large number (e.g., 100) and the socket index for the id.
Tip: The Server can tell if an Object is newly created if the method Object::isModified(df::ObjectAttribute::ID)
returns true.
Tip: The Server can tell the type of an Object with either a dynamic_cast
(e.g., if ((dynamic_cast <Fruit *> (p_o)) != NULL) ...
for a Fruit) or via Object::getType()
(e.g., if (p_o -> getType() == SWORD_STRING) ...
for a Sword).
Tip: A modified object ID can be checked via Object::isModified()
and a df::ObjectAttribute::ID
mask (e.g., unsigned int mask = (unsigned int) df::ObjectAttribute::ID
).
Tip: A modified Points value can be checked via ViewObject::isModified()
and a df::ViewObjectAttribute::VALUE
mask (e.g., (unsigned int) df::ViewObjectAttribute::VALUE
).
Details: Synch | Messages | Game Objects | Server | Client
Core client-only functionality is most easily handled by a Client object.
Upon creation, the Client object sets the NetworkManager to client mode (setServer(false)
). It should also provide a way for the player to enter the server hostname (e.g., by spawning a ServerEntry object).
The Client should handle network events. For network events:
Upon close, the Cient should close the network connection and shutdown the game engine.
Upon data, the Client should determine the message type:
exit - the Client should shutdown the game engine.
game over - the Client should spawn a GameOver object and, when its animation finishes, shutdown the game engine.
delete - the Client should destroy (WM.markForDelete()
) the game object with the indicated id.
sync - the Client should first determine if the object exists. If not, it should create an Object of the right type. Then, for the previous object or the newly-created one, it should synchronize the attributes via deserialize()
.
Tip: The Client can tell if an Object already exists via trying to fetch it with WM.objectWithId()
.
Tip: For modularity and readability, the Client may want a method that creates an Object of the indicated type and returns a pointer to it. e.g., Object *createObject(std::string object_type)
. It can use this method upon receiving a sync message from the server and not having an Object with the indicated id.
Details: Synch | Messages | Game Objects | Server | Client
Overview | Details | Tips | Submission | Grading
It may be helpful to extend the NetworkManager to support more general sending messages. While sent messages have a common header, the body depends upon the type. Versions of the sendMessage()
method can be invoked based on the message type and parameters passed in, formatting and actually sending the message as appropriate. Versions could include:
// Send message from Server to Client.
// GAME_OVER or EXIT
// Return 1 if something sent, 0 if nothing sent, -1 if error.
int sendMessage(MessageType msg_type, int sock_index=-1);
// Send message from Server to Client.
// SYNC_OBJECT or DELETE_OBJECT
// Return 1 if something sent, 0 if nothing sent, -1 if error.
int sendMessage(MessageType msg_type, df::Object *p_obj, int sock_index=-1);
// Send message from Client to Server.
// MOUSE_INPUT
// Return 1 if something sent, 0 if nothing sent, -1 if error.
int sendMessage(MessageType msg_type, df::Vector mouse_position, int sock_index=-1);
The sock_index=-1
parameter defaults indicate the send should be to all connected sockets.
For development and testing on a single computer, the hostname localhost
can be used to run a server and have clients on the same computer connect to it. Having multiple clients running on one computer is easier when the clients are not in fullscreen mode. With window_style:default,
. Then, the window dimensions can be made small - small enough to fit multiple client windows on one monitor via something like: window_horizontal_pixels:600,
and window_vertical_pixels:450,
.
For mouse input for a specific client, it can be helpful to only respond when the mouse is over the corresponding window. This can be done in
// Check if mouse outside game window.
sf::RenderWindow *p_win = DM.getWindow();
sf::Vector2i lp = sf::Mouse::getPosition(*p_win);if (lp.x > df::Config::getInstance().getWindowHorizontalPixels() ||
0 ||
lp.x <
lp.y > df::Config::getInstance().getWindowVerticalPixels() ||0) {
lp.y < // Outside window so don't respond to mouse input.
else {
} // Inside window so respond to mouse input.
}
For development, start with just the Sword, and not other objects. Have it created at the server when a connection comes in from a client. Then, mouse input at the client is sent to the server and the server moves the sword. The new position is sent back to the client and sees the Sword move. Doing this will help get the foundations of server synchronization with the client (to create the Sword), client-to-server communication (mouse movement) to move the Sword, and then again server synchronizaiton with the client (to move the Sword).
Once the Sword is successfully implemented and debugged, then add a single piece of moving Fruit, created on the server and synced with the client. Then, add Fruit slicing and out of bounds (done at the server), then deleted and synced (with the client)
Then, the Grocer (at the server) that spawns multiple Fruits. Then, add Points. Then, GameOver. Then, the Timer. Lastly, Kudos.
Make sure the window size (in characters) is the same on the server as it is on all the clients. This is true even if the server is headless, as the window size parameters provide the default game world size, too. e.g.,
#
# Server configuration file.
#
# Run in headless mode (no graphics window or input).
headless:true,
# Window dimensions in characters.
window_horizontal_chars:80,
window_vertical_chars:26,
Overview | Details | Tips | Submission | Grading
Your assignment is to be submitted electronically (via Canvas) on the time and day due. You must hand in the following:
Source code:
.h
files.A README file listing: platform (Windows, Mac or Linux), how to compile (if there are special settings), how to run (ditto) and anything else needed to understand (and grade) your game. You should indicate how the two players compete or cooperate, depending upon your implementation.
VIDEO showing: A) your code compiling, B) your
Before submitting, "clean" your project:
in Visual Studio:
Build
-> Clean solution
vs-2022/.vs
directoryvs-2022/x64
directoryin Linux/Mac: make clean
This will remove all the temporary (compiled) files. Failure to do may mean you file is too big to submit!
You do not need to submit any Dragonfly or SFML files.
Use zip
to archive your files.
To submit your assignment (say, lastname-proj3.zip
) via Canvas:
Open: Assignment - Project 3 Click:
Upload
Click:Drag your zip file,
Select:lastname-proj3.zip
Click:Submit Assignment
When successfully submitted, you should see a message similar to:
SUBMITTED on September 29, 2024 3:54pm
Important - you must click the Submit Assignment
button at the end or your file will not be submitted!
Overview | Details | Tips | Submission | Grading
Swords - 30% : Correctly supporting creation and synchronization of Swords across multiple clients is worth about 1/3 of the grade. This includes a single server with 2+ clients that can connect and move (and see each other's movement) of Swords. Implementing this aspect is crucial to demonstrate an understanding of multiplayer game programming and get the rest of the multiplayer game features working.
Fruit, Slicing and Points - 30% : Determining slicing on the server and synchronizing it (with explosions and sound) on the client is part of the core game functionality and worth about 1/3 of the grade.
Grocer, Timer, Game Over, and Kudos - 30% : Getting the "extras" working (created, synchronized) provides for complete gameplay and is worth about the remaining third of the grade.
Documentation - 10% : Not to be overlooked is including the documentation provided, as well as having that documentation be clear, readable and pertinent to the assignment. This primarily includes the README described above but also any needed supporting information. Having well-structured, readable and commented code is part of Documentation, too. Getting in the habit of good documentation is important for large software projects, especially when done in teams.
Note, bugs and/or code that has not been tested can result in points taken off for any of the areas above.
100-90. The submission clearly exceeds requirements. The Swords are synchronized appropriately and appear to move flawlessly. Fruit-slicing and Points also work flawlessly. All extras - Grocer, Timer, Game Over, and Kudos - are in place for full-
89-80. The submission clearly meets requirements. The Swords are synchronized appropriately and appear to move smoothly. Fruit-slicing and Points work well. Most of the extras - Grocer, Timer, Game Over, and Kudos - are in place and work well. Documentation is thorough and clear and code is well-structured, and mostly well-commented and readable.
79-70. The submission barely meets requirements. The Swords are synchronized but may not work smoothly or robustly. Fruit-slicing and Points may not work consistently. Some extras - Grocer, Timer, Game Over, and Kudos - may be missing or not robust. Documentation is present, but may be unclear or missing aspects and code may be difficult to read and parts of the code poorly-structured and/or uncommented and difficult to read.
69-60. The project fails to meet requirements in key places. The Swords may not be correctly synchronized or working correctly. Fruit-slicing and Points may not work correctly nor robustly. Extras - Grocer, Timer, Game Over, and Kudos - are mostly missing. Documentation is inadequate or missing and some parts of the code are poorly-structured, uncommented and difficult to read.
59-0. The project does not meet many of the key requirements. The Swords are not be correctly synchronized or working correctly. Fruit-slicing and Points do not work correctly nor robustly. Many or even all extras - Grocer, Timer, Game Over, and Kudos - are missing. Documentation is woefully inadequate or missing and code is poorly structured, uncommented and difficult to read.
Overview | Details | Tips | Submission | Grading