과일 합치기까지 구현한 상태(특정 시점 이후 실행X -> 수정 필요)

 

<구현해야 할 기능>

  1. 같은 과일끼리 충돌하면 다음 단계 과일로 합쳐지는 기능
  2. 첫 클릭에는 원하는 위치로 이동, 두 번째 클릭에 낙하
  3. 이전 과일 낙하 후 새 과일 생성
  4. 점수 UI
  5. 게임 진행도에 따른 과일 생성 확률 조정

[ MakeFruits2.cs ]

( MakeFruits"2"인 이유: MakeFruits는 과일 생성과 위치 이동을 한 번에 하려다 보니 꼬여서 버림 )

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

public class MakeFruit2 : MonoBehaviour
{
    private GameObject fruit;
    [SerializeField] private GameObject cherry;
    [SerializeField] private GameObject strawberry;
    [SerializeField] private GameObject grape;
    [SerializeField] private GameObject orange;
    [SerializeField] private GameObject apple;
    
    private GameObject instance;
    private int click = 0;		// 제거
    
    void Awake()
    {
        click = 0;				// 제거
        Make();
    }
    
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            MoveFruit();
            click += 1;			// 제거
        }
    }
    
    void Make()
    {
        int fruitNumber = Random.Range(8, 13);
        
        //Vector3 pos = transform.position;
        //pos.y += 3.5f;					<= 원래는 모든 과일 프리팹에 추가할 생각으로 짠 코드. 인스턴스화 위치를 pos로 하려 했다.
        
        switch (fruitNumber)
        {
            case 8: fruit = cherry; break;
            case 9: fruit = strawberry; break;
            case 10: fruit = grape; break;
            case 11: fruit = orange; break;
            case 12: fruit = apple; break;
            default: break;
        }
        
        instance = Instantiate(fruit, transform.position, transform.rotation);	// 빈 오브젝트 Fruits에 부여, Fruits의 position에 과일 생성
    }
    
    private void MoveFruit()		// 경우를 나누지 않는 걸로 수정
    {
        if (click == 1)				
        {
            Vector3 pos = instance.transform.position;
            pos.x += Input.mousePosition.x;
            
            Destroy(instance);
            instance = Instantiate(fruit, pos, transform.rotation);
        }
        else if (click == 2)
        {
            fruit.AddComponent<Rigidbody2D>();
            click = 0;
            
            StartCoroutine("DropWait");
            Make();
        }
    }
    
    IEnumerator DropWait()
    {
        yield return new WaitForSeconds(3f);		// 0.5f로 수정
    }
}

 

위치 조정 및 낙하

처음엔 첫 클릭(click == 1)엔 위치 조정, 두 번째 클릭(click == 2)에 낙하하게 하려 했는데 너무 어려움

위치 조정도 안 되고 낙하도 지멋대로고 난리남

 

진짜 수박 게임은 클릭 위치 조정을 여러번 해도 되는 걸로 기억해서 단순화시킨 거였는데

알고 보니 진짜가 더 단순했다: 한 번 클릭하면 위치 조정과 동시에 낙하됨

그래서 그렇게 바꿈!

 

[ MakeFruits2.cs ]

void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            MoveFruit();
            //click 제거
        }
    }
    
    void Make()
    {
        int fruitNumber = Random.Range(8, 13);
        
        switch (fruitNumber)
        {
            case 8: fruit = cherry; break;
            case 9: fruit = strawberry; break;
            case 10: fruit = grape; break;
            case 11: fruit = orange; break;
            case 12: fruit = apple; break;
            default: break;
        }
        
        instance = Instantiate(fruit, transform.position, transform.rotation);
    }
    
    private void MoveFruit()		// 마우스 좌클릭했을 때 실행
    {
        Vector3 pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        pos.y = 0;
        
        instance.transform.position += pos;		// 마우스의 x좌표만큼 과일 이동
        
        if (instance.GetComponent<Rigidbody2D>() == null)	// 리지드바디 없는 경우 추가
        {
            instance.AddComponent<Rigidbody2D>();
        }
        
        StartCoroutine("DropWait");		// 낙하 대기
    }

 

