diff --git a/reference/gdscript.rst b/reference/gdscript.rst index ddcbf4c71..57b04a009 100644 --- a/reference/gdscript.rst +++ b/reference/gdscript.rst @@ -75,11 +75,6 @@ here's a simple example of how GDScript looks. const answer = 42 const thename = "Charly" - # enums (Godot 2.2+) - - enum {UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY} - enum Named {THING_1, THING_2, ANOTHER_THING = -1} - # built-in vector types var v2 = Vector2(1, 2) @@ -185,8 +180,6 @@ keywords are reserved words (tokens), they can't be used as identifiers. +------------+---------------------------------------------------------------------------------------------------------------+ | const | Defines a constant. | +------------+---------------------------------------------------------------------------------------------------------------+ -| enum | Defines an enum. (Godot 2.2+) | -+------------+---------------------------------------------------------------------------------------------------------------+ | var | Defines a variable. | +------------+---------------------------------------------------------------------------------------------------------------+ | onready | Initializes a variable once the Node the script is attached to and its children are part of the scene tree. | @@ -239,8 +232,6 @@ The following is the list of supported operators and their precedence +---------------------------------------------------------------+-----------------------------------------+ | ``or`` ``||`` | Boolean OR | +---------------------------------------------------------------+-----------------------------------------+ -| ``if x else`` | Ternary if/else (Godot 2.2+) | -+---------------------------------------------------------------+-----------------------------------------+ | ``=`` ``+=`` ``-=`` ``*=`` ``/=`` ``%=`` ``&=`` ``|=`` | Assignment, Lowest Priority | +---------------------------------------------------------------+-----------------------------------------+ @@ -526,34 +517,6 @@ expressions and must be assigned on initialization. const f = sin(20) # sin() can be used in constant expressions const g = x + 20 # invalid; this is not a constant expression! -Enums -^^^^^ - -*Note, only available in Godot 2.2 or higher.* - -Enums are basically a shorthand for constants, and are pretty useful if you -want to assign consecutive integers to some constant. - -If you pass a name to the enum, it would also put all the values inside a -constant dictionary of that name. - -:: - - enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT} - # Is the same as: - const TILE_BRICK = 0 - const TILE_FLOOR = 1 - const TILE_SPIKE = 2 - const TILE_TELEPORT = 3 - - enum State {STATE_IDLE, STATE_JUMP = 5, STATE_SHOOT} - # Is the same as: - const STATE_IDLE = 0 - const STATE_JUMP = 5 - const STATE_SHOOT = 6 - const State = {STATE_IDLE = 0, STATE_JUMP = 5, STATE_SHOOT = 6} - - Functions ~~~~~~~~~ @@ -650,13 +613,6 @@ Short statements can be written on the same line as the condition:: var x = 3 + 3 return x -Sometimes you might want to assign a different initial value based on a -boolean expression. In this case ternary-if expressions come in handy -(Godot 2.2+):: - - var x = [true-value] if [expression] else [false-value] - y += 3 if y < 10 else -1 - while ^^^^^ @@ -1185,9 +1141,6 @@ signal is received, execution will recommence. Here are some examples: # Resume execution when animation is done playing: yield( get_node("AnimationPlayer"), "finished" ) - # Wait 5 seconds, then resume execution (Godot 2.2+) - yield( get_tree().create_timer(5.0), "timeout" ) - Onready keyword ~~~~~~~~~~~~~~~ diff --git a/tutorials/_networking.rst b/tutorials/_networking.rst index 6aa714063..e41f5f88e 100644 --- a/tutorials/_networking.rst +++ b/tutorials/_networking.rst @@ -7,4 +7,3 @@ Networking ssl_certificates http_client_class - high_level_multiplayer diff --git a/tutorials/high_level_multiplayer.rst b/tutorials/high_level_multiplayer.rst deleted file mode 100644 index 16392fe7d..000000000 --- a/tutorials/high_level_multiplayer.rst +++ /dev/null @@ -1,365 +0,0 @@ -.. _doc_high_level_multiplayer: - -High level multiplayer (Godot 2.2+) -=================================== - -Why high level? ----------------- - -Godot always supported standard networking via UDP, TCP and some high level protocols such as SSL and HTTP. -These protocols are very flexible and should support everything. However, for games themselves (or unless you are working -with a custom server), using them to synchronize game state manually can be an enormous amount of work. -This is due to the inherent limitations of the protocols: - -- TCP ensures packets will always arrive, but latency is generally high due to error correction. - It's also quite a complex protocol because it understands what a "connection" is. -- UDP is a simpler protocol which only sends packets (no connection required). The fact it does no error correction - makes it pretty quick (low latency), but it has the disadvantage that packets may be lost along the way. - Added to that, the MTU (maximum packet size) for UDP is generally low (only a few hundred bytes), so transmitting - larger packets means splitting them, reorganizing them, retrying if a part fails, etc. - -Mid level abstraction ---------------------- - -Before going into how we would like to synchronize a game across the network, it would be wise to understand how the base network API -for synchronization works. Godot uses a mid-level object :ref:`NetworkedMultiplayerPeer `. -This object is not meant to be created directly, but is designed so that several implementations can provide it: - -.. image:: /img/nmpeer.png - -This object extends from :ref:`PacketPeer `, so it has all the useful methods for serializing data you are used to, thanks to -Godot's beautiful object-oriented design. It adds methods to set a peer, transfer mode, etc. It also includes signals that will let you know -when peers connect or disconnect. - -The idea is that this class interface can abstract most types of network layers, topologies and libraries. By default Godot -provides an implementation based on ENet (:ref:`NetworkedMultiplayerEnet `), but the plan -is that this could support mobile APIs (for adhoc WiFi, Bluetooth), custom device/console networking APIs, etc. - -For most common cases, using this object directly is discouraged, as Godot provides even higher level networking facilities. -Yet it is made available to scripting in case a game has specific needs for a lower level API. - -Initializing the network ------------------------- - -The object that controls networking in Godot is the same one that controls everything tree-related: :ref:`SceneTree `. - -To initialize high level networking, SceneTree must be provided a NetworkedMultiplayerPeer object. - -Initializing as a server, listening on the given port, with a given maximum of 4 peers: - -:: - - var host = NetworkedMultiplayerENet.new() - host.create_server(SERVER_PORT, 4) - get_tree().set_network_peer(host) - -Initializing as a client, connecting to a given IP and port: - -:: - - var host = NetworkedMultiplayerENet.new() - host.create_client(ip, SERVER_PORT) - get_tree().set_network_peer(host) - -Terminating the networking feature: - -:: - - get_tree().set_network_peer(null) - -Managing connections --------------------- - -Some games accept conections at any time, others during the lobby phase. Godot can be requested to no longer accept -connections at any point. To manage who connects, Godot provides the following signals in SceneTree: - -Server and Clients: - -- `network_peer_connected(int id)` -- `network_peer_disconnected(int id)` - -The above signals are called in every peer connected to the server when a new one connects or disconnects. -It is very useful to keep track of the IDs above (clients will connect with non-zero and non-one unique ID), -while the server is warranted to always use ID=1. These IDs will be useful mostly for lobby management. - -Clients: - -- `connected_to_server` -- `connection_failed` -- `server_disconnected` - -Again, all these functions are mainly useful for lobby management, or for adding/removing players on the fly. -For these tasks, the server clearly has to work as a server and you have do tasks manually such as sending a new -player that connected information about other already connected players (e.g. their names, stats, etc). - -Lobby can be implemented any way you want, but the most common way is to use a node with the same name across scenes in all peers. -Generally, an autoloaded node/singleton is a great fit for this, to always have access to e.g. "/root/lobby". - -RPC ---- - -To communicate between peers, the easiest way is to use RPC (remote procedure call). This is implemented as a set of functions -in :ref:`Node `: - -- `rpc("function_name", )` -- `rpc_id(,"function_name", )` -- `rpc_unreliable("function_name", )` -- `rpc_unreliable_id(, "function_name", )` - -Synchronizing member variables is also possible: - -- `rset("variable", value)` -- `rset_id(, "variable", value)` -- `rset_unreliable("variable", value)` -- `rset_unreliable_id(, "variable", value)` - -Functions can be called in two fashions: - -- Reliable: the function call will arrive no matter what, but may take longer because it will be re-transmitted in case of failure. -- Unreliable: if the function call does not arrive, it will not be re-transmitted, but if it arrives it will do it quickly. - -In most cases, Reliable is desired. Unreliable is mostly useful when synchronizing objects that move (sync must happen constantly, -and if a packet is lost, it's not that bad because a new one will eventually arrive). - -Back to lobby -------------- - -Let's get back to the lobby. Imagine that each player that connects to the server will tell everyone about it. - -:: - - # Typical lobby implementation, imagine this being in /root/lobby - - extends Node - - # Connect all functions - - func _ready(): - get_tree().connect("network_peer_connected", self, "_player_connected") - get_tree().connect("network_peer_disconnected", self, "_player_disconnected") - get_tree().connect("connected_to_server", self, "_connected_ok") - get_tree().connect("connection_failed", self, "_connected_fail") - get_tree().connect("server_disconnected", self, "_server_disconnected") - - # Player info, associate ID to data - var player_info = {} - # Info we send to other players - var my_info = { name = "Johnson Magenta", favorite_color = Color8(255, 0, 255) } - - func _player_connected(id): - pass # Will go unused, not useful here - - func _player_disconnected(id): - player_info.erase(id) # Erase player from info - - func _connected_ok(): - # Only called on clients, not server. Send my ID and info to all the other peers - rpc("register_player", get_tree().get_network_unique_id(), my_info) - - func _server_disconnected(): - pass # Server kicked us, show error and abort - - func _connected_fail(): - pass # Could not even connect to server, abort - - remote func register_player(id, info): - # Store the info - player_info[id] = info - # If I'm the server, let the new guy know about existing players - if (get_tree().is_network_server()): - # Send my info to new player - rpc_id(id, "register_info", 1, my_info) - # Send the info of existing players - for peer_id in player_info: - rpc_id(id, "register_info", peer_id, players[peer_id]) - - # Call function to update lobby UI here - -You might have noticed already something different, which is the usage of the `remote` keyword on the `register_player` function: - -:: - - remote func register_player(id, info): - -This keyword has two main uses. The first is to let Godot know that this function can be called from RPC. If no keywords are added, -Godot will block any attempts to call functions for security. This makes security work a lot easier (so a client can't call a function -to delete a file on another client's system). - -The second use is to specify how the function will be called via RPC. There are four different keywords: - -- `remote` -- `sync` -- `master` -- `slave` - -The `remote` keyword means that the `rpc()` call will go via network and execute remotely. - -The `sync` keyword means that the `rpc()` call will go via network and execute remotely, but will also execute locally (do a normal function call). - -The others will be explained further down. - -With this, lobby management should be more or less explained. Once you have your game going, you will most likely want to add some -extra security to make sure clients don't do anything funny (just validate the info they send from time to time, or before -game start). For the sake of simplicity and the fact each game will share different information, this was not done here. - -Starting the game ------------------ - -Once enough people has gathered in the lobby, the server will most likely want to start the game. This is honestly nothing -special in itself, but we'll explain a few nice tricks that can be done at this point to make your life much easier. - -Player scenes -^^^^^^^^^^^^^ - -In most games, each player will likely have its own scene. Remember that this is a multiplayer game, so in every peer -you need to instance **one scene for each player connected to it**. For a 4 player game, each peer needs to instance 4 player nodes. - -So, how to name such nodes? In Godot nodes need to have an unique name. It must also be relatively easy for a player to tell which -nodes represent each player id. - -The solution is to simply name the *root nodes of the instanced player scenes as their network ID*. This way, they will be the same in -every peer and RPC will work great! Here is an example: - -:: - - remote func pre_configure_game(): - # Load world - var world = load(which_level).instance() - get_node("/root").add_child(world) - - # Load my player - var my_player = preload("res://player.tscn").instance() - my_player.set_name(str(get_tree().get_network_unique_id())) - my_player.set_network_mode(NETWORK_MODE_MASTER) # Will be explained later - get_node("/root/world/players").add_child(my_player) - - # Load other players - for p in player_info: - var player = preload("res://player.tscn").instance() - player.set_name(str(p)) - player.set_network_mode(NETWORK_MODE_SLAVE) # Will be explained later - get_node("/root/world/players").add_child(player) - - # Tell server (remember, server is always ID=1) that this peer is done pre-configuring - rpc_id(1, "done_preconfiguring", get_tree().get_network_unique_id()) - -Synchronized game start -^^^^^^^^^^^^^^^^^^^^^^^ - -Setting up players might take different amount of time on every peer due to lag and any large number of reasons. -To make sure the game will actually start when everyone is ready, pausing the game can be very useful: - -:: - - remote func pre_configure_game(): - get_tree().set_pause(true) # Pre-pause - # The rest is the same as in the code in the previous section (look above) - -When the server gets the OK from all the peers, it can tell them to start, as for example: - -:: - - var players_done = [] - remote func done_preconfiguring(who): - # Here is some checks you can do, as example - assert(get_tree().is_network_server()) - assert(who in player_info) # Exists - assert(not who in players_done) # Was not added yet - - players_done.append(who) - - if (players_done.size() == player_info.size()): - rpc("post_configure_game") - - remote func post_configure_game(): - get_tree().set_pause(false) - # Game starts now! - -Synchronizing the game ----------------------- - -In most games, the goal of supporting multiplayer neworking is to make sure that the game runs synchronized in all the peers playing it. -Besides supplying an RPC and remote member variable set implementation, Godot adds the concept of master and slave network modes. - -Master and slave modes -^^^^^^^^^^^^^^^^^^^^^^ - -Very similarly to how the pause mode works in regular nodes (with pause, process, inherit modes), nodes can be set a "network mode" -with the function :ref:`Node.set_network_mode(mode) `. The mode can be: Master, Slave and Inherit. - -The Inherit mode assumes the value of the parent node. If the parent node is also in this mode, it will go up in the parenthood chain until it finds a specific mode. -If no non-inherit mode is found, Master will be assumed for the server and Slave for clients. - -This means that, upon loading scenes, the server is by default the master and clients are the slaves. Checking that a node is in master mode is done by calling: - -:: - - is_network_master() - -If you have paid attention to the previous example, it's possible you noticed each node being set a role when being loaded in each peer: - -:: - - [...] - # Load my player - var my_player = preload("res://player.tscn").instance() - my_player.set_name(str(get_tree().get_network_unique_id())) - my_player.set_network_mode(NETWORK_MODE_MASTER) - get_node("/root/world/players").add_child(my_player) - - # Load other players - for p in player_info: - var player = preload("res://player.tscn").instance() - player.set_name(str(p)) - player.set_network_mode(NETWORK_MODE_SLAVE) - get_node("/root/world/players").add_child(player) - [...] - - -Here, each time this piece of code is executed on each peer, the peer makes the node it controls master, and the ones it does not slaves. -The modes for each are different on each peer. To clarify, here is an example of how this looks in the -`bomber demo `_: - -.. image:: /img/nmms.png - - -Master and slave keywords -^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. FIXME: Clarify the equivalents to the GDScript keywords in C# and Visual Script. - -The real advantage of this model is when used with the `master`/`slave` keywords in GDScript (or their equivalent in C# and Visual Script). -Similarly to the `remote` keyword, functions can also be tagged with them: - -Example bomb code: - -:: - - for p in bodies_in_area: - if (p.has_method("exploded")): - p.rpc("exploded", bomb_owner) - -Example player code: - -:: - - slave func stun(): - stunned = true - - master func exploded(by_who): - if (stunned): - return # Already stunned - - rpc("stun") - stun() # Stun myself, could have used sync keyword too. - -In the above example, a bomb explodes somewhere (likely managed by whoever is master). The bomb knows the bodies in the area, so it checks them -and checks that they contain an `exploded` function. - -If they do, the bomb calls `exploded` on it. However, the `exploded` method in the player has a `master` keyword. This means that only the player -who is master for that instance will actually get the function. - -This instance, then, calls the `stun` function in the same instances of that same player (but in different peers), and only those which are set as slave, -making the player look stunned in all the peers (as well as the current, master one). - -.. FIXME: Document the sync keyword