예제 #1
0
        /// <summary>
        ///		Unity's FixedUpdate method
        ///		Handles prediction, server processing, reconciliation
        ///		& FixedUpdate of state
        /// </summary>
        void FixedUpdate()
        {
            // Don't start until initialization is done, stop updating if the input is lost
            if (!_networkReady)
            {
                return;
            }
            if (!_initialized && !Initialize())
            {
                return;
            }
            if ((networkObject.IsServer || _isLocalOwner) && _inputListener == null)
            {
                return;
            }

            #region Netcode Logic
            // Server Authority - snap the position on all clients to the server's position
            if (!networkObject.IsServer)
            {
                _rigidBody.position = networkObject.position;

                if (_isLocalOwner && networkObject.frame != 0 && _lastNetworkFrame <= networkObject.frame)
                {
                    _lastNetworkFrame = networkObject.frame;
                    Reconcile();
                }
            }

            if (!_isLocalOwner && !networkObject.IsServer)
            {
                return;
            }

            // Local client prediction & server authoritative logic
            if (_inputListener.FramesToPlay.Count <= 0)
            {
                return;
            }
            _currentInput   = _inputListener.FramesToPlay.Pop();
            _lastLocalFrame = _currentInput.frameNumber;

            // Try to do a player update (if this fails, something's weird)
            try
            {
                PlayerUpdate(_currentInput);
            }
            catch (Exception e)
            {
                Debug.LogError("Malformed input frame.");
                Debug.LogError(e);
            }

            // Reconciliation only happens on the local client
            if (_isLocalOwner && !networkObject.IsServer)
            {
                _inputListener.FramesToReconcile.Add(_currentInput);
            }
            #endregion
        }
 private void Update()
 {
     /*
      * Poll the input in Update
      *
      * I've heard some discussions about this vs polling in
      * FixedUpdate on the Forge Networking Discord
      *
      * From what I can tell it is an opinionated choice
      * and there's no benefit/problem one way or the other,
      * although theoretically you are one input behind in
      * Update since FixedUpdate runs before Update per:
      * https://docs.unity3d.com/Manual/ExecutionOrder.html
      */
     _lastInputFrame = _inputFrame;
     _inputFrame     = new InputFrame()
     {
         right      = Input.GetKey(KeyCode.L) || Input.GetKey(KeyCode.RightArrow),
         down       = Input.GetKey(KeyCode.K) || Input.GetKey(KeyCode.DownArrow),
         left       = Input.GetKey(KeyCode.J) || Input.GetKey(KeyCode.LeftArrow),
         up         = Input.GetKey(KeyCode.I) || Input.GetKey(KeyCode.UpArrow),
         horizontal = Input.GetAxisRaw("Horizontal"),
         vertical   = Input.GetAxisRaw("Vertical")
     };
 }
예제 #3
0
        private void MoveTank(InputFrame input)
        {
            // Create a vector in the direction the tank is facing with a magnitude based on the input, speed and the time between frames.
            Vector3 movement = transform.forward * input.vertical * 5 * Time.deltaTime;

            // Apply this movement to the rigidbody's position.
            _rigidBody.MovePosition(_rigidBody.position + movement);
        }
예제 #4
0
        /// <summary>
        /// Move the player's simulation (rigid body)
        /// </summary>
        /// <param name="input"></param>
        private void Move(InputFrame input)
        {
            // Move the player, clamping the movement so diagonals aren't faster
            Vector2 translation =
                Vector2.ClampMagnitude(new Vector2(input.horizontal, input.vertical) * Speed * Time.fixedDeltaTime, Speed);

            _rigidBody.position += translation;
            _rigidBody.velocity  = translation;
        }
 /// <summary>
 ///		Send input state to the server for processing
 /// </summary>
 /// <param name="args"></param>
 public override void SyncInputs(RpcArgs args)
 {
     if (networkObject.IsServer)
     {
         var        bytes  = args.GetNext <Byte[]>();
         InputFrame newest = (InputFrame)ByteArray.Deserialize(bytes);
         FramesToPlay.Add(newest);
     }
 }
 private void PlayerUpdate(InputFrame input)
 {
     // Set the velocity to zero, move the player based on the next input, then detect & resolve collisions
     _rigidBody.velocity = Vector2.zero;
     if (input != null && input.HasInput)
     {
         Move(input);
     }
     PhysicsCollisions();
 }
예제 #7
0
        private void TurnTank(InputFrame input)
        {
            // Determine the number of degrees to be turned based on the input, speed and time between frames.
            float turn = input.horizontal * 40 * Time.deltaTime;

            // Make this into a rotation in the y axis.
            Quaternion turnRotation = Quaternion.Euler(0f, turn, 0f);

            // Apply this rotation to the rigidbody's rotation.
            _rigidBody.MoveRotation(_rigidBody.rotation * turnRotation);
        }
 /// <summary>
 /// Player update composed of movement and collision processing
 /// </summary>
 /// <param name="input"></param>
 private void PlayerUpdate(InputFrame input)
 {
     // Set the velocity to zero, move the player based on the next input, then detect & resolve collisions
     _rigidBody.velocity = Vector3.zero;                                                                         // DKE Changed Vector2 to Vector3
     if (input != null && input.HasInput)
     {
         //Debug.Log("PlayerUpdate has input");
         //Move(input);      // DKE: removed
         MoveTank(input);    // DKE: Replaced Move by MoveTank + TurnTank
         TurnTank(input);    // DKE: added
         PhysicsCollisions();
     }
 }
 // RPC for receiving InputFrames from the clients
 public override void SyncInputs(RpcArgs args)
 {
     if (networkObject.IsServer)
     {
         var bytes = args.GetNext <Byte[]>();
         if (sendSingleInputs)
         {
             InputFrame nextInputFrame = (InputFrame)ByteArrayUtilities.ByteArrayToObject(bytes);
             FramesToPlay.Add(nextInputFrame);
         }
         else
         {
             List <InputFrame> networkInputFrames = (List <InputFrame>)ByteArrayUtilities.ByteArrayToObject(bytes);
             FramesToPlay.AddRange(networkInputFrames);
         }
     }
 }
