Prosto

따라하는 유니티 2D 프로젝트ⓑ -3 본문

Programing/Unity 3D

따라하는 유니티 2D 프로젝트ⓑ -3

Prosto 2016. 10. 9. 00:01

 

 

따라하는 유니티 2D 프로젝트ⓑ 강좌 세 번째 시간입니다.

 

이번에 함께 할 작업은 블록을 매니저를 블록을 오브젝트 풀로 생성하고

그 블록들을 일정한 형태를 가지도록 생성하겠습니다.

그리고 중간에 블록들을 처리하며 생기는 예외적인 부분들에 대해

수정하는 방법을 진행할 겁니다. (Debug 모드, 한 프레임씩 실행)

 

 

 

그럼 시작하겠습니다.

 

 

가장 먼저 저번에 작업했던 프로젝트를 실행합니다. 그리고 순서대로 직접 해보며 따라오시면 됩니다.

 

 

프로젝트를 열면 저번에 실행된 곳까지 저장되어있는 것을 볼 수 있습니다.

 

우리가 이번에 할 작업은

블록 매니저를 통하여 블록들을 일정한 형태로 만들어보는 것입니다.

그 과정에서 프리팹이라는 것도 만들어볼 테고, 오브젝트 풀(Object Pool),

스크립트 작성, Debug 등을 해볼 수 있습니다.

차근차근 같이 진행해볼까요?

 

 

자, 먼저 블록매니저 스크립트를 작성하기 위하여

Scripts - Block 폴더에

마우스 우측 클릭 - Create - C# Script 순으로 선택하여

스크립트를 생성해주세요.

 

 

이름은 BlockManager로 했습니다.

(생성된 모습입니다.)

 

 

그러면 이번에는 스크립트를 작성해볼까요?

 

public GameObject NormalBlock;

우리가 프리팹을 넣어줘 사용될 오브젝트를 받아두는 곳입니다.

이 받아둔 오브젝트를 통해서

새 오브젝트(복제품)들을 만들 수 있습니다.

 

밑에 있는

private GameObject[] normalBlocks;는

오브젝트 풀을 만들 GameObject 배열입니다.

 

private int normalBlockNum = 20;는

normalBlock을 몇 개 생성할 것인지에 대한 수치 값입니다.

 

그리고 Awake 부분도 작성해주시고요.

함수는 바로 밑 이미지에 나옵니다.

 

 

자, Awake() 함수 밑에 이어서 작성해주시면 됩니다.

1번 함수 CreateNormalBlock()은 오브젝트 풀을 생성하는 부분이고,

2번 함수 SetNormalBlock(Vector3 pos)는 오브젝트 풀로 생성된 블록을

위치값(Vector3값)을 전달해주면 해당 위치에 활성화 시켜줍니다.

 

여기서 우리가 확인하고 넘어갈 부분은 오브젝트 생성 부분입니다.

1번 함수에 있는 for문을 봅시다.

normalBlocks[i] = GameObject.Instantiate(NormalBlock, Vector3.zero, Quaternion.identity) as GameObject;

 이 부분이 오브젝트를 생성(복사)하는 부분입니다.

 

풀어서 보자면,

 

normalBlocks (게임오브젝트)배열의 i번째에

GameObject.Instantiate(생성하겠다)

 

그리고 인자값들( 게임오브젝트, 위치(pos), 각도(Quaternion) )이 있죠?

 

NormalBlock이라는 오브젝트를

Vector3.zero( == 0,0,0) 위치에

Quaternion.identity( == 기본각도)로

 

as GameObject;

그리고 게임오브젝트 형식으로.

라는 말입니다.

 

그러니까 다시 말하면

normalBlocks 게임오브젝트 배열의 i번째에

NormalBlock 오브젝트를 (0,0,0)위치에 기본각도로 만들겠다!

라는 의미입니다.

 

그러니 생성할 위치 부분을 바꾸면 위치가 달라질 테고,

각도를 바꾸면 각도가, 게임오브젝트를 바꾸면

게임오브젝트가 달라지겠죠?

 

