In the last class, we went over some networking capabilities using the enet library. The enet library is a C-based library (similar to raylib) that is used to manage game-oriented networking. In general, once you have set up the networking system, you handle network messages every time you draw a frame, and it will operate very quickly and seamlessly.
I have provided a zip download for you on discord, but I've also put it here so you can download it. Hopefully you have set up your code to properly use the library, along with Raylib, and we can now write an application that uses both to make a networked game! If you are struggling with this setup, please ping me on discord, and I will help you complete the setup. You NEED a working setup to do this homework.
Networking is an extremely complex and difficult problem to solve. In essense, imagine a room of 100 people all trying to communicate. Some people may be trying to talk to people next to them, or maybe shout to someone on the other side of the room. Now imagine that they are in a building with 100 rooms, and there are thousands of buildings like this all over the world. And people in any room need to be able to communicate with people in any other building! That is the problem that people are trying to solve with networking.
Thankfully, people have been working on these problems for many many years, and libraries such as enet have been created so we don't have to worry about the small details of networking.
The Internet is really a set of computers all plugged into the same system, and they can talk to each other by asking Internet equipment (such as routers) to send messages to another computer. To do this, you need 2 things -- the IP (or Internet Protocol) address of the other computer, and a port. The port is a number from 0 to 65536 (or 216) that distinguishes which thing on that other computer you want to talk to. If any of you have set up or used a networked server for game playing, you probably are used to specifying an IP address and port. The difference in our system is that we are going to do this in code instead of using a text field to accept the parameters.
Enet sends messages to the server, and then the server can send messages to the client. Messages can be anything, and what we are going to send is data of a specific type. I have written a small client system based on enet (inside the zip file as client.d) which can send any type of message to the server. Copy this client.d to your "source" directory in your project. I have also written a server that takes any message received from a client, and sends it to all the clients (including the one who sent it).
The client can register a handler function for any type, and when it receives a message of that type, it will call that function with the message as if the server called that function directly. This way, you can tell the program what to do when a message of a certain type is received.
The enet library itself takes care of making sure the messages get to the right places. The clients and server communicate to let the clients know any new messages that were sent, if any new clients connected, or disconnected. So using these pieces, writing your code to deal with networking becomes really straightforward. A message from the server just becomes another piece of input for you to handle (like a mouse click, or a key press).
Your job using these libraries is to make the application look like you are all playing on the same system.
Let's write a simple program that allows you to send a color to the server for your "player". Then the server will send it to all the other players, and they can display the networked players on the screen so you all are seeing the same thing. The only difference is, I'll let you decide how to use the color and how to draw the other players! So everyone's program will look different, the only similar thing will be the colors of the players.
In order for the Client
to understand the messages, EVERYONE has to use the SAME message type. If you don't, you will get errors! So make sure your ColorMessage
matches exactly (including names, capitalization, and the type of data inside it) what is listed here.
Here is the code to start with. I want you to fill in the code that handles what to do with the color message, and also write the code to handle your changing of your own color (including sending the color message out):
import std.stdio;
import raylib;
import enet.enet;
import client;
// this is the message we will send to the others
struct ColorMessage
{
Color color;
}
void main()
{
InitWindow(500, 500, "Color Match");
Color me = Colors.BLACK; // my color
Color[size_t] others; // colors of all the users
// initialize enet
enet_initialize();
// create the client
Client client;
client.initialize("codingcat.club", 6666);
void handleColorMsg(size_t id, ColorMessage msg)
{
// in here, id is the id of the sender, and msg is the color message
// they sent.
// uncomment this line to see when the colors are received.
// writeln("client id ", id, " sent color ", msg.color);
// [INSERT CODE HERE] we want to save the color associated with the
// id in the `others` variable.
}
// register the function for the client to call when it receives a ColorMessage
client.onMsg(&handleColorMsg);
// begin by telling everyone the current color
client.send(ColorMessage(me));
SetTargetFPS(60);
while(!WindowShouldClose())
{
// handle network traffic
client.process();
// handle mouse clicks
// [INSERT CODE HERE] design a way to set the color
// Don't forget to send the new color to the server!
BeginDrawing();
ClearBackground(Colors.WHITE);
// draw my color
// [INSERT CODE HERE] maybe a circle? Put it somewhere on the screen!
// client.validPeers is an array of ids of other players that are
// currently registered on the server.
foreach(idx, id; client.validPeers)
{
// get the color stored for that peer from the associative array
Color otherColor = others.get(id, Colors.GRAY);
// [INSERT CODE HERE] Draw the other player
// note that idx is a number from 0 to the number of players you can use to
// figure out where on the screen to draw it.
}
// Draw anything else you need for the User Interface.
EndDrawing();
}
// disconnect the client from the server
client.disconnect();
// shut down the networking system
enet_deinitialize();
// close the window
CloseWindow();
}
You need to fill in the parts that say [INSERT CODE HERE]. Let me take a few paragraphs to unpack what I want you to do here. The handler function, handleColorMsg
, is going to be called whenever a client sets its color. So inside that function, you need to do something with that message. The others
associative array is an array that uses size_t
as the index (and that is the id type of the clients), and uses Color
as the value. If you don't remember, you can access values in an associative array by using brackets on the index. To set a value use others[id] = aColor;
and to get a value, you can use others[id]
The code is registering the handler for you with the onMsg
call. Take note at how we just have to tell the client "use this thing", and it figures out how to receive the messages and call your function. That code is all written in the client.d file.
Inside the loop, you can see that network processing is handled by calling client.process();
. This line sends and receives messages from the server, and calls any message handlers you have registered.
The major part you need to write is the drawing of your player's color (stored as the me
variable), the drawing of the other players' colors inside the foreach loop, and dealing with changing the color. I purposely left this blank (I will post a short video in discord showing how my client works). This is all normal raylib stuff, which you have done before -- draw circles rectangles, whatever you like. Handle keyboard presses or mouse clicks to get the color to change. Use whatever colors you want! The one thing to remember is that you must send the server your new color if it changes. You only should send it when it changes (otherwise, the server will get 60 messages a second from everyone).
One final note: you will notice this line is different for getting a value from the associative array:
Color otherColor = others.get(id, Colors.GRAY);
What get
does is to retrieve the value for the given id if it exists. If it doesn't exist, it returns the value you passed in (in this case, Colors.GRAY
). If instead, I just did others[id]
and the value didn't yet exist, it would kill the program with an Exception. Why do you think we need to do it this way?
I don't expect you to all have a completely working client by the time class happens on Friday. But do the best you can to get there. I hope at the end of class, we all have working clients that can talk to each other. I think you can probably see how this can progress to a Rock-paper-scissors game! I'm leaving the server open at codingcat.club, on port 6666, but if you find the server isn't working, let me know, and I will restart it.
©2021 Steven Schveighoffer