Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Resolved Issue Integrating "Starter Assets - Third Person Character Controller" with Photon PUN 2.

Discussion in 'Editor & General Support' started by fuster_mza, Oct 10, 2023.

  1. fuster_mza

    fuster_mza

    Joined:
    Jun 26, 2017
    Posts:
    3
    Hello everyone,

    I'm facing an issue while trying to integrate the "Photon PUN 2" package into my project, which is based on the "Starter Assets - Third Person Character Controller." I've implemented the necessary checks using photonView.IsMine to distinguish between the movement and actions of my character.
    here's the ThirdPersonController.cs modified with these checks:
    Code (CSharp):
    1.  using UnityEngine;
    2. using Photon.Pun;
    3. #if ENABLE_INPUT_SYSTEM
    4. using UnityEngine.InputSystem;
    5. #endif
    6.  
    7. /* Note: animations are called via the controller for both the character and capsule using animator null checks
    8. */
    9.  
    10. namespace StarterAssets
    11. {
    12.     [RequireComponent(typeof(CharacterController))]
    13. #if ENABLE_INPUT_SYSTEM
    14.     [RequireComponent(typeof(PlayerInput))]
    15. #endif
    16.     public class ThirdPersonController : MonoBehaviourPunCallbacks
    17.     {
    18.         [Header("Player")]
    19.         [Tooltip("Move speed of the character in m/s")]
    20.         public float MoveSpeed = 2.0f;
    21.  
    22.         [Tooltip("Sprint speed of the character in m/s")]
    23.         public float SprintSpeed = 5.335f;
    24.  
    25.         [Tooltip("How fast the character turns to face movement direction")]
    26.         [Range(0.0f, 0.3f)]
    27.         public float RotationSmoothTime = 0.12f;
    28.  
    29.         [Tooltip("Acceleration and deceleration")]
    30.         public float SpeedChangeRate = 10.0f;
    31.  
    32.         public AudioClip LandingAudioClip;
    33.         public AudioClip[] FootstepAudioClips;
    34.         [Range(0, 1)] public float FootstepAudioVolume = 0.5f;
    35.  
    36.         [Space(10)]
    37.         [Tooltip("The height the player can jump")]
    38.         public float JumpHeight = 1.2f;
    39.  
    40.         [Tooltip("The character uses its own gravity value. The engine default is -9.81f")]
    41.         public float Gravity = -15.0f;
    42.  
    43.         [Space(10)]
    44.         [Tooltip("Time required to pass before being able to jump again. Set to 0f to instantly jump again")]
    45.         public float JumpTimeout = 0.50f;
    46.  
    47.         [Tooltip("Time required to pass before entering the fall state. Useful for walking down stairs")]
    48.         public float FallTimeout = 0.15f;
    49.  
    50.         [Header("Player Grounded")]
    51.         [Tooltip("If the character is grounded or not. Not part of the CharacterController built in grounded check")]
    52.         public bool Grounded = true;
    53.  
    54.         [Tooltip("Useful for rough ground")]
    55.         public float GroundedOffset = -0.14f;
    56.  
    57.         [Tooltip("The radius of the grounded check. Should match the radius of the CharacterController")]
    58.         public float GroundedRadius = 0.28f;
    59.  
    60.         [Tooltip("What layers the character uses as ground")]
    61.         public LayerMask GroundLayers;
    62.  
    63.         [Header("Cinemachine")]
    64.         [Tooltip("The follow target set in the Cinemachine Virtual Camera that the camera will follow")]
    65.         public GameObject CinemachineCameraTarget;
    66.  
    67.         [Tooltip("How far in degrees can you move the camera up")]
    68.         public float TopClamp = 70.0f;
    69.  
    70.         [Tooltip("How far in degrees can you move the camera down")]
    71.         public float BottomClamp = -30.0f;
    72.  
    73.         [Tooltip("Additional degress to override the camera. Useful for fine tuning camera position when locked")]
    74.         public float CameraAngleOverride = 0.0f;
    75.  
    76.         [Tooltip("For locking the camera position on all axis")]
    77.         public bool LockCameraPosition = false;
    78.  
    79.         // cinemachine
    80.         private float _cinemachineTargetYaw;
    81.         private float _cinemachineTargetPitch;
    82.  
    83.         // player
    84.         private float _speed;
    85.         private float _animationBlend;
    86.         private float _targetRotation = 0.0f;
    87.         private float _rotationVelocity;
    88.         private float _verticalVelocity;
    89.         private float _terminalVelocity = 53.0f;
    90.  
    91.         // timeout deltatime
    92.         private float _jumpTimeoutDelta;
    93.         private float _fallTimeoutDelta;
    94.  
    95.         // animation IDs
    96.         private int _animIDSpeed;
    97.         private int _animIDGrounded;
    98.         private int _animIDJump;
    99.         private int _animIDFreeFall;
    100.         private int _animIDMotionSpeed;
    101.  
    102. #if ENABLE_INPUT_SYSTEM
    103.         private PlayerInput _playerInput;
    104. #endif
    105.         private Animator _animator;
    106.         private CharacterController _controller;
    107.         private StarterAssetsInputs _input;
    108.         private GameObject _mainCamera;
    109.  
    110.         private const float _threshold = 0.01f;
    111.  
    112.         private bool _hasAnimator;
    113.         private bool _isLocalObject;
    114.         private bool IsCurrentDeviceMouse
    115.         {
    116.             get
    117.             {
    118. #if ENABLE_INPUT_SYSTEM
    119.                 return _playerInput.currentControlScheme == "KeyboardMouse";
    120. #else
    121.                 return false;
    122. #endif
    123.             }
    124.         }
    125.  
    126.  
    127.         private void Awake()
    128.         {
    129.             // get a reference to our main camera
    130.             if (_mainCamera == null)
    131.             {
    132.                 _mainCamera = GameObject.FindGameObjectWithTag("MainCamera");
    133.             }
    134.         }
    135.  
    136.         private void Start()
    137.         {
    138.             _isLocalObject = photonView.IsMine; //HERE I DEFINE THIS BOOL IF PHOTONVIEW IS MINE.
    139.             _cinemachineTargetYaw = CinemachineCameraTarget.transform.rotation.eulerAngles.y;
    140.            
    141.             _hasAnimator = TryGetComponent(out _animator);
    142.             _controller = GetComponent<CharacterController>();
    143.             _input = GetComponent<StarterAssetsInputs>();
    144. #if ENABLE_INPUT_SYSTEM
    145.             _playerInput = GetComponent<PlayerInput>();
    146. #else
    147.             Debug.LogError( "Starter Assets package is missing dependencies. Please use Tools/Starter Assets/Reinstall Dependencies to fix it");
    148. #endif
    149.  
    150.             AssignAnimationIDs();
    151.  
    152.             // reset our timeouts on start
    153.             _jumpTimeoutDelta = JumpTimeout;
    154.             _fallTimeoutDelta = FallTimeout;
    155.         }
    156.  
    157.         private void Update()
    158.         {
    159.             if(_isLocalObject) // THEN I CHECK HERE
    160.             {
    161.                
    162.                 _hasAnimator = TryGetComponent(out _animator);
    163.  
    164.                 JumpAndGravity();
    165.                 GroundedCheck();
    166.                 Move();
    167.             }
    168.         }
    169.  
    170.         private void LateUpdate()
    171.         {
    172.             if(_isLocalObject) // AND HERE.
    173.             {
    174.                 CameraRotation();
    175.             }
    176.            
    177.         }
    178.  
    179.         private void AssignAnimationIDs()
    180.         {
    181.             _animIDSpeed = Animator.StringToHash("Speed");
    182.             _animIDGrounded = Animator.StringToHash("Grounded");
    183.             _animIDJump = Animator.StringToHash("Jump");
    184.             _animIDFreeFall = Animator.StringToHash("FreeFall");
    185.             _animIDMotionSpeed = Animator.StringToHash("MotionSpeed");
    186.         }
    187.  
    188.         private void GroundedCheck()
    189.         {
    190.             // set sphere position, with offset
    191.             Vector3 spherePosition = new Vector3(transform.position.x, transform.position.y - GroundedOffset,
    192.                 transform.position.z);
    193.             Grounded = Physics.CheckSphere(spherePosition, GroundedRadius, GroundLayers,
    194.                 QueryTriggerInteraction.Ignore);
    195.  
    196.             // update animator if using character
    197.             if (_hasAnimator)
    198.             {
    199.                 _animator.SetBool(_animIDGrounded, Grounded);
    200.             }
    201.         }
    202.  
    203.         private void CameraRotation()
    204.         {
    205.             // if there is an input and camera position is not fixed
    206.             if (_input.look.sqrMagnitude >= _threshold && !LockCameraPosition)
    207.             {
    208.                 //Don't multiply mouse input by Time.deltaTime;
    209.                 float deltaTimeMultiplier = IsCurrentDeviceMouse ? 1.0f : Time.deltaTime;
    210.  
    211.                 _cinemachineTargetYaw += _input.look.x * deltaTimeMultiplier;
    212.                 _cinemachineTargetPitch += _input.look.y * deltaTimeMultiplier;
    213.             }
    214.  
    215.             // clamp our rotations so our values are limited 360 degrees
    216.             _cinemachineTargetYaw = ClampAngle(_cinemachineTargetYaw, float.MinValue, float.MaxValue);
    217.             _cinemachineTargetPitch = ClampAngle(_cinemachineTargetPitch, BottomClamp, TopClamp);
    218.  
    219.             // Cinemachine will follow this target
    220.             CinemachineCameraTarget.transform.rotation = Quaternion.Euler(_cinemachineTargetPitch + CameraAngleOverride,
    221.                 _cinemachineTargetYaw, 0.0f);
    222.         }
    223.  
    224.         private void Move()
    225.         {
    226.             // set target speed based on move speed, sprint speed and if sprint is pressed
    227.             float targetSpeed = _input.sprint ? SprintSpeed : MoveSpeed;
    228.  
    229.             // a simplistic acceleration and deceleration designed to be easy to remove, replace, or iterate upon
    230.  
    231.             // note: Vector2's == operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    232.             // if there is no input, set the target speed to 0
    233.             if (_input.move == Vector2.zero) targetSpeed = 0.0f;
    234.  
    235.             // a reference to the players current horizontal velocity
    236.             float currentHorizontalSpeed = new Vector3(_controller.velocity.x, 0.0f, _controller.velocity.z).magnitude;
    237.  
    238.             float speedOffset = 0.1f;
    239.             float inputMagnitude = _input.analogMovement ? _input.move.magnitude : 1f;
    240.  
    241.             // accelerate or decelerate to target speed
    242.             if (currentHorizontalSpeed < targetSpeed - speedOffset ||
    243.                 currentHorizontalSpeed > targetSpeed + speedOffset)
    244.             {
    245.                 // creates curved result rather than a linear one giving a more organic speed change
    246.                 // note T in Lerp is clamped, so we don't need to clamp our speed
    247.                 _speed = Mathf.Lerp(currentHorizontalSpeed, targetSpeed * inputMagnitude,
    248.                     Time.deltaTime * SpeedChangeRate);
    249.  
    250.                 // round speed to 3 decimal places
    251.                 _speed = Mathf.Round(_speed * 1000f) / 1000f;
    252.             }
    253.             else
    254.             {
    255.                 _speed = targetSpeed;
    256.             }
    257.  
    258.             _animationBlend = Mathf.Lerp(_animationBlend, targetSpeed, Time.deltaTime * SpeedChangeRate);
    259.             if (_animationBlend < 0.01f) _animationBlend = 0f;
    260.  
    261.             // normalise input direction
    262.             Vector3 inputDirection = new Vector3(_input.move.x, 0.0f, _input.move.y).normalized;
    263.  
    264.             // note: Vector2's != operator uses approximation so is not floating point error prone, and is cheaper than magnitude
    265.             // if there is a move input rotate player when the player is moving
    266.             if (_input.move != Vector2.zero)
    267.             {
    268.                 _targetRotation = Mathf.Atan2(inputDirection.x, inputDirection.z) * Mathf.Rad2Deg +
    269.                                   _mainCamera.transform.eulerAngles.y;
    270.                 float rotation = Mathf.SmoothDampAngle(transform.eulerAngles.y, _targetRotation, ref _rotationVelocity,
    271.                     RotationSmoothTime);
    272.  
    273.                 // rotate to face input direction relative to camera position
    274.                 transform.rotation = Quaternion.Euler(0.0f, rotation, 0.0f);
    275.             }
    276.  
    277.  
    278.             Vector3 targetDirection = Quaternion.Euler(0.0f, _targetRotation, 0.0f) * Vector3.forward;
    279.  
    280.             // move the player
    281.             _controller.Move(targetDirection.normalized * (_speed * Time.deltaTime) +
    282.                              new Vector3(0.0f, _verticalVelocity, 0.0f) * Time.deltaTime);
    283.  
    284.             // update animator if using character
    285.             if (_hasAnimator)
    286.             {
    287.                 _animator.SetFloat(_animIDSpeed, _animationBlend);
    288.                 _animator.SetFloat(_animIDMotionSpeed, inputMagnitude);
    289.             }
    290.         }
    291.  
    292.         private void JumpAndGravity()
    293.         {
    294.             if (Grounded)
    295.             {
    296.                 // reset the fall timeout timer
    297.                 _fallTimeoutDelta = FallTimeout;
    298.  
    299.                 // update animator if using character
    300.                 if (_hasAnimator)
    301.                 {
    302.                     _animator.SetBool(_animIDJump, false);
    303.                     _animator.SetBool(_animIDFreeFall, false);
    304.                 }
    305.  
    306.                 // stop our velocity dropping infinitely when grounded
    307.                 if (_verticalVelocity < 0.0f)
    308.                 {
    309.                     _verticalVelocity = -2f;
    310.                 }
    311.  
    312.                 // Jump
    313.                 if (_input.jump && _jumpTimeoutDelta <= 0.0f)
    314.                 {
    315.                     // the square root of H * -2 * G = how much velocity needed to reach desired height
    316.                     _verticalVelocity = Mathf.Sqrt(JumpHeight * -2f * Gravity);
    317.  
    318.                     // update animator if using character
    319.                     if (_hasAnimator)
    320.                     {
    321.                         _animator.SetBool(_animIDJump, true);
    322.                     }
    323.                 }
    324.  
    325.                 // jump timeout
    326.                 if (_jumpTimeoutDelta >= 0.0f)
    327.                 {
    328.                     _jumpTimeoutDelta -= Time.deltaTime;
    329.                 }
    330.             }
    331.             else
    332.             {
    333.                 // reset the jump timeout timer
    334.                 _jumpTimeoutDelta = JumpTimeout;
    335.  
    336.                 // fall timeout
    337.                 if (_fallTimeoutDelta >= 0.0f)
    338.                 {
    339.                     _fallTimeoutDelta -= Time.deltaTime;
    340.                 }
    341.                 else
    342.                 {
    343.                     // update animator if using character
    344.                     if (_hasAnimator)
    345.                     {
    346.                         _animator.SetBool(_animIDFreeFall, true);
    347.                     }
    348.                 }
    349.  
    350.                 // if we are not grounded, do not jump
    351.                 _input.jump = false;
    352.             }
    353.  
    354.             // apply gravity over time if under terminal (multiply by delta time twice to linearly speed up over time)
    355.             if (_verticalVelocity < _terminalVelocity)
    356.             {
    357.                 _verticalVelocity += Gravity * Time.deltaTime;
    358.             }
    359.         }
    360.  
    361.         private static float ClampAngle(float lfAngle, float lfMin, float lfMax)
    362.         {
    363.             if (lfAngle < -360f) lfAngle += 360f;
    364.             if (lfAngle > 360f) lfAngle -= 360f;
    365.             return Mathf.Clamp(lfAngle, lfMin, lfMax);
    366.         }
    367.  
    368.         private void OnDrawGizmosSelected()
    369.         {
    370.             Color transparentGreen = new Color(0.0f, 1.0f, 0.0f, 0.35f);
    371.             Color transparentRed = new Color(1.0f, 0.0f, 0.0f, 0.35f);
    372.  
    373.             if (Grounded) Gizmos.color = transparentGreen;
    374.             else Gizmos.color = transparentRed;
    375.  
    376.             // when selected, draw a gizmo in the position of, and matching radius of, the grounded collider
    377.             Gizmos.DrawSphere(
    378.                 new Vector3(transform.position.x, transform.position.y - GroundedOffset, transform.position.z),
    379.                 GroundedRadius);
    380.         }
    381.  
    382.         private void OnFootstep(AnimationEvent animationEvent)
    383.         {
    384.             if (animationEvent.animatorClipInfo.weight > 0.5f)
    385.             {
    386.                 if (FootstepAudioClips.Length > 0)
    387.                 {
    388.                     var index = Random.Range(0, FootstepAudioClips.Length);
    389.                     AudioSource.PlayClipAtPoint(FootstepAudioClips[index], transform.TransformPoint(_controller.center), FootstepAudioVolume);
    390.                 }
    391.             }
    392.         }
    393.  
    394.         private void OnLand(AnimationEvent animationEvent)
    395.         {
    396.             if (animationEvent.animatorClipInfo.weight > 0.5f)
    397.             {
    398.                 AudioSource.PlayClipAtPoint(LandingAudioClip, transform.TransformPoint(_controller.center), FootstepAudioVolume);
    399.             }
    400.         }
    401.     }
    402. }
    And here is my code to get the follow of the camera:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Photon.Pun;
    5. using Cinemachine;
    6.  
    7. public class TargetAssigner : MonoBehaviourPunCallbacks
    8. {
    9.     [SerializeField] private CinemachineVirtualCamera followCamera;
    10.     // Start is called before the first frame update
    11.     void Start()
    12.     {
    13.         if(photonView.IsMine)
    14.         {
    15.             followCamera = FindObjectOfType<CinemachineVirtualCamera>();      
    16.             followCamera.Follow = transform;
    17.         }
    18.     }
    19. }
    20.  
    and this is my Player prefab


    Initially, when the first player enters the game, everything works as expected.

    However, the problem arises when the second player joins. At that point, the second player seems to be unable to perform any actions or movements. there are no error messages appearing in the console log.

    I've been troubleshooting this for a while, but I can't seem to pinpoint the root of the issue. Has anyone else encountered a similar problem or has any insights on what might be causing this behavior? Any help or suggestions would be greatly appreciated.

    Thank you in advance!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    Multiplayer code needs to be debugged just like any other code.

    Time to start debugging! Here is how you can begin your exciting new debugging adventures:

    You must find a way to get the information you need in order to reason about what the problem is.

    Once you understand what the problem is, you may begin to reason about a solution to the problem.

    What is often happening in these cases is one of the following:

    - the code you think is executing is not actually executing at all
    - the code is executing far EARLIER or LATER than you think
    - the code is executing far LESS OFTEN than you think
    - the code is executing far MORE OFTEN than you think
    - the code is executing on another GameObject than you think it is
    - you're getting an error or warning and you haven't noticed it in the console window

    To help gain more insight into your problem, I recommend liberally sprinkling
    Debug.Log()
    statements through your code to display information in realtime.

    Doing this should help you answer these types of questions:

    - is this code even running? which parts are running? how often does it run? what order does it run in?
    - what are the names of the GameObjects or Components involved?
    - what are the values of the variables involved? Are they initialized? Are the values reasonable?
    - are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

    Knowing this information will help you reason about the behavior you are seeing.

    You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as
    Debug.Log("Problem!",this);


    If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

    You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

    You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

    You could also just display various important quantities in UI Text elements to watch them change as you play the game.

    Visit Google for how to see console output from builds. If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer for iOS: https://forum.unity.com/threads/how-to-capturing-device-logs-on-ios.529920/ or this answer for Android: https://forum.unity.com/threads/how-to-capturing-device-logs-on-android.528680/

    If you are working in VR, it might be useful to make your on onscreen log output, or integrate one from the asset store, so you can see what is happening as you operate your software.

    Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

    If your problem is with OnCollision-type functions, print the name of what is passed in!

    Here's an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

    https://forum.unity.com/threads/coroutine-missing-hint-and-error.1103197/#post-7100494

    "When in doubt, print it out!(tm)" - Kurt Dekker (and many others)

    Note: the
    print()
    function is an alias for Debug.Log() provided by the MonoBehaviour class.
     
    fuster_mza likes this.
  3. fuster_mza

    fuster_mza

    Joined:
    Jun 26, 2017
    Posts:
    3
    Thank you for your response. In fact, I've been using the
    Debug.Log
    function to verify that the validation
    photonView.IsMine
    is being entered. The curious thing is that the second player (joining from the editor) does receive this log, indicating that it should execute its respective movement functions. However, it still doesn't do so.

    Additionally, I have another script that uses the PhotonNetwork.Instantiate function with its corresponding PlayerArmature prefab from Resources folder, and this part is functioning correctly.
     
  4. fuster_mza

    fuster_mza

    Joined:
    Jun 26, 2017
    Posts:
    3
    i found what was the problem. when i spawn two players, their PlayerInput will block the input for the remote player. so i disabled this component and when i go to spawn, in the Start() method i check photonView.IsMine to enable it