처음엔 pos = Input.mousePosition 로 했는데, 이게 게임 세계 좌표가 아니라 화면 좌표여서 위치 조정이 안 됐던 거임

그래서 Camera.main.ScreenToWorldPoint 로 바꿔줬더니 잘됨 bb (지피티 최고)

드디어 위치 조정도 되고 낙하도 잘된다!!!

 

새 과일 생성

근데 낙하 후 3초 기다리라고 했는데 안 기다림

Make() IEnumerator DropWait 안에 넣었더니 해결됨 왠지는 잘 모르겠음.. (역시 지피티가 알려줌)

실행해봤더니 3초 길어서 0.5초로 수정함

 

근데 문제가 생김

"클릭했을 때 낙하"되게 하려고 앞 5개의 과일은 리지드바디를 없앴단 말이야? ( 앞 5개 과일만 '클릭'으로 생성 나머지는 합성으로 생성)

근데 합성하면 합성 위치에 그대로 있는 경우가 생김...

 

합성 결과가 앞 5개 과일에 해당해서 생기는 문제였음 ㅠ

그래서 Combine.cs를 수정해줌 다음 단계 객체 생성 후 리지드바디가 없으면 추가해주는 걸로!

 

[ Combine.cs ]

private void OnCollisionEnter2D(Collision2D collision)
{
    if ( string.Compare(this.gameObject.name, collision.gameObject.name) == 0 )
    {
        isCombined = true;

        Combine combine = collision.gameObject.GetComponent<Combine>();
        if (combine.isCombined)
        {
            return;
        }
        else
        {
            Destroy(collision.gameObject);
            Destroy(this.gameObject);
            instance = Instantiate(nextLevel, transform.position, transform.rotation);
            
            // 합성된 과일에 리지드바디가 없는 경우 추가
            if( instance.GetComponent<Rigidbody2D>() == null)
            {
                instance.AddComponent<Rigidbody2D>();
            }

            StartCoroutine("CombineWait");		// 너무 빨리 합성돼서 0.5초 기다리도록 추가해줌
        }

    }
}

 

오 이제 진짜 잘됨

 

위치 조정 + 낙하는 이렇게 생각보다 쉽게 끝 ~~

 


 

마우스 클릭할 때마다 과일을 랜덤으로 생성하는 것까지 구현한 상태 (생성과 동시에 낙하)

 

<구현해야 할 기능>

  1. 같은 과일끼리 충돌하면 다음 단계 과일로 합쳐지는 기능
  2. 첫 클릭에는 원하는 위치로 이동, 두 번째 클릭에 낙하
  3. 이전 과일 낙하 후 새 과일 생성
  4. 점수 UI
  5. 게임 진행도에 따른 과일 생성 확률 조정

[ Combine.cs ]

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

public class Combine : MonoBehaviour
{
    [SerializeField]
    private GameObject nextLevel;
	
    private void OnCollisionEnter(Collision collision)
    {
    	if ( string.Compare(this.gameObject.name, collision.gameObject.name) == 0 )
        {
            Instantiate(nextLevel, transform.position, transform.rotation);
            Destroy(collision.gameObject);
            Destroy(this.gameObject);
        }
    }
}

 

합치기 기능(1번)을 구현하고 싶은데 처음엔 아예 안 됐음! ㅠㅠ

알고 보니 OnCollisionEnter는 3D만 됨 2D는 OnCollisionEnter2D를 써야 됨 ㅎ 머슥

 

(처음엔 string.Compare도 아니고 == 연산자 썼음)

더보기

==, string.Equals() : 객체 비교

string.Compare() : 내용 비교 (아스키 비교)

 

그래서 2D로 고쳤음! 이번엔 충돌도 되고 합쳐지기도..한다고 볼 수 있는데

딸기 두 개가 충돌했는데 냅다 수박까지 만듦; 그것도 여러 개 ㅡㅡ

 

충돌이 여러 번 돼서 그런가..했는데 생각해보니 모든 객체가 다음 단계 과일을 생성하고 서로를 지움!!

두 물체가 충돌하면 '각자' 다음 단계 과일을 생성한다는 뜻..

