본문 바로가기

코딩 공부/Unity C#

2-1주차 옵션 과제(돌 제한적 생성 및 파괴)

더보기

프로젝트 설명

사라지는 것보다 불태우는 것이 낫다고 판단하여 바위를 소재로 한 게임을 제작하기로 했습니다. 독일어를 할 줄 모른다면 코딩할 때 독일어로 들리는 횡설수설(군터 글리벤 글라우헨 글로벤)을 중얼거릴 수도 있습니다(....).

이 프로젝트에서는 매초마다 무작위 속도로 무작위 바위를 생성하여 게임 창에 바위가 3개 이상 나오지 않도록 합니다. 바위가 게임 창을 벗어나면 해당 바위는 파괴됩니다.

왜 신경 써야 할까요?

이 모듈에서 배운 기본 Unity 기능과 선택 기능을 사용하여 연습할 수 있습니다. 타이머가 만료되면 게임에서 무작위적인 일이 발생하는 것은 매우 일반적인 게임 동작입니다. 또한 게임 오브젝트를 파괴하여 게임 월드를 떠나는 것도 매우 일반적이므로 이 또한 실습해 볼 수 있습니다.

학습 목표

이 과제의 학습 목표는 다음과 같습니다:

unity 에디터를 사용하여 게임에 스프라이트와 게임 오브젝트를 추가합니다.

스크립트를 생성하고 게임 오브젝트에 첨부하여 게임 오브젝트 동작을 구현합니다.

스크립트 내에서 피직스 2D 시스템을 사용하여 게임 오브젝트를 이동합니다.

unity 게임에서 프리팹을 빌드하고 사용합니다.

if 문을 사용하여 의사 결정을 내리는 C# 코드를 개발합니다.

 

구현 단계

이제 전문화의 두 번째 과정을 마쳤으므로 이제 구현 단계는 실제로 구현 단계가 아닙니다. 대신 기능적 요구 사항이므로 직접 구현하는 방법을 알아내야 합니다. 솔루션이 수행해야 할 작업은 다음과 같습니다:

빈 창에서 시작하세요. 약 1초 후에 첫 번째 바위가 나타날 테니 걱정하지 마세요

매초마다 창에 바위가 3개 미만이면 창 중앙에 무작위 스프라이트로 새 바위를 생성합니다. 새 바위를 움직이려면 제가 제공한 무작위 속도 코드를 사용해야 하지만, 바위가 너무 빨리 움직이지 않도록 MinImpulseForce  MaxImpulseForce 값을 변경해야 할 수도 있습니다. 즉, 결과 속도가 충분히 느려서 창에 있는 3개의 바위 중 하나라도 사라지기 전에 모두 볼 수 있는지 확인해야 합니다

바위가 창 밖으로 나가면 스스로 파괴되어야 합니다. 1초 이내에 이전 총알의 기능으로 대체됩니다

유용한 힌트

필요한 기능을 구현하려면 Rock  RockSpawner 스크립트를 작성하여 각각 바위 프리팹(예, 프리팹을 사용해야 함)과 메인 카메라에 부착해야 합니다. RockSpawner 스크립트를 구현할 때 스폰 테디 강의가 도움이 될 것입니다.

현재 게임 내에 있는 바위를 세려면 바위 프리팹에 태그를 지정하여 GameObject FindGameObjectsWithTag 메서드를 사용하여 모든 바위를 가져올 수 있도록 하는 것이 유용할 수 있습니다. 해당 메서드가 반환하는 배열 객체의 Length 프로퍼티에 액세스하면 현재 게임에 있는 바위의 수를 확인할 수 있습니다.

Rock 스크립트에 OnBecameInvisible 메서드를 추가해야 하며, 이 메서드는 스크립트가 연결된 게임 오브젝트가 카메라에 더 이상 보이지 않을 때 호출됩니다(이때 게임 오브젝트를 소멸할 수 있습니다). 해당 메서드에 대해 자세히 알아보려면 Unity 스크립팅 레퍼런스의 문서를 참조하세요.

게임이 실행되는 동안 계층 구조 창을 보면 바위가 파괴되는 것을 확인할 수 있습니다. 게임 오브젝트는 스폰될 때 추가되고 파괴될 때 제거됩니다.

