하참이의 아이디어노트

Unity 강의 4일차 (1) - 물어 오기 놀이 본문

Unity/Unity 기초

Unity 강의 4일차 (1) - 물어 오기 놀이

하참이 2025. 1. 17. 06:25

본 강의는 다음 문서를 참고하여 제작하였습니다. 자세한 내용은 하단 링크를 참조하시거나 댓글로 질문 남겨주시면 성심성의껏 답변 드리겠습니다.

 

https://learn.unity.com/tutorial/gwaje-2-muleo-ogi-noli?uv=2021.3&pathwayId=63ca4663edbc2a7be103183f&missionId=62ceb5c2edbc2a08dbf5f531&projectId=63c74056edbc2a4df1bc02f7#

 

과제 2 - 물어 오기 놀이 - Unity Learn

과제 개요: 이 과제에서는 배열과 난수 생성 기술을 사용한 프로그래밍을 통해 개를 보내서 하늘에서 무작위로 떨어지는 공이 땅에 닿기 전에 물어 오도록 만들어야 합니다. 이 과제를 완료하려

learn.unity.com

 


 

본 강의는 위 문서의 문제 풀이 입니다. 먼저 문제를 풀어보고 정답을 확인하는 느낌으로 봐주시면 감사하겠습니다.

 

 

1. 우선 다운로드 링크에 따라 자료를 다운로드 합니다.

 

https://unity-connect-prd.storage.googleapis.com/20210506/b167f1c0-bbb1-4537-9b9b-c9e599ed16a0/Challenge%202%20-%20Starter%20Files.zip

 

2. 압축을 해제하고 임포트 합니다.

 

3. 과제 결과와 문제를 확인합니다.

 

과제 결과:

1. 화면 위 임의의 X 위치에 세 가지 공 중 하나가 무작위로 생성됩니다.
2. 사용자가 스페이스바를 누르면 개가 생성되고 달려가서 공을 잡습니다.
3. 개가 공과 충돌하면 공이 파괴됩니다.
4. 공이 지면에 닿으면 "Game Over" 디버그 메시지가 표시됩니다.
5. 개와 공이 화면에서 벗어나면 씬에서 제거됩니다.

 

 

1번은 화면 위 임의의 X 위치에 세 가지 공.. 전에 했던 프로젝트와 비슷하겠네요!

 

2번은 피자를 날리는 것 처럼 강아지가 날라가나봅니다. 궤도를 그리는 것인지는 잘 모르겠네요.

 

3번 역시 피자와 강아지가 충돌했을 때 서로 파괴하는 걸 생각하면 될 것 같네요.

 

4번은 설명을 생략하였지만 매우 쉬운 로그 생성이 있으니 걱정 안해도 될 것 같구요. 조건만 보면 될 것 같네요.

 

5번 역시 이미 했던 내용입니다.

 

 

문제를 봅시다.

 

 

 

 

 

 

 

하늘의 표적으로 날아가야 할 공이 아닌 강아지가 날라다니네요. 이는 아마 관련 Manager에서 공 대신 강아지 오브젝트가 할당되어서 그런 것 같습니다.

 

마침 SpawnManager 오브젝트가 있네요. 컴포넌트에 Manager 가 있는지 확인해보고, 강아지가 할당되어있는지 확인해봅니다.

 

 

 

역시 전부 강아지가 설정되어있네요. 세가지 랜덤한 공이 나오기로 되어있으니 프리팹에 공이 있는지 확인해봅니다.

 

 

 

색깔별로 3개가 있네요. SpawnManager 컴포넌트의 배열을 저 공들로 채워줍시다.

 

 

 

 

다시 게임을 실행해봅시다.

 

 

 

랜덤한 위치에 공이 생성되기는 하는데... 파란색 공만 나타나네요. 이것은 7번 문제에 있으니 다음에 보도록 하죠.

 

 

 

 

 

플레이어가 개 대신 녹색 공을 생성한다고 합니다. 스페이스바를 누르니 녹색공이 바닥에 생성되네요.

 

이 경우에는 PlayerController에서 키 세팅이 되어있을 것이고, PlayerController는 Player 게임 오브젝트에 할당 되어있을 것 입니다. 

 

Player 게임 오브젝트의 컴포넌트를 살펴봅시다.

 

 

 

 

PlayerControllerX가 있습니다. 그리고 DogPrefab이라고 친절히 명명되어있는 프로퍼티에는 Ball2 프리팹이 할당되어있네요. 이것을 Dog 프리팹으로 바꿔줍니다.

 

 

 

 

강아지가 잘 나가고, 공을 물어오는 것 까지 확인했습니다. 그런데 공을 너무 잘 물어서 그런지 닿지 않은것같은데도 공이 잘 없어지네요..

 

