① First person View Controller — Sprint, Jump, Crouch (study notes)

d

The first is whether the switch of the lens can be executed, and then the key setting.

You need FOV before and after opening, “defaultFOV” and “zoomFOV”, then open time “timeToZoom”, “isZooming” if it is True, it means that the mirror is opening, and if it is False, it means that the mirror is closing.

Finally, a reference is needed to facilitate the interruption of the control open lens coroutine.

 [Header("Functional Options")]
     [SerializeField] private bool canZoom = true;
 
 [Header("Controls")]
     [SerializeField] private KeyCode zoomKey = KeyCode.Mouse1;
     
 [Header("Zoom Parameters")]
    // Start time
    [SerializeField] private float timeToZoom = 0.35 f;
    // FOV
    [SerializeField, Range (120.0 30.0 f, f)] private float zoomFOV = 30.0 f;
    [SerializeField] private bool isZooming;
    // FOV is the default in normal state
    private float defaultFOV;
    // Execute the FOV change coroutine, similar to the previous coroutine executed by pressing C squat
    private Coroutine zoomCoroutine;
Copy the code

Main code:

private void Awake()
    {
        / / the default FOV
        defaultFOV = playerCamera.fieldOfView;
    }
void Update()
    {

        GroundCheck();
        if (CanMove)
        {
            HandleMovementInput();
            HandleMouseLook();
            if (canJump)
                HandleJump();
            if (canCrouch)
                HandleCrouch();
            if (canUseHeadbob)
                HandleHeadbob();
            // Add lens
            if(canZoom) HandleZoom(); ApplyFinalMovement(); }}private void HandleZoom()
    {
        // Hold down the lens
            // Right click
            if (Input.GetKeyDown(zoomKey) || Input.GetKeyUp(zoomKey))
            {
                // Zooming in
                isZooming = Input.GetKeyDown(zoomKey);
                // If the coroutine is not empty, stop the coroutine in the switch mirror
                if(zoomCoroutine ! =null)
                {
                    StopCoroutine(zoomCoroutine);
                    zoomCoroutine = null; } zoomCoroutine = StartCoroutine(ToggleZoom(isZooming)); }}Copy the code

Coroutines:

It’s almost exactly the same as the coroutine for squatting.

private IEnumerator ToggleZoom(bool isEnter)
    {
        float targetFOV = isEnter ? zoomFOV : defaultFOV;
        float currentFOV = playerCamera.fieldOfView;
        float timeElapsed = 0f;

        while (timeElapsed < timeToZoom)
        {
            playerCamera.fieldOfView = Mathf.Lerp(currentFOV, targetFOV, timeElapsed / timeToZoom);
            timeElapsed += Time.deltaTime;
            yield return null;
        }

        playerCamera.fieldOfView = targetFOV;
        zoomCoroutine = null;
    }
Copy the code

Modify the squat code

Here by the way, the previous issue, squat code modified:

private bool ShouldCrouch => canCrouch && 
(Input.GetKeyDown(holdToCrouchKey) || Input.GetKeyUp(holdToCrouchKey) || Input.GetKeyDown(switchCrouchKey)) 
&& isGrounded;

private void HandleCrouch()
    {
        if (ShouldCrouch)
        {
            if (Input.GetKeyDown(holdToCrouchKey)) isCrouching = true;
            else if(Input.GetKeyUp(holdToCrouchKey)) isCrouching = false;
            elseisCrouching = ! isCrouching;if(crouchCoroutine ! =null)
            {
                StopCoroutine(crouchCoroutine);
                crouchCoroutine = null; } StartCoroutine(CrouchStand()); }}private IEnumerator CrouchStand()
    {
        if (isCrouching && Physics.Raycast(playerCamera.transform.position, transform.up, 1f)) yield break;

        // duringCrouchAnimation = true;

        float timeElapsed = 0;
        float targetHeight = isCrouching ? crouchHeight : standingHeight;
        float currentHeight = characterController.height;
        Vector3 targetCenter = isCrouching ? crouchingCenter : standingCenter;
        Vector3 currentCenter = characterController.center;

        Vector3 targetGroundCheckPosition = isCrouching ? groundCheckCrouchPosition : groundCheckStandingPosition;
        Vector3 currentGroundCheckPosition = groundCheck.localPosition;

        while (timeElapsed < timeToCrouch)
        {
            float chrouchPercentage = timeElapsed / timeToCrouch;
            characterController.height = Mathf.Lerp(currentHeight, targetHeight, chrouchPercentage);
            characterController.center = Vector3.Lerp(currentCenter, targetCenter, chrouchPercentage);

            groundCheck.localPosition = Vector3.Lerp(currentGroundCheckPosition, targetGroundCheckPosition, chrouchPercentage);

            timeElapsed += Time.deltaTime;
            yield return null;
        }

        characterController.height = targetHeight;
        characterController.center = targetCenter;

        groundCheck.localPosition = targetGroundCheckPosition;

        // isCrouching = ! isCrouching;
        // duringCrouchAnimation = false;
    }
Copy the code

The head of swing

[Header("Functional Options")]
    [SerializeField] private bool canUseHeadbob = true;
    
