하참이의 아이디어노트

Unity 강의 6일차 (1) - 풍선, 폭탄, 부울 본문

Unity/Unity 기초

Unity 강의 6일차 (1) - 풍선, 폭탄, 부울

하참이 2025. 1. 21. 20:16

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

 

https://learn.unity.com/tutorial/gwaje-3-pungseon-pogtan-buul?uv=2021.3&missionId=63c7676bedbc2a66daff99b1&pathwayId=63ca4663edbc2a7be103183f&contentId=63ca2f51edbc2a58daff82a4&projectId=63ca1d01edbc2a5f4807369e#

 

과제 3 - 풍선, 폭탄, 부울 - Unity Learn

과제 개요: 지금까지 배운 물리, 배경 스크롤, 특수 효과에 대한 내용을 마을의 떠다니는 풍선에 적용하여, 폭탄을 피하며 토큰을 획득해 보세요. 이 프로젝트에는 오류가 많기 때문에 많은 문제

learn.unity.com

 


 

이번 강의는 과제입니다. 하단 링크 또는 위 문서에서 과제를 다운받은 뒤 문제풀이 형식으로 진행됩니다. 먼저 직접 풀어보시고 답안을 맞춰보는 형식을 권장합니다.

 

https://unity-connect-prd.storage.googleapis.com/20210506/13b7b5f4-501e-43a6-9e67-cc9f71a7e406/Challenge%203%20-%20Starter%20Files.zip

 


 

과제를 다운로드 하고 압축을 해제한 뒤, 임포트합니다.

 

Challenge 3 > Challenge3 씬을 실행합니다. 

 

 

 

목표를 확인합니다.

 

 

1. 플레이어가 스페이스바를 누르면 풍선이 위로 떠오릅니다.
2. 배경을 매끄럽게 반복하여 풍선의 움직임을 시뮬레이션합니다.
3. 폭탄과 돈 토큰이 타이머에서 무작위로 생성됩니다.
4. 돈과 충돌하면 파티클 효과와 음향 효과가 재생됩니다.
5. 폭탄과 충돌하면 폭발이 일어나고 배경이 멈춥니다.

 

 

 

플레이어가 스페이스바를 누르면 풍선이 위로 떠오르는 것은 Rigidbody 컴포넌트를 이용하고 AddForce 함수를 사용하면 될 것 같습니다.

 

배경의 반복으로 풍선의 움직임 시뮬레이션은 MoveLeft와 RepeatBackground 코드를 재활용하면 될 것 같네요.

 

폭탄과 돈 토큰의 생성은 SpawnManager로 생성하면 될 것 같습니다.

 

돈과 충돌하면 생성되는 파티클과 음향효과는 Collider를 사용하고 OnCollisionEnter 함수 안에서 ParticleSystem과 AudioSource, AudioClip을 사용하면 될 것 같습니다.

 

폭탄과 충돌하면 배경이 멈추는 것은 GameOver 시스템을 만들면 되겠군요

 

지금까지 했던것과 다를것이 전혀 없습니다. 한 번 가보자구요.

 

 

 

 

플레이어가 풍선을 제어할 수 없다고 합니다. PlayerController에 문제가 있어보이는군요. 해당 스크립트를 들어가봅니다.

 

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

public class PlayerControllerX : MonoBehaviour
{
    public bool gameOver;

    public float floatForce;
    private float gravityModifier = 1.5f;
    private Rigidbody playerRb;

    public ParticleSystem explosionParticle;
    public ParticleSystem fireworksParticle;

    private AudioSource playerAudio;
    public AudioClip moneySound;
    public AudioClip explodeSound;


    // Start is called before the first frame update
    void Start()
    {
        Physics.gravity *= gravityModifier;
        playerAudio = GetComponent<AudioSource>();

        // Apply a small upward force at the start of the game
        playerRb.AddForce(Vector3.up * 5, ForceMode.Impulse);

    }

    // Update is called once per frame
    void Update()
    {
        // While space is pressed and player is low enough, float up
        if (Input.GetKey(KeyCode.Space) && !gameOver)
        {
            playerRb.AddForce(Vector3.up * floatForce);
        }
    }

    private void OnCollisionEnter(Collision other)
    {
        // if player collides with bomb, explode and set gameOver to true
        if (other.gameObject.CompareTag("Bomb"))
        {
            explosionParticle.Play();
            playerAudio.PlayOneShot(explodeSound, 1.0f);
            gameOver = true;
            Debug.Log("Game Over!");
            Destroy(other.gameObject);
        } 

        // if player collides with money, fireworks
        else if (other.gameObject.CompareTag("Money"))
        {
            fireworksParticle.Play();
            playerAudio.PlayOneShot(moneySound, 1.0f);
            Destroy(other.gameObject);

        }

    }

}

 

 

보아하니 playerRb에 값을 잘 넣어주었지만 playerRb 변수에는 Player 게임 오브젝트의 Rigidbody 컴포넌트가 할당되지 않았습니다.

 

