Character movement cloning on multiplayer

Hi, I would like to create a multiplayer game using Colyseus , but I am having a trouble with the players’ movement. When I run the game and both entities’ movement scripts are enabled, they replicate each other’s movement. However, when one movement script is disabled, it works fine (but entity2 does not have movement “because it disabled”). What should I do to ensure each entity has its own movement instead using entity’s 1 movement?

here is the project for the code reference
[Colyseus test | Editor]
(PlayCanvas | HTML5 Game Engine)

Hi @Cyrlon_Ian_Cayabyab and welcome!

Do you use global variables maybe? Your project link is unavailable unfortunately.

Maybe you can compare your setup with the setup of the example project, that you can find on the page below.

Thank you for the reply, I don’t know why the link is unavailable but here is my code to connect it to colyseus.

here is also the video when I turn off the Uranus Controller Thirdperson script for the second entity.

here is also the video when they both turn on Uranus Controller Thirdperson script.

var NetworkManager = pc.createScript('networkManager');

// Define script
NetworkManager.prototype.initialize = async function () {
    this.currentPlayerEntity = null;

    //// Initialize Colyseus client Public
    //this.app.colyseus = new Colyseus.Client("wss://colyseus-multiplayer-playcanvas.glitch.me");
    
    // Initialize Colyseus client Local
    this.app.colyseus = new Colyseus.Client("ws://localhost:2567");

    // Join or create a room
    this.room = await this.app.colyseus.joinOrCreate("VLands_Rooms");

    // Store player entities
    this.playerEntities = {};

    const player1EntityToClone = this.app.root.findByName("Avatar1");
    const player2EntityToClone = this.app.root.findByName("Avatar2");

    if (!player1EntityToClone || !player2EntityToClone) {
        console.error("Error: Avatar1 or Avatar2 not found in the scene.");
        return;
    }

    // Listen for new players joining the room
    this.room.state.players.onAdd((player, sessionId) => {
        console.log("A player has joined! Their unique session id is", sessionId);

        if (this.room.sessionId === sessionId) {
            // Current player
            const entity1 = player1EntityToClone.clone();
            entity1.enabled = true;
            entity1.setPosition(player.x, player.y, player.z);
            player1EntityToClone.parent.addChild(entity1);

            this.currentPlayerEntity = entity1; // Assign the current player entity
            this.playerEntities[sessionId] = entity1;

            console.log("This is session id of player 1 (current player):", sessionId);
        } else {
            // Other players
            const entity2 = player2EntityToClone.clone();
            entity2.enabled = true;
            entity2.setPosition(player.x, player.y, player.z);
            player2EntityToClone.parent.addChild(entity2);

            this.playerEntities[sessionId] = entity2;

            console.log("This is session id of player 2 (other player):", sessionId);

            // Update position when other players move
            player.onChange(() => {
                if (this.playerEntities[sessionId]) {
                    this.playerEntities[sessionId].setPosition(player.x, player.y, player.z);
                }
            });
        }
    });

    // Remove players when they leave
    this.room.state.players.onRemove((player, sessionId) => {
        if (this.playerEntities[sessionId]) {
            this.playerEntities[sessionId].destroy();
            delete this.playerEntities[sessionId];
            console.log("Player removed:", sessionId);
        }
    });
};

// Update code called every frame
NetworkManager.prototype.update = function (dt) {
    if (this.currentPlayerEntity) {
        // Get current player's position
        const position = this.currentPlayerEntity.getPosition();

        // Send the current player's position to the server
        this.room.send("updatePosition", {
            x: position.x,
            y: position.y,
            z: position.z,
        });

        console.log("Position sent to server:", position.x, position.y, position.z);
    } else {
        console.log("Current player entity is not set.");
    }
};

Is your project private maybe?

It’s hard to say what’s wrong with your setup.

The first video works almost correct, only it looks like the wrong entity is moving on the other side?

Colyseus test Main Island | Editor

It is in private, didn’t notice that here is the project.
made it public

the first video is when i turn off the script of the controller for player’s movement. for the 2nd entity.

This could be correct to do, because at some multiplayer controllers every player is using player 1 for the local side and player 2 for the connected players. Unfortunately, I didn’t use Colyseus myself yet, so I’m not sure of that’s also the case here.

1 Like

Thank you for the review and taking time to look at my project much appreciated.

You’re welcome. If you are unable to solve the problem, I can try to debug your project when I’m on my laptop again.

1 Like

I just checked your project on my laptop and it looks like I’m correct here.

Player 1 is used for all the players on the local side. This entity need to have your controller script and camera component. Player 2 should only be a visual entity, so no controller scripts and no camera component.

You probably don’t send the rotation of the players to the server yet, so the rotation is not updated.

Hello, thank you for your response, can i ask for the reference of the rotation you are talking about in the server? this is my first time doing this so i only just know the basics, thank you in advance.

