소소한 나의 하루들

2d 플랫포머(3) - 플레이어 이동 구현하기 본문

개발/유니티

2d 플랫포머(3) - 플레이어 이동 구현하기

소소한 나의 하루 2024. 1. 22. 15:34

출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared

 

📚 유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com

★Hierarchy에서 자식 오브젝트의 Position은 ‘부모 오브젝트’에 대한 상대좌표이다.

 

RigidBody 컴포넌트

https://docs.unity3d.com/kr/2023.2/Manual/rigidbody-physics-section.html

 

리지드바디 물리 - Unity 매뉴얼

물리 시뮬레이션에서 리지드바디는 움직임, 중력, 충돌과 같은 물리 기반 동작을 가능하게 합니다.

docs.unity3d.com

Rigidbody는 GameObject가 물리 제어로 동작하게 합니다. 리지드바디는 힘과 토크를 받아 오브젝트가 사실적으로 움직이도록 해줍니다. 리지드바디가 포함된 모든 게임 오브젝트는 중력의 영향을 받아야 하며 스크립팅을 통해 가해진 힘으로 움직이거나 NVIDIA PhysX 물리 엔진을 통해 다른 오브젝트와 상호 작용해야 합니다.

물리를 사용할 때는 오브젝트의 트랜스폼을 직접 변경하면 안 된다는 사실을 기억해야합니다.

※일반적으로 동일한 게임 오브젝트의 RigidBody와 Transform을 모두 조작하지 않고 둘 중 하나만 조작해야한다.

트랜스폼 조작과 리지드바디 조작의 가장 큰 차이점은 힘의 사용입니다. 리지드바디는 힘과 토크를 받을 수 있지만 트랜스폼은 그렇지 않습니다. 트랜스폼도 트랜스폼되고 회전할 수는 있지만 물리를 사용할 때와는 다릅니다. 한 번 직접 해보면 그 차이가 보일 것입니다. 리지드바디에 힘/토크를 더하면 오브젝트 Transform 컴포넌트의 포지션과 회전을 바꿉니다. 그러므로 둘 중에서 하나만을 사용해야 하는 것입니다. 트랜스폼을 바꾸면서 물리를 사용하면 충돌 및 기타 연산에 문제가 발생할 수 있습니다.

중력의 영향을 받고 스크립팅을 통해 힘을 받을 수 있지만 원하는 대로 정확하게 동작시키려면 Collider나 조인트를 추가해야 합니다.


저번 시간에 이어서 이번에는 플레이어 이동에 대해서 알아본다.

3d에서 배운 플레이어 이동 개념이 많이 사용된다.

RigidBody를 사용한 이동에 대해서 알아볼 것이다.

우선 캐릭터가 움직일 수 있는 충분한 플랫폼을 구성하기위해 바닥 타일 오브젝트를 추가한다.


#1. 물리 이동

플레이어 이동 구현을 위해 스크립트 파일을 하나 만든다.

해당 스크립트를 Player 오브젝트에 적용하고, 실행시키면 직접 Horizontal키를 이용해 이동시킬 수 있다.

FixedUpdate()는 프로젝트 설정에 따라 다르지만, 기본값(default)는 1초에 약 50번 실행된다. 따라서 1초동안 꾹 누르면 50번이나 실행되고, 속도가 점점 빨라진다.

이렇게 되면 안된다.


#2. 최대속력 제한하기

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

public class Move : MonoBehaviour
{
    public float maxSpeed;
    Rigidbody2D rigid;

    private void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
    }

    private void FixedUpdate()
    {
        //Move By Button Control
        float h = Input.GetAxisRaw("Horizontal");

        rigid.AddForce(Vector2.right * h, ForceMode2D.Impulse);

        if (rigid.velocity.x > maxSpeed) //right max speed
            rigid.velocity = new Vector2(maxSpeed, rigid.velocity.y);
        else if (rigid.velocity.x < maxSpeed * (-1)) //left max speed
            rigid.velocity = new Vector2(maxSpeed * (-1), rigid.velocity.y);
    }
}

따라서 Player가 가속을 무한으로 받지 않도록 하기 위해서 속도의 최대값을 제한할 필요가 있다.

최대속도를 maxSpeed라고 설정한다.

rigid.velocity.x

velocity는 RigidBody의 현재 속도이다. [velocity도 벡터] : 현재 RigidBody를 담은 변수 rigid의 현재 속도를 뜻한다.