주의: 코드가 제대로 작동하여 플레이어가 빌드된 게임을 플레이할 수 있고 모든 것이 완벽하게 작동하더라도 Unity 에디터에서는 작동하지 않는 것처럼 보일 수 있습니다. 특히 바위가 게임 창을 떠날 때 즉시 파괴되지 않을 수 있습니다. 그 이유는 Unity 에디터에서 씬 창도 카메라를 사용하여 렌더링되므로 바위가 게임 카메라와 씬 창 카메라 모두의 시야에서 벗어날 때까지 OnBecameInvisible 가 호출되지 않기 때문입니다. 가장 좋은 방법은 게임을 빌드하고 빌드된 게임을 플레이하는 것입니다. 하지만 에디터에 계속 머물고 싶다면 계층 구조 창에서 메인 카메라를 더블 클릭한 다음 카메라 뷰의 가장자리를 표시하는 상자가 시야에서 사라질 때까지 Ctrl + 마우스 가운데 휠을 사용하여 씬 뷰를 확대합니다.

게임 테스트

다음은 이전 버전의 코스에서 게임이 올바르게 작동하는지 확인하기 위해 사용한 루브릭입니다:

1. 창 중앙에 새로운 바위가 무작위 바위 스프라이트와 함께 스폰되었습니다(무작위로 인해 동일한 스프라이트가 있는 바위가 여러 개 연속으로 생성될 수 있습니다. 결국 3개의 바위 스프라이트가 모두 나타나면 솔루션이 올바르게 작동하는 것입니다.)

2. 새로운 바위는 즉시 임의의 속도로 움직이기 시작합니다

3. 바위가 3개가 될 때까지 약 1초마다 새로운 바위가 스폰됩니다

4. 창에 바위가 3개 이상 있을 수 없습니다

5. 창을 떠난 바위는 1초 이내에 창 중앙에 무작위 바위 스프라이트와 무작위 속도로 스폰되는 새로운 바위로 대체됩니다


 

구상순서: 돌 움직이기 - 랜덤 스프라이트로 돌 생성 - GameObject FindGameObjectsWithTag 메서드 사용하여 돌 개수 확인 및 생성 제한 - OnBecameInvisible 메서드 및 돌 파

 

 돌 움직이기 (class Rock)

    void Start()
    {
        // apply impulse force to get game object moving
        const float MinImpulseForce = 1f;
        const float MaxImpulseForce = 2f;
        float angle = Random.Range(0, 2 * Mathf.PI);
        Vector2 direction = new Vector2(
            Mathf.Cos(angle), Mathf.Sin(angle));
        float magnitude = Random.Range(MinImpulseForce, MaxImpulseForce);
        GetComponent<Rigidbody2D>().AddForce(
            direction * magnitude,
            ForceMode2D.Impulse);
    }

- 오브젝트(prefabRock)에 Rock, RigidBody2D 컴포넌트 추가

 

랜덤 스프라이트로 오브젝트 스폰 (class RockSpawner)

    // 오브젝트 프리팹
    [SerializeField]
    GameObject prefabRock;

    // 스프라이트 
    [SerializeField]
    Sprite rockSprite0;
    [SerializeField]
    Sprite rockSprite1;
    [SerializeField] 
    Sprite rockSprite2;
  
      // 위치 변수 저장
    float spawnLocationX;
    float spawnLocationY;
  
   // 타이머 설정
    const int rockSpawnTime = 1;
    Timer spawnTimer;

Main Camera의 컴포넌트

- SerializeField로 변수를 선언하여 유니티창에 위와 같이 나타남

 