리소스를 사용하지 않고 생성하면

이런 방법으로 많이 사용되니 기억해두시길 바랍니다.

 

 

이렇게 실행하면 일단 오브젝트 풀로 블록들이 생성은 될 것입니다.

(비활성화[SetActive(false)]시켜뒀으니 게임 화면에는 안 나오고 Hierarchy 탭에서는 확인 가능하겠죠?)

 

하지만, 우리가 지금 하고싶은 것은

블록들이 일정한 형태로 생성되는 것이니

아직 끝난 게 아니죠?

 

먼저 그냥 값을 가지고 있는 것보다는

각 블록에 따라 크기, 생성 가능한 꼭대기 위치

정도는 알고 기준으로 삼고싶으니

그 값을 쉽게 얻을 수 있도록 작업을 해볼까요?

 

이렇게 복사해둔 오브젝트들 중 하나를 선택합니다.

 

 

그리고 위치를 카메라 중앙 꼭대기에 맞춰주세요.

 

Inspector 탭에 나오는

Block_prosto (1)의 값입니다.

 

 

그런데 위치값, 크기 정도가 필요한데 필요없는 값들이 많죠?

제거해줍시다.

Blcok Info (Script)를 제거해볼까요?

Block Info위에서 마우스 우측 클릭을 하고,

Remove Component를 누르면 제거됩니다.

 

 

다음으로 Sprite Renderer를 제거합니다.

방법을 똑같습니다.

Sprite Renderer 위에서 마우스 우측 클릭

그리고 Remove Component를 선택하면 됩니다.

 

 

그리고 마지막으로 트리거로 사용하진 않을 것이니

Is Trigger는 해제시켜줍니다.

 

 

그리고 이렇게 Scene을 보면

이미지 없이 초록색으로 테두리만 나오는 것을 확인할 수 있습니다.

 

이름도 사용하기 좋게 바꿉니다.

(NormalBlockCenterTop이라고 이름을 변경했습니다.)

 

 

이렇게 아까 NormalBlockCenterTop이라고 생성해둔 오브젝트를 통해 정보를 얻어올 수 있습니다.

Vector3 값으로 중앙상단 값을 받아올 변수, 크기 값을 얻어올 변수 두 개를 선언해주고

 

GameObject.Find("NormalBlockCenterTop").transform으로 Transform을 얻어 tempTf에 임시로 저장한 뒤

해당 transform에서 필요한 정보와 GetComponent로 BoxCollider2D의 size 값을 받았습니다.

 

GameObject.Find(이름);

을 통해서 활성화 상태(active true)인 오브젝트를 찾을 수 있습니다.

(찾은 대상을 그대로 사용하거나 필요한 정보만 얻어오거나 모두 가능)

 

지금 여기서 보면

GameObject.Find("NormalBlockCenterTop").transform

마지막에 .transform을 통해 Find를 통해 얻은 게임오브젝트의

Transform 값을 얻을 수 있게 됩니다.

 

tempTf.GetComponent<BoxCollider2D>().size;

이렇게 사용하여

 

tempTf에게서 BoxCollider2D라는 (GetComponent)컴포넌트를 얻고,

하위의 size라는 변수(Vector2)값을 얻어왔죠?

거기에 현재 transform의 scale 값을 곱하여

제대로 된 사이즈 값을 얻을 수 있습니다.

(만약 scale이 1이 아니라 1.5였으면 사이즈가 바뀌어야하니

이렇게 작성해둔 겁니다.)

 

 

그리고 생성하는(위치 시키는) 부분입니다.

5개씩 4줄로 나오도록 임시생성해봤습니다.

(아까 만들어뒀던 SetNormalBlock(Vector3)함수를 사용했죠?)

 

(사실 맵 설정에서 그리드 형식으로 나누어 사용하면

훨씬 쉽고 편하게 할 수 있지만,

저는 다른 방식으로도 사용할 것이기에 그리드 형식을 사용하지 않았습니다.)

 

 

이제 스크립트 작성이 끝났습니다.

 

