Was wollen wir machen?
Synchronisation von Spieleraktionen im Spiel mit einer Client-Server-Architektur. Es sollte möglich sein, über den Browser zu spielen.
Lassen Sie uns zum Beispiel einen einfachen Chatraum implementieren:
Beim Anschließen:
Der Client erhält eine eindeutige ID.
Der Kunde erhält Informationen über alle anderen Spieler (ID + Name);
Alle anderen Spieler erhalten Informationen über den neuen Spieler (ID + Standardname);
Auf der Konsole wird eine Anmeldemeldung angezeigt.
Bei Verbindungsverlust:
Alle anderen Spieler erhalten Informationen über den Ausstieg des Spielers vom Server (ID).
Auf der Konsole wird eine Beendigungsmeldung angezeigt.
Beim Ändern des Namens:
Wenn der Name bereits vergeben ist, erhält der Spieler eine Fehlermeldung.
Alle Spieler werden über die Namensänderung informiert.
Auf der Konsole wird eine Meldung angezeigt.
Beim Senden einer Nachricht an den Chat:
Alle Spieler sehen die Nachricht im Protokoll / in der Konsole.
Hinweis: Nichts hindert Sie daran, komplexere Netzwerke zu implementieren (z. B. die Bewegung von Spielern, einige andere Aktionen). Dies geht jedoch über den Rahmen dieses Artikels hinaus und ist an sich ein recht komplexes Thema. Chat ist das einfachste Beispiel dafür, dass ein solcher Ansatz zur Datenübertragung im Prinzip funktioniert - und das ist der Zweck meines Artikels.
Was ist passiert?
Das fertige Projekt kann hier studiert werden: https://github.com/ktori/godobuf-over-websocket-demo
Screenshots finden Sie am Ende des Artikels.
Was werden wir verwenden?
Godot - free and open source ;
Protobuf - / ;
Godobuf - Godot, .gd (GDScript) .proto;
Ktor - Kotlin ( Kotlin - , - - - Protobuf, ).
, , :
;
, ;
VCS, .. ;
- - /.
Protobuf - , , , JSON - ;
Protobuf , .
- :
/ protobuf , , , ;
, protobuf , , .
.proto-, - game.proto. , ( - ).
:
syntax = "proto3";
//
option java_package = "me.ktori.game.proto";
//
option java_outer_classname = "GameProto";
, :
-
, - RPC Cl**Result . gRPC - godobuf gRPC-. :
//
// -
//
//
message ClSetName {
string name = 1;
}
//
message ClSendChatMessage {
string text = 1;
}
// ,
message ClMessage {
// ,
// ,
oneof data {
ClSetName set_name = 1;
ClSendChatMessage send_chat_message = 2;
}
}
-
//
// -
//
// ClSetName
message ClSetNameResult {
// -
bool success = 1;
}
// -
message ClMessageResult {
oneof result {
ClSetNameResult set_name = 1;
}
}
//
// ID
message SvConnected {
int32 id = 1;
string name = 2;
}
//
// ID
message SvClientConnected {
int32 id = 1;
string name = 2;
}
//
// ID
message SvClientDisconnected {
int32 id = 1;
}
//
// ID
message SvNameChanged {
int32 id = 1;
string name = 2;
}
//
message SvChatMessage {
int32 from = 1;
string text = 2;
}
//
message SvMessage {
// SvMessage
oneof data {
ClMessageResult result = 1;
SvConnected connected = 2;
SvClientConnected client_connected = 3;
SvClientDisconnected client_disconnected = 4;
SvNameChanged name_changed = 5;
SvChatMessage chat_message = 6;
}
}
:
ClMessage
;
SvMessage
;
result -
ClMessageResult
.
naming convention:
ClFooBar
, ;
SvFooBar
, , :
ClFooBarResult
ClFooBar
.
Godot
( 2D ).
Godobuf
: https://github.com/oniksan/godobuf, README - addons.
WebSocketClient
( WebSocketClient). : , URL .
, - :
extends Node2D
var ws: WebSocketClient
#
func _ready():
# WebSocketClient
ws = WebSocketClient.new()
ws.connect("connection_established", self, "_on_ws_connection_established")
ws.connect("data_received", self, "_on_ws_data_received")
# 8080
ws.connect_to_url("ws://127.0.0.1:8080")
#
func _on_ws_connection_established(_protocol):
pass
#
func _on_ws_data_received():
pass
protobuf:GDScript
! Godobuf proto- :
- , .
- . pressed
Send Rename . show_message
, Label VBoxContainer, .
- .
:
const GameProto = preload("res://game_proto.gd")
ClMessage Send/Rename:
# $Name
func _on_SetName_pressed():
var msg = GameProto.ClMessage.new()
var sn = msg.new_set_name()
sn.set_name(name_input.text)
send_msg(msg)
# $Message
func _on_SendMessage_pressed():
var msg = GameProto.ClMessage.new()
var scm = msg.new_send_chat_message()
scm.set_text(message_input.text)
message_input.clear()
send_msg(msg)
- send_msg. :
# ClMessage
func send_msg(msg: GameProto.ClMessage):
# ClMessage PoolByteArray ws
ws.get_peer(1).put_packet(msg.to_bytes())
to_bytes
( ClMessage
) godobuf - !
- . , - , .
#
func _process(_delta):
# ,
ws.poll()
#
func _on_ws_connection_established(_protocol):
show_message("Connection established!")
#
func _on_ws_data_received():
#
for i in range(ws.get_peer(1).get_available_packet_count()):
#
var bytes = ws.get_peer(1).get_packet()
var sv_msg = GameProto.SvMessage.new()
#
sv_msg.from_bytes(bytes)
#
_on_proto_msg_received(sv_msg)
#
func _on_proto_msg_received(msg: GameProto.SvMessage):
# .. oneof -
#
if msg.has_connected():
pass
elif msg.has_client_connected():
pass
elif msg.has_client_disconnected():
pass
elif msg.has_chat_message():
pass
elif msg.has_name_changed():
pass
elif msg.has_result():
pass
else:
push_warning("Received unknown message: %s" % msg.to_string())
poll
WebSocketClient
, . _process
- ID :
# ID
var own_id: int
# ID <>
var names = Dictionary()
:
# _on_proto_msg_received
if msg.has_connected():
var c = msg.get_connected()
own_id = c.get_id()
name_input.text = c.get_name()
show_message("Welcome! Your ID is %d and your assigned name is '%s'." % [c.get_id(), c.get_name()])
if/elif . GitHub: Main.gd
. - Kotlin Ktor. , GitHub - .
:
gradle- :
server - ;
proto - - :
com.google.protobuf, com.google.protobuf:protobuf-java ;
, / -.
- , broadcast- , .
Godot- , Linux/Windows/Android .. - .
. , :
Fehlerbehandlung (z. B. Weitergabe einer separaten Nachricht
error
anClMessageResult
);
Verbindungsverlust / Handhabung der Wiederherstellung;
Vieles andere.
Ich hoffe, dieser Artikel war hilfreich und hat geholfen, Godot, Websockets und Protobuf zu verstehen.