(삭제 먼저하고 생성을 하는 걸로 바꿔도 봤는데 크게 다를 건 없었다)

 

어떻게 하면 같은 부딪혔을 때, 다음 단계의 과일을 하나만 만들 수 있을지 생각을 해봤다

 

그래서 한 생각: 플래그 활용!

 둘 중 하나는 무조건 충돌을 '먼저' 감지하지 않나?라는 생각에서 비롯된 방법

 

[ Combine.cs ]

public class Combine : MonoBehaviour
{
    [SerializeField]
    private GameObject nextLevel;
    
    bool isCombined = false;

    private void OnCollisionEnter2D (Collision2D collision)
    {
    	if ( string.Compare(this.gameObject.name, collision.gameObject.name) == 0 )
        {
            isCombined = true;
            
            Combine combine = collision.gameObject.GetComponent<Combine>();
            
            if ( combine.isCombined )
            { return; }
            else
            {
            	Destroy(collision.gameObject);
                Destroy(this.gameObject);
                
                Instantiate(nextLevel, transform.position, transform.rotation);
            }
        }
    }
}

 

(클릭으로는 오렌지까지만 생성되는 상태. 이후 과일은 합성으로 생성됨)

된다!!! ㅠㅠ 

(특정 시점 이후 특정 과일이 안 합쳐지는 현상이 발생하나 나중에 해결하자)

 

과일 단계: 체리 -> 딸기 -> 포도 -> 오렌지 -> 사과 -> 배 -> 레몬 -> 복숭아 -> 파인애플 -> 코코넛 -> 수박

 

 


 

기초 문법

C#은 객체지향 언어

 

변수 선언

- float 타입 선언 시 숫자 끝에 꼭 f 붙여야 함

- string 타입 존재

 

스크립트 작성

- using : 네임스페이스(라이브러리 제공)에서 코드 가져옴

- Start() : 코드 실행 시작점. 게임이 실행될 때 자동으로 한 번 실행. main 함수랑 비슷한 역할인 듯?

- Debug.Log() : 콘솔 출력. using을 통해 불러온 UnityEngine에 포함.

- 스코프 : 선언된 변수/메서드 등이 관측되는 유효 범위. 중괄호 단위.

- 스크립트 이름과 클래스 이름은 동일

 

제어문: JAVA와 동일

배열: JAVA와 동일

 

 

오브젝트

클래스: 틀

       - 멤버: 필드(변수) + 메서드(함수)

       - 접근 제한자: public, private(기본값), protected

오브젝트: 실재하는 물건. 실체

인스턴스: 클래스를 이용해 실체화(인스턴스화)한 오브젝트

       - 같은 클래스를 이용해서 생성되었어도 서로 독립적

 

MonoBehaviour 클래스

- 게임 오브젝트의 컴포넌트로 추가하는 방법으로만 실체화 가능

- 컴포넌트는 게임 오브젝트에 추가될 때에 컴포넌트로서 필요한 초기화 과정을 거침

- MonoBehaviour를 상속한 클래스는 컴포넌트로서만 동작 - new 생성자() 사용 X

- new 사용시 필요한 초기화 과정과 게임 오브젝트에 추가되는 과정 생략 -> 정상 동작 X

 

참조 타입

- 기본 타입과 달리 왜 new 연산자를 사용하는가? => 변수 자체가 오브젝트가 아니기 때문

- '참조 타입' 변수는 오브젝트 자체가 아닌, 오브젝트를 가리키는 변수

- 아무도 가리키지 않는 오브젝트는 C#의 가비지 컬렉터가 정리

- 하나의 실체에 여러 개의 참조 변수 존재 가능

- class로 만든 타입은 대부분 참조 타입으로 동작 (예외: string은 클래스로 선언되었지만 값 타입으로 동작. immutable)

 

값 타입

- 참조 타입과 달리 변수 자체에 실체를 저장

- 참조 타입이 값 타입을 가리킴

 

=> 참조 타입 변수에 오브젝트/컴포넌트 할당 => 변수를 이용해 제어 가능

 

 