마지막으로 필요한 준비물들을 만들고 실제로 사용해볼까요?

(게임매니저 생성, 프리팹 생성 - 등록)

먼저 매니저 스크립트들을 관리할 게임매니저를 만들어보겠습니다.

Hierarchy 탭에서 마우스 우측 클릭

Create Empty를 선택합니다.

 

생성되는 GameObject의 이름을

GameManager로 바꿔줍니다.

 

이렇게 만들어진 GameManager 오브젝트에

BlockManager 스크립트를 넣어봅시다.

 

Hierarchy탭의 GameManager를 선택한 후

 

Scirpts - Block에 위치한

BlockManager 스크립트를

(GameManager의) Inspector 탭 하단에 Add Component 밑에 드래그 앤 드랍해줍니다.

 

 

이번에는 프리팹 생성을 해볼까요?

먼저 프리팹들을 모아둘 폴더를 만듭니다.

Assets 폴더에 우측 클릭 - Create - Folder 순으로 선택하면 되겠죠?

 

 

이름은 Prefabs로 해줍니다.

 

 

프리팹을 만드는 방법입니다.

먼저 우리가 만들어둔 Blcok_prosto의 이름을

NormalBlock으로 변경한 후,

Prefabs 폴더에 그대로 드래그 앤 드랍하면 프리팹으로 만들어집니다.

 

이렇게 정상적으로 생성되면

해당 이름(NormalBlock)으로 프리팹이 생성된 게 보입니다.

그리고 Hierarchy 탭에서는 NormalBlock 오브젝트 이름이 파란색으로 바뀝니다.

(프리팹으로 등록된 오브젝트라는 겁니다.)

 

 

프리팹으로 만들어진 오브젝트와 나머지들을 필요없으니

선택하고 삭제합니다.

삭제는 윈도우에서와 마찬가지로

Delete 키를 누르면 삭제됩니다.

(이밖에도 마우스 우측 클릭 - Delete를 선택하여 제거할 수 있습니다.)

(실수로 잘못 제거했다면 Ctrl + Z로 작업을 취소할 수 있습니다.)

 

제거된 Hierarchy 탭의 모습입니다.

 

 

이번에는 프리팹으로 생성했던 것을

BlockManager에 등록해볼 겁니다.

 

이렇게 Hierarchy 탭에서는 GameManager를 선택하고,

Prefebs 폴더의 NormalBlock을

Block Manager (script)에 있는 Normal Blcok에 드래그 앤 드랍해줍니다.

 

(추가가 된다면 이런 모습입니다.)

(추가할 때 Normal Block 옆 네모 칸에 드래그 앤 드랍하면 됩니다.)

 

 

그리고 실행해보면

정상적으로 4x5의 블록들이 생성된 것을 확인할 수 있습니다.

 

 

하지만 여기서 문제가 있습니다.

뭔가 정상적으로 작동하지 않습니다.

튕기는 방향이 이상한 방향입니다.

 

뭐가 문제일까요?

 

이런 때에 우리는 Debug를 이용하여 찾아볼 수도 있습니다.

 

한번 Debug를 통해 값들이 정상적인지 확인해볼까요?

 

유니티 우측 상단에 리스트 같이 생긴 아이콘을 눌러줍니다.

그러면 Normal Debug Lock 등등 메뉴가 보이는데요.

우리는 지금 Normal이 선택된 상태입니다.

Debug를 선택하여 Debug 모드로 변경해봅시다.

 

 

이렇게 Debug 모드로 변경되면

Inspector 탭이 Debug로 바뀐 것을 확인할 수 있습니다.

뿐더러 값들도 더욱 상세한 값들이 나옵니다.

(그리고 public 값들과 함께 private으로 선언했던 값들도 확인할 수 있습니다.)

 

 

이렇게 적당히 하나를 선택하여

값이 적절히 들어갔는지 확인해봅시다.

(튕기는 방향 이상은 블록 정보(BlockInfo)가 잘못되었을 가능성이 높으니까요.)

 