만약, rigid의 velocity x축방향 속도가 maxSpeed를 넘을 때 속도를 maxSpeed까지로 제한하도록 코드를 작성한다고 하면 y의 값을 0으로 설정하면 안된다.

공중으로 점프하는 경우가 있을 때 일정 속도를 넘어서면 공중에서 멈춰버릴 수 있기 때문이다.

: 따라서 RigidBody의 y축 속도는 그대로 작성해야한다. rigid.velocity.y

이제 왼쪽 방향의 speed도 제한해야한다.

이때는 rigid.velocity.x*(-1)이라고 작성하는 것이 아니라 제한속도 maxSpeed*(-1)보다 현재속도 velocity.x가 작은지를 살펴봐야하는 것이다.

 

스크립트를 저장하면, Player 오브젝트의 Inspector 창에 Max Speed를 입력하는 란이 생성된다. *이때 스크립트의 maxSpeed 변수는 public 키워드로 입력해야한다. public 키워드로 입력해준 변수는 실행 도중에 값을 변경할 수 있다.

실행시키고, Max Speed에 숫자를 입력해주면 입력해준 속도 이상으로 캐릭터를 조작할 수 없다.


#3. 재질 만들기 (오르막)

나중에 지형에 오르막길을 추가할 예정인데, 오르막을 추가한다면 마찰력을 빼야한다. 그래야 플레이어가 올라갈 수 있다. (기본 지형에는 마찰력이 있는 상태이다.)

Project 창에서 우클릭>Create>2D>Phisic Materials 2D를 클릭하여 ‘재질’ 생성하기

2d는 재질 옵션이 마찰력(Friction), 탄성력(Bounciness) 2가지가 있다.

일단 마찰력을 0로 입력해주고 모든 지형 다중선택 후, 오브젝트의 Box Collider 2D의 Materials에 생성한 재질 파일을 드래그 앤 드롭으로 적용한다.

마찰력을 0으로 설정하니, 마치 빙판처럼 미끄러워졌다. 이제 Player 오브젝트의 RigidBody에서 이것을 잡아준다.


#4. 저항 설정

공기저항 값 + 단위벡터의 속도값으로 조절

마찰력은 이미 0으로 설정했으니, 마찰력으로 잡을 수는 없고 이제 공기저항으로 잡아본다.

Player 오브젝트의 RigidBody 2D 컴포넌트에 Linear DragAngular Drag가 있다.
※유니티6로 업데이트되면서 Liner Damping, Angular Damping으로 바뀐 것 같다. 세부적인 기능의 차이는 아직 모르겠다.

Angular는 회전이다. 따라서 회전은 건드리지 말고, Linear Drag만 건드린다.

Linear Drag는 공기 저항이다. 이동 시 속도를 느리게 해준다.

Linear Drag를 너무 높게 설정하면 안된다. 점프 후 낙하 시에도 공기 저항을 받기 때문이다.

Linear Drag를 2로 설정해준다.

키보드에서 손을 떼면 속력이 급격하게 줄어드는 것을 구현해본다.

 

*‘꾹 누르는’ 지속적인 입력은 FixedUpdate에서 하지만, '단발적인 입력'은 일반적인 Update()에서 하는 것이 좋다. FixedUpdate는 1초에 약 50회 돌고, Update는 1초에 60번 돈다. 따라서 단발적인 키 입력같은 경우를 FixedUpdate에 작성하면 조금(10프레임) 손해보게된다. (씹히는 경우가 발생할 수 있음)

private void Update()
{
    if (Input.GetButtonUp("Horizontal")) 
        rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);
}

normalized는 벡터 크기를 1로 만든 상태(단위벡터)를 뜻한다.

벡터이므로 성분값을 나타내야한다. .normalized.x

if (Input.GetButtonUp("Horizontal"))

rigid.velocity = new Vector2(0.5, rigid.velocity.y);

Horizontal키를 떼면 속도가 오른쪽으로만 0.5로 맞춰진다. (제한된다)

그런데 지금 오른쪽으로 이동중인지, 왼쪽으로 이동중인지 모른다. 위처럼 작성하면 오른쪽으로만 가게된다.

지금 가고있는 방향이 있을 것이다. rigid.velocity.x 이것은 크기와 방향이 모두 있다. 그래서 어느방향이든지 크기가 1인 단위벡터로 만들어줘야한다.

rigid.velocity.normalized *Normalize는 함수

