Animations and Multiplayer - How to pass and receive?

Hi there everyone, is there any example (project, live or source) how to pass and receive animation data to and from Socket.io?
Rotations / movements are working fine finally,

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');

     
    var socket = io.connect('https://soft-mixed-joke.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) {
        this.players[data.id].entity.rigidbody.teleport(data.x, data.y, data.z, data.rx, data.ry, data.rz);
    }
};

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 rot = this.player.getEulerAngles();
        Network.socket.emit('positionUpdate', {id: Network.id, x: pos.x, y: pos.y, z: pos.z, rx: rot.x, ry: rot.y - 180, rz: rot.z});
    }
};

And the other part

var PlayerMovement = pc.createScript('playerMovement');

PlayerMovement.attributes.add('speed', { type: 'number', default: 0.09 });

PlayerMovement.prototype.initialize = function () {
    var app = this.app;
    var camera = app.root.findByName('Camera');
    this.cameraScript = camera.script.cameraMovement;    
};




// Temp variable to avoid garbarge colleciton
PlayerMovement.worldDirection = new pc.Vec3();
PlayerMovement.tempDirection = new pc.Vec3();

PlayerMovement.prototype.update = function (dt) {
    var app = this.app;
    var worldDirection = PlayerMovement.worldDirection;
    worldDirection.set(0, 0, 0);
    
    var tempDirection = PlayerMovement.tempDirection;
    
    var forward = this.entity.forward;
    var right = this.entity.right;

    var x = 0;
    var z = 0; 
    
    if (app.keyboard.isPressed(pc.KEY_A)) {
        x -= 1;
    }

    if (app.keyboard.isPressed(pc.KEY_D)) {
        x += 1;
    }

    if (app.keyboard.isPressed(pc.KEY_W)) {
        z += 1;
    }

    if (app.keyboard.isPressed(pc.KEY_S)) {
        z -= 1;
    }

    if (x !== 0 || z !== 0) {
        worldDirection.add(tempDirection.copy(forward).mulScalar(z));
        worldDirection.add(tempDirection.copy(right).mulScalar(x));        
        worldDirection.normalize();
        
        var pos = new pc.Vec3(worldDirection.x * dt, 0, worldDirection.z * dt);
        pos.normalize().scale(this.speed);
        pos.add(this.entity.getPosition());

        var targetY = this.cameraScript.eulers.x + 180;
        var rot = new pc.Vec3(0, targetY, 0);

        this.entity.rigidbody.teleport(pos, rot);
    }
    
    this.entity.anim.setFloat('xDirection', x);
    this.entity.anim.setFloat('zDirection', z);
};

I don’t want you guys to finish it, want to learn, love to learn from examples. can’t find those unfortunately, thanks!

Hi @Newbie_Coder and welcome,

Since you are already sending an object with position/rotation, it’s trivial to add more properties to that.

For example you have this line, which creates an objects to send data to the server. You can add more properties to it:

Network.socket.emit('positionUpdate', {
    id: Network.id,
    x: pos.x,
    y: pos.y,
    z: pos.z,
    rx: rot.x,
    ry: rot.y - 180,
    rz: rot.z,
    score: 10
});

On the receiving side, you can ready that data coming from the server like this:

const playerScore = data.score;

In a similar manner you can add animation states to that object, which you will have to read on each client and act on them.

Just note, in many cases to save network bandwidth it’s quite common to find the right animation state based on movement. So if the player is moving forward, his client should understand that e.g. the animation forward state should be played.

That way you don’t have to network animation states, at least not all of them. You only network actions e.g. the player has jumped. And each client knows what to do with that since they are all playing the same game.

Thanks for your reply, I thought I’d apply basic walking animation on player movement just to see how it would look but got no luck either, graph state animations, I feel like the start/end preventing it from playing or Am I wrong?

Network.prototype.movePlayer = function (data) {
    if (this.initialized && !this.players[data.id].deleted) {
        this.players[data.id].entity.rigidbody.teleport(data.x, data.y, data.z, data.rx, data.ry, data.rz);
       this.players[data.id].entity.anim.setTrigger("Forward"); // ?
                
    }

Fiksavimas

Hi @Newbie_Coder! Does this work for the local player?

Hey, no seems like these 2 lines does all the job

this.entity.anim.setFloat('xDirection', x);
    this.entity.anim.setFloat('zDirection', z);
};

Tried to bind it on a new key and trigger anim like so

    if (app.keyboard.isPressed(pc.KEY_R)) {
     
       this.entity.anim.setFloat('xDirection', x);     
       this.entity.anim.setTrigger('xDirection');
    }

No luck either, filename for walking is Jog Forward.fbx

