-
-
Notifications
You must be signed in to change notification settings - Fork 235
[WIP] Add Multiplayer Example #905
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
e3d63b7
start replacing gdscript classes with rust classes
ValorZard 3e8e3fa
replicated most of the gameplay from the gdscript side
ValorZard 96821ba
replace gdscript version of player with rust
ValorZard 2931157
commiting to save progress, but theres some weird bugs cropping up no…
ValorZard 9eb685c
redid scene manager, but still getting error regarding player desync
ValorZard d3ab6b6
fix networking again, did some refactors
ValorZard b814869
converted everything, now time to debug all this
ValorZard d14981c
refactor a bit while trying to hunt down bug
ValorZard 33b7080
fixed connection bug, but now we have bigger problems
ValorZard 6098b09
it works now i guess
ValorZard 30258c4
fixed edge cases with making game wait
ValorZard be54093
attempt to turn this into an actual game, but im tired
ValorZard e1f74f9
revert accidental changes to dodge the creeps
ValorZard 0d0cc6e
remove unnecessary GDScript and scenes
ValorZard 71bab84
remove game_manager and just pass around player_database instead
ValorZard 58c250f
remove binaries that accidentally got generated inside repo
ValorZard e6e7210
remove assets and replace them with dodge the creep player sprite for…
ValorZard 2e69a8a
make all instances of NetworkId called network_id for sanity sake + a…
ValorZard abc1612
made server in charge of starting the game
ValorZard 2f9098b
did some refactoring, still can't get rid of inconsistent connection bug
ValorZard 57ea430
no more weird sync errors! but now the players have dissapeared
ValorZard 2e67f77
add more debugging tools
ValorZard f67e73b
add more comments, and add callback on death
ValorZard 9cd0859
add address bar for custom address + added basic README
ValorZard 1f0dd45
fix ci
ValorZard f89291e
address review comments
ValorZard 4116339
fix formatting besides one weird cliipy error
ValorZard 043db66
Replace every instance of magic number 1 with MultiplayerPeer::TARGET…
ValorZard 9679781
restarting from scratch, look at previous commit if you want to pick …
ValorZard a1d7b30
this is where i stop
ValorZard File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
res://rust.gdextension |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Multiplayer Bomber | ||
|
||
A multiplayer implementation of the classic bomberman game. | ||
One of the players should press **Host**, while other player(s) | ||
should type in the host's IP address and press **Join**. | ||
|
||
Language: GDScript | ||
|
||
Renderer: Compatibility | ||
|
||
Check out this demo on the asset library: https://godotengine.org/asset-library/asset/139 | ||
|
||
## Screenshots | ||
|
||
 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
