Reputation: 71
The explaination is going to be a bit lengthy, please bear with it and any help is appreciated
Creating a multiplayer air hockey type game with a paddle and a ball.
Elements of game
Replicate exact paddle movement and collision between instances.
Sending paddle data from Instance 1 to Instance 2. Consuming data in Instance 2 to mimic paddle movement.
Sending data: using websockets and JavaScript bridge. Let's treat that as blackbox for now.
func handle_drag_input(delta):
var current_pos = global_position
var target_pos = get_global_mouse_position()
if is_selected:
var velocity = calc_velocity(target_pos, current_pos, delta)
# disabling infinite impuse
move_and_slide(velocity,Vector2.ZERO,false,4,PI/4,false)
apply_impulse(100)
player_state = {"paddle":{"velocity":velocity, "pos":current_pos},
"mouse":{"pos":target_pos,"active":true}}
else:
player_state = {"paddle":{"velocity":Vector2.ZERO, "pos":current_pos},
"mouse":{"pos":target_pos,"active":false}}
func _input(event: InputEvent):
if event is InputEventMouseButton:
if event.button_index == BUTTON_LEFT and not event.is_pressed():
is_selected = false
func _on_Player_input_event(viewport, event, shape_idx):
if Input.is_action_just_pressed('click'):
is_selected = true
func calc_velocity(target_pos, current_pos, delta):
var new_pos = global_position.linear_interpolate(target_pos, 500 * delta)
var velocity = new_pos - current_pos
return velocity
func apply_impulse(push):
for index in get_slide_count():
var collision = get_slide_collision(index)
if collision.collider.is_in_group("bodies"):
collision.collider.apply_central_impulse(-collision.normal * push)
the handle_drag_input
is being called inside _physcis_process
and it works fine.
As you can see the data I'm sending from Instance1:
{"paddle":{"velocity":velocity, "pos":current_pos},
"mouse":{"pos":target_pos,"active":false}}
The paddle of Instance2 takes the data and consumes it in a similar way inside _physics_process()
:
func _physics_process(delta):
var mouse_active = player_state.mouse.active #from instance 1
var mouse_pos = player_state.mouse.pos #from instance 1
var current_pos = player_state.paddle.pos #from instance 1
var velocity = calc_velocity(mouse_pos,current_pos,delta)
# to make sure the paddle is in Same position as in Instance 1
if not is_initial_position_used:
self.global_position = player_state.paddle.pos
is_initial_position_used = true
if mouse_active:
# disabling infinite impuse
move_and_slide(velocity,Vector2.ZERO,false,4,PI/4,false)
apply_impulse(100)
Ball physics in Instance 2 occasionally behave unpredictably compared to Instance 1. even after the padddle velocity, impulse are same as that of paddle on Instance1.
I am able to mimic the exact movement of the Paddle of instance 1 however the ball is the issue.
Possibly the force applied to the ball different in both instances?
Sol: disabled infinite impulse and added fixed impulse apply_impulse(100)
in both.
Possibly the velocity and position of the paddles may differ? Sol: sending mouse position and paddle position of Instance 1 and calculating velocity at Instance 2 to make sure it is same as that of Instance 1
Any other approach is also welcomed.. Any help is appreciated. I have been stuck in this for a while now. Thank you.
Upvotes: 1
Views: 99
Reputation: 11
How do I track down the exact issue?
This is more of a general coding question, but if you had no where else to start, you could track down the issue by finding every point in the code where the ball's position changes. Try to understand exactly what's happening at each of these points. If that doesn't tell you how the positions are changing differently, try printing a time stamp, followed by all of the input values before the change, followed by the position after the change. This will let you see which input values differ between instances at the time when the ball's position goes out of sync. Whichever input values differ are likely responsible. You would then repeat this entire process recursively for each input variable until you find out where and how the problem is originating.
Is there a role of collision shape in the inconsistency (as in the collision contact points)?
Yes but this isn't the root of the issue. If the positions of the ball or paddle are already slightly different between instances, then the ball will bounce off at a slightly different angle, which makes the initial difference in position even worse, especially for small objects where the difference in angles will be greater.
What am I doing wrong here?
The main reason the problem occurs is because the ball's(and paddle's) velocity rely on delta(the time between updates), which is not going to be the same between instances. If the velocity is off, the positions will be off unless you correct them like you do with the paddle. Even small differences can compound over time, especially when collisions happen at different angles. Sending the delta of one instance to another to simulate the first isn't a reasonable solution because it's unnecessarily complicated, it would require a perfect network in order to stay in sync, and you could still end up with differences to the different ways floating point numbers are handled.
The basic structure of how you should keep positions roughly the same between instances is to have the "server"(probably Instance 1) hold a "true state" of positions and velocities which it sends to the "client(s)", and have the client(s) send change requests to the server. It's genrally good to keep the client change requests as limitted as possible: ideally, only send player mouse inputs and make the server process how those inputs affect the state of the world.
There are many ways to handle it after this point. An easy way is to force the client to match the server state whenever it gets a new packet(This is what you're already doing with the paddle. Instance 2 behaves like a client, matching the "true" paddle position from Instance 1. You can do the same thing for the ball and the "player2 paddle") If you want to make it even easier, you can let the client handle it's own player state directly(for example by let Instance 2 control the "true" position of a "player2 paddle" and send that position to be matched by Instance 1 in the same way the ball and "player1 paddle" are handled in the other direction).
This way has a ton of problems and is very dependent on good network conditions to be close to acceptable, but it can be a good starting point. This article goes into more detail on some better strategies you can implement.
Upvotes: 1