1
Fork 0
mirror of https://github.com/Steffo99/nanogolf.git synced 2024-11-24 00:54:19 +00:00
This commit is contained in:
Steffo 2024-03-21 05:46:24 +01:00
parent c9c9ccecfa
commit 9e81f4ff84
Signed by: steffo
GPG key ID: 5ADA3868646C3FC0
8 changed files with 300 additions and 3 deletions

View file

@ -1,4 +1,4 @@
# Premessa
# Godot
[Godot Engine] è un motore general purpose ed [open source] per la realizzazione di videogiochi multi-piattaforma.
@ -57,6 +57,10 @@ Altri nodi possono connettersi ai segnali per effettuare dei comportamenti in ri
![I segnali messi a disposizione da GolfBall.](img/signals.png)
#### Process
Esistono anche due segnali speciali che non vengono mostrati dall'editor, detti ***`process(delta)`*** e ***`physics_process(delta)`***: essi vengono emessi ricorsivamente dalla radice dell'albero dei nodi a tutti i figli rispettivamente **ad ogni fotogramma disegnato a schermo** e ad ogni **quanto di tempo della simulazione fisica**.
### Script
Il comportamento di ciascun nodo può essere esteso collegandoci uno ***script***, un file di codice in uno dei linguaggi supportati che viene valutato all'avvio del gioco.
@ -69,6 +73,8 @@ Gli script possono aggiungere al nodo sia delle loro proprietà, sia dei loro se
![Uno script vuoto denominato GolfHole che estende Area2D.](img/script.png)
Se esistono, i metodi `_process` e `_physics_process` vengono automaticamente collegati agli omonimi segnali, permettendo l'esecuzione di codice a intervalli regolari.
## Scene
Se un nodo è l'equivalente di un oggetto, una ***scena*** è l'equivalente di una *classe* nella programmazione ad oggetti.

View file

@ -1 +0,0 @@
# Architettura

176
docs/3 - Protocolli.md Normal file
View file

