본문 바로가기
Games/DevLogs

[DevLogs] 구체 로봇 플레이어 만들기 - 아이디어와 기본 이동

by NCTP 2024. 5. 28.

"파괴" 를 바탕으로 만드는 게임에 어울리는 플레이어 캐릭터

지금 진행하고 있는 프로젝트의 메인 키워드는 "파괴" 다.

 

플레이어나 지형지물의 Mesh가 게임 진행에 따라 파괴되는 점이 부각되는 메카 게임을 만들고 있었다.

문득, 지금 이 플레이어의 형태나, 나아가서 게임의 구조 자체가 "파괴" 라는 키워드를 제대로 살리지 못하고 있는 것 같다.

지금 진행하던 게임의 플레이어

 

좀 더 빠르고 활동적인 플레이어를 만들고 싶었는데, 마음에 드는 메카 애니메이션 리소스를 찾는 것이 여간 쉬운 일이 아니여서 계속 고민을 하고 있었다.

 

타협에 타협에 타협을 하다보니... 결국 본래 원하던 방향과는 너무나 멀어진 것. "파괴" 라는 핵심에서 멀어지는 것만 같았다.

 

그러던 도중, 과거에 취해 옛날에 들었던 노래들을 듣고 있었다.

마이클 사이런스의 "Recking ball"이라는 노래를 듣다가 가슴뛰는 아이디어가 떠올랐다.

 

레킹 볼라는 파괴와 아주 가까운 물체에 대하여...

 

레킹 볼이라고 하니 오버워치의 레킹볼도 떠올랐고,

오버워치의 레킹볼같은 플레이어가 이곳저곳에 박으면서 다 부숴버리는 게임을 만들어야겠다!

라고 생각하고, 새로운 플레이어를 만들기 시작했다.

 

Metroid Player

 

레퍼런스는 닌텐도 DS의 메트로이드 프라임 헌터의 플레이어로 잡았다. 

플레이어 - 기본 이동

  • WASD : 기본 이동
  • Spacebar : 점프
private void Move()
    {
        float horizontal = Input.GetAxis("Horizontal");
        float vertical = Input.GetAxis("Vertical");

        Vector3 direction = new Vector3(horizontal, 0.0f, vertical);

        _rb.AddForce(direction * speed);

        if(Input.GetKeyDown(KeyCode.Space) && isGrounded)
        {
            _rb.AddForce(0.0f, jumpForce, 0.0f);
        }
        if(Physics.Raycast(transform.position, Vector3.down, out _hitInfo, 0.6f))
        {
            isGrounded = true;
            Debug.Log("Grounded!");
        }
        else
        {
            isGrounded = false;
            Debug.Log("Not Grounded!");
        }

    }

 

구체의 물리적인 느낌을 그대로 살려주기 위해서, Rigidbody의 AddForce를 기본으로 플레이어 입력을 구현했다.

플레이어는 플레이하면서 구형의 로봇을 "굴리면서" 지형을 돌아다니는 기분을 느낄 수 있다.

 

게임의 재미와 연출을 위해서, 게임에서 주어지는 다양한 상황에서 주어진 모든 물리력이 "순간" 사라지는 기능이 필요하다.

  • 점프한 상태에서 그대로 내려찍는 기술
  • 완전히 다른 방향으로 이동하는 기술
  • 등등 ...

이를 위해 rigidbody의 force를 초기화하는 코드를 작성했다.

public void ResetRigidbody()
{
    _rb.velocity = Vector3.zero;
}

 

다른 곳에서 이 함수를 부를 수 있을 거라 생각해서 public으로 선언해주었다.

빨간 큐브가 앞.

 

카메라 - 기본

이제 카메라를 달아줘야 하는데, 간단하게 CamFollow 스크립트를 작성했다.

간혹 플레이어에게 카메라를 붙이고 끝내는 경우가 있긴 했는데,

이렇게 역동적인 움직임이 기대될 때는 따로 떼어놓는 편이 당연히 좋다고 생각한다.

플레이어 상태에 따라 카메라 연출도 줄 수 있고.

 

void FollowPlayer()
    {
        transform.position = Target.transform.position + positionOffSet;
        _targetDirection = Target.transform.position - transform.position;
        _targetRotation = Quaternion.LookRotation(_targetDirection);
        transform.rotation = Quaternion.Slerp(transform.rotation, _targetRotation, _rotationSpeed);
    }

 

지금 예상하고 있는 게임의 무대는 실내가 될 것 같다. 플레이어와 카메라 사이에 벽이나 장애물이 있으면 카메라가 플레이어를 제대로 포착하지 못하게 된다.

따라서 카메라가 벽을 통과하지 않도록 처리를 해줄 필요가 있다.

 + 카메라가 "정확히" 플레이어를 바라보는 것보다, 그 주위를 바라볼 수 있도록 OffSet을 추가했다.

void FollowPlayer()
    {
        transform.position = Target.transform.position + positionOffSet;
        _targetDirection = 
        ((Target.transform.position + directionOffSet) - transform.position).normalized; // 카메라 연출을 위한 OffSet 추가
        _targetRotation = Quaternion.LookRotation(_targetDirection);
        transform.rotation = Quaternion.Slerp(transform.rotation, _targetRotation, _rotationSpeed);

        int layerMask = 1 << LayerMask.NameToLayer("Wall"); // 벽만 감지하도록 LayerMask 정의
        if(Physics.Raycast(transform.position, _targetDirection, out _hitInfo, 10.0f, layerMask))
        {
            Debug.Log("Wall Detected");
        }
    }

 

새로 추가한 OffSet은 가시성을 높여주는 효과도 있지만, positionOffSet과 directionOffSet을 통해 원하는 카메라 연출을 구현하는 데에 사용할 수 있다. (흔들림이나, 줌 인/줌 아웃 등)

 

카메라에 추가한 기능들은 다음과 같다.

  • 줌인 / 줌 아웃
  • 카메라 흔들림 (시간, 흔들림 정도 조절 가능)
void CamShake(float amount, float time)
{
    Vector3 originalOffSet = directionOffSet;
    StartCoroutine(CamShakeCoroutine(amount, time, originalOffSet));
}

IEnumerator CamShakeCoroutine(float amount, float time, Vector3 originalOffSet)
{
    float timer = time;
    while(timer > 0)
    {
        timer -= Time.deltaTime;
        Vector3 noise = 
        new Vector3(Random.Range(-amount, amount), Random.Range(-amount, amount), Random.Range(-amount, amount));
        directionOffSet += noise;
        yield return null;

    }
    if(timer <= 0) directionOffSet = originalOffSet;
}

void CamZoomIn(float amount)
{
    Camera.main.fieldOfView -= amount;

}

void CamZoomOut(float amount)
{
    Camera.main.fieldOfView += amount;
}

카메라 흔들림 / 줌인아웃

 

그냥 CamShake만 불러와도 바로 카메라를 흔들 수 있도록 구현했다. 언제든 간편하게 불러올 수 있는 편의성이 중요하다고 생각한다.

 

이번에는 플레이어의 기본 입력과 카메라 기능들에 대해 구현해보았다.

다음에는 충돌 판정과, 입체기동 같은 화려한 움직임들과 UI를 구현하자.

 

 

댓글