일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 해시
- 포톤
- 유니티
- Unity
- 유클리드호제법
- Unity2D
- 이분탐색
- UnrealEngine
- BFS
- 순열
- Unity3d
- C++
- 워크플로
- 프로그래머스
- Photon
- 구현
- c#
- 스파르타내일배움캠프TIL
- unityui
- 문자열
- Inventory
- 알고리즘
- 스택
- UE4
- 스파르타내일배움캠프
- Firebase
- QueryDSL
- 내일배움캠프
- FSM
- 언리얼엔진
- Today
- Total
개발 낙서장
[Unity 2D] 캐릭터 움직임 본문
이번에 간단한 3D 게임 포트폴리오도 허접하긴 하지만 끝났고 나중에 실무에 들어가게 되면 다양한 분야의 개발을 할 수 있어야 하니 이번엔 2D에 대해 공부해야겠다고 생각이 들었다.
여러 에셋들은 대부분 유니티 에셋 스토어에서 받아서 사용했다.
캐릭터
일단 키 조작을 통해 움직이는 캐릭터를 구현해 보도록 하자.
움직임에는 크게 두 가지 방법이 있는 것으로 알고 있는데 하나는 transform을 이용하는 방법, 하나는 Rigidbody 물리 효과를 이용하는 방법이 있다.
둘 중 하나만 사용하던 두 가지를 전부 사용하던 일단 필요한 컴포넌트들은 부착해줘야 한다.
유닛 2D 모델에 Collider 2D와 Rigidbody 2D를 부착하고 값을 간단히 조정해 주면 준비는 끝났다.
Rigidbody2D _rigidbody;
CapsuleCollider2D _collider;
Animator _animator;
float _moveX;
bool _isWalking;
public float _moveSpeed = 3f;
public float _maxSpeed = 10f;
public float _jumpForce;
private void Awake()
{
_rigidbody = GetComponent<Rigidbody2D>();
_collider = GetComponent<CapsuleCollider2D>();
_animator = GetComponent<Animator>();
}
캐릭터에 부착할 스크립트를 하나 만들고 간단한 변수들을 설정해준다.
private void Update()
{
_moveX = Input.GetAxisRaw("Horizontal");
_animator.SetBool("IsWalking", _isWalking);
if(_moveX < 0f || _moveX > 0f)
{
_isWalking = true;
transform.localScale = new Vector2(-_moveX, 1f);
}
else
{
_isWalking = false;
}
if(Input.GetButtonDown("Jump"))
{
_rigidbody.AddForce(Vector2.up * _jumpForce, ForceMode2D.Impulse);
}
}
Input.GetAxisRaw(string axisName)이라는 함수는 Edit->Project Setting->InputManager->Axes에 있는 친구들의 입력을 받았는지 그리고 그 값을 -1, 0, 1로 반환해 주는 함수이다.
Horizontal은 '수평의'라는 뜻으로 좌우 방향키 입력값을 반환한다.
즉 -1이면 왼쪽 방향키 입력, 1이면 오른쪽 방향키 입력, 0이면 중립이다.
-1이거나 1이면 방향키를 누르고 있는 것이므로 _isWalking
을 true
로 아니면 false
로 해줬다.
점프 기능도 그냥 간단히 만들어주었다.
Translate
public void Translate(Vector3 translation);
public void Translate(float x, float y, float z);
Transform.Translate 함수는 해당 Vector 값만큼 이동시키는 함수이다. 좌우 입력을 받으면 x 좌표만 매 프레임마다 _moveSpeed
만큼 이동해 주면 된다.
private void FixedUpdate()
{
transform.Translate(new Vector2(_moveX * _moveSpeed * Time.fixedDeltaTime, 0));
}
일단 움직임 기능은 하는 것으로 보이긴 하는데 부드러운 움직임이라고 보기엔 어렵고 좀 딱딱하다.
그리고 개인적으로 Transform을 직접적으로 변경하는 걸 싫어하는 편이다. 분명히 개발하다 보면 오류가 발생하기 마련인데 조작이 다양하고 복잡해질수록 이런 부분에서 버그가 많이 발생하는 것 같다.
그냥 이런 것도 있구나 하고 알아보는 정도로 넘어갔다.
Rigidbody
Rigidbody를 이용한 움직임은 유니티에서 제공하는 물리 효과를 통해 오브젝트에 힘을 가하여 움직일 수 있도록 하는 방법이다.
대표적으로 AddForce라는 함수가 있고 Rigidbody의 velocity 옵션을 변경해 주는 방법, MovePosition 함수를 이용하는 방법 총 세 가지가 있는데 그 중에서 나는 AddForce 함수를 이용하려고 한다.
velocity랑 MovePosition은 직접적으로 값을 변경해주는 방법이라 결국 움직임이 자연스럽지 못하고 버그가 발생할 가능성이 농후하다.
반대로 AddForce는 물체에 벡터값 방향으로 힘을 가하는 방법이어서 좌표계 관련 버그가 발생할 가능성이 적고 가속도가 붙는 방식이라 조금 더 자연스러운 움직임이 가능하다는 게 장점이다.
단점으로는 지형에 자유롭지 못하고 갑자기 많은 힘을 받을 수 있다는 건데 힘이 강한 상태에서 내리막길을 내려갈 경우 캐릭터가 슈퍼 점프를 한다던가 가속도가 갑자기 확 붙는다던가 하는 현상이 생길 수 있는데 이건 잘 조절하면 되는 문제라고 생각한다.
private void FixedUpdate()
{
_rigidbody.AddForce(Vector2.right * _moveX * _moveSpeed, ForceMode2D.Impulse);
}
함수 사용법은 간단하다. 힘을 가하고 싶은 방향 * 힘의 세기를 넣어주면 된다.
ForceMode는 말 그대로 힘을 어떤 방식으로 가할지 선택하는 것인데 2D에는 Force와 Impulse 모드가 있다.
Force는 질량을 사용해 연속적인 힘을 가하는 것이고 Impulse는 질량을 사용해 한번에 힘을 가하는 것이다.
움직임은 입력과 즉시 반응해야 하니 Impulse 모드를 사용하는 것이 좋다.
일단 확실히 움직임이 좀 더 부드럽긴 한데 우려했던 일이 벌어졌다. 버튼을 누르고 있을수록 점점 가속도를 받다 보니 속도가 너무 빨라져 지형에 끼어버리는 문제가 발생했다.
이건 아까 이야기했던 Rigidbody의 velocity 속성을 이용하면 되는데 속도의 최댓값을 설정해 velocity 값이 이 이상을 넘어서지 않도록 하면 된다.
velocity 또한 좌우값에 따라 양수, 음수로 바뀌기 때문에 이를 이용해 조건식을 세워 값을 조절하면 된다.
private void FixedUpdate()
{
_rigidbody.AddForce(Vector2.right * _moveX * _moveSpeed, ForceMode2D.Impulse);
if (_rigidbody.velocity.x > _maxSpeed)
_rigidbody.velocity = new Vector2(_maxSpeed, _rigidbody.velocity.y);
else if(_rigidbody.velocity.x < -_maxSpeed)
_rigidbody.velocity = new Vector2(-_maxSpeed, _rigidbody.velocity.y);
}
_moveSpeed
를 1, _maxSpeed
를 3으로 설정한 결과이다. 확실히 움직임이 부드러워졌다. 역시 킹갓 유니티. 그럴듯한 에셋에 유니티의 기본 기능만 사용했는데 벌써 움직이는 유닛 하나를 만들어냈다.
물론 아직 굉장히 불안한 부분이 하나 있다. 바로 아까 간단하게 구현했던 점프인데 점프키를 누르면 누를수록 스스로 나로호가 되어 저 하늘 높이 올라가 버린다.
이 역시 AddForce를 사용했기 때문에 발생하는 문제인데 AddForce는 힘을 가할수록 그 힘이 중첩되어 점점 강해지기 때문에 반드시 조건으로 제한을 두며 사용해야 한다.
점프는 캐릭터가 땅에 닿아있을 때만 사용하게 하려면 땅에 닿아있는지 체크하는 bool _isGrounded
값을 만들어 사용하면 될 것 같다.
- 점프 버튼이 입력됐을 때 _isGrounded가 true인지
- 바닥 방향으로 Ray를 쏴서 충돌한 오브젝트의 레이어가 Ground인지
- 조건을 만족하면 점프! 그 이후 y방향 속력이 0이 되면 땅에 닿은 것이므로 _isGrounded는 true로 변경
근데 여기서 문제가 하나 발생하는데 좌우 움직임에서 좌우 방향으로만 힘을 가했다 하더라도 AddForce 특성상 위아래 방향에 아주 조그마한 힘이 들어갈 수밖에 없다.
실제로 Debug를 통해 y 속력 로그를 봤을 때 아주 아주 아주 작은 값이 출력됐다.
그래서 velocity.y의 조건식에서 0을 기준으로 할 경우 점프가 되지 않았었다. 그래서 아래와 같은 방법으로 해결했다.
[Update]
private void Update()
{
_moveX = Input.GetAxisRaw("Horizontal");
_animator.SetBool("IsWalking", _isWalking);
if(_moveX < 0f || _moveX > 0f)
{
_isWalking = true;
transform.localScale = new Vector2(-_moveX, 1f);
}
else
{
_isWalking = false;
}
if(Input.GetButton("Jump") && _isGrounded)
{
RaycastHit2D hit = Physics2D.Raycast(transform.position, Vector2.down, 0.5f, LayerMask.GetMask("Ground"));
if (hit)
{
_rigidbody.AddForce(Vector2.up * _jumpForce, ForceMode2D.Impulse);
_isGrounded = false;
}
}
}
[FixedUpdate]
private void FixedUpdate()
{
_rigidbody.AddForce(Vector2.right * _moveX * _moveSpeed, ForceMode2D.Impulse);
if (_rigidbody.velocity.x > _maxSpeed)
_rigidbody.velocity = new Vector2(_maxSpeed, _rigidbody.velocity.y);
else if(_rigidbody.velocity.x < -_maxSpeed)
_rigidbody.velocity = new Vector2(-_maxSpeed, _rigidbody.velocity.y);
if (Mathf.Abs(_rigidbody.velocity.y) >= 0.1f)
_isGrounded = false;
else if(Mathf.Abs(_rigidbody.velocity.y) < 0.1f)
_isGrounded = true;
}
점프 버튼을 받으면 _isGrounded
가 true인지 체크한 후에 바닥 방향으로 Ray를 쏴서 바닥 레이어가 hit했을 경우 점프해 준다.
y 속력의 절댓값이 0.1 밑이라면 사실상 y축 움직임이 거의 없는 것이므로 땅에 닿아있다고 판단해 _isGrounded
를 true로, 그 반대면 false로 값을 지정해 준다.
갑자기 확 점프하거나 공중에서 연속으로 점프되는 이상한 현상이 발생하지 않고 정상적으로 잘 되는 모습이다!
사실 3D할 때랑 방법 자체는 다르지 않은데 아무래도 축이 두 개뿐이라 좀 더 직관적이고 구현하기 수월한 것 같다.
다만 2D는 대부분 스프라이트로 작업하기 때문에 이미지나 레이어 등에 신경을 많이 써야 할 것 같다.
우선 움직임은 간단하게 좌우 방향, 점프로 마무리하고 다음에는 여러 동작 및 몬스터 같은 다른 유닛들을 다루어봐야겠다.
'Unity > Devlog' 카테고리의 다른 글
[Unity2D] 포톤 서버로 멀티 구현 (0) | 2023.02.20 |
---|---|
[Unity 2D] 유닛 확장 (0) | 2023.01.23 |
[Unity 3D] NPC 퀘스트 팝업 (0) | 2023.01.16 |
[Unity 3D] NPC 대화 (0) | 2023.01.14 |
[Unity 3D] 퀘스트 (0) | 2023.01.09 |