how do we send point/score to other player in multiplayer games ???

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By mdswamp

hi, i want to show live score on the two screens of two players who play my fighting game :wink:
i have two problems here:
1- sometimes character feels the punch (it shows with animation), but i dont see any score for that.
2- sometimes it shows two point for one punch (i disable monitoring/monitarable in animation player when the punch shape goes back, so there is no chance that this problem happens when shape is going back or something like that)

and i should mention that there isn’t any problem in offline mode and it works perfectly.

this is the script for counting and showing scores on screen:

extends Node2D

onready var player1 = get_node("Player01")
onready var player2 = get_node("Player02")
onready var p1score = get_node("P01Score/Point01")
onready var p2score = get_node("P02Score/Point02")
onready var game_countdown = get_node("GameCountDown")
onready var timer_label = get_node("TimerLabel")


var count1 = 0
var count2 = 0
var minutes = 1
var seconds = 60

func _ready():
	game_countdown.start()
	if (get_tree().is_network_server()):
		player2.set_network_master(get_tree().get_network_connected_peers()[0])
	else:
		player2.set_network_master(get_tree().get_network_unique_id())
	print("unique id: ",get_tree().get_network_unique_id())

func _process(delta):
		score_update()
		rset("count1", player2.counter)
		rset("count2", player1.counter)

sync func score_update():
	if get_tree().is_network_server():
		if str(player2.counter) != p1score.text:
			count1 = player2.counter  #counter which i get from collision function
			p1score.set_text(str(player2.counter))
	else:
		if str(player1.counter) != p2score.text:
			count2 = player1.counter  #counter which i get from collision function
			p2score.set_text(str(player1.counter))

func _on_GameCountDown_timeout():  #this one is just a countdown for the game
     if seconds >=0 && minutes == 1:
        seconds -= 1
        timer_label.set_text(str(minutes," :",seconds))
     if seconds >=0 && minutes == 0:
        seconds -= 1
        timer_label.set_text(str(minutes," :",seconds))
     if seconds == -1 && minutes == 1:
        seconds = 59
        minutes = 0
        timer_label.set_text(str(minutes," :",seconds))

and if you curious about that how scores send to script above, i use function like this one in players scripts:

sync func _on_Fist_area_entered(area):
     if head_fr.get_collision_mask() == 2 && block == 0:
        animations.play("Hurt")
        counter+=1   # we get this one for counting score

THANK YOU :slight_smile:

:bust_in_silhouette: Reply From: dant4

hello,
I think you may do some confusion in your code.
If I understand correctly, this code is running on all clients and server. So, your update will be called by the salves as well.
The following code should only be done by the master:

rset("count1", player2.counter)
rset("count2", player1.counter)

Then, the slaves will see their “count1” and “count2” by synchronized. By the way, count1 should not be player1.counter?

In general, don’t change count1 and count2 on the slaves, only read these values. So try to separate the logic of the slaves from the logic of the master.

hi dant4, sry for late response
i was trying to separate logic of master and slave as you said and i found something unpleasant. i dont think that i had this problem in offline mode
if i push one kinematic body to other one, shape which is pushed begins to vibrate and sometimes they enter each other, i think thats the reason which sometimes it dose not count score. it seems my movements code has some issue (im not sure). can you help me with this? thank you…

func _physics_process(delta):
     var velocity = Vector2()
if Input.is_action_pressed("p02_right"):
           velocity.x += 1
if Input.is_action_pressed("p02_left"):
           velocity.x -= 1
if Input.is_action_pressed("p02_down"):
           velocity.y += 1
if Input.is_action_pressed("p02_up"):
           velocity.y -= 1
   
rpc_unreliable("setPosition",Vector2(position.x - velocity.x, position.y))

if velocity.length() > 0:
   velocity = velocity.normalized()*speed*delta
var motion = move_and_collide(velocity)
if (motion):
   motion = move_and_slide(velocity * 40)

slave func setPosition(pos):
  position = pos

p.s: score of each player counts in script of the other player, thats why rset(“count1”, player2.counter) is like that :wink:

mdswamp | 2018-10-17 08:06

I’ll try to code a sample later this day.
As a side note, if you let each player broadcasts its score, you could have some cheating issue later. But maybe it’s not the point of your dev.

dant4 | 2018-10-17 13:06

thank you dant4 :slight_smile:

but about score:

