Trying to synchronize the bounce of a ball in a multiplayer game

OS: Windows 11
Unity: 2022.3.15f1
Needle Version: 3.47.10 NE: 3.48.3

Hi :slight_smile: Hoping you’re all doing really fine, It’s me DiegoEloko again hehe.
I’m aware that currently, Needle doesn’t support physics networking but I remember that a guy said something about network an object’s position with the aim of reproduce locally the same physics. Now, I’m trying to do a multiplayer soccer-like test where the players can bounce a ball against walls and against players. I don’t know almost nothing about physics but I could get a bounce reaction like in a billiard ball (thats the movement which I am looking for) and let me explain what do I have:

  1. The ball has a NetworkBounce script.
  2. The general idea : Each time that the ball bounce with a player (an object with a PlayerState script) it checks if that player is local, if that is true then it bounce normally (in local) and sends the information about the normal vector of the contact to the other players.
  3. The other players receive that normal and calculate its own physics.

This is my current script :

import { Behaviour, Collision,Rigidbody,serializable,} from "@needle-tools/engine";
import {NetworkConnection, PlayerState} from "@needle-tools/engine";
import { Vector3 } from "three";

class VectorData{
   x : number;
   y : number;
   z : number; 
   constructor(x:number,y:number,z:number){ 
        this.x=x; 
        this.y=y; 
        this.z=z;  
   } 
}

export class NetworkBounce extends Behaviour{

    @serializable()
    rb! : Rigidbody; 

    private surfaceNormal : Vector3 = new Vector3(0,0,0);
    
    private net !: NetworkConnection;


    start(): void {

        this.net = this.context.connection;
        this.setBeginListen(); 
        this.rb= this.gameObject.getComponent(Rigidbody)!; 
    }

    update(): void {
        if(this.context.input.isKeyUp("z")){
            console.log("Initial impulse");
            if(this.rb){
                //this.rb.applyForce(new Vector3(1,0,1)); 
                this.rb.applyImpulse(new Vector3(3,0,3)); 

                //EXPERIMENTAL>>>
                this.net.send("initialImpulse", new VectorData(0,0,0))
            }
        }
        
    }

    onCollisionEnter(col: Collision) {
        
        //If hit a player 
        if(col.gameObject.getComponent(PlayerState)!){
            
            //Only if the player is in local, it broadcast the collision with the normal information
            if(col.gameObject.getComponent(PlayerState)!.isLocalPlayer==true){
                //Local bounce 
                this.bounce(col);
                //Send information
                this.sendNormalSuperficieData(this.surfaceNormal); 
            }else{
                //Nothing happens
                
            }
        }else{
            //Then it bounce with a static object 
            this.bounce(col);
        }
    }

    bounce(col: Collision){
        this.surfaceNormal = col.contacts[0].normal; 

        //console.log("Normal local: ("+this.surfaceNormal.x+", "+this.surfaceNormal.y+", "+this.surfaceNormal.z+")"); 
        
        //this.sendNormalSuperficieData(this.surfaceNormal); 

        const velNormal = this.rb.getVelocity().dot(this.surfaceNormal); 

        const normalMultiplied = this.surfaceNormal.multiplyScalar(2*velNormal); 

        const newVelocity = this.rb.getVelocity().sub(normalMultiplied); 
        
        // console.log("Velocidad local calculada: ("+newVelocity.x+", "+newVelocity.y+", "+newVelocity.z+")");
        

        this.rb.setVelocity(newVelocity)
        
        
    }

    bounceByNormal(){
        
        //console.log(this.surfaceNormal);
        //console.log("Normal recibida: ("+this.surfaceNormal.x+", "+this.surfaceNormal.y+", "+this.surfaceNormal.z+")");
        
        const velNormal = this.rb.getVelocity().dot(this.surfaceNormal); 
        const normalMultiplied = this.surfaceNormal.multiplyScalar(2*velNormal); 
        const newVelocity = this.rb.getVelocity().sub(normalMultiplied); 
        
        // console.log("Velocidad recibida calculada: ("+newVelocity.x+", "+newVelocity.y+", "+newVelocity.z+")");
        
        this.rb.setVelocity(newVelocity)
        // console.log("Velocidad recibida final: "+this.rb.getVelocity());
        // console.log("DEBERIA APLICAR NUEVA VELOCIDAD");
        // console.log(this.rb.getVelocity());
        
    }

    //NetworkBehavior : 

    sendNormalSuperficieData(normal :Vector3){
        this.net.send("normalData", new VectorData(normal.x, normal.y, normal.z))
    }

