Multijoueur multiplateforme sans douleur sur Godot

Que voulons-nous faire?

Synchronisation des actions des joueurs dans le jeu avec une architecture client-serveur. Il devrait être possible de jouer à partir du navigateur.





Par exemple, implémentons une salle de chat simple:





  1. Lors de la connexion:





    1. Le client reçoit un identifiant unique;





    2. Le client reçoit des informations sur tous les autres joueurs (ID + nom);





    3. Tous les autres joueurs reçoivent des informations sur le nouveau joueur (ID + nom par défaut);





    4. Un message de connexion apparaît sur la console.





  2. En cas de perte de connexion:





    1. Tous les autres joueurs reçoivent des informations sur la sortie du joueur du serveur (ID);





    2. Un message de sortie apparaît sur la console.





  3. Lors du changement de nom:





    1. Si le nom est déjà pris, le joueur reçoit une erreur;





    2. Tous les joueurs sont informés du changement de nom;





    3. Un message apparaît sur la console.





  4. Lors de l'envoi d'un message pour discuter:





    1. Tous les joueurs voient le message dans le journal / la console.





Remarque: rien ne vous empêche de mettre en œuvre un réseau plus complexe (par exemple, le mouvement des joueurs, certaines autres actions) - mais cela dépasse le cadre de cet article et est en soi un sujet assez complexe. Le chat est l'exemple le plus simple pour démontrer qu'une telle approche de transfert de données fonctionne en principe - et c'est le but de mon article.





Qu'est-il arrivé?

Le projet fini peut être étudié ici: https://github.com/ktori/godobuf-over-websocket-demo





Des captures d'écran se trouvent à la fin de l'article.





Qu'allons-nous utiliser?

  • Godot - moteur de jeu multiplateforme gratuit et 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.





Projet après l'installation de l'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- :





Fenêtre Godobuf
Godobuf

- , .





 Scène

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





Client natif
Client WebSocket
WebSocket-

. , :





  • Gestion des erreurs (par exemple, passer un message séparé error



    à ClMessageResult



    );





  • Gestion de la perte / restauration de connexion;





  • Beaucoup plus.





J'espère que cet article a été utile et a aidé à comprendre Godot, Websockets et protobuf.








All Articles