extends Area2D | ||
|
||
var in_area: Array = [] | ||
var from_player: int | ||
|
||
# Called from the animation. | ||
func explode(): | ||
if not is_multiplayer_authority(): | ||
# Explode only on authority. | ||
return | ||
for p in in_area: | ||
if p.has_method("exploded"): | ||
# Checks if there is wall in between bomb and the object | ||
var world_state: PhysicsDirectSpaceState2D = get_world_2d().direct_space_state | ||
var query := PhysicsRayQueryParameters2D.create(position, p.position) | ||
query.hit_from_inside = true | ||
var result: Dictionary = world_state.intersect_ray(query) | ||
if not result.collider is TileMap: | ||
# Exploded can only be called by the authority, but will also be called locally. | ||
p.exploded.rpc(from_player) | ||
|
||
|
||
func done(): | ||
if is_multiplayer_authority(): | ||
queue_free() | ||
|
||
|
||
func _on_bomb_body_enter(body): | ||
if not body in in_area: | ||
in_area.append(body) | ||
|
||
|
||
func _on_bomb_body_exit(body): | ||
in_area.erase(body) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
[gd_scene load_steps=9 format=3 uid="uid://enwoaqi0rnei"] | ||
|
||
[ext_resource type="Script" path="res://bomb.gd" id="1"] | ||
[ext_resource type="Texture2D" uid="uid://bdomqql6y50po" path="res://brickfloor.png" id="2"] | ||
[ext_resource type="Texture2D" uid="uid://drfbkdqmj0gu2" path="res://explosion.png" id="3"] | ||
|
||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_1ih13"] | ||
size = Vector2(16, 192) | ||
|
||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_whso6"] | ||
size = Vector2(192, 16) | ||
|
||
[sub_resource type="Curve" id="Curve_4yges"] | ||
max_value = 2.0 | ||
_data = [Vector2(0.00150494, 0.398437), 0.0, 0.0, 0, 0, Vector2(0.0152287, 1.42969), 0.0, 0.0, 0, 0, Vector2(0.478607, 1.30078), 0.0, 0.0, 0, 0, Vector2(1, 0.291016), 0.0, 0.0, 0, 0] | ||
point_count = 4 | ||
|
||
[sub_resource type="Animation" id="Animation_21j5c"] | ||
length = 4.0 | ||
tracks/0/type = "value" | ||
tracks/0/imported = false | ||
tracks/0/enabled = true | ||
tracks/0/path = NodePath("Sprite:self_modulate") | ||
tracks/0/interp = 1 | ||
tracks/0/loop_wrap = true | ||
tracks/0/keys = { | ||
"times": PackedFloat32Array(0, 0.4, 0.6, 0.8, 1.1, 1.3, 1.5, 1.8, 1.9, 2, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 3), | ||
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), | ||
"update": 0, | ||
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(8, 8, 8, 1), Color(1, 1, 1, 1), Color(1, 1, 1, 0)] | ||
} | ||
tracks/1/type = "method" | ||
tracks/1/imported = false | ||
tracks/1/enabled = true | ||
tracks/1/path = NodePath(".") | ||
tracks/1/interp = 1 | ||
tracks/1/loop_wrap = true | ||
tracks/1/keys = { | ||
"times": PackedFloat32Array(2.8, 3.4), | ||
"transitions": PackedFloat32Array(1, 1), | ||
"values": [{ | ||
"args": [], | ||
"method": &"explode" | ||
}, { | ||
"args": [], | ||
"method": &"done" | ||
}] | ||
} | ||
tracks/2/type = "value" | ||
tracks/2/imported = false | ||
tracks/2/enabled = true | ||
tracks/2/path = NodePath("Explosion1:emitting") | ||
tracks/2/interp = 1 | ||
tracks/2/loop_wrap = true | ||
tracks/2/keys = { | ||
"times": PackedFloat32Array(0, 2.8), | ||
"transitions": PackedFloat32Array(1, 1), | ||
"update": 1, | ||
"values": [false, true] | ||
} | ||
tracks/3/type = "value" | ||
tracks/3/imported = false | ||
tracks/3/enabled = true | ||
tracks/3/path = NodePath("Explosion2:emitting") | ||
tracks/3/interp = 1 | ||
tracks/3/loop_wrap = true | ||
tracks/3/keys = { | ||
"times": PackedFloat32Array(0, 2.8), | ||
"transitions": PackedFloat32Array(1, 1), | ||
"update": 1, | ||
"values": [false, true] | ||
} | ||
|
||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_h2w7m"] | ||
_data = { | ||
"anim": SubResource("Animation_21j5c") | ||
} | ||
|
||
[node name="Bomb" type="Area2D"] | ||
monitorable = false | ||
script = ExtResource("1") | ||
|
||
[node name="Sprite" type="Sprite2D" parent="."] | ||
self_modulate = Color(1, 1, 1, 0) | ||
position = Vector2(-2.92606, -2.92606) | ||
texture = ExtResource("2") | ||
region_enabled = true | ||
region_rect = Rect2(144, 0, 48, 48) | ||
|
||
[node name="Shape1" type="CollisionShape2D" parent="."] | ||
shape = SubResource("RectangleShape2D_1ih13") | ||
|
||
[node name="Shape2" type="CollisionShape2D" parent="."] | ||
shape = SubResource("RectangleShape2D_whso6") | ||
|
||
[node name="Explosion1" type="CPUParticles2D" parent="."] | ||
emitting = false | ||
lifetime = 0.5 | ||
one_shot = true | ||
explosiveness = 0.95 | ||
texture = ExtResource("3") | ||
emission_shape = 3 | ||
emission_rect_extents = Vector2(80, 1) | ||
gravity = Vector2(0, 0) | ||
initial_velocity_min = 1.0 | ||
initial_velocity_max = 1.0 | ||
angular_velocity_min = 187.35 | ||
angular_velocity_max = 188.35 | ||
scale_amount_curve = SubResource("Curve_4yges") | ||
|
||
[node name="Explosion2" type="CPUParticles2D" parent="."] | ||
rotation = 1.57162 | ||
emitting = false | ||
lifetime = 0.5 | ||
one_shot = true | ||
explosiveness = 0.95 | ||
texture = ExtResource("3") | ||
emission_shape = 3 | ||
emission_rect_extents = Vector2(80, 1) | ||
gravity = Vector2(0, 0) | ||
initial_velocity_min = 1.0 | ||
initial_velocity_max = 1.0 | ||
angular_velocity_min = 187.35 | ||
angular_velocity_max = 188.35 | ||
scale_amount_curve = SubResource("Curve_4yges") | ||
|
||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."] | ||
autoplay = "anim" | ||
libraries = { | ||
"": SubResource("AnimationLibrary_h2w7m") | ||
} | ||
|
||
[connection signal="body_entered" from="." to="." method="_on_bomb_body_enter"] | ||
[connection signal="body_exited" from="." to="." method="_on_bomb_body_exit"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
extends MultiplayerSpawner | ||
|
||
func _init(): | ||
spawn_function = _spawn_bomb | ||
|
||
|
||
func _spawn_bomb(data): | ||
print(data) | ||
if data.size() != 2 or typeof(data[0]) != TYPE_VECTOR2 or typeof(data[1]) != TYPE_INT: | ||
return null | ||
var bomb = preload("res://bomb.tscn").instantiate() | ||
bomb.position = data[0] | ||
bomb.from_player = data[1] | ||
return bomb |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
extends Node | ||
|
||
# Default game server port. Can be any number between 1024 and 49151. | ||
# Not on the list of registered or common ports as of November 2020: | ||
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers | ||
const DEFAULT_PORT = 10567 | ||
|
||
# Max number of players. | ||
const MAX_PEERS = 12 | ||
|
||
var peer = null | ||
|
||
# Name for my player. | ||
var player_name = "The Warrior" | ||
|
||
# Names for remote players in id:name format. | ||
var players = {} | ||
var players_ready = [] | ||
|
||
# Signals to let lobby GUI know what's going on. | ||
signal player_list_changed() | ||
signal connection_failed() | ||
signal connection_succeeded() | ||
signal game_ended() | ||
signal game_error(what) | ||
|
||
# Callback from SceneTree. | ||
func _player_connected(id): | ||
# Registration of a client beings here, tell the connected player that we are here. | ||
register_player.rpc_id(id, player_name) | ||
|
||
|
||
# Callback from SceneTree. | ||
func _player_disconnected(id): | ||
if has_node("/root/World"): # Game is in progress. | ||
if multiplayer.is_server(): | ||
game_error.emit("Player " + players[id] + " disconnected") | ||
end_game() | ||
else: # Game is not in progress. | ||
# Unregister this player. | ||
unregister_player(id) | ||
|
||
|
||
# Callback from SceneTree, only for clients (not server). | ||
func _connected_ok(): | ||
# We just connected to a server | ||
connection_succeeded.emit() | ||
|
||
|
||
# Callback from SceneTree, only for clients (not server). | ||
func _server_disconnected(): | ||
game_error.emit("Server disconnected") | ||
end_game() | ||
|
||
|
||
# Callback from SceneTree, only for clients (not server). | ||
func _connected_fail(): | ||
multiplayer.set_network_peer(null) # Remove peer | ||
connection_failed.emit() | ||
|
||
|
||
# Lobby management functions. | ||
@rpc("any_peer") | ||
func register_player(new_player_name): | ||
var id = multiplayer.get_remote_sender_id() | ||
players[id] = new_player_name | ||
player_list_changed.emit() | ||
|
||
|
||
func unregister_player(id): | ||
players.erase(id) | ||
player_list_changed.emit() | ||
|
||
|
||
@rpc("call_local") | ||
func load_world(): | ||
# Change scene. | ||
var world = load("res://world.tscn").instantiate() | ||
get_tree().get_root().add_child(world) | ||
get_tree().get_root().get_node("Lobby").hide() | ||
|
||
# Set up score. | ||
world.get_node("Score").add_player(multiplayer.get_unique_id(), player_name) | ||
for pn in players: | ||
world.get_node("Score").add_player(pn, players[pn]) | ||
get_tree().set_pause(false) # Unpause and unleash the game! | ||
|
||
|
||
func host_game(new_player_name): | ||
player_name = new_player_name | ||
peer = ENetMultiplayerPeer.new() | ||
peer.create_server(DEFAULT_PORT, MAX_PEERS) | ||
multiplayer.set_multiplayer_peer(peer) | ||
|
||
|
||
func join_game(ip, new_player_name): | ||
player_name = new_player_name | ||
peer = ENetMultiplayerPeer.new() | ||
peer.create_client(ip, DEFAULT_PORT) | ||
multiplayer.set_multiplayer_peer(peer) | ||
|
||
|
||
func get_player_list(): | ||
return players.values() | ||
|
||
|
||
func get_player_name(): | ||
return player_name | ||
|
||
|
||
func begin_game(): | ||
assert(multiplayer.is_server()) | ||
load_world.rpc() | ||
|
||
var world = get_tree().get_root().get_node("World") | ||
var player_scene = load("res://player.tscn") | ||
|
||
# Create a dictionary with peer id and respective spawn points, could be improved by randomizing. | ||
var spawn_points = {} | ||
spawn_points[1] = 0 # Server in spawn point 0. | ||
var spawn_point_idx = 1 | ||
for p in players: | ||
spawn_points[p] = spawn_point_idx | ||
spawn_point_idx += 1 | ||
|
||
for p_id in spawn_points: | ||
var spawn_pos = world.get_node("SpawnPoints/" + str(spawn_points[p_id])).position | ||
var player = player_scene.instantiate() | ||
player.synced_position = spawn_pos | ||
player.name = str(p_id) | ||
player.set_player_name(player_name if p_id == multiplayer.get_unique_id() else players[p_id]) | ||
world.get_node("Players").add_child(player) | ||
|
||
|
||
func end_game(): | ||
if has_node("/root/World"): # Game is in progress. | ||
# End it | ||
get_node("/root/World").queue_free() | ||
|
||
game_ended.emit() | ||
players.clear() | ||
|
||
|
||
func _ready(): | ||
multiplayer.peer_connected.connect(_player_connected) | ||
multiplayer.peer_disconnected.connect(_player_disconnected) | ||
multiplayer.connected_to_server.connect(_connected_ok) | ||
multiplayer.connection_failed.connect(_connected_fail) | ||
multiplayer.server_disconnected.connect(_server_disconnected) |
Binary file not shown.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you forget newline