게임을 실행하면 다음과 같은 오류가 나오는 것을 알 수 있습니다.

 

 

 

 

 

PlayerController의 Start()를 다음과 같이 수정합니다.

 

 

    void Start()
    {
        Physics.gravity *= gravityModifier;
        playerAudio = GetComponent<AudioSource>();
        playerRb = GetComponent<Rigidbody>();

        // Apply a small upward force at the start of the game
        playerRb.AddForce(Vector3.up * 5, ForceMode.Impulse);

    }

 

 

 

 

 

풍선이 스페이스바를 누르는 동안 잘 떠오르는 것을 확인할 수 있습니다. 

 

+ 스페이스바를 누르면 풍선이 너무 확 떠오르는 경향이 있네요. 조작이 너무 어렵습니다. Mass를 1.5로 설정하면 적절해지는 것을 확인할 수 있습니다.

 

 

 

 

 

게임이 끝날 때만 이동하다니, 게임이 끝나면 화면이 멈추는 것과 반대인 것 같습니다. MoveLeft 코드에서 if문의 조건이 잘못 설정되어있을 것이라고 추측할 수 있습니다.

 

 

 

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

public class MoveLeftX : MonoBehaviour
{
    public float speed;
    private PlayerControllerX playerControllerScript;
    private float leftBound = -10;

    // Start is called before the first frame update
    void Start()
    {
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControllerX>();
    }

    // Update is called once per frame
    void Update()
    {
        // If game is not over, move to the left
        if (playerControllerScript.gameOver)
        {
            transform.Translate(Vector3.left * speed * Time.deltaTime, Space.World);
        }

        // If object goes off screen that is NOT the background, destroy it
        if (transform.position.x < leftBound && !gameObject.CompareTag("Background"))
        {
            Destroy(gameObject);
        }

    }
}

 

 

역시나네요. Update의 첫번째 if문을 보면 gameOver 상태일 경우 배경이 움직이도록 설정되어있습니다. ! 부정문을 추가해 게임 오버 상태가 아닐 경우에 배경이 움직이도록 수정합니다.

 

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

public class MoveLeftX : MonoBehaviour
{
    public float speed;
    private PlayerControllerX playerControllerScript;
    private float leftBound = -10;

    // Start is called before the first frame update
    void Start()
    {
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControllerX>();
    }

    // Update is called once per frame
    void Update()
    {
        // If game is not over, move to the left
        if (!playerControllerScript.gameOver)
        {
            transform.Translate(Vector3.left * speed * Time.deltaTime, Space.World);
        }

        // If object goes off screen that is NOT the background, destroy it
        if (transform.position.x < leftBound && !gameObject.CompareTag("Background"))
        {
            Destroy(gameObject);
        }

    }
}

 

 

 

게임을 시작하면 배경이 움직이고 폭탄에 부딪히면 배경이 멈추는 것을 확인하실 수 있습니다.

 

 

 

 

 

 

 

오브젝트 생성은 SpawnManager로 다루었었죠. 컴포넌트부터 살펴봅시다.

 

 

 

 

 

 

 

프로퍼티는 잘 설정 되어있네요. 스크립트를 살펴봅시다.

 

 

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

public class SpawnManagerX : MonoBehaviour
{
    public GameObject[] objectPrefabs;
    private float spawnDelay = 2;
    private float spawnInterval = 1.5f;

    private PlayerControllerX playerControllerScript;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("PrawnsObject", spawnDelay, spawnInterval);
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControllerX>();
    }

    // Spawn obstacles
    void SpawnObjects ()
    {
        // Set random spawn location and random object index
        Vector3 spawnLocation = new Vector3(30, Random.Range(5, 15), 0);
        int index = Random.Range(0, objectPrefabs.Length);

        // If game is still active, spawn new object
        if (!playerControllerScript.gameOver)
        {
            Instantiate(objectPrefabs[index], spawnLocation, objectPrefabs[index].transform.rotation);
        }

    }
}

 

 

 

이런! InvokeRepeating의 첫번째 인자, 반복할 함수의 이름 문자열에 오타가 있네요!

 

PrawnsObjecet를 SpawnObjects로 변경합니다.

 

 

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

public class SpawnManagerX : MonoBehaviour
{
    public GameObject[] objectPrefabs;
    private float spawnDelay = 2;
    private float spawnInterval = 1.5f;

    private PlayerControllerX playerControllerScript;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("SpawnObjects", spawnDelay, spawnInterval);
        playerControllerScript = GameObject.Find("Player").GetComponent<PlayerControllerX>();
    }

    // Spawn obstacles
    void SpawnObjects ()
    {
        // Set random spawn location and random object index
        Vector3 spawnLocation = new Vector3(30, Random.Range(5, 15), 0);
        int index = Random.Range(0, objectPrefabs.Length);

        // If game is still active, spawn new object
        if (!playerControllerScript.gameOver)
        {
            Instantiate(objectPrefabs[index], spawnLocation, objectPrefabs[index].transform.rotation);
        }

    }
}

 

 

 

 

