<p>Follow along to make your first mini WebSocket application in GDScript (with a little JSON). The client and the server will be two separate projects. I build upon the <a href="http://www.narwalengineering.com/2018/07/01/godot-tutorial-simple-chat-room-using-multiplayer-api/">NetworkedMultiplayerENet chat room tutorial by Miziziziz</a>(dead link) and the <a href="https://docs.godotengine.org/en/stable/tutorials/networking/websocket.html">HTML5 and WebSocket tutorial in the Godot documentation</a>. </p>
<p>Follow along to make your first mini WebSocket application in GDScript (with a little JSON). The client and the server will be two separate projects. I build upon the <a href="http://web.archive.org/web/20220928131714/http://www.narwalengineering.com/2018/07/01/godot-tutorial-simple-chat-room-using-multiplayer-api/">NetworkedMultiplayerENet chat room tutorial by Miziziziz</a>(archive link) and the <a href="https://docs.godotengine.org/en/stable/tutorials/networking/websocket.html">HTML5 and WebSocket tutorial in the Godot documentation</a>. </p>
<hr>
<hr>
<h2>Why WebSocket over UDP? </h2>
<h2>Why WebSocket over UDP? </h2>
<p>UDP is fast but inaccurate. It is best used for real-time action gameplay. </p>
<p>UDP is fast but inaccurate. It is best used for real-time action gameplay. TCP is slow but accurate. It is best for sharing data. You can read more about it on the <a href="https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html">Multiplayer doc page</a>. </p>
<p>TCP is slow but accurate. It is best for sharing data. </p>
<p>WebSocket uses a TCP connection. Ultimately, I am studying Godot to make a slow-paced adventure browser game, so this is one of the protocols I am considering and the protocol we will use for the tutorial. </p>
<p>In Godot, the TCP stuff is actually UDP. If you want TCP, you should use WebSocket. </p>
<h2>Do I need a dedicated server for testing? </h2>
<h2>Do I need a dedicated server for testing? </h2>
<p>Nope! You can test client & server code on your own computer. I will test on both a single computer at home and a VPS on a server stationed in another country. </p>
<p>Nope! You can test client & server code on your own computer. I am only testing on a single computer at home for now without accessing an outside VPS, etc. </p>
<hr>
<hr>
@ -32,10 +31,9 @@ august 31, 2022<br>
<pre><code>
<pre><code>
const PORT = 8080
const PORT = 8080
var _server = WebSocketServer.new()
var _server = WebSocketServer.new()
var peers = []
</code></pre>
</code></pre>
<p><code>PORT</code> is the port your applications will use. If you are having trouble connecting, try 9001, etc, until you find a free one. <code>peers</code> will let us access everyone currently connected to the server. </p>
<p><code>PORT</code> is the port your applications will use. If you are having trouble connecting, try 9001, etc, until you find a free one. </p>
<h3>Build your skeleton. </h3>
<h3>Build your skeleton. </h3>
<pre><code>
<pre><code>
@ -107,7 +105,7 @@ func _process(_delta):
<img src="/static/img/ent/Chatbox_LabelSizeFlags.png" alt="(Screenshot: VBoxContainer's Size Flags are checked for Horizontal Fill and Expand and Vertical Fill and Expand.)">
<img src="/static/img/ent/Chatbox_LabelSizeFlags.png" alt="(Screenshot: VBoxContainer's Size Flags are checked for Horizontal Fill and Expand and Vertical Fill and Expand.)">
<p>For the ChatLog (TextEdit), check the Show Line Numbers and set Size Flags to Fill and Expand for both Horizontal and Vertical. </p>
<p>For the ChatLog (TextEdit), check the Show Line Numbers and Wrap Enabled options then set Size Flags to Fill and Expand for both Horizontal and Vertical. </p>
<p>For the ChatInput (LineEdit), under Placeholder, set the Text to "Enter your message..." and the Size Flags to Fill and Expand Horizontally. </p>
<p>For the ChatInput (LineEdit), under Placeholder, set the Text to "Enter your message..." and the Size Flags to Fill and Expand Horizontally. </p>
@ -210,10 +208,487 @@ func _process(_delta):
<p>But however you run, if you run the server first and then the client, you will see your connection in the debug output! You have completed an extremely basic networking application. You can see the full project at this stage on my repo: <a href="https://git.socklab.moe/chimchooree/chatserver/src/commit/95dba37181f2cfa37fe03fe495b1c4b520c91101">server</a> and <a href="https://git.socklab.moe/chimchooree/chatclient/src/commit/a70269e9bcde4cdc66115f14c631b0073cdea91e">client</a>.
<p>But however you run, if you run the server first and then the client, you will see your connection in the debug output! You have completed an extremely basic networking application. You can see the full project at this stage on my repo: <a href="https://git.socklab.moe/chimchooree/chatserver/src/commit/95dba37181f2cfa37fe03fe495b1c4b520c91101">server</a> and <a href="https://git.socklab.moe/chimchooree/chatclient/src/commit/a70269e9bcde4cdc66115f14c631b0073cdea91e">client</a>.
<p>Now let's add some chat features. </p>
<hr>
<h2>Communicating between Server + Client. </h2>
<p>If we can pass one more hurdle, writing the chat room code will barely even need a tutorial: passing information between the server and client. </p>
<h3>Writing packets in JSON. </h3>
<p>Information will be sent in packets. You can literally pass your chat message strings, etc, as packets without much touchup. Something like <code>put_packet("hey how's it going?".to_utf8())</code> will run, but even a little chat app will be passing lots of different data back and forth. JSON strings are freeform enough to fit any online application's needs. If you don't know JSON syntax, glance over the <a href="https://docs.godotengine.org/en/stable/classes/class_json.html">Godot documentation</a>. It's very close to the dictionary in most languages. </p>
<p>Some examples of packets we'll use are test packets, chat message packets, server message packets, user joining packets, and user leaving packets. I'm going to use the 'type' key to distinguish incoming packets. </p>
<p>To send them, you need to convert them to JSON strings then convert those strings into UTF-8. You will have to do the reverse on the receiving end. Outgoing packets will look like </p>
<p>while incoming messages will need extra preparation: </p>
<pre><code>
var json = JSON.parse(_client.get_peer(1).get_packet().get_string_from_utf8())
var packet = json.result
</code></pre>
<h3>JSON packets in action. </h3>
Let's build up the server's data reception method.
<pre><code>
Server.gd (Node)
func _on_data_received(id):
var original = _server.get_peer(id).get_packet().get_string_from_utf8()
var json = JSON.parse(original)
var packet = json.result
if typeof(packet) != 18:
push_error("%s is not a dictionary" % [packet])
get_tree().quit()
print_debug("Got data from client %d: %s" % [id, packet])
</code></pre>
<p>We don't have to send the <code>id</code> argument manually. The server will understand the source of incoming data automatically. <code>_server.get_peer(id)</code> gives us the source. <code>_server.get_peer(id).get_packet()</code> gives us the packet they are sending. </p>
<p><code>typeof</code> returns 18 for dictionaries. You can see all the types and their enum values in <a href="https://docs.godotengine.org/en/stable/classes/class_@globalscope.html">Godot's @GlobalScope documentation</a>. Packets should <i>always</i> be dictionaries the way I'm writing this, so if anything else is received, the application will push an error and crash. Replace the code with a print_debug warning if you want, but I like the immediacy of a crash. </p>
<p>For now, it will print any valid packet's contents.</p>
<p>The client's data reception method should be similar:</p>
<pre><code>
Client.gd (Node)
func _on_data_received():
var json = JSON.parse(_client.get_peer(1).get_packet().get_string_from_utf8())
var packet = json.result
if typeof(packet) != 18:
push_error("%s is not a dictionary" % [packet])
get_tree().quit()
print_debug("Got data from server: ", packet)
</code></pre>
<p>Let's add a few put_packets to receive, too. The _connected methods on the server and client are the easiest places to test. <p>
<pre><code>
Server.gd (Node)
func _connected(id, protocol):
print_debug("Client %d connected with protocol: %s"
% [id, protocol])
_server.get_peer(id).put_packet(JSON.print(
{'type': 'test', 'message': "test packet from server"}).to_utf8())
</code></pre>
<pre><code>
Client.gd (Node)
func _connected(protocol = ""):
print_debug("Connected with protocol: ", protocol)
_client.get_peer(1).put_packet(JSON.print(
{'type': 'test', 'message': "test packet from client"}).to_utf8())
</code></pre>
<p>Let's see if the server and client can share data now. </p>
<img src="/static/img/ent/Chatbox_ClientCommunicating.png" alt="(Screenshot: Godot's debug info echos both test packets sent from the server and client.)">
<p>Boom. That's enough networking knowledge to get you started. You can see the full project at this stage on my repo: <a href="https://git.socklab.moe/chimchooree/chatserver/src/commit/bea0dd872d6a7aa7e6fd941f16f20d5a89d5e676">server</a> and <a href="https://git.socklab.moe/chimchooree/chatclient/src/commit/6f78065920d5109b98c4823920cfa5a5f4d70247">client</a>.</p>
<hr>
<h2>Writing that chat room. </h2>
<h3>Writing the UI. </h3>
<p>A chat room is driven by the UI, so UI.gd is the best place to start. The UI needs to let the user join, send messages, and see messages sent by the user and others. Anything beyond showing buttons and letting the user type into fields is beyond the scope of the UI and will be passed to the root node. </p>
<img src="/static/img/ent/Chatbox_Hide.png" alt="(Screenshot: The ChatInput is hidden at the start.)">
<p>Also hide the ChatInput by clicking the eye on the node tree. </p>
<p>The <code>display_message</code> method will lightly format incoming chat messages and messages from other sources then show them as a line in the chat log. If the user clicks the join button but left the username field blank, an alert only visible to the user will be shown in the chat log. Otherwise, the join button and username field are hidden, and the chat input becomes available. <code>join_chat</code> is called off the parent, so the client can handle the rest. The <code>_input</code> method sends messages entered by the user to the root then clears the field for future messages. </p>
<h3>Writing the first part of the client's back end. </h3>
<p>When the user clicks the join button, the client saves the username and sends it to the server. We can distinguish this from chat_messages, etc, by marking it as a "user_joined" packet. The client also sends the username and chat message from the UI's <code>_input</code> to the server. Both errors and voluntary or involuntary disconnections will stop the process. That covers everything we need to <i>send</i> messages. We'll cover <i>receiving</i> messages after we finish the server. </p>
<p>Upon a connection, the <code>_connected</code> method adds the id of the connection to the <code>peers</code> array. Upon a disconnection, it is erased. This way, you can access all users at any time by looping through the peers array. </p>
<p>The most dynamic method is <code>_on_data_received</code>, since it will react to a variety of packets. If it's a user_joined packet, it will save the id of his connection and his chosen username to the ID dictionary. Then the server will send a server_message to every connected client to announce the new member. If it is some kind of message, it will also send that to every client so everyone can see the same messages. </p>
<p>Lastly, upon a disconnection, the server will send a server_message to all clients about the departure. </p>
<p>Now let's make sure the client is ready to receive all these messages...and we'll be done! </p>
if packet['type'] == "chat_message" or packet['type'] == "server_message":
receive_message(packet)
</code></pre>
<p>If the client receives a chat_message or server_message from the server, it will call <code>receive_message</code>, which passes it to the UI for formatting and display. </p>
<p>Export both your server and client, so you can open lots of clients and fill your room with lots of people. Everyone will be able to see people joining, leaving, and chatting. Just like a real chat app! </p>
<img src="/static/img/ent/ChatClient_Conversation_1Kings22.png" alt="(Screenshot: A little chatplay using Jehoshaphat, Ahab, and Micaiah from 1 Kings 22.)">
<p>So the scrollbar covers some of the messages. And the scrolling is unusably annoying. The oldest messages should either disappear, or the chat should always be locked at the bottom on the latest messages. And there's no way to change your username without totally exiting and rejoining. And there must be some way to disable the pointless graphical window on the server. And there's probably a million more issues in addition to those. </p>
<p>But we're here to learn the basics, so we did an adequately good job. So good job anyway! Have fun improving from here and consider writing TCP guides of your own because UDP dominates the tutorial market. Later.^^ </p>
<hr>
<hr>
<h2>Check over the finished scripts. </h2>
<p>You can see the finished project on my repo: <a href="https://git.socklab.moe/chimchooree/chatserver">server</a> and <a href="https://git.socklab.moe/chimchooree/chatclient">client</a>. </p>
<p>This is a list of everything I need to add before the Blessfrey is complete, version by version. I can add more in subsequent updates, but I have to draw the line somewhere. The list is broken into versions. </p>
<br>
<p>Focus and finish the game! <br></p>
<br><hr><br>
<h2>legend </h2>
<p><span class=mundane>nothing at all</span>, <span class=common>designed</span>, <span class=unusual>basic implementation</span>, <span class=rare>intentionally designed, documented, but with known issues</span>, <span class=unique>playtested and polished...or at least as done as it will ever be</span> </p>
<br><hr><br>
<h2><span class=unique>0.0 - ship literally anything at all</span> </h2>