바로 5번 문제에 나오네요.

 

 

 

 

 

 

이 경우에는 2가지를 생각해볼 수 있습니다.

 

첫번째로는 충돌 판정을 짠 스크립트에 문제가 있을 경우,

 

두번째로는 Collider 컴포넌트의 범위가 정상보다 클 경우입니다.

 

두번째를 먼저 확인해보도록 하겠습니다. Dog의 콜라이더를 확인해봅니다.

 

 

 

와우! 이렇게 충돌 범위가 크니 공이 닿지 않아도 닿은 것처럼 판정이 된 것처럼 보입니다.

 

 Collider를 줄여줍니다.

 

 

 

넉넉하게 줄였으니 프리팹 변경사항을 Override Apply를 해주고, 게임을 실행해보니 잘 실행되는 것을 확인할 수 있습니다.

 

 

 

 

공도, 강아지도 파괴가 되지 않고 있었던 것을 확인할 수 있었습니다.

 

이 경우는 DestroyOutOfBounds 스크립트가 있는지를 확인부터 해봅시다.

 

 

 

강아지는 물론 공에도 다 붙어있는 것을 확인할 수 있습니다. 그러면 스크립트가 문제겠네요. 스크립트를 들어가 확인해보도록 하겠습니다.

 

 

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

public class DestroyOutOfBoundsX : MonoBehaviour
{
    private float leftLimit = 30;
    private float bottomLimit = -5;

    // Update is called once per frame
    void Update()
    {
        // Destroy dogs if x position less than left limit
        if (transform.position.x > leftLimit)
        {
            Destroy(gameObject);
        } 
        // Destroy balls if y position is less than bottomLimit
        else if (transform.position.z < bottomLimit)
        {
            Destroy(gameObject);
        }

    }
}

 

x 포지션이 leftLimit보다 크면... 이것은 x축으로 이동하는 강아지에게 해당되는 내용이겠네요. 

 

혹시 모르니 씬 뷰의 Axis 기즈모를 확인해봅시다.

 

 

빨간색 x축이 우측... 그러니 왼쪽으로 갈 수록 Dog 게임 오브젝트의 position.x는 작아질 것 입니다.

 

그런데 leftLimit보다 크다면 제거해야한다는 말은 이미 작은 상태에서 더 작아진다는 것을 의미하겠지요.

 

부등호의 방향을 바꾸고 leftLimit을 음수로 바꿔줘보겠습니다.

 

 

 

z축에 관해서는 살짝 함정이 있습니다. z축이 점점 작아지는게 아니고 y축이 작아지는 것이죠.

 

이 역시 y축으로 바꿔줘보겠습니다.

 

 

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

public class DestroyOutOfBoundsX : MonoBehaviour
{
    private float leftLimit = -30;
    private float bottomLimit = -5;

    // Update is called once per frame
    void Update()
    {
        // Destroy dogs if x position less than left limit
        if (transform.position.x < leftLimit)
        {
            Destroy(gameObject);
        } 
        // Destroy balls if y position is less than bottomLimit
        else if (transform.position.y < bottomLimit)
        {
            Destroy(gameObject);
        }

    }
}

 

 

이렇게 되면 강아지와 공이 범위 밖에서 잘 사라지는 것을 확인할 수 있습니다.

 

 

 

한 가지 유형의 공만 생성되는 문제가 있었지요.

 

이 경우는 SpawnManager의 문제가 있다는 것을 아실 것 입니다.

 

코드를 확인해봅니다.

 

 

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

public class SpawnManagerX : MonoBehaviour
{
    public GameObject[] ballPrefabs;

    private float spawnLimitXLeft = -22;
    private float spawnLimitXRight = 7;
    private float spawnPosY = 30;

    private float startDelay = 1.0f;
    private float spawnInterval = 4.0f;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval);
    }

    // Spawn random ball at random x position at top of play area
    void SpawnRandomBall ()
    {
        // Generate random ball index and random spawn position
        Vector3 spawnPos = new Vector3(Random.Range(spawnLimitXLeft, spawnLimitXRight), spawnPosY, 0);

        // instantiate ball at random spawn location
        Instantiate(ballPrefabs[0], spawnPos, ballPrefabs[0].transform.rotation);
    }

}

 

 

공을 생성하는 Instantiate함수에서 ballPrefabs[0], 즉 배열 0번에 할당된 공만 생성되도록 코드가 짜여있었네요.

 

공을 스폰할때, 즉 SpawnRandomBall 함수가 호출 될 때 0~2 범위의 랜덤한 공 번호를 받도록 합니다.

 

 

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