보면 thisBlockPos가 해당 블록의 위치와 다른 것을 볼 수 있습니다.

이러니 충돌된 위치와 확인하는 위치가 차이가 생겨

이상한 방향으로 튀는 거겠죠?

 

( 어째서 thisBlockPos가 다르게 설정된 것 같나요?

우리가 작성했던 스크립트를 생각해보며

문제점을 찾아보시고 진행하시면

공부에 더 도움이 될 것 같네요. )

 

문제점을 확인했으니 Debug 모드를 다시 Normal 모드로 변경해줍시다.

(이게.. 디버그 모드에서 아주 가끔씩 유니티가 갑자기 종료되기도 하더라고요.)

 

 

그러면 문제를 다시 바로잡기 위해서 또 스크립트를 가봅시다.

BlockInfo 스크립트를 열어주세요.

 

 

문제점이 바로 여깁니다.

Awake에서 thisBlockPos를 받은 게 문제죠.

 

왜 그럴까요?

 

이건 Awake를 알아야합니다.

 

Awake는 생성될 때 한번만 호출되는 부분입니다.

그런데 여기서 위치값을 받았으니 문제가 발생한 겁니다.

(오브젝트 풀이 아니라면 생성된 곳이 곧 위치겠지만요.)

(그리고 참고로 스크립트 내 함수 실행 순서는

Awake - Start - Update입니다. )

 

오브젝트 풀로 생성하며 처음 좌표를 Vector3.zero(0,0,0)로 받았으니

생성되며 저 좌표값을 받아뒀겠죠?

 

그리고 우리가 사용할 때는 다시 활성화시켜 그대로 위치만 변경시켰으니

저 값은 그대로 (0,0,0)일 겁니다.

 

그러면 사실 저 부분은 유동적인 것이니

따로 함수로 만들어줘야겠죠?

 

이렇게 바뀌었습니다.

Awake에서 thisBlockPos를 받지 않고

별도의 SetBlockInfo 함수가 public 형태로 만들어졌습니다.

(외부에서 참조가 가능한 형태죠?)

 

그리고 BlockManager 스크립트를 변경합니다.

(이미 만들었던 스크립트라면 상단의 탭을 누르는 게 편합니다.)

 

BlockInfo 스크립트 값도 설정해야되니 Block이라는 struct(구조체)를 만들었습니다.

이 구조체들이 배열을 이루어 pool로 사용되어야 겠죠?

 

(기존의 GameObject[] normalBlocks는 주석처리하였죠?)

 

이렇게 바꾸고보니 빨간 줄들이 많습니다.

바뀌었으니 배열 생성, 오브젝트 생성 모두 바뀌어야겠죠?

 

normalBlocks = new Block[normalBlockNum];로 오브젝트 풀 생성이 바뀌었고,

 

normalBlocks[i]로 참조하던 오브젝트는

normalBlocks[i].obj로 바뀌었습니다.

( 함께 해당 블록의 info는

normalBlock[i].info로 사용하겠죠? )

이번에 해준 작업은

normalBlocks[i].info를 얻어오는 작업입니다.

여기서도 GetComponent를 통해서

컴포넌트(스크립트)를 얻어왔죠?

 

 

그리고 이렇게 SetNormalBlock(Vector3)에서

해당 오브젝트를 활성화시켜줄 때

info에 있는 SetBlockInfo()까지 함께 호출했습니다.

( 그러면 활성화되고 position이 새로운 pos로 바뀐 후,

해당 위치를 현재 블록의 위치 값으로 설정하겠죠? )

 

그리고 실행해보면 일단 잘 됩니다.

올바른 방향으로 튕겨나갑니다.

하지만...

또 문제가 있습니다.

(일단 이것만 해결해주면 됩니다.)

 

블록에 닿고 튕겨나갈 때.

바로 이 순간(한 프레임)에

두 개의 블록이 닿는다면

블록 두 개를 모두 없애버립니다.

(그리고 x축 반전 + x축 반전으로 반전없음(그대로 진행)이라는 결과가 나오죠.)

 

 

