Example #1
0
    // CharacterController movement is physics based and requires FixedUpdate.
    // (using Update causes strange movement speeds in builds otherwise)
    void FixedUpdate()
    {
        // only control movement for local player
        if (isLocalPlayer)
        {
            // get input and desired direction based on camera and ground
            Vector2 inputDir   = player.IsMovementAllowed() ? GetInputDirection() : Vector2.zero;
            Vector3 desiredDir = GetDesiredDirection(inputDir);
            Debug.DrawLine(transform.position, transform.position + desiredDir, Color.cyan);

            // update state machine
            if (state == MoveState.IDLE)
            {
                state = UpdateIDLE(inputDir, desiredDir);
            }
            else if (state == MoveState.WALKING)
            {
                state = UpdateWALKING(inputDir, desiredDir);
            }
            else
            {
                Debug.LogError("Unhandled Movement State: " + state);
            }

            // move depending on latest moveDir changes
            //Debug.Log(name + " step speed=" + moveDir.magnitude + " in state=" + state);
            controller.Move(moveDir * Time.fixedDeltaTime); // note: returns CollisionFlags if needed
            velocity = controller.velocity;                 // for animations and fall damage

            // broadcast to server
            CmdFixedMove(new Move(route, state, transform.position, transform.rotation.eulerAngles.y));
        }
        // server/other clients need to do some caching and scaling too
        else
        {
            // apply next pending move if we have at least 'minMoveBuffer'
            // moves pending to make sure that we also still have one to apply
            // in the next FixedUpdate call.
            // => it's better to apply 1,1,1,1 move instead of 2,0,2,1,0 moves.
            if (pendingMoves.Count > 0 && pendingMoves.Count >= minMoveBuffer)
            {
                // too many pending moves?
                // (only the server has authority to reset!)
                if (isServer && pendingMoves.Count >= maxMoveBuffer)
                {
                    // force reset
                    Warp(transform.position);
                }
                // more than combine threshold?
                // (both server and client can combine moves if needed)
                else if (pendingMoves.Count >= 2 && pendingMoves.Count >= combineMovesAfter)
                {
                    Vector3   previousPosition = transform.position;
                    MoveState previousState    = state;

                    // combine the next two moves to minimize overall delay.
                    // => if we are always behind 5 moves then we might as well
                    //    accelerate a bit to always be behind 4, 3, or 2 moves
                    //    to minimize movement delay.
                    //
                    // note: calling controller.Move() multiple times in one
                    //       FixedUpdate doesn't work well and isn't
                    //       recommended. adding the two vectors together works
                    //       better.
                    //
                    // note: we COULD warp to first.position and then move to
                    //       second.position to avoid the double-move which
                    //       always comes with the risk of getting out of sync
                    //       because A,B went behind a wall while A+B went into
                    //       the wall.
                    //
                    //       BUT if we do warp then we would ALWAYS risk wall
                    //       hack cheats since the server would sometimes set
                    //       the position without checking physics via .Move.
                    //
                    //       INSTEAD we risk move(A+B) walkning into a wall and
                    //       reset if rubberbanding gets too far off. at least
                    //       this method can be made cheat safe.
                    Move    first  = pendingMoves.Dequeue();
                    Move    second = pendingMoves.Dequeue();
                    Vector3 move   = second.position - transform.position; // calculate the delta before each move. using .position is 100% accurate and never gets out of sync.

                    // check if allowed (only check on server)
                    if (!isServer || IsValidMove(move))
                    {
                        state = second.state;
                        // multiple moves. use velocity between the two moves, not between position and second move.
                        // -> and divide by fixedDeltaTime because velocity is
                        //    direction / second
                        velocity           = (second.position - first.position) / Time.fixedDeltaTime;
                        transform.rotation = Quaternion.Euler(0, second.yRotation, 0);
                        controller.Move(move);
                        ++combinedMoves; // debug information only

                        // safety checks on server
                        if (isServer)
                        {
                            // check speed AFTER actually moving with the TRUE
                            // velocity. this works better than checking before
                            // moving because now we know the true velocity
                            // after collisions.
                            if (!WasValidSpeed(velocity, previousState, state, true))
                            {
                                Warp(previousPosition);
                            }

                            // rubberbanding check if server
                            RubberbandCheck(second.position);
                        }
                    }
                    // force reset to last allowed position if not allowed
                    else
                    {
                        Warp(transform.position);
                        Debug.LogWarning(name + " combined move: " + move + " rejected and force reset to: " + transform.position + "@" + DateTime.UtcNow);
                    }
                }
                // less than combine threshold?
                else
                {
                    Vector3   previousPosition = transform.position;
                    MoveState previousState    = state;

                    // apply one move
                    Move    next = pendingMoves.Dequeue();
                    Vector3 move = next.position - transform.position; // calculate the delta before each move. using .position is 100% accurate and never gets out of sync.

                    // check if allowed (only check on server)
                    if (!isServer || IsValidMove(move))
                    {
                        state = next.state;
                        transform.rotation = Quaternion.Euler(0, next.yRotation, 0);
                        controller.Move(move);
                        velocity = controller.velocity; // only one move, so controller velocity is true.

                        // safety checks on server
                        if (isServer)
                        {
                            // check speed AFTER actually moving with the TRUE
                            // velocity. this works better than checking before
                            // moving because now we know the true velocity
                            // after collisions.
                            if (!WasValidSpeed(velocity, previousState, state, false))
                            {
                                Warp(previousPosition);
                            }

                            // rubberbanding check if server
                            RubberbandCheck(next.position);
                        }
                    }
                    // force reset to last allowed position if not allowed
                    else
                    {
                        Warp(transform.position);
                        Debug.LogWarning(name + " single move: " + move + " rejected and force reset to: " + transform.position + "@" + DateTime.UtcNow);
                    }
                }
            }
            //else Debug.Log("none pending. client should send faster...");
        }

        // set last state after everything else is done.
        lastState = state;
    }