랜덤으로 잘 생성이 되는 것을 확인할 수 있습니다.

 

 

 

 

 

폭죽효과가 풍선 옆에서 나타나는 것은 생성 포지션이 잘못되었다는 뜻이겠지요. 우선 Player 하위의 파티클이 제대로 된 위치에 있는지 클릭하여 확인해봅시다.

 

 

 

 

 

 

 

 

 

 

Player의 하위 오브젝트로 Player 기준으로 Transform 이 설정됩니다. 그러므로 Position.X가 8 이라는 것은 게임 오브젝트의 우측으로 멀찍이 가 있다는 뜻이겠지요. Position.X 을 0으로 변경하면 잘 생성되는 것을 확인할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

배경이 중간에 끊기듯이 나옵니다. 이 경우는 RepeatBackground의 임계점이 적절치 못해서 그럴 것이라고 예상할 수 있습니다.

 

RepeatBackground 스크립트를 확인해봅니다.

 

 

 

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

public class RepeatBackgroundX : MonoBehaviour
{
    private Vector3 startPos;
    private float repeatWidth;

    private void Start()
    {
        startPos = transform.position; // Establish the default starting position 
        repeatWidth = GetComponent<BoxCollider>().size.y / 2; // Set repeat width to half of the background
    }

    private void Update()
    {
        // If background moves left by its repeat width, move it back to start position
        if (transform.position.x < startPos.x - repeatWidth)
        {
            transform.position = startPos;
        }
    }

 
}

 

 

 

제일 의심이 갔던 Update()의 if문은 잘 적혀있습니다.

 

어? 자세히보니 repeatWidth는 BoxCollider의 size.y의 절반으로 세팅되어있었네요. X축을 기준으로 움직이기 때문에 repeatWidth는 BoxCollider의 size.x의 절반이어야 합니다.

 

다음과 같이 수정합니다.

 

 

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

public class RepeatBackgroundX : MonoBehaviour
{
    private Vector3 startPos;
    private float repeatWidth;

    private void Start()
    {
        startPos = transform.position; // Establish the default starting position 
        repeatWidth = GetComponent<BoxCollider>().size.x / 2; // Set repeat width to half of the background
    }

    private void Update()
    {
        // If background moves left by its repeat width, move it back to start position
        if (transform.position.x < startPos.x - repeatWidth)
        {
            transform.position = startPos;
        }
    }

 
}

 

 

 

 

배경이 부드럽게 움직이는 것을 확인할 수 있습니다.

 

 

 

 

 

 

 

 

임계점을 부여해야하는 문제일 수도 있고, 천장 오브젝트를 생성해도 해결할 수 있는 문제입니다.

 

임계점을 부여하는 코드는 Transform을 변경하는 방식을 사용했기에 가능했고, 아직 배우지 않은 영역입니다.

 

쉽고 깔끔한 천장 오브젝트를 생성하는 방식을 사용해봅시다.

 

Ground 게임 오브젝트를 Ctrl+D로 복사합니다.

 

 

 

 

 

 

이름을 ceiling으로 변경합니다.

 

 

 

transform을 다음과 같이 변경합니다.

 

 

 

 

 

rotation을 x축 기준으로 180 회전하는 것을 잊지 맙시다. plane 게임 오브젝트는 단방향 오브젝트로, 회전시키지 않을 경우 빛이 통과되지 않아 게임이 어두워지고, 풍선은 위로 날라갈 수 있습니다.

 

 

 

이렇게 천장이 만들어지면 풍선의 높이가 제한되어 가속도를 받아 풍선이 매우 빨라지는 경우도 예방할 수 있습니다.

 

 

 

 

 

마지막 문제입니다!

 

풍선이 바닥에서 튀어 오르는 것처럼 만들어 화면 밑으로 사라지지 않게 해야한다고 합니다. 또한 효과음도 생성해야 한다고 하네요.

 

 

이거.. 1일차에 했던 프리미티브에 탄성을 부여했던 것이 기억나네요. 너무 오랜만이면 해당 게시글을 참고하셔도 좋습니다.

 

 

 

Challenge3 폴더 우클릭 -> Create -> Physic Material로 물리 머터리얼을 생성합니다. 이름은 BouncyBalloon으로 설정합시다.

 

 

 

BouncyBallon의 Bounciness를 1로 설정합니다.

 

 

 

 

Ground 게임 오브젝트를 보면 Mesh Collider가 존재합니다. BouncyBalloon을 드래그합니다.

 

 

 

 

 

 

 

바닥에 닿으면 통! 하고 튕기는 것을 확인할 수 있습니다. 천장에도 추가해 게임을 다채롭게 변경할 수 있습니다.

 

 

 

 

 

 

 

 

 


 

슬슬 혼자서도 과제를 해결해 나갈 수 있는 실력이 되는 것 같습니다.

 

좋습니다! 계속 나아가자구요!