Multiplayer Sync with Physics Objects

Hey, I wanted to make it so that in my game, Ball Playergrounds, physics objects like a box that can be pushed are synced between players, and possibly a way to let players push players. How would I approach this?

Oh, and unrelated, how would I allow players to input bugs found in-game and it save to an external site like GitHub or something?

Hey @Cryptonaph,

You may want to look into a multiplayer framework for this. I wrote a guide on how to integrate Photon multiplayer into PlayCanvas here which might be of help in this regard.

You could add a form via external HTML to your game(docs here), and use an external API to save it where you need. If you need further guidance on this, Iā€™d suggest creating a new topic.

Hope this helps!

Thanks, Iā€™ll take a look at it. Do you happen to know anything on the physics syncing issue?

Thatā€™s why I had suggested looking at Photon :wink:. A multiplayer framework will help you achieve your goals with this.

Sorry, but I donā€™t really understand how this would be implemented with what I currently have. Any examples on how I could implement it? Seeing as the only things I need are for physics objects like pushables to be server side and for players to be able to push one another (which would come after pushables being server side) I donā€™t know what I would do.

I believe the example project in my starter kit post has physics enabled, where players can interact with other players in the scene physically. Look at how the scene is set up in terms of networking(this is in the guide), and then you can extend that to any physics functionality you need.

Unfortunately I am unable to use this as Iā€™m doing this in school and they have blocked the photon site. Any known ways I can do it with Socket? I tried syncing the rotation and position of the box, which works when only one player is there, and when a second player connects the box is where the first player put it, but upon trying to move it while both players are connected it tries to go between client positions, rendering it nearly impossible to use. I need to find some way to fix that.

While I donā€™t use socket in production myself, the issue you describe sounds like youā€™re not relaying the events that move the box between your two players. Iā€™d suggest studying how to transmit events between players via the server - what youā€™re basically trying to achieve is that when player A moves the box, an event is transmitted to all other players via the server, which can then be picked up by players B and C, which will cause the box movement to occur on receiving the event.

Well, the players can see other players moving around, but the only issue is due to the player body being a dynamic rigidbody I have to make the cloned object (shown to the player, it represents other players) a static rigidbody, which then teleports to the other players position. The game would have the capability to have many players at once. The issue with the box (which no longer works for whatever reason with what I had written so I removed it) is that it is also dynamic, so when the player runs into it, it bounces away from the player with the force it is hit with. Iā€™m not sure how I could make it do what I need.

I do have an idea, and the best way it can be described is the way that Roblox handles unanchored objects. Since Roblox has a client-side and a server-side, by default any part that is in the workspace that is published is server-side, and if it is unanchored a player can push it, which instead of being pushed only for that player, its position is always server-side, broadcasting it to all players. When I tried something like this, it wouldnā€™t update properly, and the box would be pushed by one player, moving it to a new position, but due to the other players box being at its native coordinates, it would struggle to determine which of the two positions it would set to, since it would be receiving both positions at once and not giving a dominance to the one of who touched it.

Is there a way you can distinguish between the two events? The way I normally handle this in Photon is by sending an ā€˜event codeā€™ along with the actual event data, so I know which event I need to give preference to. Perhaps try implementing something similar server side?

Iā€™m not sure if there is a way I could. All I know is I had a function in the update portion of the network.js script that would send the position and euler angles of the box, then the box would get teleported to that position with the rotation given. But since the network.js script would be on multiple clients it would send more than one, and the server wouldnā€™t know which one to use.

Along with the position and euler angles of the box, see if you can send a simple string or integer that contains a unique event code, with which you can check against.

How would that work or change it?

Is there some way I could just host the entity on the socket server?

How are you transmitting the position and euler angles now? Just send a string or an integer across with the Vec3 the same way.

I just send the individual values.

Along with the individual values which you mention, send one more value that contains the event code, and you should be done.

Sorry to keep this going on but hopefully this request should help with ending this. Could you give me a demonstration of how I would implement this into my code? These are the scripts:

network.js

var Network = pc.createScript('network');

// static variables
Network.id = null;
Network.socket = null;

// initialize code called once per entity
Network.prototype.initialize = function() {
    this.player = this.app.root.findByName('Player');
    this.other = this.app.root.findByName('Other');
    this.object = this.app.root.findByName("Pushable");
    this.normalVel = new pc.Vec3(0,0,0);
    //var socket = io.connect('https://eager-puzzling-steed.glitch.me'); // Glitch hosted server
    var socket = io.connect('https://free-auspicious-lentil.glitch.me'); // Glitch hosted server
    Network.socket = socket;
    
    socket.emit ('initialize');
    

    var self = this;
    socket.on ('playerData', function (data) {
        self.initializePlayers (data);
    });

    socket.on ('playerJoined', function (data) {
        self.addPlayer(data);
    });

    socket.on ('playerMoved', function (data) {
        self.movePlayer(data);
    });

    socket.on ('killPlayer', function (data) {
        self.removePlayer(data);
    });
};