RockSpawner 메서드 (오류포)

    // Start is called before the first frame update
    void Start()
    {      
        spawnTimer = gameObject.AddComponent<Timer>();
        spawnTimer.Duration = rockSpawnTime;
        spawnTimer.Run();
    }

    // Update is called once per frame
    void Update()
    {
        if (spawnTimer.Finished)
        {
            // 돌 스폰
            SpawnRock();

            // 타이머 재시작
            spawnTimer.Run();
        }
    }

    void SpawnRock()
    {
        // 중앙에 돌 생성
        Vector3 location = new Vector3 (spawnLocationX, spawnLocationY, -Camera.main.transform.position.z);
        Vector3 worldLocation = Camera.main.ScreenToWorldPoint (location);  // 월드뷰로 전환
        GameObject rock = Instantiate(prefabRock) as GameObject;  // 프리팹 오브젝트로 저장
        rock.transform.position = worldLocation;

        // 랜덤 스프라이트로 오브젝트 생성
        SpriteRenderer spriteRenderer = rock.GetComponent<SpriteRenderer>();
        int spriteNumber = Random.Range(0, 3);
        if (spriteNumber == 0)
        {
            spriteRenderer.sprite = rockSprite0;
        }
        else if (spriteNumber == 1)
        {
            spriteRenderer.sprite = rockSprite1;
        }
        else
        {
            spriteRenderer.sprite = rockSprite2;
        }
    }

 

위치 설정 코드에 오류가 있었다

 ↓중앙에서 생성되어야할 오브젝트가 잘못된 위치 설정으로 게임뷰의 왼쪽 아래에서만 나옴

        // 중앙에 돌 생성
        Vector3 location = new Vector3 (spawnLocationX, spawnLocationY, -Camera.main.transform.position.z);
        Vector3 worldLocation = Camera.main.ScreenToWorldPoint (location);  // 월드뷰로 전환
        GameObject rock = Instantiate(prefabRock) as GameObject;  // 프리팹 오브젝트로 저장
        rock.transform.position = worldLocation;

- 오류원인: 벡터의 좌표를 월드 좌표로 변환했기 때문이다.

 

피드백 후 수정 답안

        // 중앙에 돌 생성
        GameObject rock = Instantiate(prefabRock) as GameObject;  // 프리팹 오브젝트로 저장
        rock.transform.position = Vector3.zero;

 

- 위치를 (0, 0, 0)으로 설정하면 중앙에서 오브젝트가 생성된다


벡터의 월드 좌표 변환은 언제 필요한가요?

월드좌표변환이 필요한 코드

const int SpawnBorderSize = 100;

 // save spawn boundaries for efficiency
        minSpawnX = SpawnBorderSize;
        maxSpawnX = Screen.width - SpawnBorderSize;
        minSpawnY = SpawnBorderSize;
        maxSpawnY = Screen.height - SpawnBorderSize;

void SpawnBear()
    {
        // generate random location and create new teddy bear
        Vector3 location = new Vector3(Random.Range(minSpawnX, maxSpawnX),
            Random.Range(minSpawnY, maxSpawnY),
            -Camera.main.transform.position.z);
        Vector3 worldLocation = Camera.main.ScreenToWorldPoint(location);
        GameObject teddyBear = Instantiate(prefabTeddyBear) as GameObject;
        teddyBear.transform.position = worldLocation;
    }

- 화면 좌표(Screen Coordinates)를 기반으로 작업할 때는 월드 좌표(World Coordinates)로 변환해야함

- minSpawnX, maxSpawnY 등의 변수 설정을  Screen Coordinates기반으로 하였음

Screen.width와 Screen.height가 사용되었기 때문


 

GameObject FindGameObjectsWithTag와 FindWithTag의 차이

다수의 오브젝트 VS 단일 오브젝트

오브젝트의 개수 세고 생성 제한 조건 만들기

    void Update()
    {
        int amountOfRock = GameObject.FindGameObjectsWithTag("Rock").Length;
        if (spawnTimer.Finished && amountOfRock < 3)
        {
            // 돌 스폰
            SpawnRock();

            // 타이머 재시작
            spawnTimer.Run();
        }
    }

 

OnBecameInvisible사용 (Rock class)

    void OnBecameInvisible()
    {
        Destroy(gameObject);
    }

- 클래스 안에 메서드를 만들면 알아서 콜됨

- 오브젝트가 카메라 밖으로 나가서 안 보였을 때를 처리하는 함수이다

'코딩 공부 > Unity C#' 카테고리의 다른 글

마우스 버튼 처리  (0) 2024.06.24
마우스 위치 처리 & 클램핑(Clamping)  (2) 2024.06.24
오브젝트 폭발시키기  (0) 2024.06.20
태그 기반 오브젝트 파괴  (0) 2024.06.19
프리팹을 활용한 오브젝트 스폰  (0) 2024.06.18