Multiplayer lintas platform bebas rasa sakit di Godot

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:





  1. Saat menghubungkan:





    1. Klien menerima ID unik;





    2. Klien menerima informasi tentang semua pemain lain (ID + nama);





    3. Semua pemain lain menerima informasi tentang pemain baru (ID + nama default);





    4. Pesan login muncul di konsol.





  2. Saat kehilangan koneksi:





    1. Semua pemain lain menerima informasi tentang keluarnya pemain dari server (ID);





    2. Pesan keluar muncul di konsol.





  3. Saat mengganti nama:





    1. Jika namanya sudah diambil, pemain menerima kesalahan;





    2. Semua pemain diberi tahu tentang perubahan nama;





    3. Sebuah pesan muncul di konsol.





  4. Saat mengirim pesan ke obrolan:





    1. 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 , , .





: game.proto





.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.





Proyek setelah menginstal addon godobuf
godobuf

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- :





Jendela Godobuf
Godobuf

- , .





 Tempat kejadian

- . 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 .. - .





Klien asli
Klien WebSocket
WebSocket-

. , :





  • Penanganan kesalahan (misalnya, meneruskan pesan terpisah error



    ke ClMessageResult



    );





  • Kehilangan koneksi / penanganan pemulihan;





  • Lebih banyak.





Saya harap artikel ini bermanfaat dan membantu untuk memahami Godot, websockets dan protobuf.








All Articles