Problem with syncing first person camera

Options
dawidos2221
edited August 2020 in Photon Bolt
Hello, I'm new in creating multiplayer games, I'm following the 'Advanced Tutorial' from documentation, but Im trying to create it as first person shooter, right now player can connect, their position and rotation is synced, so client's see that someone's rotating on the horizontal axis, i made also vertical movement of camera, and it works for server and clients, but the thing is that clients don't see server rotating camera verticaly, im trying to solve this problem for few hours and nothing comes to my mind. Anyone can help?

PlayerMotor.cs (added camera rotation vertical)
using UnityEngine;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

[RequireComponent(typeof(CharacterController))]
public class PlayerMotor : MonoBehaviour
{

	public struct State
	{
		public Vector3 position;
		public Vector3 velocity;
		public bool isGrounded;
		public int jumpFrames;
	}

	State _state;
	CharacterController _cc;

	[SerializeField]
	float skinWidth = 0.08f;

	[SerializeField]
	float gravityForce = -9.81f;

	[SerializeField]
	float jumpForce = +40f;

	[SerializeField]
	int jumpTotalFrames = 30;

	[SerializeField]
	float movingSpeed = 4f;

	[SerializeField]
	float maxVelocity = 32f;

	[SerializeField]
	Vector3 drag = new Vector3(1f, 0f, 1f);

	public LayerMask layerMask;

	Vector3 sphere
	{
		get
		{
			Vector3 p;

			p = transform.position;
			p.y += _cc.radius;
			p.y -= (skinWidth * 2);

			return p;
		}
	}

	Vector3 waist
	{
		get
		{
			Vector3 p;

			p = transform.position;
			p.y += _cc.height / 2f;

			return p;
		}
	}

	public bool jumpStartedThisFrame
	{
		get
		{
			return _state.jumpFrames == (jumpTotalFrames - 1);
		}
	}

	void Awake()
	{
		_cc = GetComponent<CharacterController>();
		_state = new State();
		_state.position = transform.localPosition;
	}

	public void SetState(Vector3 position, Vector3 velocity, bool isGrounded, int jumpFrames)
	{
		// assign new state
		_state.position = position;
		_state.velocity = velocity;
		_state.jumpFrames = jumpFrames;
		_state.isGrounded = isGrounded;

		// assign local position
		_cc.Move(_state.position - transform.localPosition);
	}

	void Move(Vector3 velocity)
	{
		bool isGrounded = false;

		isGrounded = isGrounded || _cc.Move(velocity * BoltNetwork.FrameDeltaTime) == CollisionFlags.Below;
		isGrounded = isGrounded || _cc.isGrounded;
		isGrounded = isGrounded || Physics.CheckSphere(sphere, _cc.radius, layerMask);

		if (isGrounded && !_state.isGrounded)
		{
			_state.velocity = new Vector3();
		}

		_state.isGrounded = isGrounded;
		_state.position = transform.localPosition;
	}

	public State Move(bool forward, bool backward, bool left, bool right, bool jump, float yaw, float pitch)
	{
		var moving = false;
		var movingDir = Vector3.zero;

		if (forward ^ backward)
		{
			movingDir.z = forward ? +1 : -1;
		}

		if (left ^ right)
		{
			movingDir.x = right ? +1 : -1;
		}

		if (movingDir.x != 0 || movingDir.z != 0)
		{
			moving = true;
			movingDir = Vector3.Normalize(Quaternion.Euler(0, yaw, 0) * movingDir);
		}

		//
		if (_state.isGrounded)
		{
			if (jump && _state.jumpFrames == 0)
			{
				_state.jumpFrames = (byte)jumpTotalFrames;
				_state.velocity += movingDir * movingSpeed;
			}

			if (moving && _state.jumpFrames == 0)
			{
				Move(movingDir * movingSpeed);
			}
		}
		else
		{
			_state.velocity.y += gravityForce * BoltNetwork.FrameDeltaTime;
		}

		if (_state.jumpFrames > 0)
		{
			// calculate force
			float force;
			force = (float)_state.jumpFrames / (float)jumpTotalFrames;
			force = jumpForce * force;

			Move(new Vector3(0, force, 0));
		}

		// decrease jump frames
		_state.jumpFrames = Mathf.Max(0, _state.jumpFrames - 1);

		// clamp velocity
		_state.velocity = Vector3.ClampMagnitude(_state.velocity, maxVelocity);

		// apply drag
		_state.velocity.x = ApplyDrag(_state.velocity.x, drag.x);
		_state.velocity.y = ApplyDrag(_state.velocity.y, drag.y);
		_state.velocity.z = ApplyDrag(_state.velocity.z, drag.z);

		// this might seem weird, but it actually gets around a ton of issues - we basically apply 
		// gravity on the Y axis on every frame to simulate instant gravity if you step over a ledge
		_state.velocity.y = Mathf.Min(_state.velocity.y, gravityForce);

		// apply movement
		Move(_state.velocity);

		// set local rotation
		transform.localRotation = Quaternion.Euler(0, yaw, 0);
		//BoltLog.Error($"{transform.name} {(int)pitch}");
		transform.GetChild(0).localRotation = Quaternion.Euler(pitch, 0, 0);

		// detect tunneling
		DetectTunneling();

		// update position
		_state.position = transform.localPosition;

		// done
		return _state;
	}

	float ApplyDrag(float value, float drag)
	{
		if (value < 0)
		{
			return Mathf.Min(value + (drag * BoltNetwork.FrameDeltaTime), 0f);
		}
		else if (value > 0)
		{
			return Mathf.Max(value - (drag * BoltNetwork.FrameDeltaTime), 0f);
		}

		return value;
	}