I’m not sure what you try to achieve. I see you use setTrigger while the parameter seems to be a float. Did you make the Anim State Graph yourself or are you trying to modify an example?

If you try to change the animation whit a key, you have to make sure the x on your setFloat line is the correct value and not undefined.

I’m trying to achieve walking animation for remote players ‘Other’ players of the server, but before that, trying to ignite the animation with the key just to see how it works

The walking anims for the WASD keys works as it should, and yes I’m trying to modify the basic 3rd player character movement example

What are all the ways that could play animation?

Alright, in that case you need to check which value is needed to activate the desired animation. Then you can send that information to the network. Something like below.

this.players[data.id].entity.anim.setFloat('xDirection', -1);
1 Like

Yes, this does fire an animation, been trying to detect position / movement of an enitity, velocity gives me false readings, even when the player is stopped it gives me readings jumping from 0.XXX to X.XXX
maybe possible with possition? what if we compare position from frame 1 and frame 2? is it possible?

Ideal example would be to detect movements on x z and y axis so I could set up different anims

Isn’t enough to sent the active animation to the netwerk and apply this animation? What are you trying to solve?

Since position is sent and received in every frame so does the animation, constantly plays, need a way to detect movements on x y z rather than ‘position’ in general, compare between frames? Hmm

What is the problem? Is the animation (of the other player) not working correctly?

It does work but I need a way to stop the animation when the player stops moving (as it is updated every frame), getVelocity won’t work in my case due to mixed readings, any other ideas?

Are you talking about the local player or the network player? I mean every local player knows what happens, so every local player has to sent that information to the network.

I’m getting it to work as it should, at least the basic version
What’s the proper way to syntax ‘more or less than’ ?

if (aa.lengthSq() > 0 and less than?){

if (aa.lengthSq() > 0 || aa.lengthSq() < 0) {

}

Okay the animations are working fine in this basic example

// calculate velocity
    const position = this.players[data.id].entity.getPosition();
    aa = this.velocity.sub2(position, this.previousPosition);
    
    // store position as previous
    bb = this.previousPosition.copy(position);
    console.log(aa.lengthSq());  
     console.log(this.players[data.id]);  



      if (aa.lengthSq() > 0.0000001){
   this.players[data.id].entity.anim.setFloat('zDirection', 1);
      } else {
      this.players[data.id].entity.anim.setFloat('zDirection', 0);      
      }

One last issue happens when there are more than 2 players online, it sets the same animation for all the rest of the players

And I think it happens since I’m selecting all ‘Other’ players to apply the animation based on their velocity like this.players[data.id].entity
And it has to be applied to each different player, so my question is, since every player has its own ‘ID’ in data as can be seen here

  Network.socket.emit('positionUpdate', {id: Network.id, x: pos.x, y: pos.y, z: pos.z, rx: rot.x, ry: rot.y, rz: rot.z});

How do I calcute velocity and apply animation for each entity (player) by ID

I understand that this.players[data.id].entity has to be like Get entity by id but don’t understand how to apply, thanks for your patience

If I had to do what I think you are trying to do, I would do something like below. Most likely not written appropriately, but just to give you an idea.

Network.prototype.updatePosition = function () {
    if (this.initialized) {    
        var pos = this.player.getPosition();
        var anim = this.player.anim.activeState;
        Network.socket.emit('positionUpdate', {id: Network.id, x: pos.x, y: pos.y, z: pos.z, a: anim});
    }
};
Network.prototype.movePlayer = function (data) {
    if (this.initialized && !this.players[data.id].deleted) {
        this.players[data.id].entity.rigidbody.teleport(data.x, data.y, data.z);
        this.players[data.id].entity.anim.play(data.a);
    }
};

Okay, I’m able to send and read movements
var anim = this.player.anim.baseLayer.activeState;

Now the problem is setting the animation to play, whenever I add a new parameter and condition for example on Forward all Forward animations stops, even for local, tried with Trigger, Boolean, Float, Integer same result…

Fiksavimas
(Forward)

Update:

Found a solution to make it work on pre-setted conditions and parameters

var anim = this.player.anim.baseLayer.activeState;

        var anim2 = 0;

        if (anim == 'Forward') {

            anim2 = 1;

        }

this.players[data.id].entity.anim.setFloat('zDirection', data.a);

Will post a new reply if new questions will come, thanks for the help and see you soon!

Okay, got it working on all animations, what I did = Duplicated orginal player anim graph and added triggers on each animation (removed orginal ones), and applied to Other player, my question is, how bad is it for server? And let’s say when there are like 10 player in one field of view? FPS, latency? What’s the best practice? Disable anims like ‘idle’, how to optimize for best performance? Thanks

And, is it normal to have a little glitch before starting of other player animations?