[Header("Headbob Parameters")]
    // How often the head moves up and down while walking
    [SerializeField] private float walkBobSpeed = 10f;
    // How much your head moves up and down while walking
    [SerializeField] private float walkBobAmount = 0.03 f;
    [SerializeField] private float sprintBobSpeed = 15f;
    [SerializeField] private float sprintBobAmount = 0.1 f;
    [SerializeField] private float crouchBobSpeed = 8f;
    [SerializeField] private float crouchBobAmount = 0.025 f;
    // The initial Y position
    private float defaultYPos = 0;
    / / timer
    private float headbobTimer;
    
    private void Awake()
    {
        // Headbob, the initial y position of the camera
        defaultYPos = playerCamera.transform.localPosition.y;
    }
    void Update()
    {

        GroundCheck();
        if (CanMove)
        {
            HandleMovementInput();
            HandleMouseLook();
            if (canJump)
                HandleJump();
            if (canCrouch)
                HandleCrouch();
            if (canUseHeadbob)
                HandleHeadbob();
            if(canZoom) HandleZoom(); ApplyFinalMovement(); }}Copy the code

Swing core code:

Mathf.Sin(headbobTimer) * (isCrouching ? crouchBobAmount : IsSprinting ? sprintBobAmount : walkBobAmount)

Use the sine function, time as the horizontal axis, and then when the time increases, the Y-axis swings between [-1, 1], and then change the Y-axis coordinates of the camera, the effect of up and down swing will appear.

Amount is the Amount of movement up and down and Speed is the Speed at which time moves to the right. The faster the Speed, the faster the movement up and down.

    private void HandleHeadbob()
    {
        if(! isGrounded)return;
        // Moving forward or left or right
        if (Mathf.Abs(moveDirection.x) > 0.1 f || Mathf.Abs(moveDirection.z) > 0.1 f)
        {
            // The speed of swing, squat, sprint, walk are different
            // Sine function, time as the X-axis, the speed of progress, affects the frequency
            headbobTimer += Time.deltaTime * (isCrouching ? crouchBobSpeed : IsSprinting ? sprintBobSpeed : walkBobSpeed);
            playerCamera.transform.localPosition = new Vector3(
                playerCamera.transform.localPosition.x,
                // The upper and lower limits of the swing range are 1, multiplied by the coefficient behind to weaken the swing range, positive and negative influence the directiondefaultYPos + Mathf.Sin(headbobTimer) * (isCrouching ? crouchBobAmount : IsSprinting ? sprintBobAmount : walkBobAmount), playerCamera.transform.localPosition.z ); }}Copy the code

Slope slide

The “Character Controller” will stop you from progressing when you encounter slopes that exceed the slope limit.

But you can jump on it and get stuck on the ramp.

So we need to realize that the character will slide on the ramp.

Realize the principle of

Shoot a ray straight down from the figure (red).

Physics.Raycast(transform.position, Vector3.down, out RaycastHit slopeHit, rayLength)
Copy the code

“SlopeHit” is the ray’s first contact point (purple). Slopehit.normal, and you get the normal vector of the contact point.

    // The downward ray, the normal vector of the detected slope
    hitPointNormal = slopeHit.normal;
Copy the code

So the Angle between the red line and the blue line, ①, is equal to ② and ③, which is the slope of the slope.

The slide is completed by symmetrizing the normal vector along the Y-axis and moving the character in the direction of the red vector.

moveDirection += new Vector3(hitPointNormal.x, -hitPointNormal.y, hitPointNormal.z) * slopeSpeed;
Copy the code

The complete code

    [Header("Functional Options")]
        // Will slide on the ramp
        [SerializeField] private bool willSlideOnSlopes = true;
    [Header("Movement Parameters")]
        // Speed of slide
        [SerializeField] private float slopeSpeed = 8.0 f;
        
    // SLIDING PARAMETERS
    // Probe the length of the ray down the ramp
    [SerializeField] private float rayLength = 2f;
    // Save the ray contact point, the plane normal vector
    private Vector3 hitPointNormal;
    // Whether you need to be in the sliding state
    private bool IsSliding
    {
        get
        {
            // Debug.DrawRay(transform.position, Vector3.down * rayLength, Color.red);
            // On the ground, and the ray detects the plane
            if (isGrounded && Physics.Raycast(transform.position, Vector3.down, out RaycastHit slopeHit, rayLength))
            {
                // The downward ray, the normal vector of the detected slope
                hitPointNormal = slopeHit.normal;
                // The slope of the ramp is greater than the limit
                return Vector3.Angle(Vector3.up, hitPointNormal) > characterController.slopeLimit;
                
                // The following can be used as a test
                // Debug.DrawRay(slopeHit.point, slopeHit.normal, Color.green);
                // Debug.DrawRay(slopeHit.point, new Vector3(hitPointNormal.x, -hitPointNormal.y, hitPointNormal.z), Color.red);
            }
            return false; }}Copy the code

ApplyFinalMovement() applies movement in it.

    private void ApplyFinalMovement()
    {
        if(! isGrounded) { moveDirection.y += gravity * Time.deltaTime; }if (willSlideOnSlopes && IsSliding)
        {
            // Add the ramp direction velocity vector
            moveDirection += new Vector3(hitPointNormal.x, -hitPointNormal.y, hitPointNormal.z) * slopeSpeed;
        }
        characterController.Move(moveDirection * Time.deltaTime);
    }
Copy the code

Modifying ground detection

In this case, stuck on the inclined plane, it will show that it is not on the ground, so it is changed to capsule detection.

private void GroundCheck()
    {
        isGrounded = Physics.CheckCapsule(transform.position, groundCheck.position, characterController.radius, groundLayer);

        if (isGrounded && moveDirection.y < 0.0 f)
        {
            moveDirection.y = 2.0 f; }}Copy the code

Below is the adjustment of “Character Controller” to the capsule. Height refers to the overall height, and radius refers to the radius of the upper and lower hemispheres.

Physics.CheckCapsule(A, B, radius, groundLayer);Copy the code

What this means is, if you take A and B as the center, you draw the upper and lower hemispheres of the capsule, as shown here.

Then the yellow part is the detection range, the card edge does not show the grounding problem is greatly alleviated.