23. 버튼 하나로 기능 2개 통제 (회피 & 대시)

회피 기능과 대시 기능을 만들었는데.. 기획자님의 요구사항은 이랬다.

  1. 버튼을 입력하고 있으면 대시 기능
  2. 버튼을 빠르게 두 번 입력하면 회피 기능

결국엔 기능을 호출하려면 Update에서 대시나 회피 기능을 호출해야 된다는 건데 버튼 하나로 통제한다는 것이 쉽지가 않았다.

왜냐하면 가끔 두번 호출되기도 하고 GetKeyDown이나 GetKeyUp이 아예 호출 안되는 경우도 있기 때문이다.

그래서 인터넷 검색을 해보니 나와 비슷한 고민을 하는 사람이 많았다. 해결법으로 코루틴을 추천한 사람이 있었는데 리소스는 많이 먹겠지만 확실히 좋은 방법이긴 했다.

private void Start()
{
    StartCoroutine(DodgeListener());
}

private IEnumerator DodgeListener()
{
    while(true)
    {
        if(Input.GetKeyDown(KeyCode.J))
        {
            yield return DoubleClick();
        }

        yield return null;
    }
}

Start 문으로 게임이 시작되고 DodgeListener()을 호출하는데 반복문으로 인해 게임이 켜져있는 동안에는 지속적으로 ‘J’ 버튼이 입력되는지 체크한다.

‘J’ 버튼을 입력하면 DoubleClick()을 호출한다.

