유니티 프로젝트/점토게임

[개발일지] 2. 점토가 돌아다니도록 하기

dubu0721 2025. 1. 4. 18:59

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

 

[유니티] 코루틴의 사용법 총정리 - Unity Coroutine

코루틴(Coroutine) 1. 어디에 쓰이는가? 우선, 코루틴이 어떤 상황에서 필요한지 알아보자. 유니티에서 특정 코드가 반복적으로 실행되기 위해서는 Update문에 코드를 작성하면 되는데, 간혹 Update가

coding-of-today.tistory.com

 

[Unity] 코루틴(Coroutine)

 

[Unity] 코루틴(Coroutine)

1. Unity 코루틴(Coroutine)이란?유니티(Unity)에서 코루틴(Coroution)은 프레임 간에 멈추고 재개할 수 있는 특별한 형태의 메서드입니다. 코루틴은 특정 시간 동안 대기하거나 비동기 작업을 수행할 때

unity-programming-study.tistory.com

 

유니티로 배우는 C# 강좌 Part 完 - 코루틴

 

코루틴 다루기 (1) - 코루틴 기초 | 유니티

 

코루틴 - Unity 매뉴얼

 

코루틴 - Unity 매뉴얼

코루틴을 사용하면 작업을 다수의 프레임에 분산할 수 있습니다. Unity에서 코루틴은 실행을 일시 정지하고 제어를 Unity에 반환하지만 중단한 부분에서 다음 프레임을 계속할 수 있는 메서드입니

docs.unity3d.com

 

3.2 타일맵 콜라이더

[Unity] 타일맵에 콜라이더(충돌체) 추가하기 — A Game Programmer

 

[Unity] 타일맵에 콜라이더(충돌체) 추가하기

플랫폼 게임을 만드는데 노란 공이 떨어져서 바닥에 닿으면 튕기게 만들고 싶다고 가정해보자. 노란 공에 물리효과를 주기 위해서 Circle Collider 2D 컴포넌트와 Rigidbody 2D 컴포넌트를 부착해주었다

devshovelinglife.tistory.com

 

3.3 OnCollisionEnter2D

[Unity2D] OnCollision과 OnTrigger

 

[Unity2D] OnCollision과 OnTrigger

현재 rpg 게임을 공부중에서 적과의 충돌을 어떻게 할지 생각을 해보게 되었다.플레이어 역시 콜라이더가 있을거고 적 역시 콜라이더가 있을것이다.OnCollisionEnter2D와 OnTriggerEnter2D에 대해서 생각

velog.io

 

3.4 Transform Move

좀 잘 정리해놓은 글이 있길래 가져와봤다. 앞으로도 이거 복습하면 될 것 같다.

[Unity] 트랜스폼(Transform) 이동(Move)

 

[Unity] 트랜스폼(Transform) 이동(Move)

유니티 좌표계 프로그램마다 사용하는 좌표계가 다릅니다. 따라서 좌표계에 대해서 이해하는 것은 매우 중요합니다. 유니티에서는 위치를 표현할 때 왼손 좌표계를 이용합니다. X축은 빨간색, Y

ssabi.tistory.com

[Unity] 오브젝트 위치 이동시키기

 

[Unity] 오브젝트 위치 이동시키기

오브젝트를 움직이는 데 아는 방법들을 정리해보려고 한다.새로운 내용을 아는데로 계속 추가해나갈것.제일 기본적인 방법으로는 transform.position에 vector3 값을 직접 넣거나 연산을 하여 움직이

velog.io