Unity Moving Player Leg Multiplayer

2019-07-24 16:00发布

问题:

I am currently working on a game and I have the following situation:

I have a Player prefab gameobject with a script attached to it (see below). I have set up Network Manager and I have set up an account under "Services" to be able to use the multiplayer aspect.

I have the basics set up so that the player does spawn and multiplayer does work. The player is able to move and I see the movement on the other players on each build session.

I have a chunk of code that when the player is "walking" (if either keys A, W, S, or D is pressed, I call "CmdWalk()".

Basically CmdWalk() makes it so that it changes my player's legs rotation so that it makes it seem like it is walking. (I am not into animation so this is the only way I know).

The issue is that only the local player is able to see their player "walk", the other players online does not see the movement. I am not sure what I have done wrong, can someone please help.

Below is the entire script I have for the player:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class PlayerController : NetworkBehaviour
{
    public float speedH = 2.0f;
    private float yaw = 0.0f;

    public float WalkingTime; //timer for walking animation
    public GameObject PlayerLeftLeg;
    public GameObject PlayerRightLeg;

    private float PlayerStatMenuTimer;
    public GameObject PlayerStatsMenu;

    // Update is called once per frame
    void Update () 
    {

        if (!isLocalPlayer)
        {
            return;
        }

        //keep track of time for player stat menu
        //if not here than menua will show and hide like a thousand times when pressed once due to update reading code per frame
        PlayerStatMenuTimer = PlayerStatMenuTimer + 1 * Time.deltaTime;

        //moving player left right forward backward
        var x = Input.GetAxis ("Horizontal") * Time.deltaTime * 50.0f;
        var z = Input.GetAxis ("Vertical") * Time.deltaTime * 50.0f;
        transform.Translate (x, 0, z);

        //rotating player or "Looking"
        yaw += speedH * Input.GetAxis ("Mouse X");
        transform.eulerAngles = new Vector3 (0.0f, yaw, 0.0f);

        //if player is using WASD to move then do leg moving animation
        //if not moving then set legs to be still and reset in standing position
        //FYI:  "transform.TransformVector(1,0,0)" was used instead of "Vector3.forward" was because
        //   vector3.forward is local space, so when i rotate player the sense of "forward" also changes, thus i needed
        //  a code that uses the world space, thus i used "transform.TransformVector(1,0,0)"
        if (Input.GetKey (KeyCode.W) || Input.GetKey (KeyCode.S) || Input.GetKey (KeyCode.A) || Input.GetKey (KeyCode.D))
        {
            CmdWalk ();
        }
        else
        {
            //if player not walking then reset
            PlayerRightLeg.transform.rotation = Quaternion.AngleAxis (0, Vector3.forward);
            PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis (0, Vector3.forward);
            WalkingTime = 0;
        }

        //get hidden mouse pointer back and unlock
        if (Input.GetKey (KeyCode.Escape))
        {
            Cursor.lockState = CursorLockMode.None;
        }

        //opens and closes stat menu
        if (Input.GetKey (KeyCode.Return) && (PlayerStatMenuTimer >= 1) && (PlayerStatsMenu.activeSelf == false)) 
        {
            Cursor.lockState = CursorLockMode.None;
            PlayerStatsMenu.SetActive (true);
            PlayerStatMenuTimer = 0;

            //call the script "GetplayerStats" and call function "retrieceplayerstats"
            var GetStats = GetComponent<GetPlayerStats> ();
            GetStats.RetrievePlayerStats ();

        }
        else if (Input.GetKey (KeyCode.Return) && PlayerStatMenuTimer >= 1 && PlayerStatsMenu == true) 
        {
            Cursor.lockState = CursorLockMode.Locked;
            PlayerStatsMenu.SetActive (false);
            PlayerStatMenuTimer = 0;
        }
    }

    private void Awake ()
    {
        //this code locks mouse onto center of window
        //Screen.lockCursor = true;
        Cursor.lockState = CursorLockMode.Locked;
    }

    [Command]
    void CmdWalk ()
    {
        //timer
        WalkingTime += Time.deltaTime;

        //right leg stepping forward
        if (WalkingTime > 0 && WalkingTime < .4)
        {
            PlayerRightLeg.transform.rotation = Quaternion.AngleAxis (PlayerRightLeg.transform.rotation.x - (60 * WalkingTime), transform.TransformVector (1, 0, 0));
            PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis (PlayerLeftLeg.transform.rotation.x + (60 * WalkingTime), transform.TransformVector (1, 0, 0));
        }

        //left leg stepping forward
        if (WalkingTime >.4 && WalkingTime < 1.2)
        {
            PlayerRightLeg.transform.rotation = Quaternion.AngleAxis (PlayerRightLeg.transform.rotation.x + (60 * (WalkingTime - .8f)), transform.TransformVector (1, 0, 0));
            PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis (PlayerLeftLeg.transform.rotation.x - (60 * (WalkingTime - .8f)), transform.TransformVector (1, 0, 0));
        }

        //right leg stepping forward
        if (WalkingTime > 1.2 && WalkingTime < 1.59)
        {
            PlayerRightLeg.transform.rotation = Quaternion.AngleAxis (PlayerRightLeg.transform.rotation.x - (60 * (WalkingTime - 1.6f)), transform.TransformVector (1, 0, 0));
            PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis (PlayerLeftLeg.transform.rotation.x + (60 * (WalkingTime - 1.6f)), transform.TransformVector (1, 0, 0));
        }

        //resetting
        if (WalkingTime > 1.6)
        {
            PlayerRightLeg.transform.rotation = Quaternion.AngleAxis (0, Vector3.forward);
            PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis (0, Vector3.forward);
            WalkingTime = 0;
        }
    }
}

Sorry for the amount of code but the only parts that needs to be looked at is the "IF statement" for keys "A" "W" "S" "D" and the "Void CmdWalk()"

Thanks.

回答1:

It surprises me that the local Player is seeing the movement. I'ld say only the Host / Server Player can see it since

[Command]
void CmdWalk()
{
    //timer
    WalkingTime += Time.deltaTime;

    //right leg stepping forward
    if (WalkingTime > 0 && WalkingTime < .4)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * WalkingTime), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * WalkingTime), transform.TransformVector(1, 0, 0));
    }

    //left leg stepping forward
    if (WalkingTime > .4 && WalkingTime < 1.2)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x + (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x - (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
    }

    //right leg stepping forward
    if (WalkingTime > 1.2 && WalkingTime < 1.59)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
    }

    //resetting
    if (WalkingTime > 1.6)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        WalkingTime = 0;
    }
}

