/
CoreNetworking.txt
201 lines (157 loc) · 22.6 KB
/
CoreNetworking.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
Networking Overview
Networking is a notoriously difficult concept to get right in game development, so it's no suprise that it's not the easiest thing to learn in Core. Luckily Core and UE4 do most of the heavy lifting and give us some relatively simple ways to synchronize data between clients and the server.
When you play a single player game everything runs on your computer. All of the game state, scores, locations of objects, and graphics are managed and displayed on your own machine. There is one single copy of the game running locally, and it can access any of the game's state whenever needed. When you press the W key to move, the game checks to make sure you're not running into a wall, your character moves forward, and then a movement animation plays. Multiplayer games are much different: You still run a copy of the game on your local machine, but each player also runs their own copy of the game and the server runs yet another copy of the game.
The server acts as the final authority for everything important that needs to change in the game: where things are, what they're doing, and what they're allowed to do. The server sends updates to each client as these "networked" objects change. Your local copy of the game receives these updates from the server and updates your local copy of the game accordingly. The more often these updates happen, the more synchronized the game will be (I think Core servers currently run at 10HZ which works out to about 10 updates per second). Your local copy of the game can safely handle things that never need to change (like a rock) and things that shouldn't be synchronized with other players (like a player's UI). You can think of the server as running the "real" version of the game and each client running a close approximation of what's happening on the server.
Take the same example of moving your character forward but now in a networked game. When you press the W key your client does some local collision checks to make sure you're not running into an obstacle, then your client asks the server for permission to move your character forward. The server checks its version of the game to make sure there aren't any obstacles in the way on the server's version of the game. Then it will move your character's position forward on the server. That information will be "replicated" back to each client to update the player's position on everyone's version of the game. This is a major oversimplification and there's a bit of trickery behind the scenes to make the game feel more responsive.
The way Core developers decide whether something is managed by the client or by the server is by using contexts. Understanding these contexts and picking the right one is very important. If we allowed something like the player's HP to be managed by the client, cheaters would be able to set these values to whatever they want to become invincible. This is why it's important to never trust the client and treat the server as the single authority of truth in your game.
If you want a more technical/accurate description of what goes on under the hood, check out this article from Unreal Engine (the game engine Core is built on): https://docs.unrealengine.com/en-US/Gameplay/Networking/Overview/index.html
All of the examples below are in Community Content bundle called "Networking-Examples". You should drag one example onto your hierarchy at a time and run the game in multiplayer preview with 2 players. Compare side-by-side how player1, player2, and the "server" (the editor preview window) see things.
------------------------------------------------------------
Contexts
https://docs.coregames.com/api/examples/#contexts
In Core, contexts are like folders and exist in one of two states: networked and non-networked. You can nest multiple contexts but only the outermost one has any effect. Inside of it, every child context acts like a folder.
Scripts can either run on one or more clients, or on the server. When looking through the Core API Docs, be sure to check the Tags column to see if a function/event/property is "Server-Only" or "Client-Only".
When a script spawns an object, it inherits the script's context, even if it is somewhere else in the hierarchy. This means that a script in a server context can never spawn objects that clients can see or interact with.
Some might find it confusing that the root folder of a context (client, server, static) is networked while the things inside are not. Take for example a client context. The ClientContext root folder is (networked) but everything inside is marked as (client). The server will only be able to access the ClientContext root folder, and changes to that folder will replicate to every client - if you move the folder by changing its game world position, all of the client objects inside will also move on the client accordingly. You can't access the objects inside the folder from a networked script though, everything inside is invisible to the server and can only be access from a client script. When you run your project in multiplayer preview, you'll notice how anything inside the folder disappears - The editor preview is basically what the server sees. It's pretty much just a folder that enforces a rule "everything inside this folder is client only" even if you nest different contexts (server, static) inside it they will still end up as client only.
There are five types of contexts, Client Context, Default (Non-Networked), Static Context, Server Context and Networked.
+---------------------------------------------------------------------------------------------------------------------------+
| | Default (Non-networked) | Networked | Client Context | Server Context | Static Context |
|--------------------|-------------------------|----------------------|----------------|----------------|-------------------|
| Objects can change | no | Yes (only by server) | Yes | Yes | No |
|--------------------|-------------------------|----------------------|----------------|----------------|-------------------|
| Collision | Yes | Yes | No | No | Yes |
|--------------------|-------------------------|----------------------|----------------|----------------|-------------------|
| Objects exist on | Client and Server | Client and Server | Client | Server | Client and Server |
|--------------------|-------------------------|----------------------|----------------|----------------|-------------------|
| Scripts run on | Server | Server | Client | Server | Client and Server |
+---------------------------------------------------------------------------------------------------------------------------+
Default
Objects Cannot change but they can have collision. Objects Seen by server and client. Scripts run on the server only.
When you join a game server, all of the Default objects are loaded onto your version of the game. The client and server never need to transmit data about these objects because they can never change.
Your client can safely calculate collisions with Default objects because the server will never disagree with their position or geometry.
Scripts in a Default context run on the server and have access to all objects in the hierarchy except for those inside client contexts.
Objects that you never want to change but you want to have collision should go inside a Default context. For example environment decorations like trees and rocks whose only gameplay function is to look pretty and have collision.
[Other examples of stuff to put in Default Context?]
Networked
Objects Can be changed by the server and clients will see those changes. Objects Can have collision. Objects exist both on the client and on the server. Scripts run on the server only.
Networked objects live on the server and can be changed only by scripts running on the server. Networked objects get replicated to every client as they change but this costs network resources, too many networked objects moving around will cause lag.
Networked scripts run on the server and have access to all objects in the hierarchy except for those inside client contexts.
Anything whose position or state needs to be constantly updated to each client should be in a networked context - For example a moving platform that players jump on and ride.
[Other examples of stuff to put in Networked context?]
[Networking-Example-01: A networked script moves a networked object back and forth, and this change is seen on all clients]
Client
Objects can change and do not have collision, but they will block any cameras unless explicitly set otherwise. Objects exist only on the client. Scripts run on the client only.
Client contexts are great for doing things that don't necessarily need to be synchronized, and allow us to show things to certain players and hide them from other players.
There are things you are required to run on the client such as your User Interface and many VFX. Most of these requirements will either be noted in the description of the Core API or in the Tags column (Client-Only).
Scripts in a client context run on each client and have access to all objects except for those inside Server contexts.
You can almost think of client scripts like handing out a recipe to multiple people to take home and make. If everyone takes the recipe home and follows it perfectly, everyone will end up with the same cake (more or less).
Game.GetLocalPlayer() lets us determine which client is running the script and do things only they can see.
For example in a client script we can connect an event that runs when a weapon is fired which checks if the weapon owner is the local player. If it is, we show them and only them a weapon cooldown timer.
[Spawning a client script inside of another client script, how does this work - Is there a way to get a script to only run on a single client?]
[Networking-Example-02: Show an object for a single player when a trigger is stepped on]
[Networking-Example-03: Show an object for all players when a trigger is stepped on]
[Networking-Example-04: Run the exact same code on the client and on the network to show 2 skulls (one on the server, one on the client) appear/disappear when the trigger is stepped on, to show the delay from networking]
Server
Objects cannot change and do not have collision. Objects exist only on the server, they get removed from the client-side copy of the game sent to players. Scripts run on the server only.
Provides a safeguard for creators if they want to conceal game logic because the scripts are removed from the copy of the game sent to players.
For example if you want to give players a special reward if they enter a password you should put that in a server context, otherwise cheater may be able to read their copy of the script and see what the password is.
Scripts run on the server and have access to all objects in the hierarchy except for those inside client contexts.
standardCombo(gabriel): "The only case where I use server context is in a template where I need to cut down on the number of networked objects and the server folder acts as a single object, allowing multiple server scripts to be under it"
RyanZ: "Server context is useful if you don't want any clients to see that code. Any scripts that are referenced from a server context and not referenced anywhere outside of a server context are stripped from the data that clients download." "A script in a server context can’t spawn networked objects"
[Networking-Example-05: There is a skull that can be seen in the editor preview (the "server") but not on any game client - the spookiest kind of skull]
Static
Almost like the default state (non-networked). Scripts can spawn objects inside a static context. Scripts run on both the server and the client.
Useful for things reproduced easily on the client and server with minimal data (procedurally generated maps) and can also prevent you from duplicating things that may perform different actions on the server and client.
You can basically create anything without ever using a static context. It's useful to understand what they do and how they work but it's probably fine if you don't fully get it.
zurishmi (Eric M): "a static context can be used to create procedurally generated maps without causing networking issues. You would set it up so a single seed value is shared, then both the client and server create the same objects use a pseudo-random generator with the shared seed. It can be used as a minor optimization over networked objects, for a group that moves/rotates, but the individual pieces' transforms never change. You can see an example of that in the Basic Door component."
standardcombo: "I'm using static context for Valor Strike (and the bomb planting framework), and it's not for level generation or optimization. I have code for the bomb areas and I want that to be setup on both client and server, so the UI knows when the bomb carrier enters the zone and the server has authority over the rule of where the bomb can be planted. Instead of 2 scripts doing exactly the same thing (registering the bomb zones at startup) a single script is inside a static context." - This is a pretty complicated example that requires you to understand the use of require(). It basically registers these bomb zones in both the client and the server versions of the same script at the same time, without registering them in two places. This makes it so adding a new bomb zone is as simple as dropping a new bomb zone into the static context, instead of updating the info in two places at once (one in a server context and one in a client context). Whenever a player steps on the trigger zone, both server and client API react accordingly (enable an ability on the server, update GUI on the client).
[Networking-Example-06: Create a static context with children, and move the parent in a networked script - basically the same as example 1, except instead of waiting]
------------------------------------------------------------
Events
Events are a way to broadcast that something has happened in your game and pass data about what happened, such as a point being scored or a player stepping on a trigger, and are a great way to pass information between the client and the server.
Many CoreObjects give us events that we can connect functions to, and the Events namespace gives us the ability to create our own custom events that work the same way.
Each "Machine" can broadcast 10 events per second: so each client can broadcast 10 events and the server can broadcast 10 events every second
Events.Connect
Any context
Registers the given function to the event name which will be called every time the event is fired using Broadcast
Events.Broadcast
Any context
Broadcasts the given event and fires all listeners attached to the given event name if any exists. The events are not networked and can (only?) fire events defined in the same context.
Events.BroadcastToPlayer
Server-Only
Broadcasts the given event to a specific client over the network and fires all listeners attached to the given event name if any exists on that client.
This is a networked event. The maximum size a networked event can send is 128bytes and all networked events are subjected to a rate limit of 10 events per second.
Events.BroadcastToAllPlayers
Server-Only
Broadcasts the given event to all clients over the network and fires all listeners attached to the given event name if any exists.
This is a networked event. The maximum size a networked event can send is 128bytes and all networked events are subjected to a rate limit of 10 events per second.
Events.ConnectForPlayer
Server-Only
Registers the given function to the event name which will be called every time the event is fired using BroadcastToServer.
Events.BroadcastToServer
Client-Only
Broadcasts the given event to the server over the network and fires all listeners attached to the given event name if any exists on the server.
This is a networked event. The maximum size a networked event can send is 128bytes and all networked events are subjected to a rate limit of 10 events per second.
------------------------------------------------------------
Event Examples
The followinge examples work by printing things to the event log, so make sure you turn on View > Event Log
The while loop on each event broadcast ensures that the event fires, and if it's hitting the rate limit it waits until the next frame and tries again.
while Events.Broadcast("BroadcastEvent") == BroadcastEventResultCode.EXCEEDED_RATE_LIMIT do
Task.Wait()
end
Events.Connect + Events.Broadcast
Broadcast an event in the same context
[Networking-Example-07: Show how this only fires the event in the same context, but things outside the context don't see the event]
[Networking-Example-08: Show how to pass multiple pieces of data through an event]
Events.Connect + Events.BroadcastToPlayer
Broadcast an event from the server to a single client
[Networking-Example-09: Broadcast an event from the server to a single client, other clients don't see it]
Events.Connect + Events.BroadcastToAllPlayers
Broadcast an event from the server to all clients
[Networking-Example-10: Broadcast an event from the server to all clients]
Events.ConnectForPlayer + Events.BroadcastToServer
Broadcast an event from a single client to the server
[Networking-Example-11: Broadcast an event from the client to the server and print the name of the player who sent the event]
------------------------------------------------------------
Networked properties
Networked properties are another way of transmitting certain data from the server to the client and to have that data associated with a specific object.
An example use for this would be NPC enemy health. When the NPC enemy is hit by a player weapon, the server would change an "HP" Networked Property on the NPC object, and the client would constantly be checking for a change in that property's value and update the health bar accordingly.
CoreObject.networkedPropertyChangedEvent
You can hook a function up to this event on either the client or the server to fire anytime the networked property changes.
For the NPC HP example, you would update the health bar GUI to the new property value whenever this fires
CoreObject.SetNetworkedCustomProperty
This sets the property to a new value, and can only be set by server
[Networking-Example-12: Have an UI text counter on screen managed through a networked custom property on the networked script, stepping on a trigger increases the count via a client script listener]
------------------------------------------------------------
Player
The player is not a Core Object, but it's kinda like a networked object - Changes to a player will replicate to all clients.
Just like with networked objects, a client can read the state of the player but can't modify it.
If you take a look at the Core API docs section about Players you can see how any function that SETS values (except for cameras) are Server-Only, while any function that just READS values are allowed in any context.
bindingPressedEvent
If you hook this event up in a client context, only the player who pressed the key will have the event fire on their client.
[Networking-Example-13: A key Pressed listener is connected in a client script, and updates a text box to the key pressed - but only on the player's screen who pressed the key]
resourceChangedEvent
[Networking-Example-14: Simple example for connecting a resourceChangedEvent to a player and printing the parameters from the event to the console log. We could connect the resourceChangedEvent for all players if we wanted to, because this event is replicated from the server (unlike Player.bindingPressedEvent)]
------------------------------------------------------------
serverUserData
This is a table that is associated with a CoreObject, and can only be accessed by a script on the server. Can be used to share data between scripts on the server.
[Networking-Example-15: grab a value from serveruserdata and print the value, while the client script prints nil]
clientUserData
This is very similar to serverUserData, except it is client-only and each client has their own version of the table. Values written to this table on one client will not replicate to any other client or the server.
[Networking-Example-16: Same as example 16, except setting the value on clientuserdata. Note that each player sets their own value separately]
------------------------------------------------------------
Scripts
_G
This is a global table that can be accessed from any script, but there is a separate version of the table for each machine (one for the server + one for each client).
Values set on one client's _G table can't be read by any other clients or the server, and values set on the server version of _G cannot be read by any clients.
[Networking-Example-17: Demonstrate how each client and the server have their own version of _G, each context attempts to print a value set in another context]
require()
Basically this function returns to you a table that gets generated by the script asset reference you pass in. The first time (per machine) a script is called by require(), it will run the script and return whatever the script returns, which should be a table. Any other time the script is called by require() on the same machine it will return a reference to the exact same table. Changes to that table in one script will be seen by other scripts on the same machine that require it.
When you use require(), you don't do it on a script that exists in the hierarchy (that is what .context is for). Instead you drag a script from the script assets section of the editor onto a custom property as an Asset Reference.
Each machine runs and returns its own version of the required script, so you can't require a script on the server then require the same script on a client script and expect it to share data. Each client also returns its own version of the required script. You can almost think of using require() like creating another global table similar to _G.
This is extremely handy to use as a central source of data in the same context. It can be used like the singleton pattern in object oriented programming, where you want one and only one instance of some table that many scripts use as an authoritative source of data and functionality.
[Networking-Example-18: Require the same script on both the client and on the server and show how they increment values independently]
script.context
"Returns the table containing any non-local variables and functions created by the script. This can be used to call (or overwrite!) functions on another script."
Just like require() and _G, this is dependent on the context that it is called in.
[Networking-Example-19: Read values and call functions from another script in the same context]