HackMD
  • Beta
    Beta  Get a sneak peek of HackMD’s new design
    Turn on the feature preview and give us feedback.
    Go → Got it
      • Create new note
      • Create a note from template
    • Beta  Get a sneak peek of HackMD’s new design
      Beta  Get a sneak peek of HackMD’s new design
      Turn on the feature preview and give us feedback.
      Go → Got it
      • Sharing Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • More (Comment, Invitee)
      • Publishing
        Please check the box to agree to the Community Guidelines.
        Everyone on the web can find and read all notes of this public team.
        After the note is published, everyone on the web can find and read this note.
        See all published notes on profile page.
      • Commenting Enable
        Disabled Forbidden Owners Signed-in users Everyone
      • Permission
        • Forbidden
        • Owners
        • Signed-in users
        • Everyone
      • Invitee
      • No invitee
      • Options
      • Versions and GitHub Sync
      • Transfer ownership
      • Delete this note
      • Template
      • Save as template
      • Insert from template
      • Export
      • Dropbox
      • Google Drive Export to Google Drive
      • Gist
      • Import
      • Dropbox
      • Google Drive Import from Google Drive
      • Gist
      • Clipboard
      • Download
      • Markdown
      • HTML
      • Raw HTML
    Menu Sharing Create Help
    Create Create new note Create a note from template
    Menu
    Options
    Versions and GitHub Sync Transfer ownership Delete this note
    Export
    Dropbox Google Drive Export to Google Drive Gist
    Import
    Dropbox Google Drive Import from Google Drive Gist Clipboard
    Download
    Markdown HTML Raw HTML
    Back
    Sharing
    Sharing Link copied
    /edit
    View mode
    • Edit mode
    • View mode
    • Book mode
    • Slide mode
    Edit mode View mode Book mode Slide mode
    Note Permission
    Read
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    Write
    Only me
    • Only me
    • Signed-in users
    • Everyone
    Only me Signed-in users Everyone
    More (Comment, Invitee)
    Publishing
    Please check the box to agree to the Community Guidelines.
    Everyone on the web can find and read all notes of this public team.
    After the note is published, everyone on the web can find and read this note.
    See all published notes on profile page.
    More (Comment, Invitee)
    Commenting Enable
    Disabled Forbidden Owners Signed-in users Everyone
    Permission
    Owners
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Invitee
    No invitee
       owned this note    owned this note      
    Published Linked with GitHub
    Like BookmarkBookmarked
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # WebRTC architecture This is for Snopek Games' WebRTC with Nakama demo. NON EXHAUSTIVE, EDITS WELCOME ## Singletons any script in the project can access all of these files ### Build useless, only used by GitLab CI when building the game ### GameState for now, just tells your if the game is in Local or Online mode ### Nakama a singleton for communicating with a Nakama server - part of the Nakama client library from Heroic Labs. Don't change unless you need an uncommitted patch to the upstream library. ### OnlineMatch sets up all of your webrtc stuff. Don't touch this either, unless you know what your doing. (TODO: Go back and document how all of this works, theres a lot of important stuff in here) (I haven't covered everything in here yet, but I want to.) Exposes these variables: - Config variables - min_players, max_players - client_version - ice_servers - This seems to contain data relating to STUN servers the project uses. - Nakama variables - nakama_socket -> an object representation of the persistent connection the client has to the server. - More specifically, the socket is the "realtime client" which is a websocket connection to the Nakama server. - The actual game is run over WebRTC, but we are using the websocket to set up the matchmaking and act as a signaling server for the WebRTC to run on. - This is also where the in game chat is hosted - my_session_id, match_id -> both string representation of id's - matchmaker_ticker - WebRTC variables (variables starting with underscores aren't meant to be accessed from outside this script, they are private) - _webrtc_multiplayer - _webrtc_peers -> Dictionary - _webrtc_peers_connected -> Dictionary - Player variables - players -> Dictionary of players Networking enums: - MatchState{LOBBY, MATCHING, CONNECTING, WAITING_FOR_ENOUGH_PLAYERS, READY, PLAYING} - MatchMode{NONE, CREATE, JOIN, MATCHMAKER} - PlayerStatus{CONNECTING, CONNECTED} - MatchOpCode{WEBRTC_PEER_METHOD, JOIN_SUCCESS, JOIN_ERROR} Exposes these signals: - Error Signals - error (takes in a "message") - disconnected() - Match Signals - match_created(takes in a "match_id") - match_joined(takes in a "match_id") - matchmaker_matched(takes in "players", a dictionary of Player objects) - Player Signals - player_joined(takes in a Player object) - player_left(takes in a Player object) - player_status_changed(takes in a Player object, and also a "status object") - Ready Up Signals - match_ready(takes in "players", a dictionary of Player objects) - match_not_ready() #### Player SUPER IMPORTANT: Online Match has its own class called Player that is used for many of the signals seen above. It's purpose is to connect a Nakama user to a Godot peer, and includes within it a lot of important information. Variables: - session_id -> String: how Nakama identifies the current player session - peer_id -> int: how Godot identifies the peer in its High-level Multiplayer API - username -> String Methods: - _init(_session_id: String, _username: String, _peer_id: int) - A constuctor for the class - static from_presence(presence: NakamaRTAPI.UserPresence, _peer_id: int) - Returns a Player object built from Nakama presence data - static func from_dict(data: Dictionary) - converts a Player from it's Dictionary representation. - useful for deserialization. - to_dict() - converts a Player to it's Dictionary representation. You can turn it back with the function above. Useful for serialzation. ### Online Boilerplate networking stuff. Exposes these variables: - nakama_client -> the "request/response" client thats connecting to the server. - It makes HTTP requests to the server (any one time thing that doesn't require a constant connections) - For example, authentication requests. - nakama_session -> an object representation of a successful authentication attempt to the server. - nakama_socket -> see the explanation in OnlineMatch Implements these functions: - get_nakama_client - if it doesn't exist, it creates a client object to connect to the Nakama server, and sets nakama_client to it. - set_nakama_session: - sets nakama_session to the new session passed in. Closes out the old session for you - Emits signal "session_changed" if changed to any session. - However, emits "session_connected" only if the session is valid. - connect_nakama_socket - if the current socket isn't null or currently connecting, then create a new socket, and set the current nakama_socket to it. - emits "socket_connected" ## In-game Networking Scripts These are the scripts that, while not being tied to the actual gameplay, help set up with the networking stuff and keeps it running. Important note: There are a bunch of functions that are "remotesync" functions. What this means is that it can be called as a normal function, but it can also be called by remote clients on the local client. (Ex. Client B can call player_ready in an rpc call and say they want it called on Client A). When you rpc a remotesync function, you call it on both the local AND remote clients. ### Game Sets up the players and does match/player life cycle stuff Exposes these variables: - Player - just a reference to the (actual in game) Player - map_scene - just a packed scene - map - a reference to the actual map that the match will take place in - camera - just the in game camera - original_camera_position - camera will probably track the players, so its best to store the original location for easy setup between each rounds - game_started, game_over, both booleans, pretty self-explanatory - players_alive - a dictionary of all the players left alive in the round. - players_setup - a dictionary marking which players have reported back that they have finished setting up the game Exposes these signals for callbacks: - game_started() - player_dead(player_id) - game_over(player_id) Implements these functions: - game_start(players: Dictionary) - If its an online match, an rpc is set out to call _do_game_setup(players) on ALL CLIENTS, both local and remote - else, just _do_game_setup on the local client - _do_game_setup(players: Dictionary) - This is a remotesync function - We first pause the game to set everything up - If game_started is true, stop the game since we need to set everything up again - After that, we set game_started to true, game over is false, and players_alive = players since we're starting with a clean slate and all players should be alive - We then reload the map. - After that, we spawn in all of the (in game) player nodes, and connect them to their peer representations. We take care to make sure they each spawn in the correct location on the map. We also make sure that the function _on_player_dead(player_id) is connected to the signal player dead for each player object. - By the way, if its not an online match, we set up multiple local inputs for each local plyaer - If it is an online match, we spawn in the player node that is the local client last, as that is a special case, since we need to set up our inputs to be directed towards it as well as the local client's peer id. - After all that, we either do a normal game start(_do_game_start()) if its an offline match, or we call _finished_game_setup on the lobby host. - _finished_game_setup(player_id: int) - This is a mastersync function, which means this is ONLY called when the client is the host of the lobby. - Since this client is the host, it gets the confermation that each player that's alive is setup. - If all the players that are alive are set up, then the host calls _do_game_start() on every client including itself. - _do_game_start() - This is a remotesync function - First, it starts up the map. - Then, it sends out the signal "game_started", which will go be called in Main.gd as _on_Game_game_started() - It also unpauses the game - game_stop() - Stop the map being played, and set game_started to false. - Clear both players_setup and players_alive - It also removes all of the current player peer representations from the scene. - reload_map() - Remove the map - create a new instance of the map, in the same place as the old map in the scene tree - Reset the camera to its original position in the map. - kill_player(player_id) - get the player_node related to player_id - If it exists, call the die method on it. - If it doesn't have that, just queue free it, and call _on_player_dead(player_id) - _on_player_dead(player_id) - emit "player_dead" with player_id - erase the dead player from players_alive - if there's no players left alive, game_over = true and emit_signal "game_over" ### Main Gets you connected to Nakama and also sets up the WebRTC connections (We will ignore the UI stuff as it's not really relevant and its pretty self-explanatory.) Before we dive into this code, there are a few functions that use "get_tree().is_network_server()", so lets quickly explain what that is. - In each lobby, there should be one peer/player who is considered the "host" - this is the player with a peer id of 1. In private matches, this is the player who originally created the match - in public matches created through the matchmaker it's chosen pseudo-randomly. Besides the other players/peers they have the extra responsibility of keeping track of certain things, including players scores and starting and ending the game. Exposes these variables: - game -> an object representation of the Game script (as seen above) - players -> the list of all players currently in the server - players_ready -> the list of players ready to actually start the match. Represented as a bunch of session_id (succesful authentication) tokens. - players_score -> dictionary of the players current scores. This is only populated on the host We'll skip over most of the UI related functions, but there's one important one: _on_ReadyScreen_ready_pressed() -> void: - This sends a callback to all of the remote clients player_ready function (defined down below) and passes in the session id from OnlineMatch ### OnlineMatch callback functions - _on_OnlineMatch_error(message: String) - Connected to OnlineMatch "error" signal, just prints out an error on screen - _on_OnlineMatch_disconnected() - Connected to OnlineMatch "disconnected" signal, prints out a disconnected message - _on_OnlineMatch_player_left(player) - Connected to OnlineMatch "player_left" signal, takes in a (OnlineMatch) Player object - print out a message showing the player left - Removes the Player from the variable "game" (reference to Game.gd) - Deletes it from both players and players_ready - _on_OnlineMatch_player_status_changed(player, status) - Takes in an OnlineMatch Player object and status - if the status has changed so that the player is now connected - First check to see if we are the "host" for this lobby, which gives us permission to start the game. - If so, then we will loop through all of the current players in players_ready. - We will call player_ready() on each of them, sending in the peer_id of the player that has sucessfully connected. ### Gameplay methods and callbacks - player_ready(session_id: String) - Takes in a session_id, representing the game client that has signaled that its ready. - This is a "remotesync" function, meaning that it can be called as a normal function, but it can also be called by remote clients on the local client. (Ex. Client B can call player_ready in an rpc call and say they want it called on Client A) - We first print a message showing that we're ready - Then, we make sure that we are the "host" for this lobby and that the session_id being passed in isnt the local client. - Then, in players_ready, we set that the player with the session_id is ready (true) - We also do another check to see if players_ready is equal to the amount of players currently in the lobby. - If it is, then we se the match_state to playing if it isn't already, and we start the game (start_game()). - start_game() - If its an online game, it first sets players to the list of players stored in OnlineMatch, else just set it to some predefined defaults - Then, start the game (by calling game.game_start(players)) - stop_game() - Leave the online match (OnlineMatch.leave()) - Then, clear players, players_ready, and players_score - Finally, stop the game (game.game_stop()) - restart_game() - Just calls stop_game() and then start_game() The following are callbacks from Game to Main, but they are all declared in the scene (Main.tscn) - _on_Game_game_started() - callback from game_started() - sets up the game for playing - _on_Game_player_dead(player_id: int) - callback from player_dead(player_id) - Check to see if the game is online. - If so, get the id of the local player client (get_tree().get_network_unique_id()) and see if it's the same of the player_id passed into the function. - If it is, print a message saying that you're dead, you lost or something to that effect. - _on_Game_game_over(player_id: int) - callback from game_over(player_id) - Called on game over, player_id is the id of the player who won. - First, clear out everyone in players_ready - First check to see if this is an online game. If it is, just call show_winner, and pass in the player associated with the player_id - Else, first we check to see if we are the "host" of this lobby. - If we are, first we either set the player score of that player to 1 if it didn't exist yet, else we add it by one. - after that, then we just rpc show_winner, passing in is_match. - is_match being a boolean that checks if the game has reached match point. (TODO: Document this function more. It's late and I'm tired) - show_winner(name: String, session_id: String = '', score: int = 0, is_match: bool = false) - name is the name of the plauyer, and is_match is a boolean that checks if the game has reached match point. - This is a remotesync function - ## Gameplay scripts w/ Networking ### Map Constants: - TILE_SIZE - tile size Exposes these functions: - map_start() - Things you want to happen when the map starts. Not used in this game, but in Retro Tank Party this starts the powerup spawn timers, or in Fish Game this spawns a weapon on all the weapon spawners. - map_stop() - Things you want to happen when the map stops. Mostly, just stopping the things started in `map_start()`. - get_map_rect() - just returns the size of the map. ### Player Exposes thses variables: - player_name_label - reference to the UI label - hitbox - Hitbox reference - animation_player - animation player reference - player_controlled - boolean saying if this player node is the one being controlled by local inputs - input_prefix - tells which port the inputs are coming from. - speed - speed of the player Has these signals: - player_dead() - called when the player dies. Implements these functions: - set_player_name(player_name: String) - set's the player label text to the player name - attack() - hurts anyone who overlaps the hitbox except for the player node itself - hurt() - This is a very simple game, so the player just dies. - If its online, its a normal die, else die gets rpc to every single client. - die() - This is a remotesync function - the player node gets queue_free() and a "player_dead" signal is emitted. - _physics_process(delta: float) - if the local client is controlling the player node - Get the input vector, and move the player node with move and slide with the strength of speed - check to see if the player pressed attack and that an attacking animation is not being played. If so, play an attack animation - if its online, do an rpc call for update_remote_player with the new game state values - update_remote_player(_position: Vector2, is_attacking: bool) - updates the gamestate - This is marked as `puppet` because you only want it run on peers that aren't the master of this player (as set in `player.set_network_master()`) - Extremely naive position and animation sync'ing. - This will work locally, and under ideal network conditions, but likely won't be acceptable over the live internet for a large percentage of users. - You'll need to replace this with more efficient sync'ing mechanism, which could include input prediction, rollback, limiting how often sync'ing happens or any other number of techniques. - In addition to that, you'll also need to expand the number of things that are sync'd, depending on the needs of your game.

    Import from clipboard

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lost their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template is not available.


    Upgrade

    All
    • All
    • Team
    No template found.

    Create custom template


    Upgrade

    Delete template

    Do you really want to delete this template?

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Tutorials

    Book Mode Tutorial

    Slide Mode Tutorial

    YAML Metadata

    Contacts

    Facebook

    Twitter

    Feedback

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions

    Versions and GitHub Sync

    Sign in to link this note to GitHub Learn more
    This note is not linked with GitHub Learn more
     
    Add badge Pull Push GitHub Link Settings
    Upgrade now

    Version named by    

    More Less
    • Edit
    • Delete

    Note content is identical to the latest version.
    Compare with
      Choose a version
      No search result
      Version not found

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub

        Please sign in to GitHub and install the HackMD app on your GitHub repo. Learn more

         Sign in to GitHub

        HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Available push count

        Upgrade

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Upgrade

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully