├── .import ├── .gdignore ├── Pong Multiplayer.apple-touch-icon.png-018b6e618bd4a9a4ea206cbbcff25a8e.md5 ├── Pong Multiplayer.apple-touch-icon.png-018b6e618bd4a9a4ea206cbbcff25a8e.stex ├── Pong Multiplayer.icon.png-4b186b254f90ec401f898fa2db094f61.md5 ├── Pong Multiplayer.icon.png-4b186b254f90ec401f898fa2db094f61.stex ├── Pong Multiplayer.png-d8f31a8250558e6355e1ae14434a651f.md5 ├── Pong Multiplayer.png-d8f31a8250558e6355e1ae14434a651f.stex ├── ball.png-9a4ca347acb7532f6ae347744a6b04f7.md5 ├── ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex ├── icon.png-487276ed1e3a0c39cad0279d744ee560.md5 ├── icon.png-487276ed1e3a0c39cad0279d744ee560.stex ├── icon.png-73e9e3a6379fbadf9cd8afcce773e262.md5 ├── icon.png-73e9e3a6379fbadf9cd8afcce773e262.stex ├── paddle.png-0e798fb0912613386507c9904d5cc01a.md5 ├── paddle.png-0e798fb0912613386507c9904d5cc01a.stex ├── separator.png-f981c8489b9148e2e1dc63398273da74.md5 └── separator.png-f981c8489b9148e2e1dc63398273da74.stex ├── README.md ├── ball.png ├── ball.png.import ├── ball.tscn ├── export_presets.cfg ├── icon.png ├── icon.png.import ├── linux_server ├── lobby.tscn ├── logic ├── ball.gd ├── lobby.gd ├── paddle.gd └── pong.gd ├── netvars.gd ├── paddle.png ├── paddle.png.import ├── paddle.tscn ├── pong.tscn ├── project.godot ├── screenshots ├── .gdignore └── pong_multiplayer.png ├── separator.png ├── separator.png.import ├── server-cli.gd ├── server-cli.tscn └── server_cli.gd /.import/.gdignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.import/Pong Multiplayer.apple-touch-icon.png-018b6e618bd4a9a4ea206cbbcff25a8e.md5: -------------------------------------------------------------------------------- 1 | source_md5="7f4e8c7eccbb148afff433283fd0b22e" 2 | dest_md5="edacb3e2262836820ef553347371492a" 3 | 4 | -------------------------------------------------------------------------------- /.import/Pong Multiplayer.apple-touch-icon.png-018b6e618bd4a9a4ea206cbbcff25a8e.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/Pong Multiplayer.apple-touch-icon.png-018b6e618bd4a9a4ea206cbbcff25a8e.stex -------------------------------------------------------------------------------- /.import/Pong Multiplayer.icon.png-4b186b254f90ec401f898fa2db094f61.md5: -------------------------------------------------------------------------------- 1 | source_md5="6cff90b6ab3596e0cbbf56155a3ee377" 2 | dest_md5="5bc7a11c749a2a681a79d6facd22c4f3" 3 | 4 | -------------------------------------------------------------------------------- /.import/Pong Multiplayer.icon.png-4b186b254f90ec401f898fa2db094f61.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/Pong Multiplayer.icon.png-4b186b254f90ec401f898fa2db094f61.stex -------------------------------------------------------------------------------- /.import/Pong Multiplayer.png-d8f31a8250558e6355e1ae14434a651f.md5: -------------------------------------------------------------------------------- 1 | source_md5="7e41bf3051b18e392a4bb6c0cc45cd7c" 2 | dest_md5="73e4f4d3969b9486e63fd804f5f0f1a9" 3 | 4 | -------------------------------------------------------------------------------- /.import/Pong Multiplayer.png-d8f31a8250558e6355e1ae14434a651f.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/Pong Multiplayer.png-d8f31a8250558e6355e1ae14434a651f.stex -------------------------------------------------------------------------------- /.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.md5: -------------------------------------------------------------------------------- 1 | source_md5="c779e5d79d3b5d4848ba0bf05a2401e7" 2 | dest_md5="a5b846b901a8806b2cb02e14db267b54" 3 | 4 | -------------------------------------------------------------------------------- /.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex -------------------------------------------------------------------------------- /.import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5: -------------------------------------------------------------------------------- 1 | source_md5="dbd41ed4d2e25e2d2f1cce81b487a85c" 2 | dest_md5="5bc7a11c749a2a681a79d6facd22c4f3" 3 | 4 | -------------------------------------------------------------------------------- /.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex -------------------------------------------------------------------------------- /.import/icon.png-73e9e3a6379fbadf9cd8afcce773e262.md5: -------------------------------------------------------------------------------- 1 | source_md5="dbd41ed4d2e25e2d2f1cce81b487a85c" 2 | dest_md5="5bc7a11c749a2a681a79d6facd22c4f3" 3 | 4 | -------------------------------------------------------------------------------- /.import/icon.png-73e9e3a6379fbadf9cd8afcce773e262.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/icon.png-73e9e3a6379fbadf9cd8afcce773e262.stex -------------------------------------------------------------------------------- /.import/paddle.png-0e798fb0912613386507c9904d5cc01a.md5: -------------------------------------------------------------------------------- 1 | source_md5="3ab2ff9afa5e8261572f0127cacbe0ad" 2 | dest_md5="bd5104bb04cacd4614cd0c7a29fe7f84" 3 | 4 | -------------------------------------------------------------------------------- /.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex -------------------------------------------------------------------------------- /.import/separator.png-f981c8489b9148e2e1dc63398273da74.md5: -------------------------------------------------------------------------------- 1 | source_md5="3f6c4cdf695fd63014eddbf4e62e8181" 2 | dest_md5="96eb0fd30ccb1daf9e7173391a375695" 3 | 4 | -------------------------------------------------------------------------------- /.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pong Multiplayerv2 2 | A fork of Godot Engine's multiplayer [Pong](https://github.com/godotengine/godot-demo-projects/tree/master/networking/multiplayer_pong) project which I am rewriting to support the TCP protocol. 3 | 4 | With that said, I am also adding support for hosting a Linux server for this game. 5 | 6 | This project will be used in a demo where I demonstrate a server directory I've been working on for [@modcommunity](https://github.com/modcommunity). For more information on this, please check [this repository](https://github.com/gamemann/tmc-servers-engine) out! 7 | 8 | # Credits 9 | * [Godot Engine](http://godotengine.org/) 10 | * [Christian Deacon](https://github.com/gamemann) - Rewriting project in TCP to support HTML 5 and implementing Linux server. -------------------------------------------------------------------------------- /ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/ball.png -------------------------------------------------------------------------------- /ball.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://ball.png" 13 | dest_files=[ "res://.import/ball.png-9a4ca347acb7532f6ae347744a6b04f7.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /ball.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://logic/ball.gd" type="Script" id=1] 4 | [ext_resource path="res://ball.png" type="Texture" id=2] 5 | 6 | [sub_resource type="CircleShape2D" id=1] 7 | radius = 5.11969 8 | 9 | [node name="Ball" type="Area2D"] 10 | script = ExtResource( 1 ) 11 | 12 | [node name="Sprite" type="Sprite" parent="."] 13 | texture = ExtResource( 2 ) 14 | 15 | [node name="Shape" type="CollisionShape2D" parent="."] 16 | shape = SubResource( 1 ) 17 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="HTML5" 4 | platform="HTML5" 5 | runnable=true 6 | custom_features="" 7 | export_filter="all_resources" 8 | include_filter="" 9 | exclude_filter="" 10 | export_path="../pongmultiplayerv2_export/attemptone-html/pongmultiplayerv2.html" 11 | script_export_mode=1 12 | script_encryption_key="" 13 | 14 | [preset.0.options] 15 | 16 | custom_template/debug="" 17 | custom_template/release="" 18 | variant/export_type=0 19 | vram_texture_compression/for_desktop=true 20 | vram_texture_compression/for_mobile=false 21 | html/export_icon=true 22 | html/custom_html_shell="" 23 | html/head_include="" 24 | html/canvas_resize_policy=2 25 | html/focus_canvas_on_start=true 26 | html/experimental_virtual_keyboard=false 27 | progressive_web_app/enabled=false 28 | progressive_web_app/offline_page="" 29 | progressive_web_app/display=1 30 | progressive_web_app/orientation=0 31 | progressive_web_app/icon_144x144="" 32 | progressive_web_app/icon_180x180="" 33 | progressive_web_app/icon_512x512="" 34 | progressive_web_app/background_color=Color( 0, 0, 0, 1 ) 35 | 36 | [preset.1] 37 | 38 | name="Windows Desktop" 39 | platform="Windows Desktop" 40 | runnable=false 41 | custom_features="" 42 | export_filter="all_resources" 43 | include_filter="" 44 | exclude_filter="" 45 | export_path="../pongmultiplayerv2_export/windows/pongmultiplayerv2.exe" 46 | script_export_mode=1 47 | script_encryption_key="" 48 | 49 | [preset.1.options] 50 | 51 | custom_template/debug="" 52 | custom_template/release="" 53 | binary_format/64_bits=true 54 | binary_format/embed_pck=false 55 | texture_format/bptc=false 56 | texture_format/s3tc=true 57 | texture_format/etc=false 58 | texture_format/etc2=false 59 | texture_format/no_bptc_fallbacks=true 60 | codesign/enable=false 61 | codesign/identity_type=0 62 | codesign/identity="" 63 | codesign/password="" 64 | codesign/timestamp=true 65 | codesign/timestamp_server_url="" 66 | codesign/digest_algorithm=1 67 | codesign/description="" 68 | codesign/custom_options=PoolStringArray( ) 69 | application/modify_resources=true 70 | application/icon="" 71 | application/file_version="" 72 | application/product_version="" 73 | application/company_name="" 74 | application/product_name="" 75 | application/file_description="" 76 | application/copyright="" 77 | application/trademarks="" 78 | 79 | [preset.2] 80 | 81 | name="server-cli" 82 | platform="Linux/X11" 83 | runnable=true 84 | custom_features="" 85 | export_filter="scenes" 86 | export_files=PoolStringArray( "res://server-cli.tscn" ) 87 | include_filter="" 88 | exclude_filter="" 89 | export_path="../pongmultiplayerv2_export/server-cli-linux/server_test.x86_64" 90 | script_export_mode=1 91 | script_encryption_key="" 92 | 93 | [preset.2.options] 94 | 95 | custom_template/debug="" 96 | custom_template/release="" 97 | binary_format/64_bits=true 98 | binary_format/embed_pck=false 99 | texture_format/bptc=false 100 | texture_format/s3tc=true 101 | texture_format/etc=false 102 | texture_format/etc2=false 103 | texture_format/no_bptc_fallbacks=true 104 | 105 | [preset.3] 106 | 107 | name="server-cli" 108 | platform="Windows Desktop" 109 | runnable=true 110 | custom_features="" 111 | export_filter="scenes" 112 | export_files=PoolStringArray( "res://ball.tscn", "res://paddle.tscn", "res://pong.tscn", "res://server-cli.tscn" ) 113 | include_filter="" 114 | exclude_filter="" 115 | export_path="../pongmultiplayerv2_export/server-cli-linux/server_test.exe" 116 | script_export_mode=1 117 | script_encryption_key="" 118 | 119 | [preset.3.options] 120 | 121 | custom_template/debug="" 122 | custom_template/release="" 123 | binary_format/64_bits=true 124 | binary_format/embed_pck=false 125 | texture_format/bptc=false 126 | texture_format/s3tc=true 127 | texture_format/etc=false 128 | texture_format/etc2=false 129 | texture_format/no_bptc_fallbacks=true 130 | codesign/enable=false 131 | codesign/identity_type=0 132 | codesign/identity="" 133 | codesign/password="" 134 | codesign/timestamp=true 135 | codesign/timestamp_server_url="" 136 | codesign/digest_algorithm=1 137 | codesign/description="" 138 | codesign/custom_options=PoolStringArray( ) 139 | application/modify_resources=true 140 | application/icon="" 141 | application/file_version="" 142 | application/product_version="" 143 | application/company_name="" 144 | application/product_name="" 145 | application/file_description="" 146 | application/copyright="" 147 | application/trademarks="" 148 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/icon.png -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /linux_server: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/linux_server -------------------------------------------------------------------------------- /lobby.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://logic/lobby.gd" type="Script" id=1] 4 | 5 | [node name="Lobby" type="Control"] 6 | anchor_left = 0.5 7 | anchor_top = 0.5 8 | anchor_right = 0.5 9 | anchor_bottom = 0.5 10 | margin_left = -320.0 11 | margin_top = -200.0 12 | margin_right = 320.0 13 | margin_bottom = 200.0 14 | size_flags_horizontal = 2 15 | size_flags_vertical = 2 16 | 17 | [node name="Title" type="Label" parent="."] 18 | margin_left = 210.0 19 | margin_top = 40.0 20 | margin_right = 430.0 21 | margin_bottom = 80.0 22 | size_flags_horizontal = 2 23 | size_flags_vertical = 0 24 | text = "Multiplayer Pong" 25 | align = 1 26 | valign = 1 27 | 28 | [node name="LobbyPanel" type="Panel" parent="."] 29 | margin_left = 210.0 30 | margin_top = 160.0 31 | margin_right = 430.0 32 | margin_bottom = 270.0 33 | size_flags_horizontal = 2 34 | size_flags_vertical = 2 35 | script = ExtResource( 1 ) 36 | 37 | [node name="AddressLabel" type="Label" parent="LobbyPanel"] 38 | margin_left = 10.0 39 | margin_top = 10.0 40 | margin_right = 62.0 41 | margin_bottom = 24.0 42 | size_flags_horizontal = 2 43 | size_flags_vertical = 0 44 | text = "Address" 45 | 46 | [node name="Address" type="LineEdit" parent="LobbyPanel"] 47 | margin_left = 10.0 48 | margin_top = 30.0 49 | margin_right = 210.0 50 | margin_bottom = 54.0 51 | size_flags_horizontal = 2 52 | size_flags_vertical = 2 53 | text = "127.0.0.1" 54 | 55 | [node name="HostButton" type="Button" parent="LobbyPanel"] 56 | margin_left = 10.0 57 | margin_top = 60.0 58 | margin_right = 90.0 59 | margin_bottom = 80.0 60 | size_flags_horizontal = 2 61 | size_flags_vertical = 2 62 | text = "Host" 63 | 64 | [node name="JoinButton" type="Button" parent="LobbyPanel"] 65 | margin_left = 130.0 66 | margin_top = 60.0 67 | margin_right = 210.0 68 | margin_bottom = 80.0 69 | size_flags_horizontal = 2 70 | size_flags_vertical = 2 71 | text = "Join" 72 | 73 | [node name="StatusOk" type="Label" parent="LobbyPanel"] 74 | margin_left = 10.0 75 | margin_top = 90.0 76 | margin_right = 210.0 77 | margin_bottom = 104.0 78 | size_flags_horizontal = 2 79 | size_flags_vertical = 0 80 | custom_colors/font_color = Color( 0, 1, 0.015625, 1 ) 81 | align = 1 82 | 83 | [node name="StatusFail" type="Label" parent="LobbyPanel"] 84 | margin_left = 10.0 85 | margin_top = 90.0 86 | margin_right = 210.0 87 | margin_bottom = 104.0 88 | size_flags_horizontal = 2 89 | size_flags_vertical = 0 90 | custom_colors/font_color = Color( 1, 0, 0, 1 ) 91 | align = 1 92 | 93 | [node name="PortForward" type="Label" parent="LobbyPanel"] 94 | visible = false 95 | margin_left = -128.0 96 | margin_top = 136.0 97 | margin_right = 124.0 98 | margin_bottom = 184.0 99 | custom_constants/line_spacing = 6 100 | text = "If you want non-LAN clients to connect, 101 | make sure the port 8910 in UDP 102 | is forwarded on your router." 103 | align = 1 104 | __meta__ = { 105 | "_edit_use_anchors_": false 106 | } 107 | 108 | [node name="FindPublicIP" type="LinkButton" parent="LobbyPanel"] 109 | visible = false 110 | margin_left = 155.0 111 | margin_top = 152.0 112 | margin_right = 328.0 113 | margin_bottom = 166.0 114 | text = "Find your public IP address" 115 | __meta__ = { 116 | "_edit_use_anchors_": false 117 | } 118 | 119 | [connection signal="pressed" from="LobbyPanel/HostButton" to="LobbyPanel" method="_on_host_pressed"] 120 | [connection signal="pressed" from="LobbyPanel/JoinButton" to="LobbyPanel" method="_on_join_pressed"] 121 | [connection signal="pressed" from="LobbyPanel/FindPublicIP" to="LobbyPanel" method="_on_find_public_ip_pressed"] 122 | -------------------------------------------------------------------------------- /logic/ball.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | const DEFAULT_SPEED = 100 4 | 5 | var direction = Vector2.LEFT 6 | var stopped = false 7 | var _speed = DEFAULT_SPEED 8 | 9 | onready var _screen_size = get_viewport_rect().size 10 | 11 | func _process(delta): 12 | # We must poll properly under TCP and web sockets. 13 | if (NetVars.server && NetVars.server.is_listening()): 14 | NetVars.server.poll(); 15 | elif (NetVars.client && NetVars.client.get_connected_port() > 0): 16 | NetVars.client.poll(); 17 | 18 | _speed += delta 19 | # Ball will move normally for both players, 20 | # even if it's sightly out of sync between them, 21 | # so each player sees the motion as smooth and not jerky. 22 | if not stopped: 23 | translate(_speed * delta * direction) 24 | 25 | # Check screen bounds to make ball bounce. 26 | var ball_pos = position 27 | if (ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > _screen_size.y and direction.y > 0): 28 | direction.y = -direction.y 29 | 30 | if is_network_master(): 31 | # Only the master will decide when the ball is out in 32 | # the left side (it's own side). This makes the game 33 | # playable even if latency is high and ball is going 34 | # fast. Otherwise ball might be out in the other 35 | # player's screen but not this one. 36 | if ball_pos.x < 0: 37 | get_parent().rpc("update_score", false) 38 | rpc("_reset_ball", false) 39 | else: 40 | # Only the puppet will decide when the ball is out in 41 | # the right side, which is it's own side. This makes 42 | # the game playable even if latency is high and ball 43 | # is going fast. Otherwise ball might be out in the 44 | # other player's screen but not this one. 45 | if ball_pos.x > _screen_size.x: 46 | get_parent().rpc("update_score", true) 47 | rpc("_reset_ball", true) 48 | 49 | 50 | remotesync func bounce(left, random): 51 | # Using sync because both players can make it bounce. 52 | if left: 53 | direction.x = abs(direction.x) 54 | else: 55 | direction.x = -abs(direction.x) 56 | 57 | _speed *= 1.1 58 | direction.y = random * 2.0 - 1 59 | direction = direction.normalized() 60 | 61 | 62 | remotesync func stop(): 63 | stopped = true 64 | 65 | 66 | remotesync func _reset_ball(for_left): 67 | position = _screen_size / 2 68 | if for_left: 69 | direction = Vector2.LEFT 70 | else: 71 | direction = Vector2.RIGHT 72 | _speed = DEFAULT_SPEED 73 | -------------------------------------------------------------------------------- /logic/lobby.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | # Default game server port. Can be any number between 1024 and 49151. 4 | # Not on the list of registered or common ports as of November 2020: 5 | # https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers 6 | const DEFAULT_PORT = 8910 7 | 8 | onready var address = $Address 9 | onready var host_button = $HostButton 10 | onready var join_button = $JoinButton 11 | onready var status_ok = $StatusOk 12 | onready var status_fail = $StatusFail 13 | onready var port_forward_label = $PortForward 14 | onready var find_public_ip_button = $FindPublicIP 15 | 16 | func _ready(): 17 | # Connect all the callbacks related to networking. 18 | get_tree().connect("network_peer_connected", self, "_player_connected") 19 | get_tree().connect("network_peer_disconnected", self, "_player_disconnected") 20 | get_tree().connect("connected_to_server", self, "_connected_ok") 21 | get_tree().connect("connection_failed", self, "_connected_fail") 22 | get_tree().connect("server_disconnected", self, "_server_disconnected") 23 | 24 | #### Network callbacks from SceneTree #### 25 | 26 | # Callback from SceneTree. 27 | func _player_connected(_id): 28 | # Someone connected, start the game! 29 | var pong = load("res://pong.tscn").instance() 30 | # Connect deferred so we can safely erase it from the callback. 31 | pong.connect("game_finished", self, "_end_game", [], CONNECT_DEFERRED) 32 | 33 | get_tree().get_root().add_child(pong) 34 | hide() 35 | 36 | 37 | func _player_disconnected(_id): 38 | if get_tree().is_network_server(): 39 | _end_game("Client disconnected") 40 | else: 41 | _end_game("Server disconnected") 42 | 43 | 44 | # Callback from SceneTree, only for clients (not server). 45 | func _connected_ok(): 46 | pass # This function is not needed for this project. 47 | 48 | 49 | # Callback from SceneTree, only for clients (not server). 50 | func _connected_fail(): 51 | _set_status("Couldn't connect", false) 52 | 53 | get_tree().set_network_peer(null) # Remove peer. 54 | host_button.set_disabled(false) 55 | join_button.set_disabled(false) 56 | 57 | 58 | func _server_disconnected(): 59 | _end_game("Server disconnected") 60 | 61 | ##### Game creation functions ###### 62 | 63 | func _end_game(with_error = ""): 64 | if has_node("/root/Pong"): 65 | # Erase immediately, otherwise network might show 66 | # errors (this is why we connected deferred above). 67 | get_node("/root/Pong").free() 68 | show() 69 | 70 | get_tree().set_network_peer(null) # Remove peer. 71 | host_button.set_disabled(false) 72 | join_button.set_disabled(false) 73 | 74 | _set_status(with_error, false) 75 | 76 | 77 | func _set_status(text, isok): 78 | # Simple way to show status. 79 | if isok: 80 | status_ok.set_text(text) 81 | status_fail.set_text("") 82 | else: 83 | status_ok.set_text("") 84 | status_fail.set_text(text) 85 | 86 | 87 | func _on_host_pressed(): 88 | NetVars.server = WebSocketServer.new(); 89 | 90 | var err = NetVars.server.listen(DEFAULT_PORT, PoolStringArray(), 1) # Maximum of 1 peer, since it's a 2-player game. 91 | if err != OK: 92 | # Is another server running? 93 | _set_status("Can't host, address in use.",false) 94 | return 95 | 96 | get_tree().set_network_peer(NetVars.server) 97 | host_button.set_disabled(true) 98 | join_button.set_disabled(true) 99 | _set_status("Waiting for player...", true) 100 | 101 | # Only show hosting instructions when relevant. 102 | port_forward_label.visible = true 103 | find_public_ip_button.visible = true 104 | 105 | 106 | func _on_join_pressed(): 107 | var ip = address.get_text() 108 | if not ip.is_valid_ip_address(): 109 | _set_status("IP address is invalid", false) 110 | return 111 | 112 | NetVars.client = WebSocketClient.new() 113 | NetVars.client.connect_to_url("ws://" + ip + ":" + str(DEFAULT_PORT), PoolStringArray(), 1) 114 | get_tree().set_network_peer(NetVars.client) 115 | 116 | _set_status("Connecting...", true) 117 | 118 | 119 | func _on_find_public_ip_pressed(): 120 | OS.shell_open("https://icanhazip.com/") 121 | -------------------------------------------------------------------------------- /logic/paddle.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | const MOTION_SPEED = 150 4 | 5 | export var left = false 6 | 7 | var _motion = 0 8 | var _you_hidden = false 9 | 10 | onready var _screen_size_y = get_viewport_rect().size.y 11 | 12 | func _process(delta): 13 | # We must poll properly under TCP and web sockets. 14 | if (NetVars.server && NetVars.server.is_listening()): 15 | NetVars.server.poll(); 16 | elif (NetVars.client && NetVars.client.get_connected_port() > 0): 17 | NetVars.client.poll(); 18 | 19 | # Is the master of the paddle. 20 | if is_network_master(): 21 | _motion = Input.get_action_strength("move_down") - Input.get_action_strength("move_up") 22 | 23 | if not _you_hidden and _motion != 0: 24 | _hide_you_label() 25 | 26 | _motion *= MOTION_SPEED 27 | 28 | # Using unreliable to make sure position is updated as fast 29 | # as possible, even if one of the calls is dropped. 30 | rpc_unreliable("set_pos_and_motion", position, _motion) 31 | else: 32 | if not _you_hidden: 33 | _hide_you_label() 34 | 35 | translate(Vector2(0, _motion * delta)) 36 | 37 | # Set screen limits. 38 | position.y = clamp(position.y, 16, _screen_size_y - 16) 39 | 40 | 41 | # Synchronize position and speed to the other peers. 42 | puppet func set_pos_and_motion(pos, motion): 43 | position = pos 44 | _motion = motion 45 | 46 | 47 | func _hide_you_label(): 48 | _you_hidden = true 49 | get_node("You").hide() 50 | 51 | 52 | func _on_paddle_area_enter(area): 53 | if is_network_master(): 54 | # Random for new direction generated on each peer. 55 | area.rpc("bounce", left, randf()) 56 | -------------------------------------------------------------------------------- /logic/pong.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal game_finished() 4 | 5 | const SCORE_TO_WIN = 10 6 | 7 | var score_left = 0 8 | var score_right = 0 9 | 10 | onready var player2 = $Player2 11 | onready var score_left_node = $ScoreLeft 12 | onready var score_right_node = $ScoreRight 13 | onready var winner_left = $WinnerLeft 14 | onready var winner_right = $WinnerRight 15 | 16 | func _ready(): 17 | # By default, all nodes in server inherit from master, 18 | # while all nodes in clients inherit from puppet. 19 | # set_network_master is tree-recursive by default. 20 | if get_tree().is_network_server(): 21 | # For the server, give control of player 2 to the other peer. 22 | player2.set_network_master(get_tree().get_network_connected_peers()[0]) 23 | else: 24 | # For the client, give control of player 2 to itself. 25 | player2.set_network_master(get_tree().get_network_unique_id()) 26 | 27 | print("Unique id: ", get_tree().get_network_unique_id()) 28 | 29 | 30 | remotesync func update_score(add_to_left): 31 | if add_to_left: 32 | score_left += 1 33 | score_left_node.set_text(str(score_left)) 34 | else: 35 | score_right += 1 36 | score_right_node.set_text(str(score_right)) 37 | 38 | var game_ended = false 39 | if score_left == SCORE_TO_WIN: 40 | winner_left.show() 41 | game_ended = true 42 | elif score_right == SCORE_TO_WIN: 43 | winner_right.show() 44 | game_ended = true 45 | 46 | if game_ended: 47 | $ExitGame.show() 48 | $Ball.rpc("stop") 49 | 50 | 51 | func _on_exit_game_pressed(): 52 | emit_signal("game_finished") 53 | -------------------------------------------------------------------------------- /netvars.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | var client = null 4 | var server = null 5 | -------------------------------------------------------------------------------- /paddle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/paddle.png -------------------------------------------------------------------------------- /paddle.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://paddle.png" 13 | dest_files=[ "res://.import/paddle.png-0e798fb0912613386507c9904d5cc01a.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /paddle.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://logic/paddle.gd" type="Script" id=1] 4 | [ext_resource path="res://paddle.png" type="Texture" id=2] 5 | 6 | [sub_resource type="CapsuleShape2D" id=1] 7 | radius = 4.78568 8 | height = 23.6064 9 | 10 | [node name="Paddle" type="Area2D"] 11 | script = ExtResource( 1 ) 12 | 13 | [node name="Sprite" type="Sprite" parent="."] 14 | texture = ExtResource( 2 ) 15 | 16 | [node name="Shape" type="CollisionShape2D" parent="."] 17 | shape = SubResource( 1 ) 18 | 19 | [node name="You" type="Label" parent="."] 20 | margin_left = -26.0 21 | margin_top = -33.0 22 | margin_right = 27.0 23 | margin_bottom = -19.0 24 | size_flags_horizontal = 2 25 | size_flags_vertical = 0 26 | text = "You" 27 | align = 1 28 | 29 | [connection signal="area_entered" from="." to="." method="_on_paddle_area_enter"] 30 | -------------------------------------------------------------------------------- /pong.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://logic/pong.gd" type="Script" id=1] 4 | [ext_resource path="res://separator.png" type="Texture" id=2] 5 | [ext_resource path="res://paddle.tscn" type="PackedScene" id=3] 6 | [ext_resource path="res://ball.tscn" type="PackedScene" id=4] 7 | 8 | [node name="Pong" type="Node2D"] 9 | script = ExtResource( 1 ) 10 | 11 | [node name="ColorRect" type="ColorRect" parent="."] 12 | margin_right = 640.0 13 | margin_bottom = 400.0 14 | color = Color( 0.141176, 0.152941, 0.164706, 1 ) 15 | __meta__ = { 16 | "_edit_use_anchors_": false 17 | } 18 | 19 | [node name="Separator" type="Sprite" parent="."] 20 | position = Vector2( 320, 200 ) 21 | texture = ExtResource( 2 ) 22 | 23 | [node name="Player1" parent="." instance=ExtResource( 3 )] 24 | modulate = Color( 0, 1, 1, 1 ) 25 | position = Vector2( 32.49, 188.622 ) 26 | left = true 27 | 28 | [node name="Player2" parent="." instance=ExtResource( 3 )] 29 | modulate = Color( 1, 0, 1, 1 ) 30 | position = Vector2( 608.88, 188.622 ) 31 | 32 | [node name="Ball" parent="." instance=ExtResource( 4 )] 33 | position = Vector2( 320.387, 189.525 ) 34 | 35 | [node name="ScoreLeft" type="Label" parent="."] 36 | margin_left = 240.0 37 | margin_top = 10.0 38 | margin_right = 280.0 39 | margin_bottom = 30.0 40 | size_flags_horizontal = 2 41 | size_flags_vertical = 0 42 | text = "0" 43 | align = 1 44 | 45 | [node name="ScoreRight" type="Label" parent="."] 46 | margin_left = 360.0 47 | margin_top = 10.0 48 | margin_right = 400.0 49 | margin_bottom = 30.0 50 | size_flags_horizontal = 2 51 | size_flags_vertical = 0 52 | text = "0" 53 | align = 1 54 | 55 | [node name="WinnerLeft" type="Label" parent="."] 56 | visible = false 57 | margin_left = 190.0 58 | margin_top = 170.0 59 | margin_right = 267.0 60 | margin_bottom = 184.0 61 | size_flags_horizontal = 2 62 | size_flags_vertical = 0 63 | text = "The Winner!" 64 | 65 | [node name="WinnerRight" type="Label" parent="."] 66 | visible = false 67 | margin_left = 380.0 68 | margin_top = 170.0 69 | margin_right = 457.0 70 | margin_bottom = 184.0 71 | size_flags_horizontal = 2 72 | size_flags_vertical = 0 73 | text = "The Winner!" 74 | 75 | [node name="ExitGame" type="Button" parent="."] 76 | visible = false 77 | margin_left = 280.0 78 | margin_top = 340.0 79 | margin_right = 360.0 80 | margin_bottom = 360.0 81 | size_flags_horizontal = 2 82 | size_flags_vertical = 2 83 | text = "Exit Game" 84 | 85 | [node name="Camera2D" type="Camera2D" parent="."] 86 | offset = Vector2( 320, 200 ) 87 | current = true 88 | 89 | [connection signal="pressed" from="ExitGame" to="." method="_on_exit_game_pressed"] 90 | 91 | [editable path="Player1"] 92 | [editable path="Player2"] 93 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=4 10 | 11 | [application] 12 | 13 | config/name="pong-multiplayerv2" 14 | config/description="A multiplayer demo of the classical pong game. 15 | One of the players should press 'host', while the 16 | other should select the address and press 'join'." 17 | run/main_scene="res://lobby.tscn" 18 | config/icon="res://icon.png" 19 | 20 | [autoload] 21 | 22 | Global="*res://pong.tscn" 23 | NetVars="*res://netvars.gd" 24 | 25 | [debug] 26 | 27 | gdscript/warnings/return_value_discarded=false 28 | 29 | [display] 30 | 31 | window/size/width=640 32 | window/size/height=400 33 | window/dpi/allow_hidpi=true 34 | window/stretch/mode="2d" 35 | window/stretch/aspect="expand" 36 | stretch_2d=true 37 | 38 | [gdnative] 39 | 40 | singletons=[ ] 41 | 42 | [input] 43 | 44 | move_down={ 45 | "deadzone": 0.5, 46 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 47 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":13,"pressure":0.0,"pressed":false,"script":null) 48 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":1.0,"script":null) 49 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":90,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 50 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 51 | ] 52 | } 53 | move_up={ 54 | "deadzone": 0.5, 55 | "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 56 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":0,"button_index":12,"pressure":0.0,"pressed":false,"script":null) 57 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":0,"axis":1,"axis_value":-1.0,"script":null) 58 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 59 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":87,"physical_scancode":0,"unicode":0,"echo":false,"script":null) 60 | ] 61 | } 62 | 63 | [rendering] 64 | 65 | quality/driver/driver_name="GLES2" 66 | 2d/snapping/use_gpu_pixel_snap=true 67 | vram_compression/import_etc=true 68 | vram_compression/import_etc2=false 69 | quality/2d/use_pixel_snap=true 70 | -------------------------------------------------------------------------------- /screenshots/.gdignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/screenshots/.gdignore -------------------------------------------------------------------------------- /screenshots/pong_multiplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/screenshots/pong_multiplayer.png -------------------------------------------------------------------------------- /separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gamemann/pong-multiplayerv2/7903c1ceeb5ed2f4d47266ed27d9ffd0c14eedca/separator.png -------------------------------------------------------------------------------- /separator.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://separator.png" 13 | dest_files=[ "res://.import/separator.png-f981c8489b9148e2e1dc63398273da74.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /server-cli.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | func _ready(): 4 | # Connect all the callbacks related to networking. 5 | get_tree().connect("network_peer_connected", self, "_player_connected") 6 | get_tree().connect("network_peer_disconnected", self, "_player_disconnected") 7 | 8 | # Set default host info. 9 | var ip = "127.0.0.1" 10 | var port = int(rand_range(1025, 65535)) 11 | 12 | # Loop through command arguments. 13 | var arguments = {} 14 | 15 | for argument in OS.get_cmdline_args(): 16 | # Parse valid command-line arguments into a dictionary 17 | if argument.find("=") > -1: 18 | var key_value = argument.split("=") 19 | arguments[key_value[0].lstrip("--")] = key_value[1] 20 | 21 | if arguments.has("ip"): 22 | ip = str(arguments["ip"]) 23 | 24 | if arguments.has("port"): 25 | port = int(arguments["port"]) 26 | 27 | NetVars.server = WebSocketServer.new() 28 | 29 | var err = NetVars.server.listen(port, PoolStringArray(), 1) # Maximum of 1 peer, since it's a 2-player game. 30 | if err != OK: 31 | # Is another server running? 32 | print("Host in use. Please try a different IP:Port.") 33 | 34 | return 35 | 36 | get_tree().set_network_peer(NetVars.server) 37 | 38 | 39 | func _player_connected(_id): 40 | # Someone connected, start the game! 41 | var pong = load("res://pong.tscn").instance() 42 | # Connect deferred so we can safely erase it from the callback. 43 | pong.connect("game_finished", self, "_end_game", [], CONNECT_DEFERRED) 44 | 45 | get_tree().get_root().add_child(pong) 46 | hide() 47 | 48 | func _player_disconnected(_id): 49 | print("Client disconnected") 50 | 51 | func _end_game(with_error = ""): 52 | if has_node("/root/Pong"): 53 | # Erase immediately, otherwise network might show 54 | # errors (this is why we connected deferred above). 55 | get_node("/root/Pong").free() 56 | show() 57 | 58 | get_tree().set_network_peer(null) # Remove peer. 59 | print(with_error) 60 | 61 | func _process(delta): 62 | # We can process commands later on! 63 | pass 64 | -------------------------------------------------------------------------------- /server-cli.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://server-cli.gd" type="Script" id=1] 4 | 5 | [node name="Control" type="Control"] 6 | physics_interpolation_mode = 1 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | script = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /server_cli.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | func _host_server(): 4 | var arguments = {} 5 | 6 | for argument in OS.get_cmdline_args(): 7 | # Parse valid command-line arguments into a dictionary 8 | if argument.find("=") > -1: 9 | var key_value = argument.split("=") 10 | arguments[key_value[0].lstrip("--")] = key_value[1] 11 | 12 | NetVars.server = WebSocketServer.new() 13 | 14 | var err = NetVars.server.listen(arguments["port"], PoolStringArray(), 1) # Maximum of 1 peer, since it's a 2-player game. 15 | if err != OK: 16 | # Is another server running? 17 | print("Host in use. Please try a different IP:Port.") 18 | 19 | return 20 | 21 | get_tree().set_network_peer(NetVars.server) 22 | 23 | --------------------------------------------------------------------------------