If you check the updatePosition event in your network script, you will see that you sent the x, y and z axis of the player to the server and you use this to set the position of the players. You basically need to do the same for the rotation of the players by extending the current event with the rotation of the player.

Please be aware that if you use dynamic rigidbodies you need to use rigidbody.teleport() instead of setPosition(). Otherwise you only move the visual entity.

hi, again if its ok can i have a sample code of the rotation so i can study it… im currently away so i dont have may pc with me right now.So i can try it when i got in touch with my computer. thank you so much for your responses im learning a lot.

Just tried to adjust the project, but it seems with Colysues you also have a script on the server side somewhere that need to be changed. I don’t think this is in the project itself? Below some parts that need to be changed in your networkManager script.

Line 35:

entity1.rigidbody.teleport(player.px, player.py, player.pz, player.rx, player.ry, player.rz);

Line 46:

entity2.rigidbody.teleport(player.px, player.py, player.pz, player.rx, player.ry, player.rz);

Line 56:

this.playerEntities[sessionId].rigidbody.teleport(player.px, player.py, player.pz, player.rx, player.ry, player.rz);

Line 75-83:

// Get current player's position
const position = this.currentPlayerEntity.getPosition();
const rotation = this.currentPlayerEntity.getEulerAngles();

// Send the current player's position to the server
this.room.send("updatePosition", {
    px: position.x,
    py: position.y,
    pz: position.z,
    rx: rotation.x,
    ry: rotation.y,
    rz: rotation.z,
});

Please note that I have not been able to test this as I do not have access to the server script.

1 Like

By the way, I use rigidbody.telport() instead of setPosition() and setEulerAngles(), because I don’t know what kind of rigidbodies you use. You probably at those components by script somewhere. If you don’t use rigidbodies or the rigidbodies are kinematic, you can keep using setPosition() and just add setEulerAngles() for the rotations.

Thank you so much… will double check it and test it. Will also update you sir. You help me so much… so I appreciate it thank you again…

hi, it’s me again. I would you to check my server code if its correct, because it seems the server does not update the character rotate. I also use dynamic in my rigidbody but it seems only the idle animation is working.

* Code in my server side *
    //Myroom.js
    import { Room } from "@colyseus/core";
    import { MyRoomState } from "./schema/MyRoomState.js";
    import { Player } from "./schema/MyRoomState.js";
    export class MyRoom extends Room {
    maxClients = 5;

    onCreate(options) {
    this.setState(new MyRoomState());

    // Handle the "updatePosition" message
    this.onMessage("updatePosition", (client, data) => {
        const player = this.state.players.get(client.sessionId);
        if (player) {
            // Update position
            player.px = data.px;
            player.py = data.py;
            player.pz = data.pz;

            // Update rotation
            player.rx = data.rx;
            player.ry = data.ry;
            player.rz = data.rz;
            console.log("Player rotation Y:", player.ry);
        }
    });
}

onJoin(client, options) {
    console.log(client.sessionId, "joined!");
    // Create Player instance
    const player = new Player();

    // Place Player at a random position
    const FLOOR_SIZE = 4;
    player.x = -(FLOOR_SIZE / 2) + Math.random() * FLOOR_SIZE;
    player.y = 1.031;
    player.z = -(FLOOR_SIZE / 2) + Math.random() * FLOOR_SIZE;

    // Initialize rotation for the player
    player.rx = 0; // Default rotation around the X-axis
    player.ry = Math.random() * 360; // Random initial rotation around the Y-axis
    player.rz = 0; // Default rotation around the Z-axis

    // Place player in the map of players by its sessionId
    // (client.sessionId is unique per connection!)
    this.state.players.set(client.sessionId, player);

}

onLeave(client, consented) {
    console.log(client.sessionId, "left!");

    // Remove the player from the state
    this.state.players.delete(client.sessionId);
}

onDispose() {
    console.log("room", this.roomId, "disposing...");
}

}

// MyRoomState.js
import { MapSchema, Schema, type } from "@colyseus/schema";

export class Player extends Schema {
constructor() {
    super();
    // position
    this.px = 0;
    this.py = 0;
    this.pz = 0;
    // rotation
    this.rx = 0;
    this.ry = 0;
    this.rz = 0;
  }
}

// Define position properties
type("number")(Player.prototype, "px");
type("number")(Player.prototype, "py");
type("number")(Player.prototype, "pz");

// Define rotation properties
type("number")(Player.prototype, "rx");
type("number")(Player.prototype, "ry");
type("number")(Player.prototype, "rz");

export class MyRoomState extends Schema {
    constructor() {
    super();
    this.players = new MapSchema();
 }
}

type({ map: Player })(MyRoomState.prototype, "players");

and I also applied your changes to mine.

At least line 37-39 of your first code block above is incorrect.

What should I replace it? should I make it default to 0? and even when I update the rotation on the server, it doesn’t seem to apply. Regarding the animation, only the ‘idle animation’ is playing for other entities. Do you have any references I can study? Thank you in advance.

You use the old x, y and z here, instead of the new px, py and pz.