Multiplayer using Babylon.js and Colyseus

Hello, I’m exploring various things nowadays regarding multiplayer using babylon.js and colyseus. So, I came across:

  1. Real-time Multiplayer with Colyseus.
  2. Character animation with input keys like(WASD) while using .gtlf/ .glb model.

Which looks very promising. I tried and integrate both things together to achieve multiplayer character animation. In the current scenario, I am able to get a new character on joining the room and on leaving the room it’s deleting the character. But, some issues that I’m facing are:

  1. Player is not moving perfectly when two clients are connected.
  2. On pressing WASD they are colliding with each other not moving.

    room.state.players.onAdd = async (player, sessionId) => {
      // Local entity map
      var inputMap = {};

      // This sceneloader is for loading gltf models it constructor takes : location, name, scene
      scene.actionManager = new BABYLON.ActionManager(scene);
      scene.actionManager.registerAction(
        new BABYLON.ExecuteCodeAction(
          BABYLON.ActionManager.OnKeyDownTrigger,
          function (evt) {
            inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
          }
        )
      );
      scene.actionManager.registerAction(
        new BABYLON.ExecuteCodeAction(
          BABYLON.ActionManager.OnKeyUpTrigger,
          function (evt) {
            inputMap[evt.sourceEvent.key] = evt.sourceEvent.type == "keydown";
          }
        )
      );

      //Adding character
      playerEntities[sessionId] = await BABYLON.SceneLoader.ImportMeshAsync(
        null,
        "./assets/",
        "orange.glb",
        scene
      );

      //   playerEntities[sessionId] = character;
      var hero = playerEntities[sessionId].meshes[0];
      hero.scaling.scaleInPlace(1);

      // Lock the camera on character
      camera1.target = hero;

      var heroSpeed = 0.04;
      var heroSpeedBackwards = 0.02;
      var heroRotationSpeed = 0.1;
      var animating = true;

      const walkAnim = scene.getAnimationGroupByName("Walk");
      const raiseHandAnim = scene.getAnimationGroupByName("Raise");
      const idleAnim = scene.getAnimationGroupByName("Idle");
      const waveHandAnim = scene.getAnimationGroupByName("Raise");
      const sitAnim = scene.getAnimationGroupByName("Sit");
      var isCurrentPlayer = sessionId === room.sessionId;

      scene.onBeforeRenderObservable.add(() => {
        var keyDown = false;

        if (inputMap["w"]) {
          hero.moveWithCollisions(hero.forward.scaleInPlace(heroSpeed));

          // Send position update to the server
          room.send("updatePosition", {
            x: hero.position.x,
            y: hero.position.y,
            z: hero.position.z,
            rx: hero.rotation.x,
            ry: hero.rotation.y,
            rz: hero.rotation.z,
          });

          keyDown = true;
        }
        if (inputMap["s"]) {
          hero.moveWithCollisions(
            hero.forward.scaleInPlace(-heroSpeedBackwards)
          );
          // Send position update to the server
          room.send("updatePosition", {
            x: hero.position.x,
            y: hero.position.y,
            z: hero.position.z,
            rx: hero.rotation.x,
            ry: hero.rotation.y,
            rz: hero.rotation.z,
          });

          keyDown = true;
        }
        if (inputMap["a"]) {
          hero.rotate(BABYLON.Vector3.Up(), -heroRotationSpeed);
          // Send position update to the server
          room.send("updatePosition", {
            x: hero.position.x,
            y: hero.position.y,
            z: hero.position.z,
            rx: hero.rotation.x,
            ry: hero.rotation.y,
            rz: hero.rotation.z,
          });

          keyDown = true;
        }
        if (inputMap["d"]) {
          hero.rotate(BABYLON.Vector3.Up(), heroRotationSpeed);
          // Send position update to the server
          room.send("updatePosition", {
            x: hero.position.x,
            y: hero.position.y,
            z: hero.position.z,
            rx: hero.rotation.x,
            ry: hero.rotation.y,
            rz: hero.rotation.z,
          });

          keyDown = true;
        }
        if (inputMap["r"]) {
          keyDown = true;
        }
        if (inputMap["z"]) {
          keyDown = true;
        }
        if (inputMap["h"]) {
          keyDown = true;
        }

        if (keyDown) {
          if (!animating) {
            animating = true;
            if (inputMap["s"]) {
              walkAnim.start(true, 1.0, walkAnim.from, walkAnim.to, false);
            } else if (inputMap["r"]) {
              raiseHandAnim.start(
                true,
                1.0,
                raiseHandAnim.from,
                raiseHandAnim.to,
                false
              );
            } else if (inputMap["h"]) {
              waveHandAnim.start(
                true,
                1.0,
                waveHandAnim.from,
                waveHandAnim.to,
                false
              );
            } else if (inputMap["z"]) {
              sitAnim.start(true, 1.0, sitAnim.from, sitAnim.to, false);
            }
          } else {
            walkAnim.start(true, 1.0, walkAnim.from, walkAnim.to, false);
            // Send position update to the server
            room.send("updatePosition", {
              x: hero.position.x,
              y: hero.position.y,
              z: hero.position.z,
              rx: hero.rotation.x,
              ry: hero.rotation.y,
              rz: hero.rotation.z,
            });

            keyDown = true;
          }
        } else {
          if (animating) {
            //Default animation is idle when no key is down
            idleAnim.start(true, 1.0, idleAnim.from, idleAnim.to, false);

            //Stop all animations besides Idle Anim when no key is down

            walkAnim.stop();
            sitAnim.stop();
            raiseHandAnim.stop();
            waveHandAnim.stop();

            //Ensure animation are played only once per rendering loop
            animating = false;
          }
        }
      });
      playerNextPosition[sessionId] = hero.position.clone();

      player.onChange = () => {
        playerNextPosition[sessionId].set(player.x, player.y, player.z);
        hero.position.set(player.x, player.y, player.z);
        hero.rotation.set(player.rx, player.ry, player.rz);
      };

      scene.registerBeforeRender(() => {
        for (let sessionId in playerEntities) {
          var targetPosition = playerNextPosition[sessionId];

          hero.position = BABYLON.Vector3.Lerp(
            hero.position,
            targetPosition,
            0.05
          );
        }
      });
    };

This is the code I’ve made. Please help me out to get proper positioning and WASD controls.

I would recommend to have a look at GitHub - BabylonJSGames/BabylonJS-Platformer-Game-Prototype: A 3d platformer browser game prototype made with the BabylonJS framework written in TypeScript.

There is already a nice Colyseus schema and correct avatar’s movements.

Online version - https://kzclimbing.netlify.app/

Thanks, I’ll look into this.

This is a little bit hard to understand for me as I’m new to typescript but that’s ok I’m trying to crack this up. But, if you get any knowledge about my code then help me with this also :slight_smile: .