[unity] 유니티 게임 따라 만들기2 (심화 캐릭터 움직임)

김동민·2021년 9월 25일
0

https://youtu.be/uandR5M30ho [유튜버: 케이디]님의 유니티 3D강좌 영상을 보고 따라하면서 유니티 공부용으로 제작한 게임입니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    // 스피드 조정 변수
    [SerializeField]
    private float walkSpeed;
    [SerializeField]
    private float runSpeed;
    [SerializeField]
    private float crouchSpeed;


    private float applySpeed;

    [SerializeField]
    private float jumpForce;


    //상태변수
    private bool isRun = false;
    private bool isCrouch = false;
    private bool isGround = true;

    // 앉았을 때 얼마나 앉을지 결정하기
    [SerializeField]
    private float crouchPosY;
    private float originPosY;
    private float applyCrouchPosY;


    // 땅 착지 상태
    private CapsuleCollider capsuleCollider;

    //민감도
    [SerializeField]
    private float lookSensitivity;

    //카메라 한계
    [SerializeField]
    private float cameraRotationLimit;
    private float currentCameraRotationX = 0;

    //필요한 컴포넌트
    [SerializeField]
    private Camera theCamera;

    private Rigidbody myRigid;

    // Start is called before the first frame update
    void Start()
    {
        capsuleCollider = GetComponent<CapsuleCollider>();
        myRigid = GetComponent<Rigidbody>();
        applySpeed = walkSpeed;

        originPosY = theCamera.transform.localPosition.y;
        applyCrouchPosY = originPosY;
    }

    // Update is called once per frame
    void Update()
    {
        IsGround();
        TryJump();
        TryRun();
        TryCrouch();
        Move();
        CameraRotation();
        CharacterRotation();
    }

    //앉기 시도
    private void TryCrouch()
    {
        if(Input.GetKeyDown(KeyCode.LeftControl))
        {
            Crouch();
        }
    }


    //앉기 동작
    private void Crouch()
    {
        isCrouch = !isCrouch;

        if(isCrouch)
        {
            applySpeed = crouchSpeed;
            applyCrouchPosY = crouchPosY;
        }
        else
        {
            applySpeed = walkSpeed;
            applyCrouchPosY = originPosY;
        }

       StartCoroutine(CrouchCoroutine());
    }


    //부드러운 앉기 동작
    IEnumerator CrouchCoroutine()
    {
        float _posY = theCamera.transform.localPosition.y;
        int count = 0;

        while (_posY != applyCrouchPosY)
        {
            count++;
             _posY = Mathf.Lerp(_posY, applyCrouchPosY, 0.1f);
             theCamera.transform.localPosition = new Vector3(0, _posY, 0);
             if (count > 15)
                 break; 
             yield return null;
        }
        theCamera.transform.localPosition = new Vector3(0, applyCrouchPosY, 0f);
    }

    // 지면 닿아 있는지 확인
    private void IsGround()
    {
        isGround = Physics.Raycast(transform.position, Vector3.down, capsuleCollider.bounds.extents.y + 0.1f);
    }

    //점프 시도
    private void TryJump()
    {
        if (Input.GetKeyDown(KeyCode.Space) && isGround)
        {
            Jump();
        }
    }

    //점프 실행
    private void Jump()
    {
        //앉아 있을때 점프하면 일어나기
        if (isCrouch)
        Crouch();

        myRigid.velocity = transform.up * jumpForce;
    }

    //달리기 시도
    private void TryRun()
    {
        if (Input.GetKey(KeyCode.LeftShift))
        {
            Running();
        }
        if (Input.GetKeyUp(KeyCode.LeftShift))
        {
            RunningCancel();
        }
    }

    //달리기 시도
    private void Running()
    {
        //앉아 있다가 달리면 일어나기
        if (isCrouch)
        Crouch();
        isRun = true;
        applySpeed = runSpeed;
    }

    //달리기 취소
    private void RunningCancel()
    {
        isRun = false;
        applySpeed = walkSpeed;
    }

    //움직이기
    private void Move()
    {
        //플레이어가 움직는 값을 설정하기
        float _moveDirX = Input.GetAxisRaw("Horizontal");
        float _moveDriZ = Input.GetAxisRaw("Vertical");

        Vector3 _moveHorizontal = transform.right * _moveDirX;
        Vector3 _moveVertical = transform.forward * _moveDriZ;

        Vector3 _velocity = (_moveHorizontal + _moveVertical).normalized * applySpeed;

       myRigid.MovePosition(transform.position + _velocity * Time.deltaTime);
    }

    //캐릭터를 좌우로 움직이기
    private void CharacterRotation()
    {
        
        float _yRotation = Input.GetAxisRaw("Mouse X");
        Vector3 _characterRotationY = new Vector3(0f, _yRotation, 0f) * lookSensitivity;
        myRigid.MoveRotation(myRigid.rotation * Quaternion.Euler(_characterRotationY));
    }

    //카메라 상하로 움직이기
    private void CameraRotation()
    {

        
        float _xRotation = Input.GetAxisRaw("Mouse Y");
        float _cameraRotationX = _xRotation * lookSensitivity;
        currentCameraRotationX -= _cameraRotationX;
        currentCameraRotationX = Mathf.Clamp(currentCameraRotationX, -cameraRotationLimit, cameraRotationLimit);

        theCamera.transform.localEulerAngles = new Vector3(currentCameraRotationX, 0f, 0f);
    }

    
}

