diff --git a/networking/pong_multiplayer/ball.gd b/networking/pong_multiplayer/ball.gd new file mode 100644 index 00000000..20a548ab --- /dev/null +++ b/networking/pong_multiplayer/ball.gd @@ -0,0 +1,74 @@ + +extends Area2D + +const DEFAULT_SPEED=80 + +var direction = Vector2(1,0) +var ball_speed = DEFAULT_SPEED +var stopped=false + + + +onready var screen_size = get_viewport_rect().size + +sync func _reset_ball(for_left): + + set_pos( screen_size /2 ) + if (for_left): + direction = Vector2(-1,0) + else: + direction = Vector2( 1,0) + + ball_speed = DEFAULT_SPEED + +sync func stop(): + stopped=true + +func _process(delta): + + # ball will move normally for both players + # even if it's sightly out of sync between them + # so each player sees the motion as smooth and not jerky + + if (not stopped): + translate( direction * ball_speed * delta ) + + # check screen bounds to make ball bounce + + var ball_pos = get_pos() + if ((ball_pos.y < 0 and direction.y < 0) or (ball_pos.y > screen_size.y and direction.y > 0)): + direction.y = -direction.y + + if (is_network_master()): + # only master will decide when the ball is out in the left side (it's own side) + # this makes the game playable even if latency is high and ball is going fast + # otherwise ball might be out in the other player's screen but not this one + + if (ball_pos.x < 0 ): + get_parent().rpc("update_score",false) + rpc("_reset_ball",false) + else: + # only the slave will decide when the ball is out in the right side (it's own side) + # this makes the game playable even if latency is high and ball is going fast + # otherwise ball might be out in the other player's screen but not this one + + if (ball_pos.x > screen_size.x): + get_parent().rpc("update_score",true) + rpc("_reset_ball",true) + + +sync func bounce(left,random): + + #using sync because both players can make it bounce + if (left): + direction.x = abs(direction.x) + else: + direction.x = -abs(direction.x) + + ball_speed *= 1.1 + direction.y = random*2.0 - 1 + direction = direction.normalized() + +func _ready(): + set_process(true) + diff --git a/networking/pong_multiplayer/ball.png b/networking/pong_multiplayer/ball.png new file mode 100644 index 00000000..c0f6da4f Binary files /dev/null and b/networking/pong_multiplayer/ball.png differ diff --git a/networking/pong_multiplayer/ball.tscn b/networking/pong_multiplayer/ball.tscn new file mode 100644 index 00000000..cbfed496 --- /dev/null +++ b/networking/pong_multiplayer/ball.tscn @@ -0,0 +1,33 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://ball.gd" type="Script" id=1] +[ext_resource path="res://ball.png" type="Texture" id=2] + +[sub_resource type="CircleShape2D" id=1] + +custom_solver_bias = 0.0 +radius = 5.11969 + +[node name="ball" type="Area2D"] + +input/pickable = true +shapes/0/shape = SubResource( 1 ) +shapes/0/transform = Matrix32( 1, 0, 0, 1, 0, 0 ) +shapes/0/trigger = false +gravity_vec = Vector2( 0, 1 ) +gravity = 98.0 +linear_damp = 0.1 +angular_damp = 1.0 +script/script = ExtResource( 1 ) + +[node name="sprite" type="Sprite" parent="."] + +texture = ExtResource( 2 ) + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) +trigger = false +_update_shape_index = 0 + + diff --git a/networking/pong_multiplayer/engine.cfg b/networking/pong_multiplayer/engine.cfg new file mode 100644 index 00000000..30924cba --- /dev/null +++ b/networking/pong_multiplayer/engine.cfg @@ -0,0 +1,20 @@ +[application] + +name="Pong Multiplayer" +main_scene="res://lobby.tscn" +icon="res://icon.png" + +[display] + +width=640 +height=400 +stretch_2d=true + +[input] + +move_up=[key(Up)] +move_down=[key(Down)] + +[render] + +default_clear_color=#ff000000 diff --git a/networking/pong_multiplayer/icon.png b/networking/pong_multiplayer/icon.png new file mode 100644 index 00000000..eab34de5 Binary files /dev/null and b/networking/pong_multiplayer/icon.png differ diff --git a/networking/pong_multiplayer/lobby.gd b/networking/pong_multiplayer/lobby.gd new file mode 100644 index 00000000..c512a8d7 --- /dev/null +++ b/networking/pong_multiplayer/lobby.gd @@ -0,0 +1,104 @@ + +extends Control + +const DEFAULT_PORT = 8910 # some random number, pick your port properly + +#### Network callbacks from SceneTree #### + +# callback from SceneTree +func _player_connected(id): + #someone connected, start the game! + var pong = load("res://pong.tscn").instance() + pong.connect("game_finished",self,"_end_game",[],CONNECT_DEFERRED) # connect deferred so we can safely erase it from the callback + + get_tree().get_root().add_child(pong) + hide() + +func _player_disconnected(id): + + if (get_tree().is_network_server()): + _end_game("Client disconnected") + else: + _end_game("Server disconnected") + +# callback from SceneTree, only for clients (not server) +func _connected_ok(): + # will not use this one + pass + +# callback from SceneTree, only for clients (not server) +func _connected_fail(): + + _set_status("Couldn't connect",false) + + get_tree().set_network_peer(null) #remove peer + + get_node("panel/join").set_disabled(false) + get_node("panel/host").set_disabled(false) + +func _server_disconnected(): + _end_game("Server disconnected") + +##### Game creation functions ###### + +func _end_game(with_error=""): + if (has_node("/root/pong")): + #erase pong scene + get_node("/root/pong").free() # erase immediately, otherwise network might show errors (this is why we connected deferred above) + show() + + get_tree().set_network_peer(null) #remove peer + + get_node("panel/join").set_disabled(false) + get_node("panel/host").set_disabled(false) + + _set_status(with_error,false) + +func _set_status(text,isok): + #simple way to show status + if (isok): + get_node("panel/status_ok").set_text(text) + get_node("panel/status_fail").set_text("") + else: + get_node("panel/status_ok").set_text("") + get_node("panel/status_fail").set_text(text) + +func _on_host_pressed(): + + var host = NetworkedMultiplayerENet.new() + var err = host.create_server(DEFAULT_PORT,1) # max: 1 peer, since it's a 2 players game + if (err!=OK): + #is another server running? + _set_status("Can't host, address in use.",false) + return + + get_tree().set_network_peer(host) + get_node("panel/join").set_disabled(true) + get_node("panel/host").set_disabled(true) + _set_status("Waiting for player..",true) + +func _on_join_pressed(): + + var ip = get_node("panel/address").get_text() + if (not ip.is_valid_ip_address()): + _set_status("IP address is invalid",false) + return + + var host = NetworkedMultiplayerENet.new() + host.create_client(ip,DEFAULT_PORT) + get_tree().set_network_peer(host) + + _set_status("Connecting..",true) + + + +### INITIALIZER #### + +func _ready(): + # connect all the callbacks related to networking + get_tree().connect("network_peer_connected",self,"_player_connected") + get_tree().connect("network_peer_disconnected",self,"_player_disconnected") + get_tree().connect("connected_to_server",self,"_connected_ok") + get_tree().connect("connection_failed",self,"_connected_fail") + get_tree().connect("server_disconnected",self,"_server_disconnected") + diff --git a/networking/pong_multiplayer/lobby.tscn b/networking/pong_multiplayer/lobby.tscn new file mode 100644 index 00000000..09d97737 --- /dev/null +++ b/networking/pong_multiplayer/lobby.tscn @@ -0,0 +1,144 @@ +[gd_scene load_steps=2 format=1] + +[ext_resource path="res://lobby.gd" type="Script" id=1] + +[node name="lobby" type="Control"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 0.0 +margin/top = 0.0 +margin/right = 40.0 +margin/bottom = 40.0 +script/script = ExtResource( 1 ) + +[node name="title" type="Label" parent="."] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 210.0 +margin/top = 40.0 +margin/right = 430.0 +margin/bottom = 80.0 +text = "Multiplayer Pong" +align = 1 +valign = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="panel" type="Panel" parent="."] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 210.0 +margin/top = 160.0 +margin/right = 430.0 +margin/bottom = 270.0 + +[node name="address_label" type="Label" parent="panel"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 10.0 +margin/top = 10.0 +margin/right = 62.0 +margin/bottom = 24.0 +text = "Address" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="address" type="LineEdit" parent="panel"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 10.0 +margin/top = 30.0 +margin/right = 210.0 +margin/bottom = 54.0 +text = "127.0.0.1" +placeholder/alpha = 0.6 +focus_mode = 2 +caret/caret_blink = false +caret/caret_blink_speed = 0.65 + +[node name="host" type="Button" parent="panel"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 10.0 +margin/top = 60.0 +margin/right = 90.0 +margin/bottom = 80.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Host" +flat = false + +[node name="join" type="Button" parent="panel"] + +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 130.0 +margin/top = 60.0 +margin/right = 210.0 +margin/bottom = 80.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Join" +flat = false + +[node name="status_ok" type="Label" parent="panel"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 10.0 +margin/top = 90.0 +margin/right = 210.0 +margin/bottom = 104.0 +custom_colors/font_color = Color( 0, 1, 0.015625, 1 ) +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="status_fail" type="Label" parent="panel"] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 10.0 +margin/top = 90.0 +margin/right = 210.0 +margin/bottom = 104.0 +custom_colors/font_color = Color( 1, 0, 0, 1 ) +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[connection signal="pressed" from="panel/host" to="." method="_on_host_pressed"] + +[connection signal="pressed" from="panel/join" to="." method="_on_join_pressed"] + + diff --git a/networking/pong_multiplayer/paddle.gd b/networking/pong_multiplayer/paddle.gd new file mode 100644 index 00000000..06ff242d --- /dev/null +++ b/networking/pong_multiplayer/paddle.gd @@ -0,0 +1,69 @@ +extends Area2D + +export var left=false + +const MOTION_SPEED=150 + +var motion = 0 +var you_hidden=false + +onready var screen_size = get_viewport_rect().size + +#synchronize position and speed to the other peers +slave func set_pos_and_motion(p_pos,p_motion): + set_pos(p_pos) + motion=p_motion + +func _hide_you_label(): + you_hidden=true + get_node("you").hide() + +func _process(delta): + + #is the master of the paddle + if (is_network_master()): + + motion = 0 + if (Input.is_action_pressed("move_up")): + motion -= 1 + elif (Input.is_action_pressed("move_down")): + motion += 1 + + if (not you_hidden and motion!=0): + _hide_you_label() + + + motion*=MOTION_SPEED + + #using unreliable to make sure position is updated as fast as possible, even if one of the calls is dropped + rpc_unreliable("set_pos_and_motion",get_pos(),motion) + + else: + if (not you_hidden): + _hide_you_label() + + + translate( Vector2(0,motion*delta) ) + + # set screen limits + + var pos = get_pos() + + if (pos.y < 0 ): + set_pos( Vector2( pos.x, 0) ) + elif (pos.y > screen_size.y): + set_pos( Vector2( pos.x, screen_size.y) ) + + + +func _ready(): + set_process(true) + +func _on_paddle_area_enter( area ): + + if (is_network_master()): + area.rpc("bounce",left,randf()) #random for new direction generated on each peer + + + + diff --git a/networking/pong_multiplayer/paddle.png b/networking/pong_multiplayer/paddle.png new file mode 100644 index 00000000..e23491ec Binary files /dev/null and b/networking/pong_multiplayer/paddle.png differ diff --git a/networking/pong_multiplayer/paddle.tscn b/networking/pong_multiplayer/paddle.tscn new file mode 100644 index 00000000..f9e0ebe0 --- /dev/null +++ b/networking/pong_multiplayer/paddle.tscn @@ -0,0 +1,53 @@ +[gd_scene load_steps=4 format=1] + +[ext_resource path="res://paddle.gd" type="Script" id=1] +[ext_resource path="res://paddle.png" type="Texture" id=2] + +[sub_resource type="CapsuleShape2D" id=1] + +custom_solver_bias = 0.0 +radius = 4.78568 +height = 23.6064 + +[node name="paddle" type="Area2D"] + +input/pickable = true +shapes/0/shape = SubResource( 1 ) +shapes/0/transform = Matrix32( 1, 0, 0, 1, 0, 0 ) +shapes/0/trigger = false +gravity_vec = Vector2( 0, 1 ) +gravity = 98.0 +linear_damp = 0.1 +angular_damp = 1.0 +script/script = ExtResource( 1 ) +left = false + +[node name="sprite" type="Sprite" parent="."] + +texture = ExtResource( 2 ) + +[node name="shape" type="CollisionShape2D" parent="."] + +shape = SubResource( 1 ) +trigger = false +_update_shape_index = 0 + +[node name="you" type="Label" parent="."] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = -26.0 +margin/top = -33.0 +margin/right = 27.0 +margin/bottom = -19.0 +text = "You" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[connection signal="area_enter" from="." to="." method="_on_paddle_area_enter"] + + diff --git a/networking/pong_multiplayer/pong.gd b/networking/pong_multiplayer/pong.gd new file mode 100644 index 00000000..df2eb16a --- /dev/null +++ b/networking/pong_multiplayer/pong.gd @@ -0,0 +1,52 @@ + +extends Node2D + +const SCORE_TO_WIN=10 + +var score_left = 0 +var score_right = 0 + +signal game_finished() + +sync func update_score(add_to_left): + if (add_to_left): + + score_left+=1 + get_node("score_left").set_text( str(score_left) ) + else: + + score_right+=1 + get_node("score_right").set_text( str(score_right) ) + + var game_ended = false + + if (score_left==SCORE_TO_WIN): + get_node("winner_left").show() + game_ended=true + elif (score_right==SCORE_TO_WIN): + get_node("winner_right").show() + game_ended=true + + if (game_ended): + get_node("exit_game").show() + get_node("ball").rpc("stop") + +func _on_exit_game_pressed(): + emit_signal("game_finished") + +func _ready(): + + # by default, all nodes in server inherit from master + # while all nodes in clients inherit from slave + + if (get_tree().is_network_server()): + #set to not control player 2. since it's master as everything else + get_node("player2").set_network_mode(NETWORK_MODE_SLAVE) + else: + #set to control player 2, as it's slave as everything else + get_node("player2").set_network_mode(NETWORK_MODE_MASTER) + + #let each paddle know which one is left, too + get_node("player1").left=true + get_node("player2").left=false + diff --git a/networking/pong_multiplayer/pong.tscn b/networking/pong_multiplayer/pong.tscn new file mode 100644 index 00000000..ce1065f8 --- /dev/null +++ b/networking/pong_multiplayer/pong.tscn @@ -0,0 +1,122 @@ +[gd_scene load_steps=5 format=1] + +[ext_resource path="res://pong.gd" type="Script" id=1] +[ext_resource path="res://separator.png" type="Texture" id=2] +[ext_resource path="res://paddle.tscn" type="PackedScene" id=3] +[ext_resource path="res://ball.tscn" type="PackedScene" id=4] + +[node name="pong" type="Node2D"] + +script/script = ExtResource( 1 ) + +[node name="separator" type="Sprite" parent="."] + +transform/pos = Vector2( 320, 200 ) +texture = ExtResource( 2 ) + +[node name="player1" parent="." instance=ExtResource( 3 )] + +transform/pos = Vector2( 32.49, 188.622 ) + +[node name="sprite" parent="player1"] + +modulate = Color( 1, 0, 0.960938, 1 ) + +[node name="player2" parent="." instance=ExtResource( 3 )] + +transform/pos = Vector2( 608.88, 188.622 ) + +[node name="sprite" parent="player2"] + +modulate = Color( 0, 0.929688, 1, 1 ) + +[node name="ball" parent="." instance=ExtResource( 4 )] + +transform/pos = Vector2( 320.387, 189.525 ) + +[node name="score_left" type="Label" parent="."] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 240.0 +margin/top = 10.0 +margin/right = 280.0 +margin/bottom = 30.0 +text = "0" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="score_right" type="Label" parent="."] + +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 360.0 +margin/top = 10.0 +margin/right = 400.0 +margin/bottom = 30.0 +text = "0" +align = 1 +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="winner_left" type="Label" parent="."] + +visibility/visible = false +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 190.0 +margin/top = 170.0 +margin/right = 267.0 +margin/bottom = 184.0 +text = "The Winner!" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="winner_right" type="Label" parent="."] + +visibility/visible = false +focus/ignore_mouse = true +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 0 +margin/left = 380.0 +margin/top = 170.0 +margin/right = 457.0 +margin/bottom = 184.0 +text = "The Winner!" +percent_visible = 1.0 +lines_skipped = 0 +max_lines_visible = -1 + +[node name="exit_game" type="Button" parent="."] + +visibility/visible = false +focus/ignore_mouse = false +focus/stop_mouse = true +size_flags/horizontal = 2 +size_flags/vertical = 2 +margin/left = 280.0 +margin/top = 340.0 +margin/right = 360.0 +margin/bottom = 360.0 +toggle_mode = false +enabled_focus_mode = 2 +shortcut = null +text = "Exit Game" +flat = false + +[connection signal="pressed" from="exit_game" to="." method="_on_exit_game_pressed"] + + +[editable path="player1"] +[editable path="player2"] diff --git a/networking/pong_multiplayer/separator.png b/networking/pong_multiplayer/separator.png new file mode 100644 index 00000000..56874a59 Binary files /dev/null and b/networking/pong_multiplayer/separator.png differ