is only executed on the server. So actually it should also be possible already for the Host to see also the movements of other Clients.

However to pass the call back to all clients you should rather add a ClientRpc

[Command]
void CmdWalk()
{
    RpcWalk();
}

[ClientRpc]
void RpcWalk()
{
    //timer
    WalkingTime += Time.deltaTime;

    // also it is slightly more efficient to use if-else 
    // to avoid unneccesary checks since 
    // only one of those conditions can be true at a time

    //right leg stepping forward
    if (WalkingTime > 0 && WalkingTime < .4)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * WalkingTime), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * WalkingTime), transform.TransformVector(1, 0, 0));
    }

    //left leg stepping forward
    else if (WalkingTime > .4 && WalkingTime < 1.2)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x + (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x - (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
    }

    //right leg stepping forward
    else if (WalkingTime > 1.2 && WalkingTime < 1.59)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
    }

    //resetting
    else if (WalkingTime > 1.6)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        WalkingTime = 0;
    }
}

Update

The problem in general with this approach is still the network lag. The user (if not the Host) won't see the result of his own movements until the call was sent to the server and than back to himself. So I would additionally do the movement already on the local player and skip for him the Rpc call:

void Walk()
{
    //timer
    WalkingTime += Time.deltaTime;

    //right leg stepping forward
    if (WalkingTime > 0 && WalkingTime < .4)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * WalkingTime), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * WalkingTime), transform.TransformVector(1, 0, 0));
    }

    //left leg stepping forward
    else if (WalkingTime > .4 && WalkingTime < 1.2)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x + (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x - (60 * (WalkingTime - .8f)), transform.TransformVector(1, 0, 0));
    }

    //right leg stepping forward
    else if (WalkingTime > 1.2 && WalkingTime < 1.59)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(PlayerRightLeg.transform.rotation.x - (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(PlayerLeftLeg.transform.rotation.x + (60 * (WalkingTime - 1.6f)), transform.TransformVector(1, 0, 0));
    }

    //resetting
    else if (WalkingTime > 1.6)
    {
        PlayerRightLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        PlayerLeftLeg.transform.rotation = Quaternion.AngleAxis(0, Vector3.forward);
        WalkingTime = 0;
    }

    if(!isLocalPlayer) return;

    // if executed by the local Player invoke the call on the server
    CmdWalk(gameObject);
}

// passing the GameObject reference over network works
// since the player GameObject has a unique identity on all instances
// namely the NetworkIdentity
[Command]
void CmdWalk(GameObject caller)
{
    Walk();
    RpcWalk(caller);
}

[ClientRpc]
void RpcWalk(GameObject caller)
{
    // skip if server since already done it in CmdWalk
    if(isServer) return;

    // skip if this is caller since already done locally
    if(caller == gameObject) return;

    Walk();
}

and in Update use

if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
{
    Walk();
}

instead