public class SpawnManagerX : MonoBehaviour
{
    public GameObject[] ballPrefabs;

    private float spawnLimitXLeft = -22;
    private float spawnLimitXRight = 7;
    private float spawnPosY = 30;

    private float startDelay = 1.0f;
    private float spawnInterval = 4.0f;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("SpawnRandomBall", startDelay, spawnInterval);
    }

    // Spawn random ball at random x position at top of play area
    void SpawnRandomBall ()
    {
        int ballIndex = Random.Range(0, ballPrefabs.Length);

        // Generate random ball index and random spawn position
        Vector3 spawnPos = new Vector3(Random.Range(spawnLimitXLeft, spawnLimitXRight), spawnPosY, 0);

        // instantiate ball at random spawn location
        Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[0].transform.rotation);
    }

}

 

 

 

ballIndex를 받아 랜덤한 번호를 생성하도록 했습니다. 이제 랜덤한 색상의 공이 나오는 것을 확인할 수 있습니다.

 

 

 

공 생성 주기가 같은 것이 문제인가 보군요.

 

사실 이 부분은 배우지 않은 내용입니다.

 

어? spawnInterval을 Random.Range(3.0f, 5.0f)로 하면 되는 것 아닌가요?

 

Random.Range()는 함수 안에서만 사용할 수 있습니다.

 

그러면 Start 함수에 넣어서 사용하면 되는 것 아닌가요?

 

아뇨! 그러면 3.0f나 5.0f 사이의 무작위 값이 한 번 결정 되고, 그 결정된 주기가 바뀌지 않고 계속 그 주기로만 공이 생성될 것 입니다.

 

헉! 그럼 어떻게 해야하나요?

 

바로 InvokeRepeating함수가 아닌 Invoke 함수를 사용합니다.

 

코드를 다음과 같이 작성합니다.

 

 

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

public class SpawnManagerX : MonoBehaviour
{
    public GameObject[] ballPrefabs;

    private float spawnLimitXLeft = -22;
    private float spawnLimitXRight = 7;
    private float spawnPosY = 30;
    private float spawnInterval;
    private float startDelay = 1.0f; 

    // Start is called before the first frame update
    void Start()
    {
        Invoke("SpawnRandomBall", startDelay);
    }

    // Spawn random ball at random x position at top of play area
    void SpawnRandomBall ()
    {
        int ballIndex = Random.Range(0, ballPrefabs.Length);

        // Generate random ball index and random spawn position
        Vector3 spawnPos = new Vector3(Random.Range(spawnLimitXLeft, spawnLimitXRight), spawnPosY, 0);

        // instantiate ball at random spawn location
        Instantiate(ballPrefabs[ballIndex], spawnPos, ballPrefabs[0].transform.rotation);

        spawnInterval = Random.Range(3.0f, 5.0f);

        Invoke("SpawnRandomBall", spawnInterval);
    }

}

 

 

Invoke 함수는 InvokeRepeating 함수와는 다르게 주기 인자를 받지 않습니다.

 

그러므로 게임이 시작하면 처음에 한번 딜레이를 가지고 시작을 한 뒤,

 

SpawnRandomBall 함수가 실행되면 그 다음 SpawnRandomBall 함수를 실행 할 때는 spawnInterval을 딜레이로 가지고 호출을 하면 되는 것이지요.

 

 

알려주지 않은 내용을 문제로 낸 것이 일부러 인지는 모르겠지만 애를 먹거나 틀린 사람이 많을 것 같은 문제였습니다. (힌트를 보니 그저 실수인 것 같습니다...)

 

 

 

 

마지막 문제입니다! 스페이스바를 연속으로 누르지 못하게 해달라고 합니다.

 

스페이스바를 누르는 코드는 PlayerController에 있었죠.

 

주기적으로 Flag를 바꿔주도록 하면 될 것 같은 문제입니다.

 

이 코드는 위에서 배운 Invoke를 사용해 제 임의로 작성을 해놓겠습니다. 코드에 대한 설명이 필요하시면 댓글 남겨주세요!

 

 

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

public class PlayerControllerX : MonoBehaviour
{
    public GameObject dogPrefab;
    private bool canDog = true;
    private float coolTime = 1.0f;

    // Update is called once per frame
    void Update()
    {
        if (canDog)
        {
            if (Input.GetKeyDown(KeyCode.Space))
            {
                Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
                canDog = false;
                Invoke("changeCanDog", coolTime);
            }
        }

    }

    void changeCanDog()
    {
        canDog = !canDog;
    }
}

 

 


 

문제풀이를 해보니 직접 코딩을 해보고 싶다는 생각이 들지 않으신가요?

 

오늘도 수고하셨습니다!