Network.prototype.initializePlayers = function (data) {
    this.players = data.players;
    Network.id = data.id;

    for(var id in this.players){
        if(id != Network.id){
            this.players[id].entity = this.createPlayerEntity(this.players[id]);
        }
    }

    this.initialized = true;
    console.log('initialized');
};

Network.prototype.addPlayer = function (data) {
    this.players[data.id] = data;
    this.players[data.id].entity = this.createPlayerEntity(data);
};

Network.prototype.movePlayer = function (data) {
    if (this.initialized && !this.players[data.id].deleted) {
        var position = new pc.Vec3(data.x, data.y, data.z);
        this.players[data.id].entity.rigidbody.teleport(position);
    }
};

Network.prototype.removePlayer = function (data) {
    if (this.players[data].entity) {
        this.players[data].entity.destroy ();
        this.players[data].deleted = true;
    }
};

Network.prototype.createPlayerEntity = function (data) {
    var newPlayer = this.other.clone();
    newPlayer.enabled = true;

    this.other.getParent().addChild(newPlayer);

    if (data)
        newPlayer.rigidbody.teleport(data.x, data.y, data.z);

    return newPlayer;
};

// update code called every frame
Network.prototype.update = function(dt) {
    this.updatePosition();
};

Network.prototype.updatePosition = function () {
    if (this.initialized) {    
        var pos = this.player.getPosition();
        var bpos = this.object.getPosition();
        var brot = this.object.getEulerAngles();
        Network.socket.emit('positionUpdate', {id: Network.id, x: pos.x, y: pos.y, z: pos.z});
    }
};

server.js (The script on glitch.me)

var server = require('http').createServer();
var options = {
 cors: true
}

var io = require('socket.io')(server, options);
var players = {};
var boxes = {};
var bx = 0;
var by = 1;
var bz = 7;
var brx = 0;
var bry = 0;
var brz = 0;
var bvx = 0;
var bvy = 0;
var bvz = 0;

function Player (id) {
   this.id = id;
   this.code = "";
   this.x = 0;
   this.y = 0;
   this.z = 0;
   this.rx = 0;
   this.ry = 0;
   this.rz = 0;
   this.entity = null;
}

io.sockets.on('connection', function(socket) {
   socket.on ('initialize', function () {
       var id = socket.id;
       var newPlayer = new Player (id);
       // Creates a new player object with a unique ID number.

       players[id] = newPlayer;
       // Adds the newly created player to the array.

       socket.emit ('playerData', {id: id, players: players});
       // Sends the connecting client his unique ID, and data about the other players already connected.

       socket.broadcast.emit ('playerJoined', newPlayer);
       // Sends everyone except the connecting player data about the new player.
     
       socket.on ('initialize', function () {
           var id = socket.id;
           var newPlayer = new Player (id);
           players[id] = newPlayer;

           socket.emit ('playerData', {id: id, players: players});
           socket.broadcast.emit ('playerJoined', newPlayer);
       });

       socket.on ('positionUpdate', function (data) {
               if(!players[data.id]) return;
               players[data.id].x = data.x;
               players[data.id].y = data.y;
               players[data.id].z = data.z;
               players[data.id].bpos = data.bpos;
               players[data.id].brot = data.brot;
               

           socket.broadcast.emit ('playerMoved', data);
       });
     

       socket.on('disconnect',function(){
           if(!players[socket.id]) return;
           delete players[socket.id];
           // Update clients with the new player killed 
           socket.broadcast.emit('killPlayer',socket.id);
       });
   });
});

console.log ('Server started');
server.listen(3000);

I should probably say now that the server.js code has a lot of trash variables because I was experimenting with it and havenā€™t bothered to clean up.

And this is what my entities directories look like, the object that needs to be synced is the circled entity, which contains a rigid body and collision, and itā€™s parented to a render entity.
Screenshot 2022-06-02 8.37.39 AM
If you notice that there is the entity ā€œOtherBoxā€, that is because I had the idea of just not doing server sided boxes at all and instead, just like how the other players are rendered in, rendering in a ā€œghost boxā€ where the other players cube is, but it didnā€™t work out because I had no clue what I was doing.

I would be happy with you just showing me how to implement the event code, or you giving some edits that could allow physics syncing, and if you see anything that could be changed to make the code run faster please let me know.

Bump.