@ -0,0 +1,176 @@
# Protocolli di networking in Godot
Il gioco pianificato è stato realizzato utilizzando l'[API ad alto livello per il networking] integrato in Godot Engine.
## API ad alto livello
Godot Engine integra nel suo linguaggio di scripting [GDScript] alcuni concetti che facilitano lo sviluppo di applicazioni networked utilizzando il paradigma degli **oggetti condivisi** (o meglio, nel caso di Godot, il paradigma dei *nodi* condivisi).
### Peer, server, client
Le istanze del gioco interconnesse sono chiamate da Godot Engine ***peer***, sia che esse siano ***server*** (e quindi in ascolto per connessioni su un socket), sia che esse siano ***client*** (e quindi che si connettono ad un server).
#### Creare un server
Per far aprire un socket a Godot, e metterlo in ascolto, è necessario istanziare in uno script la classe `ENetMultiplayerPeer`, e poi eseguirne il metodo `create_server`:
```gdscript
const PORT := 12345
func _ready():
var peer := ENetMultiplayerPeer.new()
peer.create_server(PORT)
```
Dato che l'elaborazione dei pacchetti ricevuti viene effettuata sincronicamente ad ogni ricezione del segnale `process(delta)`, per fare in modo che Godot inizi a processare i pacchetti, è necessario assegnare l'oggetto `ENetMultiplayerPeer` creato alla proprietà `multiplayer_peer` del `MultiplayerAPI` che utilizza il gioco, a cui tutti i `Node` possiedono una referenza alla proprietà `multiplayer`:
```gdscript
multiplayer.multiplayer_peer = peer
```
#### Creare un client
La procedura per creare un client e connettersi a un server è molto simile a quella per aprire un server, con la differenza che il metodo utilizzato in questo caso è `create_client`:
```gdscript
const ADDRESS := "127.0.0.1"
const PORT := 12345
func _ready():
var peer := ENetMultiplayerPeer.new()
peer.create_server(ADDRESS, PORT)
multiplayer.multiplayer_peer = peer
```
### Separazione di client e server
È inoltre possibile avere simultaneamente più della singola istanza di `MultiplayerAPI` disponibile di default.
A tale scopo, si può usare il metodo `set_multiplayer` dell'albero dei nodi per specificare che un dato sottoalbero deve fare uso di un `MultiplayerAPI` (o di una classe che da esso eredita, come `SceneMultiplayer`) diverso:
```gdscript
func init_server(server_port: int):
var smp: SceneMultiplayer = SceneMultiplayer.new()
var peer: ENetMultiplayerPeer = ENetMultiplayerPeer.new()
if peer.create_server(server_port) != OK or peer.get_connection_status() == MultiplayerPeer.CONNECTION_DISCONNECTED:
push_error(error)
return
scene_tree.set_multiplayer(smp, ^"/root/Main/Server")
smp.multiplayer_peer = peer
func deinit_server():
var smp = ...
smp.multiplayer_peer = null
scene_tree.set_multiplayer(null, ^"/root/Main/Server")
```
Le parti esterne a questi sottoalberi, come il nodo {`Main`}, non sono networked, e sono gestite separatamente da ciascuna istanza dell'applicazione.
## Remote procedure calls
All'interno degli script è possibile taggare alcuni metodi con `@rpc`, nel seguente modo:
```gdscript
var one = 0
@rpc
func add_one():
one += 1
```
L'esecuzione dei metodi così taggati può essere richiesta dagli altri peer connessi all'istanza del `MultiplayerAPI` del sottoalbero locale.
Diventa quindi possibile chiamare la funzione in tre modi diversi:
- `add_one()`, esegue la funzione solamente sull'istanza locale
- `add_one.rpc()`, richiede l'esecuzione della funzione su tutti gli altri client remoti
- `add_one.rpc_id(ID)`, esegue la funzione solamente sull'istanza remota avente l'identificatore univoco specificato
### Oggetto di esecuzione
Le remote procedure calls vengono effettuate sui nodi remoti che si trovano alla stessa posizione nel sottoalbero del `MultiplayerAPI` del nodo chiamante.
Ad esempio, assumendo che due `SceneMultiplayer` connessi tra loro si trovino a `/root/Server` e `/root/Client`, chiamare `add_one.rpc_id(1)` su un nodo a `/root/Client/Counter` eseguirà la funzione `add_one` sul nodo `/root/Server/Counter`.
### Argomenti
Le remote procedure calls possono avere argomenti specificati come in una normale funzione:
```gdscript
var one = 0
@rpc
func add(val: int):
one += val
func add_one_rpc():
add.rpc(1)
```
Essendo però GDScript un linguaggio interamente dinamico, la deserializzazione di oggetti inviati da remoto sarebbe un rischio per la sicurezza, in quanto darebbe la possibilità agli altri peer di eseguire codice arbitrario, quindi essa non è permessa, ed è invece possibile serializzare solamente tipi primitivi, come `int`, `float`, `String` o `Vector2`.
### Modalità, permessi e autorità
È possibile specificare nel tag `@rpc` la ***modalità*** di esecuzione della RPC, in modo da restringere quali client possono eseguire remotamente la funzione:
```gdscript
var one = 0
@rpc("authority")
func add_one():
one += 1
```
Le modalità attualmente disponibili sono:
- `any_peer`, che permette a qualsiasi altro peer di eseguire la funzione;
- `authority`, che permette di eseguire la funzione solamente al peer con l'identificatore indicato dalla proprietà `multiplayer_authority` del `Node`.
### Sincronizzazione remota e locale
È inoltre possibile specificare nel tag `@rpc` la ***sincronizzazione*** della chiamata, specificando su quali peer questa verrà eseguita se chiamata con `.rpc()`:
```gdscript
var one = 0
@rpc("call_local")
func add_one():
one += 1
```
Le sincronizzazioni attualmente disponibili sono:
- `call_remote`, che esegue la funzione su tutti i peer *eccetto* quello locale
- `call_local`, che esegue la funzione su tutti i peer *incluso* quello locale
### Canale di trasferimento
Infine, è possibile specificare nel tag `@rpc` il ***canale di trasferimento*** che deve essere utilizzato per inviare la richiesta:
```gdscript
var one = 0
@rpc("reliable")
func add_one():
one += 1
```
I canali di trasferimento attualmente disponibili sono:
- `unreliable`, che invia le richieste direttamente tramite UDP, non rilevando se esse vengono perse;
- `unreliable_ordered`, che aggiunge un contatore ad ogni pacchetto inviato tramite UDP, facendo in modo che i peer possano ignorare i pacchetti arrivati nell'ordine sbagliato;
- `reliable`, che implementa un protocollo di acknowledgement su UDP molto simile a quello utilizzato da TCP, a costo di una latenza significativamente maggiore.
## Nodi di sincronizzazione
Godot mette a disposizione dei `Node` speciali per assistere con la sincronizzazione dello stato del gioco tra tutti i peer.
Essi sono:
- `MultiplayerSpawner`, che utilizza il canale `reliable` per automaticamente istanziare tutte le scene che la sua `multiplayer_authority` vi aggiunge come figli;
- `MultiplayerSynchronizer`, che permette di selezionare un canale e alcune proprietà del nodo genitore per automaticamente replicare sugli altri client ad ogni `process` o `physics_process` il valore che esse hanno sulla `multiplayer_authority`.
[API ad alto livello per il networking]: https://docs.godotengine.org/en/stable/tutorials/networking/high_level_multiplayer.html
[GDScript]: https://docs.godotengine.org/en/4.2/tutorials/scripting/gdscript/index.html

