Networked multiplayer confusion , please help - Unity game HLAPI - was working now not

by Super MegaBroBro   Last Updated June 20, 2018 12:13 PM

Hi guys I am quite new to writing any networking code for my games and I've been posting questions related to this topic already, however now my code has changed. I've followed through the Unity tutorial a couple of times over and got the basics working. I've also been studying the Unity docs on the HLAPI and Low Level as much as I can follow.

But I have this game I'm trying to make that is Football (Soccer) 11v11 players so you can only control the one player each.

Annoyingly at some point in time, I believe i had the animations all working perfectly (but at that time I wasnt able to spawn a ball on the server and have it seen on all clients, which I seem to have sorted now), now since I have been changing the code to get the ball working etc I seem to have slightly broken the animations code.

The wierd part is that the IDLE and JOG animations will work fully as expected on Host and any remote Clients. But now suddenly, my SLIDE_TACKLE and SHORT_KICK animations are only working for the Host.

I'm completely lost as to what part I changed that could make this happen. I'm really hoping (desparately) that someone here can look at my coding to see what I have done wrong (I know its a lot to ask as its a bloated horrible mess of code to you experienced guys I expect - but it's my last hope here I feel before I scrap the project and start again). Here is my code for all the classes I feel is relevant (nb. I'm 99.9999% sure I have all the correct components attached in the Inspector for each object (such as, Network Identity, Network Transfor, Network Animator, Animator, etc). Ok so here it is:

Code (CSharp):

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

    public class PlayerController : NetworkBehaviour
    {
        Rigidbody rb;
        Animator animator;
        BallControlPoint ballControlPoint;
        [SyncVar] bool isKicked = false;
        [SyncVar] float kickPower = 0f;
        [SyncVar] float kickAngleX = 0f;
        [SyncVar] bool isTurning = false;


        enum AnimState { JOG, DRIBBLE, SHORT_KICK, SLIDE_TACKLE }
        AnimState currentAnimState = AnimState.JOG;

        enum ControlState { DEFAULT, PASS_HELD }
        ControlState currentControlState = ControlState.DEFAULT;
        Collider zoneCollider;

        private void Awake()
        {
            rb = GetComponent<Rigidbody>();
            animator = GetComponent<Animator>();
            zoneCollider = GetComponentInChildren<ZoneOfControl>().gameObject.GetComponent<Collider>();
            ballControlPoint = GetComponentInChildren<BallControlPoint>();
        }

        private void FixedUpdate()
        {
            if (!isLocalPlayer)
            {
                return;
            }

            CmdCheckForButtonPresses();
        }

        private void LateUpdate()
        {
            if (!isLocalPlayer)
            {
                return;
            }

            CmdApplyMovement(Input.GetAxis("Vertical"), Input.GetAxis("Horizontal"), Input.GetAxis("Strafe"), currentAnimState);
        }

        [Command]
        void CmdCheckForButtonPresses()
        {

            // SHORT KICK BUTTON (down, held and up):
            if (Input.GetButtonDown("Fire1"))
            {
                currentControlState = ControlState.PASS_HELD;
                if (kickPower <= 1.0f)
                {
                    kickPower += Time.deltaTime;
                }
                else
                    kickPower = 1.0f;


                float aimX = Const.passAngleSensitivity * Input.GetAxis("Strafe") * Time.deltaTime;
                kickAngleX += aimX;
            }
            if (Input.GetButton("Fire1")) //
            {
                if (kickPower <= 1.0f)
                {
                    kickPower += Time.deltaTime;
                }
                else
                    kickPower = 1.0f;


                float aimX = Const.passAngleSensitivity * Input.GetAxis("Strafe") * Time.deltaTime;
                kickAngleX += aimX;


            }
            if (Input.GetButtonUp("Fire1"))
            {
                currentControlState = ControlState.DEFAULT;

                if (currentAnimState != AnimState.SHORT_KICK)
                {
                    if (currentAnimState == AnimState.DRIBBLE)
                    {
                        isKicked = true;
                        CmdKickTheBall();
                    }

                    CmdChangeAnimState(AnimState.SHORT_KICK);
                    kickPower = 0f;
                    kickAngleX = 0f;
                }

                // DEBUG ONLY::: this will kick the ball even if its not in the dribble zone.
                CmdKickTheBall();

            }

            // SLIDE TACKLE BUTTON (only on key down):
            if (Input.GetButtonDown("Fire2"))
            {
                if (currentAnimState != AnimState.SLIDE_TACKLE)
                {
                    rb.AddForce(rb.transform.forward * Const.tackleForce);
                }

                CmdChangeAnimState(AnimState.SLIDE_TACKLE);
            }
        }

        [Command]
        void CmdChangeAnimState(AnimState desiredAnimState)
        {
            //if (currentAnimState != desiredAnimState) -- decided i didnt need this as will check its not the same before calling this method
            currentAnimState = desiredAnimState;
        }

        // Applies forward (local) movement on Z Axis,
        // and updates the animation to walking or idle if its moving.
        // Also rotates the player if turn keys are pressed
        [Command]
        void CmdApplyMovement(float vertAxis, float horizAxis, float strafeAxis, AnimState animState)
        {
            switch (animState)
            {
                case AnimState.JOG:
                    // get velocity translated into local space from world space:
                    float velX = rb.transform.InverseTransformDirection(rb.velocity).x;
                    float velZ = rb.transform.InverseTransformDirection(rb.velocity).z;

                    // if not already walking max speed, then apply force as per Vertical axis key input:
                    if (velZ < Const.maxWalkSpeed && velZ > -Const.maxWalkSpeedBackwards)
                    {
                        rb.AddForce(vertAxis * rb.transform.forward * Const.walkForce);
                    }

                    // strafe the player when strafe axis is used:
                    if (velX < Const.maxStrafeSpeed && velX > -Const.maxStrafeSpeed)
                        rb.AddForce(strafeAxis * rb.transform.right * Const.strafeForce);

                    // rotate the player as per Horiz axis:
                    rb.AddTorque(0, horizAxis * Const.turnForce, 0);
                    if (horizAxis != 0)
                        isTurning = true;
                    else
                        isTurning = false;

                    // decide which jog animation to play, based on what velocities the player is moving at:
                    if (velZ > Const.animVelBuffer)
                    {
                        float playSpeed = velZ / Const.maxWalkSpeed;
                        animator.speed = playSpeed;

                        if (velX > Const.animVelDiagBuffer)
                        {
                            animator.Play(Const.animDiagForwardRight);
                        }
                        else if (velX < -Const.animVelDiagBuffer)
                        {
                            animator.Play(Const.animDiagForwardLeft);
                        }
                        else
                        {
                            animator.Play(Const.animJogForwards);
                        }
                    }
                    else if (velZ < -Const.animVelBuffer)
                    {
                        float playSpeed = velZ / -Const.maxWalkSpeedBackwards;
                        animator.speed = playSpeed;

                        if (velX > Const.animVelDiagBuffer)
                        {
                            animator.Play(Const.animDiagBackRight);
                        }
                        else if (velX < -Const.animVelDiagBuffer)
                        {
                            animator.Play(Const.animDiagBackLeft);
                        }
                        else
                        {
                            animator.Play(Const.animJogBackwards);
                        }
                    }
                    else if (velX > Const.animVelBuffer && currentControlState != ControlState.PASS_HELD && currentAnimState == AnimState.JOG) // NOTE: The strafe ability is not there when charging up a pass/shot/etc, this is becoz these directions are used to aim the pass
                    {
                        float playSpeed = velX / Const.maxStrafeSpeed;
                        animator.speed = playSpeed;

                        animator.Play(Const.animStrafeLeft);
                    }
                    else if (velX < -Const.animVelBuffer && currentControlState != ControlState.PASS_HELD && currentAnimState == AnimState.JOG) // NOTE: same thing about the strafe
                    {
                        float playSpeed = velX / -Const.maxStrafeSpeed;
                        animator.speed = playSpeed;

                        animator.Play(Const.animStrafeRight);
                    }
                    else // else its idle jog
                    {
                        animator.speed = 1.0f;
                        animator.Play(Const.animIdle);
                    }

                    break;
                case AnimState.DRIBBLE:
                    // TODO: understand why velX and velY, for example, dont have to be declared again in the switch statement, even though it cannot be JOG and DRIBBLE at same time?
                    // get velocity translated into local space from world space:
                    velX = rb.transform.InverseTransformDirection(rb.velocity).x;
                    velZ = rb.transform.InverseTransformDirection(rb.velocity).z;

                    // if not already walking max speed, then apply force as per Vertical axis key input:
                    if (velZ < Const.maxDribbleSpeed && vertAxis > 0)
                    {
                        rb.AddForce(vertAxis * rb.transform.forward * Const.walkForce);
                    }
                    // if velZ is 0, then idle dribble
                    if (velZ > Const.animVelBuffer)
                    {
                        float playSpeed2 = velZ / Const.maxDribbleSpeed;
                        animator.speed = playSpeed2;
                        animator.Play(Const.animDribble);
                    }
                    else
                    {
                        animator.speed = 1.0f;
                        animator.Play(Const.animIdle);
                    }


                    // rotate the player as per Horiz axis:
                    rb.AddTorque(0, horizAxis * Const.turnForce, 0);
                    if (horizAxis != 0)
                        isTurning = true;
                    else
                        isTurning = false;

                    break;
                case AnimState.SHORT_KICK:
                    animator.speed = 1.0f;
                    animator.Play(Const.animKickQuick_RightFoot);
                    CmdFinishNonLoopedAnimation(AnimState.JOG);
                    break;
                case AnimState.SLIDE_TACKLE:
                    // NOTE: The movement force for making a slide tackle is added in the code where the button press is handled.
                    animator.speed = 1.0f;
                    animator.Play(Const.animSlideTackle);
                    CmdFinishNonLoopedAnimation(AnimState.JOG);
                    break;
            }

        }

        [Command]
        void CmdFinishNonLoopedAnimation(AnimState stateAfterAnimEnds)
        {
            if (animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1)
            {
                CmdChangeAnimState(stateAfterAnimEnds);
            }
        }

        //// NOTE: THIS WAS HOW I HANDLED BALL ACTIONS PRIOR TO NETWORKING:::::
        //void CheckBallIntersectsControlZone(Ball ball)
        //{
        //    Collider ballCollider = ball.GetComponent<Collider>();
        //    if (ballCollider.bounds.Intersects(zoneCollider.bounds))
        //    {
        //        if (!isKicked)
        //        {
        //            switch (currentAnimState)
        //            {
        //                case AnimState.JOG:
        //                    currentAnimState = AnimState.DRIBBLE;
        //                    break;
        //                case AnimState.DRIBBLE:
        //                    if (isTurning)
        //                    {
        //                        ball.transform.position = ballControlPoint.transform.position;
        //                    }
        //                    else
        //                    {
        //                        ball.transform.position = ballControlPoint.transform.position;
        //                    }
        //                    break;
        //            }
        //        }
        //    }
        //    else
        //    {
        //        switch (currentAnimState)
        //        {
        //            case AnimState.DRIBBLE:
        //                currentAnimState = AnimState.JOG;
        //                break;
        //        }

        //        isKicked = false;
        //    }
        //}

        [Command]
        void CmdKickTheBall()
        {
            // todo: make this have a direction based on the left stick whilst the kick button is being pressed. Also make the player be able to charge the kick button up.
            Vector3 ballDirection = rb.transform.forward;
            ballDirection.y += 1;
            ballDirection.x += kickAngleX;

            MatchdayManager.instance.CmdKickBall(ballControlPoint.transform.eulerAngles, ballControlPoint.transform.position, ballDirection, kickPower);
        }




    }

that was the Player class, now the MatchdayManager.cs:

Code (CSharp):

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

    public class MatchdayManager : NetworkBehaviour {
        public static MatchdayManager instance;

        public GameObject ballPrefab;
        public Vector3 ballKickOffVector;
        [HideInInspector] public Ball ball;

        public void Awake()
        {
            instance = this;

            DontDestroyOnLoad(this);
        }

        public override void OnStartServer()
        {
            Debug.Log(Time.time + " OnStartServer called");

            CmdSpawnBall();
        }

        [Command]
        void CmdSpawnBall()
        {
            GameObject go = Instantiate(ballPrefab, ballKickOffVector, Quaternion.Euler(new Vector3(0, 0, 0)));
            ball = go.GetComponent<Ball>();
            NetworkServer.Spawn(go);

        }

        [Command]
        public void CmdKickBall(Vector3 controlPointEuler, Vector3 controlPointPos, Vector3 direction, float kickPower)
        {
            ball.transform.eulerAngles = controlPointEuler;
            ball.transform.position = controlPointPos;
            ball.AddDirectedForce(direction, kickPower * Const.maxPassForce);
        }



    }

and finally the Game class (this class is Monobehaviour , the other 2 are NetworkBehaviour):

Code (CSharp):

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class Game : MonoBehaviour {
        public static Game instance;

        public MatchdayManager matchdayManager;

        // Use this for initialization
        void Awake() {
            if (instance == null)
            {
                instance = this;
            }
            else
            {
                return;
            }

            DontDestroyOnLoad(this);
        }

        // THIS WAS ADDED PURELY BECOZ AWAKE ISN'T CALLED ON NETWORKBEHAVIOUR SCRIPTS WHEN GAME IS NOT RUN IN THE EDITOR:::::
        void Update()
        {
            if (!matchdayManager.gameObject.activeInHierarchy)
            {
                matchdayManager.gameObject.SetActive(true);
            }
        }
    }

If I've missed any relevant parts of code, or information about what I used in the Inspector and/or Heirarchy , please let me know and I'll add it immediately. Thanks



Related Questions




Delayed linear interpolation for networking

Updated June 13, 2017 23:13 PM

Creating a master server for Unity game (Unity 2017)

Updated September 08, 2017 15:13 PM