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. Dismiss Notice

why use projectOnPlane() in thirdPersonCharacter script of the standard assets example project?

Discussion in 'Scripting' started by Deleted User, Apr 10, 2015.

  1. Deleted User

    Deleted User

    Guest

    i mean i understand what the function does (its at line 55 in the script). but why is it used in the script mentioned above. shouldnt we only be concerned with the move vector on the xz plane? because the only thing that i see this line of code do is to make the player move slower up an inclined surface. and one more question. when the player is running on an inclined surface which is rotated around its centre (as shown with a blue circle in the following image) with respect to the global x-axis, the player moves slower, but when he is on an inclined surface which is rotated around its centere but with respect to the global z-axis (red circle in the following image), he doesnt slow down, in fact the move vector doesnt change at all and stays at (0,0,1) rather than (0,-x,y), with x and y being two floats.
    a link to the image:
    https://www.dropbox.com/s/6vgelt21b8vrxhx/Screenshot 2015-04-10 21.32.52.png?dl=0
    the script:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. namespace UnityStandardAssets.Characters.ThirdPerson
    4. {
    5.     [RequireComponent(typeof(Rigidbody))]
    6.     [RequireComponent(typeof(CapsuleCollider))]
    7.     [RequireComponent(typeof(Animator))]
    8.     public class ThirdPersonCharacter : MonoBehaviour
    9.     {
    10.         [SerializeField] float m_MovingTurnSpeed = 360;
    11.         [SerializeField] float m_StationaryTurnSpeed = 180;
    12.         [SerializeField] float m_JumpPower = 12f;
    13.         [Range(1f, 4f)][SerializeField] float m_GravityMultiplier = 2f;
    14.         [SerializeField] float m_RunCycleLegOffset = 0.2f; //specific to the character in sample assets, will need to be modified to work with others
    15.         [SerializeField] float m_MoveSpeedMultiplier = 1f;
    16.         [SerializeField] float m_AnimSpeedMultiplier = 1f;
    17.         [SerializeField] float m_GroundCheckDistance = 0.1f;
    18.  
    19.         Rigidbody m_Rigidbody;
    20.         Animator m_Animator;
    21.         bool m_IsGrounded;
    22.         float m_OrigGroundCheckDistance;
    23.         const float k_Half = 0.5f;
    24.         float m_TurnAmount;
    25.         float m_ForwardAmount;
    26.         Vector3 m_GroundNormal;
    27.         float m_CapsuleHeight;
    28.         Vector3 m_CapsuleCenter;
    29.         CapsuleCollider m_Capsule;
    30.         bool m_Crouching;
    31.  
    32.  
    33.         void Start()
    34.         {
    35.             m_Animator = GetComponent<Animator>();
    36.             m_Rigidbody = GetComponent<Rigidbody>();
    37.             m_Capsule = GetComponent<CapsuleCollider>();
    38.             m_CapsuleHeight = m_Capsule.height;
    39.             m_CapsuleCenter = m_Capsule.center;
    40.  
    41.             m_Rigidbody.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationY | RigidbodyConstraints.FreezeRotationZ;
    42.             m_OrigGroundCheckDistance = m_GroundCheckDistance;
    43.         }
    44.  
    45.  
    46.         public void Move(Vector3 move, bool crouch, bool jump)
    47.         {
    48.  
    49.             // convert the world relative moveInput vector into a local-relative
    50.             // turn amount and forward amount required to head in the desired
    51.             // direction.
    52.             if (move.magnitude > 1f) move.Normalize();
    53.             move = transform.InverseTransformDirection(move);
    54.             CheckGroundStatus();
    55.             move = Vector3.ProjectOnPlane(move, m_GroundNormal);
    56.             m_TurnAmount = Mathf.Atan2(move.x, move.z);
    57.             m_ForwardAmount = move.z;
    58.  
    59.             ApplyExtraTurnRotation();
    60.  
    61.             // control and velocity handling is different when grounded and airborne:
    62.             if (m_IsGrounded)
    63.             {
    64.                 HandleGroundedMovement(crouch, jump);
    65.             }
    66.             else
    67.             {
    68.                 HandleAirborneMovement();
    69.             }
    70.  
    71.             ScaleCapsuleForCrouching(crouch);
    72.             PreventStandingInLowHeadroom();
    73.  
    74.             // send input and other state parameters to the animator
    75.             UpdateAnimator(move);
    76.         }
    77.  
    78.  
    79.         void ScaleCapsuleForCrouching(bool crouch)
    80.         {
    81.             if (m_IsGrounded && crouch)
    82.             {
    83.                 if (m_Crouching) return;
    84.                 m_Capsule.height = m_Capsule.height / 2f;
    85.                 m_Capsule.center = m_Capsule.center / 2f;
    86.                 m_Crouching = true;
    87.             }
    88.             else
    89.             {
    90.                 Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
    91.                 float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
    92.                 if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength))
    93.                 {
    94.                     m_Crouching = true;
    95.                     return;
    96.                 }
    97.                 m_Capsule.height = m_CapsuleHeight;
    98.                 m_Capsule.center = m_CapsuleCenter;
    99.                 m_Crouching = false;
    100.             }
    101.         }
    102.  
    103.         void PreventStandingInLowHeadroom()
    104.         {
    105.             // prevent standing up in crouch-only zones
    106.             if (!m_Crouching)
    107.             {
    108.                 Ray crouchRay = new Ray(m_Rigidbody.position + Vector3.up * m_Capsule.radius * k_Half, Vector3.up);
    109.                 float crouchRayLength = m_CapsuleHeight - m_Capsule.radius * k_Half;
    110.                 if (Physics.SphereCast(crouchRay, m_Capsule.radius * k_Half, crouchRayLength))
    111.                 {
    112.                     m_Crouching = true;
    113.                 }
    114.             }
    115.         }
    116.  
    117.  
    118.         void UpdateAnimator(Vector3 move)
    119.         {
    120.             // update the animator parameters
    121.             m_Animator.SetFloat("Forward", m_ForwardAmount, 0.1f, Time.deltaTime);
    122.             m_Animator.SetFloat("Turn", m_TurnAmount, 0.1f, Time.deltaTime);
    123.             m_Animator.SetBool("Crouch", m_Crouching);
    124.             m_Animator.SetBool("OnGround", m_IsGrounded);
    125.             if (!m_IsGrounded)
    126.             {
    127.                 m_Animator.SetFloat("Jump", m_Rigidbody.velocity.y);
    128.             }
    129.  
    130.             // calculate which leg is behind, so as to leave that leg trailing in the jump animation
    131.             // (This code is reliant on the specific run cycle offset in our animations,
    132.             // and assumes one leg passes the other at the normalized clip times of 0.0 and 0.5)
    133.             float runCycle =
    134.                 Mathf.Repeat(
    135.                     m_Animator.GetCurrentAnimatorStateInfo(0).normalizedTime + m_RunCycleLegOffset, 1);
    136.             float jumpLeg = (runCycle < k_Half ? 1 : -1) * m_ForwardAmount;
    137.             if (m_IsGrounded)
    138.             {
    139.                 m_Animator.SetFloat("JumpLeg", jumpLeg);
    140.             }
    141.  
    142.             // the anim speed multiplier allows the overall speed of walking/running to be tweaked in the inspector,
    143.             // which affects the movement speed because of the root motion.
    144.             if (m_IsGrounded && move.magnitude > 0)
    145.             {
    146.                 m_Animator.speed = m_AnimSpeedMultiplier;
    147.             }
    148.             else
    149.             {
    150.                 // don't use that while airborne
    151.                 m_Animator.speed = 1;
    152.             }
    153.         }
    154.  
    155.  
    156.         void HandleAirborneMovement()
    157.         {
    158.             // apply extra gravity from multiplier:
    159.             Vector3 extraGravityForce = (Physics.gravity * m_GravityMultiplier) - Physics.gravity;
    160.             m_Rigidbody.AddForce(extraGravityForce);
    161.  
    162.             m_GroundCheckDistance = m_Rigidbody.velocity.y < 0 ? m_OrigGroundCheckDistance : 0.01f;
    163.         }
    164.  
    165.  
    166.         void HandleGroundedMovement(bool crouch, bool jump)
    167.         {
    168.             // check whether conditions are right to allow a jump:
    169.             if (jump && !crouch && m_Animator.GetCurrentAnimatorStateInfo(0).IsName("Grounded"))
    170.             {
    171.                 // jump!
    172.                 m_Rigidbody.velocity = new Vector3(m_Rigidbody.velocity.x, m_JumpPower, m_Rigidbody.velocity.z);
    173.                 m_IsGrounded = false;
    174.                 m_Animator.applyRootMotion = false;
    175.                 m_GroundCheckDistance = 0.1f;
    176.             }
    177.         }
    178.  
    179.         void ApplyExtraTurnRotation()
    180.         {
    181.             // help the character turn faster (this is in addition to root rotation in the animation)
    182.             float turnSpeed = Mathf.Lerp(m_StationaryTurnSpeed, m_MovingTurnSpeed, m_ForwardAmount);
    183.             transform.Rotate(0, m_TurnAmount * turnSpeed * Time.deltaTime, 0);
    184.         }
    185.  
    186.  
    187.         public void OnAnimatorMove()
    188.         {
    189.             // we implement this function to override the default root motion.
    190.             // this allows us to modify the positional speed before it's applied.
    191.             if (m_IsGrounded && Time.deltaTime > 0)
    192.             {
    193.                 Vector3 v = (m_Animator.deltaPosition * m_MoveSpeedMultiplier) / Time.deltaTime;
    194.  
    195.                 // we preserve the existing y part of the current velocity.
    196.                 v.y = m_Rigidbody.velocity.y;
    197.                 m_Rigidbody.velocity = v;
    198.             }
    199.         }
    200.  
    201.  
    202.         void CheckGroundStatus()
    203.         {
    204.             RaycastHit hitInfo;
    205. #if UNITY_EDITOR
    206.             // helper to visualise the ground check ray in the scene view
    207.             Debug.DrawLine(transform.position + (Vector3.up * 0.1f), transform.position + (Vector3.up * 0.1f) + (Vector3.down * m_GroundCheckDistance));
    208. #endif
    209.             // 0.1f is a small offset to start the ray from inside the character
    210.             // it is also good to note that the transform position in the sample assets is at the base of the character
    211.             if (Physics.Raycast(transform.position + (Vector3.up * 0.1f), Vector3.down, out hitInfo, m_GroundCheckDistance))
    212.             {
    213.                 m_GroundNormal = hitInfo.normal;
    214.                 m_IsGrounded = true;
    215.                 m_Animator.applyRootMotion = true;
    216.             }
    217.             else
    218.             {
    219.                 m_IsGrounded = false;
    220.                 m_GroundNormal = Vector3.up;
    221.                 m_Animator.applyRootMotion = false;
    222.             }
    223.         }
    224.     }
    225. }
    226.  
     
    Last edited by a moderator: Apr 10, 2015
  2. iuripujol

    iuripujol

    Joined:
    Nov 26, 2013
    Posts:
    10
    Line 55 ProjectOnPlane is using simple words detecting the surface so the character keeps sticked to the ground, for example: a character going up, in full speed (sprint or running), a little mountain won't make a little jump due to the rigidbody velocity that it is carrying thanks to the ProjectOnPlane vector, otherwise it'd reach the top and do a jump because of its rigidbody inertia.
     
    mikeNspired likes this.
  3. iuripujol

    iuripujol

    Joined:
    Nov 26, 2013
    Posts:
    10
    The second question is because the argument ProjectOnPlane doesn't work that much good for a character, use Vector3.Angle and modify the speed through the angle... the thing should look like this if you use the ground raycast:

    float Angle = 90 - Mathf.Abs(Vector3.Angle(transform.position, hit.normal));//by using this you obtain a value which is max 90 degrees and minimum 0.
    move *= 0.5f + (Angle/90f)/2;//then you divide the float Angle by 90 so you obtain values max 1 and min 0, then you divide this by 0.5f to obtain values 0.5 (0.5 + 0 = 0.5 --> the character will walk), or 1 (0.5 + 0.5 = 1 --> run in the third person character animator).
     
    Last edited: Aug 22, 2016
  4. jackknife32

    jackknife32

    Joined:
    Sep 28, 2017
    Posts:
    3
    something I did was I calculate the angle between the GroundNormal and the character's transform.forward and then performed the ProjectOnPlane calculation if the calculated angle was above or below a certain threshold. This calculation made it so that my character ran at full speed up inclined planes however when the plane became too steep it applies the ProjectOnPlane calculation and stops the character from running up the steep incline.

    Edit: This works fine for when you are moving directly towards a slope. Unfortunately I found that if you run perpendicular to a slope it allows you to easily run up a steep slope....

    Edit2: I changed the angle logic to from 125 to
    if (planeAngle > 110) 
    and it works much better now

    Here is what I did:
    Code (CSharp):
    1. float planeAngle = Vector3.Angle(transform.forward, m_GroundNormal);
    2. if (planeAngle > 110)
    3.      move = Vector3.ProjectOnPlane(move, m_GroundNormal);
     
    Last edited: Jul 12, 2018