49
docs/4 - Architettura.md Normal file
View file

@ -0,0 +1,49 @@
# Architettura di rete di Nanogolf
## [`Main`](../scenes/main.gd)
Il `Node` `Main` è alla radice di ogni istanza del gioco, e si occupa di inizializzare e deinizializzare i nodi relativi al contesto attuale in cui si trova il gioco:
- all'avvio, esso inizializza l'interfaccia grafica `MainMenu`, e attende che essa emetta con uno dei due segnali `connecting_confirmed` (l'utente ha richiesto di connettersi a un dato server) o `hosting_confirmed` (l'utente ha richiesto di voler aprire un server);
- alla richiesta di connessione, sostituisce `MainMenu` con `LobbyMenu`, e crea un'istanza di `Game` chiamata "Client", configurandovi uno `SceneMultiplayer` separato e un relativo `ENetMultiplayerPeer` che si connetta al server richiesto;
- alla richiesta di apertura server, sostituisce allo stesso modo `MainMenu` con `LobbyMenu` e crea allo stesso modo "Client", ma la configura per connettersi sempre al server locale `127.0.0.1`, e inoltre crea una ulteriore istanza di `Game` chiamata "Server" con un proprio `SceneMultiplayer` ed `ENetMultiplayerPeer` che accetti connessioni in arrivo sulla porta UDP `12345`;
- all'inizio effettivo della partita, sostituisce `LobbyMenu` con `GameHUD`, che mostra al giocatore lo stato attuale della partita, e vi connette alcuni segnali di "Client";
- al termine della partita, o quando "Client" segnala che è stata persa o chiusa la connessione al server, riporta il giocatore al `MainMenu`.
> Per via di un bug in Godot Engine, attualmente la chiusura imprevista del server causa un crash senza log di errore in tutti i client connessi.
![](img/main.png)
## [`Game`](../scenes/game.gd)
Il `Node` `Game` viene collocato sempre come figlio di `Main`, e rappresenta lo stato di gioco di un peer, che esso sia un server o un client.
Il server ha sempre autorità su di esso, e:
- gestisce connessioni e disconnessioni dei client, utilizzando la sua `PeerNodeDirectory` per mantenerne un indice composto da sottonodi di tipo `PeerNode` e aventi come nome l'identificatore del peer;
- quando un `PeerNode` connesso si identifica con il proprio nome, utilizza la sua `PlayerNodeDirectory` per controllare se è già connesso un giocatore con quel nome, creando un nuovo `PlayerNode` con quel nome se non esiste e dandone il controllo al relativo peer, oppure rimuovendo il controllo al precedente peer per darlo a quello nuovo nel caso esista già;
- quando un `PeerNode` identificato si disconnette, ridà il controllo del relativo `PlayerNode` al server;
- quando il giocatore host richiede attraverso l'interfaccia grafica di iniziare la partita, usa il `PhaseTracker` per cambiare ovunque la fase della partita da `LOBBY` a `PLAYING`, e richiede al `LevelManager` di istanziare il primo livello;
- quando il `LevelManager` segnala che la `LevelPlaylist` selezionata è esaurita, usa il `PhaseTracker` per aggiornare la fase della partita da `PLAYING` a `ENDED`.
## [`PeerNodeDirectory`](../scenes/peernode_directory.gd)
## [`PeerNode`](../scenes/peernode.gd)
## [`PlayerNodeDirectory`](../scenes/playernode_directory.gd)
## [`PlayerNode`](../scenes/playernode.gd)
## [`PhaseTracker`](../scenes/phase_tracker.gd)
## [`LevelPlaylist`](../scenes/level_playlist.gd)
## [`LevelManager`](../scenes/level_manager.gd)
## [`GolfLevel`](../scenes/golf_level.gd)
## [`GolfTee`](../scenes/golf_tee.gd)
## [`GolfBall`](../scenes/golf_ball.gd)
## [`PuttController`](../scenes/putt_controller.gd)

View file

@ -1 +0,0 @@
# Protocolli

34
docs/img/image.png.import Normal file
View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dodqigd8oaf08"
path="res://.godot/imported/image.png-7415130b462cd2005772a04de35fcb6b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/img/image.png"
dest_files=["res://.godot/imported/image.png-7415130b462cd2005772a04de35fcb6b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
docs/img/main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

34
docs/img/main.png.import Normal file
View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjmbxskm8ue6h"
path="res://.godot/imported/main.png-2e9f1cb8980dbcf29c6452cab77b1b4f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/img/main.png"
dest_files=["res://.godot/imported/main.png-2e9f1cb8980dbcf29c6452cab77b1b4f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1