    setBeginListen(){
        this.net.beginListen("normalData",(data)=>{ 
            this.surfaceNormal.x = data.x;
            this.surfaceNormal.y = data.y;
            this.surfaceNormal.z = data.z;
            this.bounceByNormal(); 
            
        })

        this.net.beginListen("initialImpulse",(data)=>{  
            this.rb.applyImpulse(new Vector3(3,0,3));
        })


    }

    setStopListen(){
        this.net.stopListen("normalData",(data)=>{})

        this.net.stopListen("initialImpulse",(data)=>{})

    }

    onDisable(): void {
        this.setStopListen(); 
    }

   
}

The problem is : Let it be my two players P1 and P2. While I’m in multiplayer, I produce an initial impulse which produce a movement to the ball which is the same in both player while any of the player interact with the ball.
When P1 hits the ball, locally, the movement is not the expected but when the remote clone of P2 in the local context of P2 hits the ball , the bounce is the expected. I let you a video about the described behavior : MultiplayerBouncing.mp4 - Google Drive

So, if someone has tried this experiment before and have a clue about how network the bounce of a ball between players, please lets share knowledge :slight_smile:

Hi DiegoEloko! It’s great to see you again! It sounds like you’re working on an interesting multiplayer soccer-like game with Needle Engine. Let’s break down your current approach and make sure you can synchronize the bouncing behavior correctly.

Current Approach Overview

  1. Ball Bounce Detection: You have a NetworkBounce script on the ball that detects collisions with players.
  2. Local Player Check: When the ball bounces off a player, it checks if that player is local.
  3. Network Synchronization: If it’s the local player, it handles the bounce locally and sends the surface normal to other players.

Suggested Improvements

To ensure that the synchronization works properly, you need to ensure that the physics reactions are consistent across all clients. Here’s a refined approach:

  1. Implement Accurate Collision Events: Use the onCollisionEnter method to detect when the ball collides with a player.

  2. Send the Bounce Data: When a local player is hit, send not just the normal vector but also the impact force and the ball’s velocity.

  3. Recreate the Bounce on Remote Players: Other players should read the received data and apply changes to their local ball’s Rigidbody accordingly.

Here’s a possible enhancement to your existing script:

import { Behaviour, Collision, Rigidbody, serializable } from "@needle-tools/engine";
import { NetworkConnection, PlayerState } from "@needle-tools/engine";
import { Vector3 } from "three";

export class NetworkBounce extends Behaviour {
    @serializable()
    rb!: Rigidbody;

    private net!: NetworkConnection;

    start(): void {
        this.net = this.context.connection;
        this.rb = this.gameObject.getComponent(Rigidbody)!;
        this.setBeginListen();
    }

    onCollisionEnter(collision: Collision): void {
        const player = collision.gameObject.getComponent(PlayerState);

        if (player) {
            const isLocal = player.isLocal();
            const normal = collision.contactNormal;
            const velocity = this.rb.velocity;

            if (isLocal) {
                // Handle local bounce
                this.handleBounce(normal, velocity);
                this.sendBounceData(normal, velocity);
            } else {
                // Handle incoming bounce from network
                this.applyNetworkBounce(normal, velocity);
            }
        }
    }

    private handleBounce(normal: Vector3, velocity: Vector3): void {
        // Update the ball's movement based on the normal vector
        const bounceVelocity = velocity.reflect(normal).multiplyScalar(1.0); // adjust the scale factor as needed
        this.rb.velocity = bounceVelocity;
    }

    private sendBounceData(normal: Vector3, velocity: Vector3): void {
        // Send the data to other players
        this.net.send('bounce', { normal, velocity });
    }

    private applyNetworkBounce(normal: Vector3, velocity: Vector3): void {
        // Apply received bounce data
        const bounceVelocity = new Vector3().copy(velocity).reflect(normal).multiplyScalar(1.0); // adjust the scale factor as needed
        this.rb.velocity = bounceVelocity; 
    }
}

Next Steps

  1. Confirm that your collision detection (onCollisionEnter) is working correctly with the PlayerState component.
  2. Make sure you add the necessary networking logic to send and receive bounce data.
  3. Adjust the scaling factors for velocity to fine-tune the bounces.

Feel free to ask if you have specific questions about any part of the implementation or if you need help with networking setup!

1 replies left - Get a PRO license for more AI replies.
thread_cUGAHvJ1Hoq9vgdJZlaz7Ndj
Reply to this message to continue the conversation