if i’m not wrong, what is happening in my multiplayer code is that when 2 players (it is one on one fighting game) try to connect, the first one become server and the second become client. then scene of arena being loaded and the game starts.
do you mean i should count scores on the arena scene for both players and not in player’s script and move all collision functions and collision conditions to the arena ring? thank you again :slight_smile:

mdswamp | 2018-10-17 15:12

ususally, the server (or host) gets the authority of things like scores.
then, you have different design :

  • you really don’t trust the clients, so they only send the input, not the position
    the server computes the position, and replicates it over the network. The client controlling this player, will adjust his local position in a second time. Not really an option for you.

  • your case, you trust a bit more your client since you don’t have the choice, so each peer replicates his player position (like you do) but, the server still computes some things like the score. The clients play some local fx when they hit something, but you need to wait for the server confirmation before really updating the score. It could be a solution, but being the host will always be an advantage.

  • for a fair fighting game you can also only broadcasts the input (no position to compute), so the other clients will play the same inputs on the slaves instead of replicating the position. It will require to add some delay in order to avoid any desync. It’s really complex to make it work but, 100% accurate for a fight game.

More info about different way to replicate here:
What Every Programmer Needs To Know About Game Networking | Gaffer On Games
or
Source Multiplayer Networking - Valve Developer Community

dant4 | 2018-10-17 16:59

So I have two player with a KinematricBody2D and the following code:

extends KinematicBody2D

var speed = 250
var id = ""

func set_id(new_id):
	id = new_id
	
func get_id():
	return id

func _physics_process(delta):
	
	if is_network_master():
		var velocity = Vector2()
		
		if Input.is_action_pressed('ui_right'):
			velocity.x += 1
		if Input.is_action_pressed('ui_left'):
			velocity.x -= 1
		if Input.is_action_pressed('ui_down'):
			velocity.y += 1
		if Input.is_action_pressed('ui_up'):
			velocity.y -= 1
			
		move_and_collide(velocity * delta * speed)
		rpc_unreliable("setPosition", position)

slave func setPosition(pos):
	position = pos

As you can see, I only send the final position using an unreliable rpc (but it could also be a replicated member)
Then, my scores are managed by a global script

extends Node

var scores = {}

func _ready():
	scores["01"] = 0
	scores["02"] = 0
	

func _on_Area2D_body_entered(body):
	# only the server updates the score
	if is_network_master():
		var id = body.get_id()
		scores[id] = scores[id] + 1
		rpc("update_score", id, scores[id])
		
sync func update_score(id, score):
	scores[id] = score
	_on_score_updated(id)
	
func _on_score_updated(id):	
	get_node("UI/Score" + id).text = str(scores[id])

So, when the server detects a collision, it will update the score, then replicates it.
See the result

Imgur: The magic of the Internet

and the source code

https://ufile.io/b6za7

dant4 | 2018-10-17 23:45

hi dant4.
this is great, its exactly the thing that i needed to understand networking better. many thanks to you.

mdswamp | 2018-10-18 11:44

nice, don’t hesitate to ask if you have more question.

dant4 | 2018-10-18 11:58

hi dant4…
im thinking about how transfer area2Ds to server.
could i instance my players to ring, and then add area2Ds to them for hit/hurt box? does it mean area2Ds are outside of clients reach?

Ring node

Player01 (Instanced Node)

Area2D - 01
Area2D - 02
.
.
.

or i should create area2Ds in ring and make them follow player head and fists? but i think this one make a big lag in game and we have many missed collisions.

sry to bother you and thank you for your time :slight_smile:

mdswamp | 2018-10-20 09:23

I think you should keep the area on the client as well, because you will need them to play some local fx/feedbzck when a hit happens.but only for fx!!
About how to do it, I can have a look this weekend unless someone kowns

dant4 | 2018-10-20 13:34

i dont wanna bother you dant 4. ive managed to do this before and players detected and sent collision signals. i just need some guidance about how to do these things to make game secure, I really appreciate your help… :slight_smile:

mdswamp | 2018-10-20 13:53

Networking is all about hidding the latency and minimizing your packets size.
When you are more familiar with that, you can think advanced anti-cheat. It will probably increase your packet size lol, so you can start over and optimize again. Only keep in mind, that you can’t let the client tell the result (= don’t send the target of the shoot, but the input of the shoot)
If you want to learn more about this, you can have a look about the GGPO middleware
GGPO - Wikipedia

It’s what almost all fighting game use.

dant4 | 2018-10-22 12:53