예제 #10
0
        /// <summary>
        ///		Reconcile inputs that haven't yet been
        ///		authoritatively processed by the server
        /// </summary>
        private void Reconcile()
        {
            // Remove any inputs up to and including the last input processed by the server
            _inputListener.FramesToReconcile.RemoveAll(f => f.frameNumber < networkObject.frame);

            // Replay them all back to the last input processed by client prediction
            if (_inputListener.FramesToReconcile.Count > 0)
            {
                for (Int32 i = 0; i < _inputListener.FramesToReconcile.Count; ++i)
                {
                    _currentInput = _inputListener.FramesToReconcile[i];
                    PlayerUpdate(_currentInput);
                }
            }

            // The error vector measures the difference between the predicted & server updated sim position (this one)
            // and the view position (the position of the MonoBehavior holding your renderer/view)
            _errorVector = _rigidBody.position - (Vector2)View.transform.position;
            _errorTimer  = 0.0f;
        }
        /// <summary>
        ///		Polling of inputs is handled in Update since they
        ///		reset every frame making weird things happen in FixedUpdate
        /// </summary>
        private void Update()
        {
            if (!_networkReady)
            {
                return;
            }

            if (!networkObject.IsServer && networkObject.IsOwner)
            {
                _inputFrame = new InputFrame
                {
                    right      = Input.GetKey(KeyCode.L) || Input.GetKey(KeyCode.RightArrow),
                    down       = Input.GetKey(KeyCode.K) || Input.GetKey(KeyCode.DownArrow),
                    left       = Input.GetKey(KeyCode.J) || Input.GetKey(KeyCode.LeftArrow),
                    up         = Input.GetKey(KeyCode.I) || Input.GetKey(KeyCode.UpArrow),
                    horizontal = Input.GetAxisRaw("Horizontal"),
                    vertical   = Input.GetAxisRaw("Vertical")
                };
            }
        }
        void FixedUpdate()
        {
            // Check if this client is the local owner
            _isLocalOwner = networkObject.MyPlayerId == networkObject.ownerNetId;

            #region Setup
            // Initial setup - only do this once
            if ((_isLocalOwner || networkObject.IsServer) && !_setup)
            {
                // Interpolation on the predicted client and server does weird things
                // Only interpolate on non-owner clients
                networkObject.positionInterpolation.Enabled = false;
                _setup = true;
            }

            // Get the input listener if it doesn't exist and this isn't a remote client
            if (_inputListener == null)
            {
                _inputListener = FindObjectsOfType <InputListener>().FirstOrDefault(x => x.networkObject.Owner.NetworkId == networkObject.ownerNetId);
            }
            #endregion

            #region Netcode Logic
            // Server Authority - snap the position on all clients to the server's position
            if (!networkObject.IsServer)
            {
                _rigidBody.position = networkObject.position;
            }

            // Client owner Reconciliation & Prediction
            if (_isLocalOwner)
            {
                if (_inputListener != null)
                {
                    // Reconciliation - only do this if the server update is current or new
                    if (networkObject.frame != 0 && _lastNetworkFrame <= networkObject.frame)
                    {
                        _lastNetworkFrame = networkObject.frame;
                        Reconcile();
                    }

                    // Prediction
                    if (_inputListener.FramesToPlay.Count > 0)
                    {
                        InputFrame input = _inputListener.FramesToPlay[0];
                        _lastLocalFrame = input.frameNumber;
                        PlayerUpdate(input);
                        _inputListener.FramesToPlay.RemoveAt(0);
                    }
                }
            }
            // Server Processing
            else if (networkObject.IsServer)
            {
                // Reset the current input - we don't want to re-use it if there no inputs in the queue
                _currentInput = null;

                if (_inputListener != null)
                {
                    // Process all available inputs each frame
                    while (_inputListener.FramesToPlay.Count > 0)
                    {
                        _currentInput   = _inputListener.FramesToPlay[0];
                        _lastLocalFrame = _currentInput.frameNumber;
                        _inputListener.FramesToPlay.RemoveAt(0);

                        // Try-catch is a good idea to handle weird serialization/deserialization errors
                        try
                        {
                            PlayerUpdate(_currentInput);
                        }
                        catch (Exception e)
                        {
                            Debug.LogError(e + " (Serverside input processing - Player.cs line 104)");
                        }
                    }
                }
            }
            #endregion
        }
 private void Move(InputFrame input)
 {
     _rigidBody.velocity  = new Vector2(input.horizontal, input.vertical) * Speed * Time.fixedDeltaTime;
     _rigidBody.position += _rigidBody.velocity;
 }