출처: 레트로의 유니티 게임 프로그래밍 에센스(개정판)

'Unity > 개념' 카테고리의 다른 글

[Unity] 5. 2D 게임  (1) 2024.09.25
[Unity] 4. 공간과 움직임  (5) 2024.09.11
[Unity] 3. transform: 방향, 크기, 회전  (1) 2024.03.29
[Unity] 1. 유니티 엔진 동작 원리  (0) 2023.12.25

컴포넌트 패턴

유니티 내 게임 오브젝트(캐릭터, 물체 등)에 특성을 부여하는 '부품(컴포넌트)'을 조립하는 방식

 

- 게임 오브젝트는 컴포넌트를 담는 빈 '껍데기'

- 상속을 이용한 캐릭터 생성은 코드의 추가/삭제/수정이 번거로움

   => 오브젝트의 기능을 컴포넌트 패턴으로 구현, 필요한 컴포넌트를 오브젝트에 붙이는 방식 채용

- 각 컴포넌트는 서로 독립적: 다른 컴포넌트에 영향을 주지 않음

더보기

ex) 트랜스폼(Transform): 오브젝트의 위치, 크기, 회전 각도 지정

      메시 필터(Mesh Filter): 오브젝트의 외곽선 지정

      메시 렌더러(Mesh Renderer): 오브젝트 표면 색을 채워 외형 지정

      박스 콜라이더(Box Collider): 다른 물체와 부딪히도록 오브젝트의 물리적 표면 형성

      리지드바디(Rigidbody): 오브젝트가 물리 엔진의 통제를 받게 함

 


 

브로드캐스팅

유니티가 유니티 내 모든 컴포넌트들에게 원하는 기능을 실행하도록 메시지를 전송하는 '전체 방송'

 

MonoBehaviour

: 유니티의 모든 컴포넌트들이 상속받는 클래스

- 컴포넌트에 필요한 기본 기능 구현 => MonoBehaviour를 상속받으면 컴포넌트의 역할 가능

- 상속받을시 유니티의 제어를 받음 => 유니티의 메시지를 들을 수 있음

 

메시지 기반 방식

: 컴포넌트의 기능을 실행하기 위해 메시지를 뿌리는 방식

- 컴포넌트들은 서로에게 관심 X => '찾아내기 전까진' 서로 있는지도 모름.

- 유니티 엔진도 오브젝트에 어떤 컴포넌트가 있는지 전부 파악하고 있지 않음.

   => 기능을 실행하기 위해 오브젝트를 찾아가는 대신, 메시지를 뿌림

- 메시지 송/수신자는 서로에게 관심 X

- 메시지에 명시된 기능을 가지고 있다면 실행, 없다면 무시 (해당 기능을 가진 모든 오브젝트가 기능 실행)

 

브로드캐스팅

★ 유니티 엔진이 메시지 수신자에게 관심이 있다 == 수신자를 정한다

    : 메시지를 전달하기 위해 컴포넌트들 간의 직접적인 소통 발생 => 컴포넌트 간의 독립성 약화!

- 컴포넌트들 간의 독립성을 유지하기 위해 메시지를 무차별적으로 여러 오브젝트에 동시에 뿌림

- 컴포넌트들이 서로 의존하지 않고 동작할 수 있는 이유

 

유니티 이벤트 함수

: 이름 철자를 똑같이 구현해두면 메시지에 의해 자동으로 실행되는 메서드

- Start(), Update(), OnTriggerEnter() 등

- 메시지와 브로드캐스팅의 원리 이용하여 동작

ex) Start()는 오브젝트 첫 활성화될 때 자동으로 한 번 실행 (∵ 활성화시 유니티가 Start 메시지 브로드캐스팅)

 

 

 


출처: 레트로의 유니티 게임 프로그래밍 에센스(개정판)

'Unity > 개념' 카테고리의 다른 글

[Unity] 5. 2D 게임  (1) 2024.09.25
[Unity] 4. 공간과 움직임  (5) 2024.09.11
[Unity] 3. transform: 방향, 크기, 회전  (1) 2024.03.29
[Unity] 2. C#  (1) 2023.12.30

+ Recent posts