Apa yang ingin kita lakukan?
Sinkronisasi tindakan pemain dalam game dengan arsitektur klien-server. Ini harus memungkinkan untuk bermain dari browser.
Misalnya, mari kita terapkan ruang obrolan sederhana:
Saat menghubungkan:
Klien menerima ID unik;
Klien menerima informasi tentang semua pemain lain (ID + nama);
Semua pemain lain menerima informasi tentang pemain baru (ID + nama default);
Pesan login muncul di konsol.
Saat kehilangan koneksi:
Semua pemain lain menerima informasi tentang keluarnya pemain dari server (ID);
Pesan keluar muncul di konsol.
Saat mengganti nama:
Jika namanya sudah diambil, pemain menerima kesalahan;
Semua pemain diberi tahu tentang perubahan nama;
Sebuah pesan muncul di konsol.
Saat mengirim pesan ke obrolan:
Semua pemain melihat pesan di log / konsol.
Catatan: tidak ada yang menghalangi Anda untuk mengimplementasikan jaringan yang lebih kompleks (misalnya, pergerakan pemain, beberapa tindakan lainnya) - tetapi ini berada di luar cakupan artikel ini dan dengan sendirinya merupakan topik yang agak kompleks. Obrolan adalah contoh paling sederhana untuk menunjukkan bahwa pendekatan seperti itu untuk mentransfer data, pada prinsipnya, berfungsi - dan inilah tujuan artikel saya.
Apa yang terjadi?
Proyek yang sudah selesai dapat dipelajari di sini: https://github.com/ktori/godobuf-over-websocket-demo
Tangkapan layar dapat ditemukan di akhir artikel.
Apa yang akan kami gunakan?
Godot - mesin game lintas platform gratis dan 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 .. - .
. , :
Penanganan kesalahan (misalnya, meneruskan pesan terpisah
error
keClMessageResult
);
Kehilangan koneksi / penanganan pemulihan;
Lebih banyak.
Saya harap artikel ini bermanfaat dan membantu untuk memahami Godot, websockets dan protobuf.