지금 오른쪽으로 크기 10만큼 이동한다고 한다. 그런데 여기서 velocity.normalized가 되면, 크기 1이 된다. 왼쪽으로 -10이면, 단위벡터로 normalized를 작성하면 -1이 되는 것이다. : normalized는 단위벡터로서 벡터의 방향을 구할 때 사용한다.

 

float형끼리 곱할 때에는 실수 뒤에 f를 붙여야한다.

지형 마찰력은 0이지만 이제 키를 놓았을 때, 이동방향으로 속도가 0.5로 제한되고, 공기저항의 영향을 받아 멈추게된다.

 

★이동하는 RigidBody 컴포넌트가 있는 오브젝트는 Constraints의 Freeze Rotation z을 체크해서 회전을 방지해야한다. (넘어짐 방지)

Freeze Rotation은 오브젝트 회전을 얼리는 옵션이다.


#5. 애니메이션

Player가 이동할 때 오른쪽은 괜찮은데, 왼쪽으로 이동할 때 문워크를 하고있다.

이동할 때는 walk 애니메이션을 사용해야하는데, 이동할 때에서 계속 idle1 애니메이션에만 고정되어있다.

1. 일단 방향부터 정해보자면, 오른쪽으로 이동할 때는 Player가 오른쪽을 봐야하고 왼쪽으로 이동할 때는 Player가 왼쪽을 봐야한다.

Player 오브젝트의 Sprite Renderer를 보면 Flip옵션이 있는데, Flip은 스프라이트를 뒤집는 옵션이다.

이제 스크립트에서 다뤄줘야할 컴포넌트는 Sprite Renderer이다.

 //Direction Sprite
 if (Input.GetButtonDown("Horiontal"))
     spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

 

.flipX는 bool 자료형이다. 따라서 true 아니면 false(기본값)이다.

.flipX = (true인지 false인지 판단) 그리고 .GetAxisRaw는 -1(왼쪽) 0(가만히) 1(오른쪽)만 반환하는 특성을 이용

Horizontal키 중 왼쪽, d키를 입력하면 flipX 옵션이 활성화되어 재생중인 애니메이션이 반대로 뒤집힌다.

더보기
더보기

오른쪽 키를 누르는 중에 왼쪽 키를 누르고 그 후 오른쪽 키를 누르면 왼쪽으로 문워크를 하는 버그 있음.

GetButton()으로 바꾸니까 해결됨

 //Direction Sprite
 if (Input.GetButton("Horizontal"))
     spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

 

2. 이제 애니메이션을 건드려봐야한다.

현재 대기상태가 기본으로 설정되어있는데, 화살표를 직접 만들수도 있다. Idle1에서 우클릭>Transition 클릭해서 다음 상태로 옮겨가고싶은 곳을 선택하면 화살표가 이어진다.

Transition은 애니메이션 상태를 옮겨가는 통로이다.

그 다음 왼쪽에서 Parameters탭 클릭, +아이콘을 눌러서 하나를 추가해준다. 추가할 수 있는 옵션이 float/int/bool/Trigger가 있는데, Animator의 Parameter는 애니메이터 매개변수이다.

*애니메이터 매개변수는 상태를 바꿀 때 필요한 변수이다. 즉 지금 추가하려는 Parameters는 Idle1 애니메이션에서 Walk1 애니메이션으로 이동할 때 필요한 매개변수인 것이다.

Bool타입의 Parameters를 추가해주고 이름을 IsWalk1이라고 입력해준다. “지금 이동하고 있는건가요?”라는 것이다.

 

그리고 방금 추가했던 Idle1과 Walk1 사이 화살표를 클릭하면 Inspector에 Conditions가 있다.

“어떤 상태가 되면, 나는 다음 애니메이션을 진행하겠다”는 것이다.

그래서 Conditions에 Animator에서 추가한 매개변수 IsWalk1을 추가해주고, true로 설정한다.

그리고 Conditions 위쪽에 보면 Timeline이 있는데, 이것은 애니메이션을 전환할 때 부드럽게 전환해주는 것이다.

Has Exit Time애니메이션이 끝날 때까지 상태를 유지하는 것을 뜻한다. (애니메이션이 끝나기 전까지는 다음 state로 넘기지 않겠다) : 이동이 멈췄는데, Walk1 애니메이션이 다 안끝났다고해서 정지 상태에서 Walk1 애니메이션이 계속 재생되면 안된다.

