mirror of
https://github.com/godotengine/godot-docs.git
synced 2026-01-03 05:48:42 +03:00
Remove references to 2.2+ features, stick to 2.1.x here
This commit is contained in:
@@ -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
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -7,4 +7,3 @@ Networking
|
||||
|
||||
ssl_certificates
|
||||
http_client_class
|
||||
high_level_multiplayer
|
||||
|
||||
@@ -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 <class_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 <class_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 <class_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 <class_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 <class_Node>`:
|
||||
|
||||
- `rpc("function_name", <optional_args>)`
|
||||
- `rpc_id(<peer_id>,"function_name", <optional_args>)`
|
||||
- `rpc_unreliable("function_name", <optional_args>)`
|
||||
- `rpc_unreliable_id(<peer_id>, "function_name", <optional_args>)`
|
||||
|
||||
Synchronizing member variables is also possible:
|
||||
|
||||
- `rset("variable", value)`
|
||||
- `rset_id(<peer_id>, "variable", value)`
|
||||
- `rset_unreliable("variable", value)`
|
||||
- `rset_unreliable_id(<peer_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) <class_Node_set_network_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 <https://github.com/godotengine/godot-demo-projects/tree/master/networking/simple_multiplayer>`_:
|
||||
|
||||
.. 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
|
||||
Reference in New Issue
Block a user