	void DetectTunneling()
	{
		RaycastHit hit;

		if (Physics.Raycast(waist, Vector3.down, out hit, _cc.height / 2, layerMask))
		{
			transform.position = hit.point;
		}
	}

	void OnDrawGizmos()
	{
		if (Application.isPlaying)
		{
			Gizmos.color = _state.isGrounded ? Color.green : Color.red;
			Gizmos.DrawWireSphere(sphere, _cc.radius);

			Gizmos.color = Color.magenta;
			Gizmos.DrawLine(waist, waist + new Vector3(0, -(_cc.height / 2f), 0));
		}
	}
}

PlayerController.cs
using Bolt;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : Bolt.EntityBehaviour<I_PlayerState>
{
    [SerializeField]
    Weapon[] weapons;
    [Space]
    public float mouseSensitivity = 10;
    AudioSource _weaponSfxSource;
    int weapon;
    PlayerMotor _motor;

    bool _forward;
    bool _backward;
    bool _left;
    bool _right;
    bool _jump;
    bool _fire;

    float _yaw;
    public float _pitch;

    private void Awake()
    {
        Cursor.visible = false;
        Cursor.lockState = CursorLockMode.Locked;
        _weaponSfxSource = GetComponent<AudioSource>();
        //cc = GetComponent<CharacterController>();
    }

    public override void Attached()
    {
        state.SetTransforms(state.Transform, transform);
        _motor = GetComponent<PlayerMotor>();

        state.OnFire = () =>
        {
            weapons[0].DisplayEffects(entity);
        };
    }

    private void Update()
    {
        HandleMovement(true);
    }

    void HandleMovement(bool mouse)
    {
        _forward = Input.GetKey(KeyCode.W);
        _backward = Input.GetKey(KeyCode.S);
        _left = Input.GetKey(KeyCode.A);
        _right = Input.GetKey(KeyCode.D);
        _jump = Input.GetKeyDown(KeyCode.Space);

        _fire = Input.GetMouseButton(0);

        if (mouse)
        {
            _yaw += (Input.GetAxisRaw("Mouse X") * mouseSensitivity * 10 * Time.deltaTime);
            _yaw %= 360f;

            _pitch += (-Input.GetAxisRaw("Mouse Y") * mouseSensitivity * 10 * Time.deltaTime);
            _pitch = Mathf.Clamp(_pitch, -85f, +85f);
        }
    }

    public override void SimulateController()
    {
        HandleMovement(false);

        if (!GetComponentInChildren<Camera>().enabled)
            GetComponentInChildren<Camera>().enabled = true;

        IPlayerCommandInput input = PlayerCommand.Create();

        input.Forward = _forward;
        input.Backward = _backward;
        input.Left = _left;
        input.Right = _right;
        input.Jump = _jump;
        input.Yaw = _yaw;
        input.Pitch = _pitch;
        input.Fire = _fire;

        entity.QueueInput(input);
    }

    public override void ExecuteCommand(Command command, bool resetState)
    {
        PlayerCommand cmd = (PlayerCommand)command;

        if (resetState)
        {
            _motor.SetState(cmd.Result.Position, cmd.Result.Velocity, cmd.Result.IsGrounded, cmd.Result.JumpFrames);
        }
        else
        {
            // apply movement (this runs on both server and client)
            PlayerMotor.State motorState = _motor.Move(cmd.Input.Forward, cmd.Input.Backward, cmd.Input.Left, cmd.Input.Right, cmd.Input.Jump, cmd.Input.Yaw, cmd.Input.Pitch);

            // copy the motor state to the commands result (this gets sent back to the client)
            cmd.Result.Position = motorState.position;
            cmd.Result.Velocity = motorState.velocity;
            cmd.Result.IsGrounded = motorState.isGrounded;
            cmd.Result.JumpFrames = motorState.jumpFrames;

            if (cmd.IsFirstExecution)
            {
                //AnimatePlayer(cmd);
                state.Pitch = cmd.Input.Pitch;

                if (cmd.Input.Fire)
                {
                    FireWeapon(cmd);
                }
            }
        }
    }

    void FireWeapon(PlayerCommand cmd)
    {
        if (weapons[0].FireFrame + weapons[0].FireInterval <= BoltNetwork.ServerFrame)
        {
            weapons[0].FireFrame = BoltNetwork.ServerFrame;
            state.Fire();
        }
    }
}

Server perspective:
https://imgur.com/juPR8LP

Client perspective:
https://imgur.com/C7gdukV

Comments

  • ramonmelo
    Options
    Hello @dawidos2221,

    On your implementation, you are changing the pitch of the weapon/camera on the "ExecuteCommand", this method will be executed only on the "Owner" and the "Controller" of the entity, meaning that on the server (that is the owner of both entities), it will work just fine, on the client (that is the controller of one of the entities), you will also see the update happening for that particular controlled entity, but not for the entity owned and controlled by the game server.

    On the original "Advanced Tutorial" sample, the weapon/camera pitch is managed by reading the Entity State on the "PlayerCamera" script and updating the rotation accordingly. That is not by mistake, but it's necessary, as you are not changing the transform the of the entity itself, but rather of another game object transform based on its state properties.

    You can use for this case, and extend your script, the State properties callbacks, that are invoked every time you change a particular property of your entity. Please take a look here: https://doc.photonengine.com/en-us/bolt/current/gameplay/state#state_callbacks.

    Also, take a look at this page where we describe who is who on the Bolt context: https://doc.photonengine.com/en-us/bolt/current/reference/glossary.
    And here, about the Command usage: https://doc.photonengine.com/en-us/bolt/current/gameplay/commands.