Example #2
0
    void FixedUpdate()
    {
        if (isLocalPlayer)
        {
            Vector2 inputDir   = GetInputDirection();
            Vector3 desiredDir = GetDesiredDirection(inputDir);
            Debug.DrawLine(transform.position, transform.position + desiredDir, Color.cyan);


            if (state == MoveState.IDLE)
            {
                state = UpdateIDLE(inputDir, desiredDir);
            }
            else if (state == MoveState.WALKING)
            {
                state = UpdateWALKINGandRUNNING(inputDir, desiredDir);
            }
            else if (state == MoveState.RUNNING)
            {
                state = UpdateWALKINGandRUNNING(inputDir, desiredDir);
            }
            else if (state == MoveState.CROUCHING)
            {
                state = UpdateCROUCHING(inputDir, desiredDir);
            }
            else if (state == MoveState.CRAWLING)
            {
                state = UpdateCRAWLING(inputDir, desiredDir);
            }
            else if (state == MoveState.AIRBORNE)
            {
                state = UpdateAIRBORNE(inputDir, desiredDir);
            }
            else if (state == MoveState.CLIMBING)
            {
                state = UpdateCLIMBING(inputDir, desiredDir);
            }
            else if (state == MoveState.SWIMMING)
            {
                state = UpdateSWIMMING(inputDir, desiredDir);
            }
            else if (state == MoveState.DEAD)
            {
                state = UpdateDEAD(inputDir, desiredDir);
            }
            else
            {
                Debug.LogError("Unhandled Movement State: " + state);
            }


            if (!controller.isGrounded)
            {
                lastFall = controller.velocity;
            }


            controller.Move(moveDir * Time.fixedDeltaTime);
            velocity = controller.velocity;


            byte rotationByte = FloatBytePacker.ScaleFloatToByte(transform.rotation.eulerAngles.y, 0, 360, byte.MinValue, byte.MaxValue);
            CmdFixedMove(new Move(route, state, transform.position, rotationByte));



            float runCycle = Mathf.Repeat(animator.GetCurrentAnimatorStateInfo(0).normalizedTime + runCycleLegOffset, 1);
            jumpLeg = (runCycle < 0.5f ? 1 : -1);


            jumpKeyPressed   = false;
            crawlKeyPressed  = false;
            crouchKeyPressed = false;
        }

        else
        {
            if (lastState != state)
            {
                AdjustControllerCollider();
            }



            if (!controller.isGrounded)
            {
                lastFall = velocity;
            }



            if (pendingMoves.Count > 0 && pendingMoves.Count >= minMoveBuffer)
            {
                if (isServer && pendingMoves.Count >= maxMoveBuffer)
                {
                    Warp(transform.position);
                }


                else if (pendingMoves.Count >= 2 && pendingMoves.Count >= combineMovesAfter)
                {
                    Move first  = pendingMoves.Dequeue();
                    Move second = pendingMoves.Dequeue();
                    state = second.state;
                    Vector3 move = second.position - transform.position;
                    velocity = second.position - first.position;
                    float yRotation = FloatBytePacker.ScaleByteToFloat(second.yRotation, byte.MinValue, byte.MaxValue, 0, 360);
                    transform.rotation = Quaternion.Euler(0, yRotation, 0);
                    controller.Move(move);
                    ++combinedMoves;


                    if (isServer)
                    {
                        RubberbandCheck(second.position);
                    }
                }

                else
                {
                    Move next = pendingMoves.Dequeue();
                    state = next.state;
                    Vector3 move      = next.position - transform.position;
                    float   yRotation = FloatBytePacker.ScaleByteToFloat(next.yRotation, byte.MinValue, byte.MaxValue, 0, 360);
                    transform.rotation = Quaternion.Euler(0, yRotation, 0);
                    controller.Move(move);
                    velocity = controller.velocity;


                    if (isServer)
                    {
                        RubberbandCheck(next.position);
                    }
                }
            }
        }


        if (isServer)
        {
            if (lastState == MoveState.AIRBORNE && state != MoveState.AIRBORNE)
            {
                ApplyFallDamage();
            }
        }


        lastState = state;
    }