0. 들어가기 전에
점토가 게임 화면을 알아서 돌아다닐 수 있도록 하는 기능을 구현했다.
이번에는 미루고 미루던 코루틴을 드디어 공부하고 실제로 코드에 적용해보는 시간을 가졌다. 직접 사용해보기 전에는 막연한 두려움이 있어서 공부하기가 싫었는데 막상 해보니까 엄청 어렵지는 않았다.
확실히 코루틴을 이용하니까 내가 원했던 기능을 쉽게 구현할 수 있었다. 내가 원했던 기능은 점토가 벽에 부딪혔을 때 방향을 바꿔서 돌아다니는 로직을 잠시 멈추고 게임 화면의 중앙으로 돌아오도록 하는 것이었다.
코루틴을 배우기 전에는 이걸 어떻게 구현해야할지 몰라서 그냥 넘어갔었다. 이번에 구현하게 되어서 정말 기분이 좋다. 사실 구현이랄 것도 크게 없긴 하지만 그래도 좋다 ^~^
구현한 기능 목록
- 점토 돌아다니기
1. 점토 돌아다니는 기능 구현
우선 점토가 스스로 돌아다닐 수 있도록 하기 위해서 C# 스크립트를 하나 만들어줬다. 스크립트의 이름은 ClayMove 라고 지었다.
1.1 ClayMove 코드
일단 ClayMove 스크립트의 코드는 다음과 같다.
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum MoveDirection
{
Minus = -1, Plus = 1, Idle = 0
}
public class ClayMove : MonoBehaviour
{
public float moveSpeed; // 움직이는 속도
public MoveDirection moveDirX; // x 축 방향
public MoveDirection moveDirY; // y 축 방향
public bool isMoving; // 돌아다니는지 여부 확인
public bool isReturning; // 중점으로 돌아가는 중인지 여부 확인
public GameObject targetPosObj; // 중점에 있는 게임 오브젝트 할당해주기
private Coroutine randomMoveCoroutine;
void Start()
{
// 코루틴의 핸들을 저장하고 이를 통해 중단하는 것이 좋음
randomMoveCoroutine = StartCoroutine(RandomMove());
}
void Update()
{
if (!isReturning)
{
// Time.deltaTime 을 곱해주는 이유는 게임 오브젝트가 순간이동 하는 것을 막기 위함.
transform.Translate(moveSpeed * Time.deltaTime * new Vector2((int)moveDirX, (int)moveDirY));
}
}
// 3초에 한 번씩 이동 방향 바꾸도록 하는 코루틴..
// 코루틴은 IEnumerator 를 반환함
private IEnumerator RandomMove()
{
while (true)
{
// 방향 랜덤으로 설정
moveDirX = (MoveDirection)Random.Range(-1, 2);
moveDirY = (MoveDirection)Random.Range(-1, 2);
yield return new WaitForSeconds(3f); // 4초 동안 기다령
}
}
private IEnumerator ReturnToCenter()
{
isReturning = true;
float curTime = 0f;
float duration = 3f; // 중점으로 돌아가는 시간 3초 만큼 줄 것..
Vector2 targetPos = targetPosObj.transform.position; // 중점에 있는 오브젝트의 위치 할당
while (curTime < duration)
{
transform.position = Vector2.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
curTime += Time.deltaTime;
yield return null; // 다음 프레임까지 대기
}
isReturning = false; // 랜덤 이동 재개
randomMoveCoroutine = StartCoroutine(RandomMove()); // 다시 시작시키기
}
// 콜라이더 컴포넌트를 가진 게임 오브젝트랑 부딪히면 이 함수로 진입
private void OnCollisionEnter2D(Collision2D collision)
{
if (isReturning) return; // 만약 이미 돌아가고 있는 중에 또 부딪히면 걍 나가라..
// 부딪힌 게임 오브젝트의 태그가 Target 이면 ReturnToCenter 코루틴 호출하도록
if (collision.gameObject.CompareTag("Wall"))
{
// 랜덤 이동 코루틴은 중단시키기
StopCoroutine(randomMoveCoroutine);
// 중앙 복귀 코루틴 호출
StartCoroutine(ReturnToCenter());
}
}
}
1.2 ClayMove 코드 설명
ClayMove 코드에 대한 설명은 다음과 같다.
1. MoveDirection 열거체
MoveDirection 이라는 이름의 열거체를 만들었다. 이를 만든 이유는 나중에 점토의 움직이는 방향을 이용해서 스프라이트를 좌우반전 시키는 데 이용할 것이기 때문이다.
그리고 그냥 -1, 1처럼 숫자로만 보이는 것 보다 Minus, Plus 라고 되어 있는게 개인적으로 더 알아보기 편하다고 생각해서 만들었다.
public enum MoveDirection
{
Minus = -1, Plus = 1, Idle = 0
}
2. ClayMove 클래스의 변수
ClayMove 클래스는 변수는 다음과 같다.
public float moveSpeed; // 움직이는 속도
public MoveDirection moveDirX; // x 축 방향
public MoveDirection moveDirY; // y 축 방향
public bool isMoving; // 돌아다니는지 여부 확인
public bool isReturning; // 중점으로 돌아가는 중인지 여부 확인
public GameObject targetPosObj; // 중점에 있는 게임 오브젝트 할당해주기
private Coroutine randomMoveCoroutine;
moveSpeed: 점토가 움직이는 속도 지정
moveDirX, moveDirY: 점토가 움직이는 방향 지정
isMoving: 점토가 돌아다니는지 여부 확인(추후에 사용할 것)
isReturning: 중점으로 돌아가는 중인지 여부 확인 위함
targetPosObj: 중점에 있는 게임 오브젝트를 할당해 줄 것(점토가 게임 화면의 중점으로 돌아갈 위치를 지정하기 위함)
randomMoveCoroutine: 코루틴의 핸들을 저장하고 이를 통해 중단하는 것이 좋다고 해서 따로 변수로 만든 것
3. Start() 내용
Start() 는 게임이 시작될 때 자동으로 한 번 호출되는 함수이다. 이 곳에서 RandomMove() 코루틴을 실행시켰다.
void Start()
{
// 코루틴의 핸들을 저장하고 이를 통해 중단하는 것이 좋음
randomMoveCoroutine = StartCoroutine(RandomMove());
}
4. Update() 내용
Update() 는 코루틴과 별개로 작동한다고 한다. 그래서 Update 에서는 코루틴을 통해 변경된 moveDirX, moveDirY 값을 이용해서 점토를 이동시키도록 했다.
isReturning 을 통해 점토를 움직일지 말지의 여부를 결정하도록 했다. 이 값이 true 면 원래 점토가 자유롭게 돌아다니던 로직을 잠시 멈추도록 하고 대신에 중앙으로 돌아가도록 했다.
void Update()
{
if (!isReturning)
{
// Time.deltaTime 을 곱해주는 이유는 게임 오브젝트가 순간이동 하는 것을 막기 위함.
transform.Translate(moveSpeed * Time.deltaTime * new Vector2((int)moveDirX, (int)moveDirY));
}
}
5. RandomMove()
3초에 한 번씩 점토의 이동 방향을 랜덤으로 바꾸도록 하는 코루틴이다. while 문을 사용해서 강제적으로 코루틴을 중단시키기 전에는 계속해서 3초마다 반복하도록 했다.
Random.Range 의 범위를 (-1, 2) 로 한 이유는 2는 포함되지 않기 때문이다. 즉, -1 부터 1까지의 수를 랜덤으로 반환받도록 하기 위해서 범위를 (-1, 2) 로 설정한 것이다.
// 3초에 한 번씩 이동 방향 바꾸도록 하는 코루틴..
// 코루틴은 IEnumerator 를 반환함
private IEnumerator RandomMove()
{
while (true)
{
// 방향 랜덤으로 설정
moveDirX = (MoveDirection)Random.Range(-1, 2);
moveDirY = (MoveDirection)Random.Range(-1, 2);
yield return new WaitForSeconds(3f); // 3초 동안 기다령
}
}
6. ReturnToCenter()
점토가 벽에 닿으면 실행되는 코루틴이다.
진입하자마자 isReturing 의 값을 true 로 바꿔주어 Update() 에서 점토가 움직이는 기능을 잠시 일어나지 않도록 했다.
curTime 과 duration 변수를 이용해서 지정된 시간이 지나면 while 문을 빠져나오고 다시 RandomMove 코루틴을 실행했다. isReturning 변수의 값도 잊지 않고 false 로 바꿔주었다.
RandomMove() 코루틴을 호출할 때마다 randomMoveCoroutine 에 새로 할당해주었다(무슨 코루틴을 종료해야 하는지 명확하게 알려주기 위해서).
private IEnumerator ReturnToCenter()
{
isReturning = true;
float curTime = 0f;
float duration = 3f; // 중점으로 돌아가는 시간 3초 만큼 줄 것..
Vector2 targetPos = targetPosObj.transform.position; // 중점에 있는 오브젝트의 위치 할당
while (curTime < duration)
{
transform.position = Vector2.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime);
curTime += Time.deltaTime;
yield return null; // 다음 프레임까지 대기
}
isReturning = false; // 랜덤 이동 재개
randomMoveCoroutine = StartCoroutine(RandomMove()); // 다시 시작시키기
}
7. OnCollisionEnter2D(Collision2D collision)
콜라이더를 가진 오브젝트끼리 부딪혔을 때 호출되는 함수이다.
오브젝트가 부딪히면 collision 에는 현재 게임 오브젝트와 부딪힌 대상 게임 오브젝트의 정보가 저장된다.
즉, 나는 점토 게임 오브젝트가 어떤 게임 오브젝트와 충돌했는지 판단하기 위해 collision.gameObject.CompareTag 메서드를 이용했다. 대상 게임 오브젝트의 태그가 Wall 이라면 벽과 충돌한 것이므로 랜덤 이동 코루틴을 중단시키고 중앙 복귀 코루틴을 실행하도록 했다.
// 콜라이더 컴포넌트를 가진 게임 오브젝트랑 부딪히면 이 함수로 진입
private void OnCollisionEnter2D(Collision2D collision)
{
if (isReturning) return; // 만약 이미 돌아가고 있는 중에 또 부딪히면 걍 나가라..
// 부딪힌 게임 오브젝트의 태그가 Target 이면 ReturnToCenter 코루틴 호출하도록
if (collision.gameObject.CompareTag("Wall"))
{
// 랜덤 이동 코루틴은 중단시키기
StopCoroutine(randomMoveCoroutine);
// 중앙 복귀 코루틴 호출
StartCoroutine(ReturnToCenter());
}
}
1.3 게임 오브젝트 설명
1. cat1_clay
얘가 현재 화면 상에서 돌아다니고 있는 점토 게임 오브젝트이다. 이 게임 오브젝트에 ClayMove 스크립트를 부착해서 비로소 게임 오브젝트가 화면을 돌아다니도록 했다.
게임 오브젝트가 타일맵과 충돌했을 때 타일맵을 뚫고 지나가지 못하게 하려면 Rigidbody 2D 컴포넌트가 필요하다고 해서 이 컴포넌트도 추가해줬다.
그리고 타일맵과 충돌한 걸 감지하기 위해 Circle Collider 2D 컴포넌트도 추가해줬다.
- ClayMove 컴포넌트:
- MoveSpeed 값은 직접 움직이는 속도 보면서 임의로 조정했다. 내 눈엔 이게 적당한 값인 것같다.
- TargetPosObj 에는 TargetObj 게임 오브젝트를 넣어놓았다. TargetObj 게임 오브젝트의 위치값으로 점토가 이동할 때 사용하기 위함이다.
- 나머지 요소들은 고정된 값이 아니라 게임 진행 도중 계속 바뀌므로 고정된 값이 아니다.
- Rigidbody 2D
- Gravity Scale 값을 0으로 설정해서 게임 오브젝트가 바닥으로 떨어지지 않도록 했다.
- Constraints 의 Freeze Rotations 를 체크해서 z 축으로 회전하지 않도록 했다.
- Circle Collider 2D
2. TargetObj
이 게임 오브젝트의 Position 으로 점토가 이동하도록 한다.
3. Border
타일맵이다. 점토가 얘랑 부딪히면 화면 중앙으로 이동하도록 하는 기능을 위해 게임 오브젝트로 만들었다.
얘한테도 Collider 컴포넌트를 주었다. 충돌 감지해야 하니까. 그리고 타일맵 한 부분마다 collider 주는 것보다 그냥 통채로 주는 게 개인적으로 더 마음에 들어서 Composite Collider 2D 컴포넌트 추가한 다음에 Tilemap Collider 2D 의 Used By Composite 을 체크했다.
2. 결과물
점토가 스스로 돌아다니다가 벽에 부딪히면 중앙으로 돌아가는 로직이 정상적으로 수행되는 것을 확인한 동영상이다. 이제 타일맵을 투명하게 만들어서 사람 눈에는 안 보이도록 할 것이다.
^~^ 완성! 재밌당
3. 참고 자료
글 초반에 언급했듯이 코루틴을 이번에 제대로 공부하고 사용해봤다. 절대 잊어버리고 싶지 않기 때문에 여기에 공부한 링크도 좀 첨부하려고 한다.
3.1 코루틴
[유니티] 코루틴의 사용법 총정리 - Unity Coroutine
3.2 타일맵 콜라이더
[Unity] 타일맵에 콜라이더(충돌체) 추가하기 — A Game Programmer
3.3 OnCollisionEnter2D
[Unity2D] OnCollision과 OnTrigger
3.4 Transform Move
좀 잘 정리해놓은 글이 있길래 가져와봤다. 앞으로도 이거 복습하면 될 것 같다.
[Unity] 트랜스폼(Transform) 이동(Move)