private IEnumerator DoubleClick()
{//달리기 버튼을 2번 누르면 회피 기능이 호출된다
    yield return new WaitForEndOfFrame();

    float count = 0f;

    while (count < dodgeButtonTime)
    {
        if(Input.GetKeyDown(KeyCode.J) &&              //'J'버튼 눌렀을 때
            dodgeTimeCheck >= dodgeTime &&             //dodgeTimeCheck가 dodgeTime 보다 크고
            dodgeNeedEnergy < playerStat.energy &&     //필요 에너지가 충분하고
            !GameManager.GetInstance().playerIsDodge)  //playerIsDodge가 false라면
        {
            GameManager.GetInstance().playerIsDodge = true;

            dodgeTimeCheck = 0;

            Dodge();
            yield break;
        }
        else if(Input.GetKeyDown(KeyCode.J) &&
                dodgeNeedEnergy > playerStat.energy)
        {
            Debug.Log("에너지가 부족합니다!");
        }

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

DoubleClick()이 호출되면 count 변수를 지역변수로 선언하는데 반복문이 호출될때마다 Time.deltaTime을 더해 count 변수는 시간이 흘러가는 것과 같다고 볼 수 있다.

dodgeButtonTime 만큼 시간이 흐르면 반복문은 종료되고 그 시간 안에 ‘J’ 버튼을 입력하고 그 밖에 다른 조건들을 충족하면 회피 기능을 호출한다.

코루틴 두 개를 활용해서 “버튼을 빠르게 두 번 입력하면 회피 기능”을 구현했다.

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

public class PlayerMove : MonoBehaviour
{
    private PlayerStat playerStat;
    public float jumpVelocity;
    private float inputX;
    private float inputZ;
    private float attackAngle;
    public Vector3 directionVec;

    ////////////
    // 회피 기능
    ////////////
    /* 1. 회피 거리는 인스펙터에서 수정할 수 있다.
     * 2. 회피 거리 만큼 순간이동 한다.
     * 3. 회피 동안에는 무적이다.(0.2초 정도?) */
    ////////////////////////////
    // 같은 버튼을 빠르게 눌렀을 때
    // 회피가 발동되게 하려면?
    ////////////////////////////
    /* 1. 코루틴을 통해 반복문을 상시 호출
     * 2. 반복문은 'J'키를 입력한다면 다른 코루틴을 호출
     * 3. 반복문을 통해 dodgeButtonTime 시간 안에 'J'키를 입력한다면 회피 기능 호출
     * 4. 회피 기능이 호출된다면 bool 변수 true
     * 5. Update 함수에서 dodgeButtonTime 만큼의 시간이 지나면 bool 변수 false */
    private Rigidbody rigidbody;
    public float dodgeDistance;
    public float dodgeTime;
    public float dodgeNeedEnergy;
    private float dodgeTimeCheck;
    public float dodgeButtonTime;          // 회피 버튼을 2번 눌러야 되는 시간
    private bool dodge;                    // bool 변수


    ///////////
    // 대시 기능
    ///////////
    /* 1. 기본 이동에서 속도를 더 빠르게 이동
     * 2. 버튼을 누르고 있으면 대시
     * 3. 플레이어 에너지가 회복되지도, 감소하지도 않는다. */
    public float dashSpeed;         // 대시 속도
    //private bool playerIsDash;    // bool 변수는 게임매니저에 있다


    private void Start() {
        playerStat = GetComponent<PlayerStat>();

        // 게임이 시작되면 아래방향 처다보기
        directionVec = new Vector3(0, 0, -2.0f);
        attackAngle = 180.0f;
        GameManager.GetInstance().attackAngle = attackAngle;

        ////////////////
        // 대쉬 기능 관리
        ////////////////
        rigidbody = GetComponent<Rigidbody>();


        ////////////
        // 회피 기능
        ////////////
        StartCoroutine(DodgeListener());
    }

    private void FixedUpdate() {

        dodgeTimeCheck += Time.deltaTime;

        /////////
        // 움직임
        /////////
        // 공격, 회피 중이 아닐 때 Move() 가능
        if(!GameManager.GetInstance().playerIsBasicAttack &&
           !GameManager.GetInstance().playerIsDodge) 
            Move();

        ///////////
        // 대시 기능
        ///////////
        if(Input.GetKey(KeyCode.J))
            Dash();
        else
            GameManager.GetInstance().playerIsDash = false;


        ////////////
        // 회피 기능
        ////////////
        if(dodgeTimeCheck > dodgeTime)
        {
            GameManager.GetInstance().playerIsDodge = false;
        }
            


        ///////////////
        // 바라보는 방향
        ///////////////
        Direction();
    }

    public void Move()
    {
        inputX = Input.GetAxisRaw("Horizontal");
        inputZ = Input.GetAxisRaw("Vertical");

        Vector3 velocity = new Vector3(inputX, jumpVelocity, inputZ);

        transform.position += velocity * playerStat.speed * Time.deltaTime;

        if(velocity == new Vector3(0,0,0)) GameManager.GetInstance().playerIsMove = false;
        else GameManager.GetInstance().playerIsMove = true;
    }

    // 대시 //
    public void Dash()
    {
        GameManager.GetInstance().playerIsDash = true; // bool 변수 true;


        inputX = Input.GetAxisRaw("Horizontal");
        inputZ = Input.GetAxisRaw("Vertical");

        Vector3 velocity = new Vector3(inputX, jumpVelocity, inputZ);

        // 대시 스피드를 곱하여 더 빠르게 이동
        transform.position += velocity * playerStat.speed * dashSpeed * Time.deltaTime;

        if(velocity == new Vector3(0,0,0)) GameManager.GetInstance().playerIsMove = false;
        else GameManager.GetInstance().playerIsMove = true;


    }


    public void Dodge()
    {
        playerStat.energy -= dodgeNeedEnergy;//회피 시 에너지 소비

        RaycastHit hit;

        Debug.DrawRay(transform.position, directionVec.normalized * dodgeDistance);

        if(Physics.Raycast(transform.position, directionVec.normalized, out hit, dodgeDistance))
        {
            GameObject hitTarget = hit.collider.gameObject;
            Debug.Log("충돌위치" + hit.point);
            Debug.Log("충돌한 오브젝트 위치" + hitTarget.transform.position);

            transform.position = hit.point;
        }
        else
        {
            transform.position += directionVec.normalized * dodgeDistance;
        }


        //transform.position += directionVec.normalized * dodgeDistance;
        
        //transform.position += directionVec.normalized * dodgeDistance / 10;
        //rigidbody.velocity = directionVec.normalized * dodgeDistance;
        //rigidbody.AddForce(directionVec.normalized * dodgeDistance, ForceMode.Impulse);
    }

    public void Direction()
    {
        if (inputX == -1 && inputZ == 1)//UpLeft
        {
            directionVec = new Vector3(-1.14f, 0, 1.14f);
            attackAngle = 315.0f;
        }
        if (inputX == 0 && inputZ == 1)//Up
        {
            directionVec = new Vector3(0, 0, 2.0f);
            attackAngle = 0.0f;
        }
        if (inputX == 1 && inputZ == 1)//UpRight
        {
            directionVec = new Vector3(1.14f, 0, 1.14f);
            attackAngle = 45.0f;
        }
        if (inputX == -1 && inputZ == 0)//Left
        {
            directionVec = new Vector3(-2.0f, 0, 0);
            attackAngle = 270.0f;
        }
        if (inputX == 0 && inputZ == 0)//Idle
        {
            directionVec = new Vector3(0, 0, 0);
        }
        if (inputX == 1 && inputZ == 0)//Right
        {
            directionVec = new Vector3(2.0f, 0, 0);
            attackAngle = 90.0f;
        }
        if (inputX == -1 && inputZ == -1)//DownLeft
        {
            directionVec = new Vector3(-1.14f, 0, -1.14f);
            attackAngle = 225.0f;
        }
        if (inputX == 0 && inputZ == -1)//Down
        {
            directionVec = new Vector3(0, 0, -2.0f);
            attackAngle = 180.0f;
        }
        if (inputX == 1 && inputZ == -1)//DownRight
        {
            directionVec = new Vector3(1.14f, 0, -1.14f);
            attackAngle = 135.0f;
        }

        //게임매니저에 플레이어 방향을 넣자.
        if(directionVec != new Vector3(0, 0, 0))
            GameManager.GetInstance().playerDirection = directionVec;
        GameManager.GetInstance().attackAngle = attackAngle;
    }

    private IEnumerator DodgeListener()
    {
        while (true)
        {
            if(Input.GetKeyDown(KeyCode.J))
            {
                yield return DoubleClick();
            }

            yield return null;
        }
    }

    private IEnumerator DoubleClick()
    {//달리기 버튼을 2번 누르면 회피 기능이 호출된다
        yield return new WaitForEndOfFrame();

        float count = 0f;

        while (count < dodgeButtonTime)
        {
            if(Input.GetKeyDown(KeyCode.J) &&               //'J'버튼 눌렀을 때
                dodgeTimeCheck >= dodgeTime &&               //dodgeTimeCheck가 dodgeTime 보다 크고
                dodgeNeedEnergy < playerStat.energy &&       //필요 에너지가 충분하고
                !GameManager.GetInstance().playerIsDodge)    //playerIsDodge가 false라면
            {
                GameManager.GetInstance().playerIsDodge = true;

                dodgeTimeCheck = 0;

                Dodge();
                yield break;
            }
            else if(Input.GetKeyDown(KeyCode.J) &&
                    dodgeNeedEnergy > playerStat.energy)
            {
                Debug.Log("에너지가 부족합니다!");
            }

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

    void OnDrawGizmos()
    {
        Gizmos.color = Color.magenta;
        Gizmos.DrawLine(transform.position,
                        GameManager.GetInstance().playerDirection.normalized + transform.position);
    }
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다