사실 블록이 충분히 여유로운 간격으로 있거나,

볼이 작다면 이러한 일은 거의 없겠지만,

우리는 예외 없이 잘 작동하도록 만들어줘야겠죠?

 

MoveBall 스크립트로 갑니다.

 

그리고 public bool 변수로

frameCollisionCheck를 false로 초기화+선언해줍니다.

(public이니 외부에서 참조가능하겠죠?)

 

그리고 FixedUpdate(이동처리가 있는 부분)의 가장 밑에

이렇게 if문으로 true 상태면 false로 바꾸도록 만들어줍니다.

 

이를 통해 FixedUpdate에서 1초에 50번은

frameCollisionCheck가 true인 상태인지 if문으로 검사하겠지만

그 정도는 우리 컴퓨터, 스마트폰 모든 부분에서 가벼운 처리죠.

 

기존의 BlockInfo 스크립트에 있는

OnTriggerEnter2D(Collider2D) 함수입니다.

이 부분도 함께 바뀌어야겠죠?

 

이렇게 바뀌었습니다.

임시로 함수에서 MoveBall을 생성하고 받아

frameCollisionCheck의 상태로 현재 충돌이 이미 이루어졌는지 아닌지를 확인합니다.

충돌이 안 이루어진 상태라면 충돌처리를 해주고,

해당 MoveBall의 frameCollisionCheck를 true로 변경해줍니다.

 

(그러면 해당 프레임에서는 더이상 충돌 후 처리가 이루어지지 않겠죠?)

(다음 프레임에서는 또 fixedUpdate에서 다시 false로 초기화시킬 테니 충돌 확인 후 처리가 될 테고요.)

 

저는 개인적으로 GetComponent를 통하여 컴포너트를 얻어온 경우

안전하게 다시 사용하지 않은 상태로 바꿔줍니다만,

지역변수(함수지역)는 어차피 지역을 벗어나면 해제되니

아래처럼 해주지 않아도 될 것 같기는 합니다.

 

그리고 실행해서 결과를 프레임별로 확인해봅시다.

( 유니티 상단의 플레이, 일시정지, 한 프레임씩 재생

기능을 통하여

프레임 단위로 테스트를 해볼 수 있습니다. )

 

 

블록에 닿기 바로 전,

 

블록에 닿은 후,

(두 개가 닿았지만, 하나만 사라졌죠?)

 

부딪힌 블록에서 방향이 바뀌어진 대로 튕겨나가고 있죠.

 

이렇게 이제는 아무런 문제없이

블록들이 오브젝트 풀로 화면에 생성되고,

제대로 하나씩 제거되며 튕겨나가고 있는 것을 확인할 수 있습니다.

 

 

 

 

지금까지 따라하는 유니티 2D 프로젝트ⓑ -3번 강좌를 보셨습니다.

이번 시간에 블록 매니저와 프리팹을 만들어 블록들을 생성해봤습니다.

(오브젝트 풀로 만들었죠? - 나중에 재사용 처리가 들어갈 예정입니다.)

(구조체를 배열로 사용하기도 했고요.)

 

그리고 블록들이 붙어서 나오는 경우에 생기는 문제점들을 보고 해결해봤습니다.

(이 때 Debug 모드를 사용해보고, 한 프레임씩 재생도 해봤죠.)

 

 

다음 시간에는 볼이 다양한 방향으로 튕겨나갈 수 있도록 해볼까요?

처음에 시작할 때 방향을 달리하거나,

(다음 시간에 가능하면 블록에 부딪히면 약간씩 다른 방향으로 튕기는 것도요.)

 

 

고생하셨습니다. 같이 따라오며 하니 어떤가요? 도움은 좀 됐나요?

그럼 프로젝트의 다음 단계를 진행하며 나오는 새로운 것들에 대해서는

지금과 같이 설명하며 진행하도록 하겠습니다.

 

 

궁금한 점 있으시면 댓글이나 따로 메일로 질문하시면 시간되는 대로 답변드리겠습니다. ( 연락 )

Comments