1편에서는 캐릭터가 방향키로 움직이는 방법을 구현하는 방법을 알아봤다.
2편에서는 점프도 하고 앉기도 하고 각 동작을 취할때 뚝 끊겨서 이동하지 않게 자연스럽게 카메라의 시야가 이동하도록 하는 방법을 알아 보는 내용이다.

//속도 조절
[SerializeField]
private float crouchSpeed;
private float applySpeed;
[SerializeField]
private float jumpForce;

//상태변수
private bool isRun = false;
private bool isCrouch = false;
private bool isGround = true;

// 앉았을 때 얼마나 앉을지 결정하기
[SerializeField]
private float crouchPosY;
private float originPosY;
private float applyCrouchPosY;
// 땅 착지 상태
private CapsuleCollider capsuleCollider;

각 동작을 하기 위한 변수들을 SerializeField에 넣어 주고 shift키와 control키를 눌렀다가 땠을 때 달리고 앉는 동작을 바꾸기 위해 참 거짓 으로 처음 설정을 해준다.

//앉기 시도
private void TryCrouch()
{
if(Input.GetKeyDown(KeyCode.LeftControl))
{
Crouch();
}
}

앉기 시도를 하면 Crouch함수를 실행하도록 하고

//앉기 동작
private void Crouch()
{
isCrouch = !isCrouch;
if(isCrouch)
{
applySpeed = crouchSpeed;
applyCrouchPosY = crouchPosY;
}
else
{
applySpeed = walkSpeed;
applyCrouchPosY = originPosY;
}
StartCoroutine(CrouchCoroutine());
}

실행했을 때 참이면 거짓으로 거짓이면 참으로 키를 누를 때마다 앉았다가 서는 상태의 변화를 할 수 있다.
새로 만든 applaySpeed를 이용하여 상태에 따라 속도가 바뀌도록 할 것이다. 그렇지 않다면 밑에 나올 1편에서 만든 속도 값을 상태에 따라 따로 Vector3값을 줘야하기 때문에 이렇게 따로 applySpeed로 만들면 번거롭지 않게 상태에 따른 속도를 바꿀 수 있다.

Vector3 _velocity = (_moveHorizontal + _moveVertical).normalized * applySpeed;

이 앉기 동작이나 다시 서는 동작을 수행할 때 좌표값이 바뀌면서 시야가 한번에 바뀌게 되는데
IEnumerator를 쓰면 속도가 그래프로 곡선을 띄면서 속도가 자연스럽게 사용자에게 보여진다.
유니티는 함수가 병행실행이 안돼서 Crouch를 실행 할 때 IEnumerator도 같이 실행되는 것 처럼 보이지만 컴퓨터가 빠르게 두개의 함수를 번갈아 가며 계산하기 때문에 같이 실행되는 것처럼 보이는 것이라고 한다.

IEnumerator CrouchCoroutine()
{
float _posY = theCamera.transform.localPosition.y;
int count = 0;
while (_posY != applyCrouchPosY)
{
count++;
_posY = Mathf.Lerp(_posY, applyCrouchPosY, 0.1f);
theCamera.transform.localPosition = new Vector3(0, _posY, 0);
if (count > 15)
break;
yield return null;
}
theCamera.transform.localPosition = new Vector3(0, applyCrouchPosY, 0f);
}

다음은 점프를 실행하는 함수이다. 점프를 하려면 일단 플레이어가 땅에 맞닿아 있어야 점프를 할 수 있어야 한다. 그렇기에 플레이어가 땅에 닿아 잇는지 검사하는 함수가 필요하다.
position을 쓰지 않고 Vector3.down를 쓴 이유는 점프의 기준이 플레이어가 아닌 무조건 땅으로 인식을 하게끔 만든 것이다.

// 지면 닿아 있는지 확인
private void IsGround()
{
isGround = Physics.Raycast(transform.position, Vector3.down, capsuleCollider.bounds.extents.y + 0.1f);
}

점프를 할 때 스페이스바키를 누름과(and) 땅에 닿아 있다면 jump를 실행하고

//점프 시도
private void TryJump()
{
if (Input.GetKeyDown(KeyCode.Space) && isGround)
{
Jump();
}
}

점프를 실행한다. 이때 속도롤 velocity를 이용하여 순간적으로 뛰게끔 만들었다.

//점프 실행
private void Jump()
{
//앉아 있을때 점프하면 일어나기
if (isCrouch)
Crouch();

    myRigid.velocity = transform.up * jumpForce;
}


그리고 유니티에서 만들어볼 맵을 위한 지형만들기로 맵에 산지역과 평지를 만들고 영상에서 제공하는 3D 나무, 풀, 돌을 시험삼아 1개씩 배치를 해 보았다.

profile
상상을 현실로 만들어내는 것이 즐겁습니다. 새로운 지식을 학습하고 기존의 지식들이 변화하는 과정들이 즐겁습니다. 개발의 과정은 개발자의 편의를 고려하고 결과는 사용자의 편의를 고려합니다.

0개의 댓글