따라서 2D 애니메이션의 경우, 일반적으로 체크를 해제해준다.

 

더보기
더보기

Exit Time은 Has Exit Time에 체크가 됐을 때, 전환이 실행되는 정확한 시간을 나타낸다. 이 시간은 ‘정규화된 시간’인데, 예를들어 0.75라면 이전 상태가 75%만큼 재생된 첫 프레임에서 Exit Time 조건은 true이고, 다음 프레임에서 false가 된다. Exit Time이 1보다 작은 전환은 반복 시마다 측정되고, 따라서 애니메이션 반복마다 적절한 전환 시간을 맞출 수 있다. 1보다 큰 전환은 한번만 측정되는데, 일정횟수 반복 후 지정한 시간에 종료할 수 있다. 예를들어 3.5 입력 시, 3.5회 반복 후 한번 측정된다.

Fixed Duration은 체크되어있을 경우 전환 시간이 초로 나타난다.

Transition Duration은 현재 상태의 지속시간을 기준으로 한 상대적인 전환 지속시간이다. Fixed Duration 체크 여부에 따라 초 또는 정규화된 시간 단위로 표시된다. (부드럽게 전환되며 겹치는 구간) 전환 그래프에서 두 파란색 마커 사이의 부분으로 시각화된다.

Transition Offset은 전환될 도착 상태에서의 플레이를 시작할 시간의 offset이다. 예를 들어 값이 0.5일 경우, 목표 상태가 타임라인의 50% 지점에서 플레이를 시작한다.

2D 도트 애니메이션 정리: Has Exit Time 끄기, 겹구간(Transition Duration) 닫기, Condition에 매개변수 설정

 

마찬가지로 Walk1→Idle1으로 가는 반대의 경우에 대해서도 Transition 화살표를 만들어주고, Parameters 값은 false, 겹치는 구간 없애고 Has Exit Time 체크 해제하면 된다.

이제 남은 것은 설정한 Parameters isWalk1의 상태값을 스크립트에서 바꿔주는 것이다.

‘지금 움직이고 있어요’라는 것은 Update에서 작성해줘야한다.

움직이는지에 대한 확인은 Player의 ‘현재 속도’로 판단하겠다.

 

Animator anime;

anime = GetComponent<Animator>();

먼저 Animator에 대한 변수를 선언해주고, GetComponent로 초기화해준다.

//Animation Transition
 if (Mathf.Abs(rigid.velocity.normalized.x) == 0)
     anime.SetBool("IsWalk", false);
 else
     anime.SetBool("IsWalk", true);

이렇게 했을 때 곧바로 애니메이션이 중단되지 않는다.

private void Update()
{
    //Stop Speed
    if (Input.GetButtonUp("Horizontal")) 
        rigid.velocity = new Vector2(rigid.velocity.normalized.x * 0.5f, rigid.velocity.y);

    //Direction Sprite
    if (Input.GetButton("Horizontal"))
        spriteRenderer.flipX = Input.GetAxisRaw("Horizontal") == -1;

    //Animation Transition
    if (Mathf.Abs(rigid.velocity.x) < 0.3)
        anime.SetBool("IsWalk", false);
    else
        anime.SetBool("IsWalk", true);
}

Animator Parameters로 생성한 변수의 자료형이 Bool이니까, SetBool(”매개변수”, 논리값) 함수를 사용해서 값을 바꿔준다.

매개변수의 이름을 정확히 써줘야한다. (대소문자 구분)

스크립트를 통해 매개변수의 상태를 바꾸고, 매개변수의 상태에 의해 애니메이션의 재생이 바뀌는 것이다.

실행시켜서 Animator 창과 Game 창의 상태를 확인해보며 조작하면 애니메이션이 바뀌는 것을 확인할 수 있다. (속도가 조금이라도 남아있으면 Walk1 애니메이션이 재생된다.)

속도가 조금이라도 남아있어서 Walk1 애니메이션이 재생되는 것이 신경쓰인다. 따라서 수정하려고 하는데, rigid.velocity.x의 값은 왼쪽이면 음수, 오른쪽이면 양수 값이 나온다. 이런 경우는 절댓값 개념을 사용해줘야 한다. (Mathf 클래스의 Abs( )함수)

유니티에서는 수학적인 함수를 제공해준다. 그러한 수학적 함수들은 Mathf 클래스에서 제공한다.

 

이제 약간의 미세한 움직임 정도는 서 있는 것으로 간주한다.

Comments