유니티 2D 게임 제작 5단계

(SLIDE TO CIRCLE)


#배경음악 추가

음악을 더한다는 것은, 감각을 하나 더한다는 것...! (크..!)



음악 벨소리 처럼 생긴 PNG파일을 히어라이어키에 넣고, 

Collider을 넣고 Trigger체크.

Sounc_Play라는 스크립트를 추가했습니다.




1.   이 게임오브젝트에 AudioSource를 추가합니다. AudioSource는 오디오를 관리하는 컴포넌트 입니다. 


2.   loop = true는 AudioSource의 설정 중 곡을 무한 재생시키는 설정입니다. 곡을 무한재생시킵니다. 왜냐면,,곡이 끝나면 다음곡이 없으니깐..계속 돌려써야합니다.


3.  clip은 AudioSource의 설정 중 오디오 파일입니다. 즉 재생시킬 배경음악 파일입니다.


4.  play()를 사용해 곡을 재생시킵니다.


5.  이 오브젝트는 Collider가 있으므로 버튼 역할을 합니다. 노래의 ON/OFF를 OnMouseDown을 통해 구현시킵니다.


Start문의 DontDestroyOnLoad는 씬이 이동되었을 때 오브젝트가 사라지는 것을 방지하는 것입니다. 

만약 이 코드가 없다면, 게임씬으로 넘어가면 이 스피커모양의 게임오브젝트가 당연히 사라지겠고, 

당연히 그 속에 들어있는 코드들과, 컴포넌트는 실행되지 않으므로 노래는 꺼지게 됩니다. 

노래가 계속 재생되게 하려면 이 게임오브젝트를 유지시켜주어야합니다.




https://play.google.com/store/apps/details?id=com.PaLab.STC







유니티 2D 게임 제작 4단계

(SLIDE TO CIRCLE)

#맵 바꾸기

만약 맵이 하나라면 재미가 없을 것이다..!



여기 위의 7개의 맵을 만들었습니다. 

빙글빙글 돌거나, 

나타났다가 사라지거나, 

차례차례 사라지거나, 

위로 올라가서 방해하거나, 

가만히 있거나 등의 서로 다른 움직임을 가지고 있습니다.





왼쪽 히어라이어키를 보면, Level들이 비활성화 되어있는데, 

이제부터 비활성화 된것을 활성화 시키고, 각각에 맞는 움직임을 구현할 것입니다.

즉 맵을 바꾸는 것이죠..!

저는 게임 종료전까지 랜덤으로 맵이 막 바뀌었으면 좋겠다고 생각했습니다.



엄청 간단해 보이지만, 이 스크립트 작성이 제일 오래걸렸습니다.

7개의 맵의 움직임과 생성, 삭제를 한 스크립트에서 해결하고 싶어서, 

그랬던 것 같습니다. 최대한 간결하게 설명하겠습니다.


지금 Start()함수, 씬이 시작될때 시작하는 처음 실행되는 함수부터

FixedUpdate함수까지 아래의 3단계를 반복합니다.

마지막 4단계는 1단계~3단계가 실행되고, 계속 실행됩니다.


1단계.  Start_Level() : 1부터 7까지의 레벨(맵)을 랜덤으로 시작합니다.

2단계.  Time_Level() : 시작한 레벨(맵)이 몇 초동안 실행되는지 확인합니다.

3단계.  End_Level() : 레벨을 끝냅니다. 

4단계.  MoveMent_Level() : 레벨의 움직임을 작동시킵니다.


밑에 if문은 안드로이드 폰의 뒤로가기를 눌렀을 때 씬 전환을 시키는 코드들입니다.






1.  0부터 100사이의 랜덤한 실수 k를 생성합니다. 


2.  k가 if문 R_1보다 크면 else if문 R_2보다 크면 else if문 R_3보다 크면 ... 즉 랜덤한 k를 이용해 어떤 레벨(맵)을 시작할지 정하는 것입니다. R_1, R_2, R_3, R_4, R_5, R_6은 모두 함수를 호출할때 지정할 수 있는 수입니다. 인자라고 하죠.. 인자를 잘 조절하여, 공평하게 어떤 레벨(맵)을 시작할 수도 있고, 특정 레벨(맵)이 시작될 확률을 높일 수 있습니다. 예를 들면, 플레이어가 게임을 잘 버틴다, 막 20바퀴를 돌고 죽질 않는다, 그러면 어려운 Level_6과 Level_7의 확률을 증가시키고 나머지 쉬운 레벨이 등장할 확률을 줄일 수도 있습니다.


3.  SetActive(true)를 통해 레벨을 활성화 시킵니다. 


4.  활성화 시킨 그 레벨이 몇번째 레벨인지 저장하기 위해, Level_Chek에 수를 저장합니다.


간단하게 정리하자면, 랜덤으로 레벨(맵)을 시작하고, 그 레벨이 몇 번째인지 저장한다.


중간에 Level_1.GetComponent<Rigidbody2D>().isKinematic = true;

이것은 맵이 오브젝트와 충돌했을때 밀리지 않도록 

운동학적으로 바꿔주는 것입니다.

사실 인스펙터에서 직접 다 바꿔줬는데, 저걸 지우지 않았네요,,


Level_4.transform.Find("Triangle").gameObject.SetActive(true);

Level_4.transform.Find("Triangle_Reverse").gameObject.SetActive(false);

이것은 Level_4는 두개의 자식을 갖고 있는데, 

둘다 동시에 활성화가 아닌, 

둘 중 하나만 활성화 시키고 나중에 다른 것을 활성화 시키려고 시작할때부터 조정한 것입니다.

이 맵에 특정한 움직임이라고 생각하면 됩니다. 




Level_Check를 통해서 각각 얼마동안 움직임을 실행할지 결정합니다.


원래는 레벨(맵)마다 움직이는 시간을 다르게 하려고 했지만, 


이상하지 않아서, 다 2초로 했습니다.













Level_Check를 통해서 현재 실행되고 있는 레벨(맵)을 비활성화 시킵니다.


중간에 포지션 이동시킨건 굳이 안해도 됐었는데, 굳이 해버린것이고, 


Level_6의 4줄의 코드는 자식들을 다 활성화 시켜주고, 부모를 비활성화 시키는 것입니다. 


왜냐면 자식을 다 비활성화 시키는 움직임을 하는 코드가 있기 때문입니다.













Level_Check를 통해서 현재 실행되고 있는 레벨(맵)의 움직임을 실행시킵니다.


사실이건 껍데기에 불과합니다. 함수에서 함수를 실행시키고 있는데,


각 레벨에 맞는 함수를 다 따로 만들었습니다.












레벨1은 아무런 움직임이 없습니다.


레벨2는 포지션벡 y값에 0.01f를 더해주는것입니다. 

이렇게 되면 레벨2 오브젝트들은 위로 올라갑니다.


레벨3은 회전시키는 것입니다.


레벨4는 깜빡이처럼 1개 활성 1개 비활성을 반복해주는 것입니다.


레벨 5는 아무런 움직임이 없습니다.


레벨 6은 자식 오브젝트를 차례로 비활성화 시키는 것입니다.


레벨 7은 회전시키는 것입니다.










총 정리를 해보면, 


1단계.  Start_Level() : 1부터 7까지의 레벨(맵)을 랜덤으로 시작합니다.

2단계.  Time_Level() : 시작한 레벨(맵)이 몇 초동안 실행되는지 확인합니다.

3단계.  End_Level() : 레벨을 끝냅니다. 


4단계.  MoveMent_Level() : 레벨의 움직임을 작동시킵니다.



하하하,,힘들다,,



https://play.google.com/store/apps/details?id=com.PaLab.STC

유니티 2D 게임 제작 3단계

(SLIDE TO CIRCLE)

#회전 수 체크






과연 이 빨간색 공이 원을 몇바퀴 돌았을까?

를 체크하기 위해선,

음,,여러가지 방법이 있을 수 있다.



음,,빨간색 공과 중심을 잇는 벡터의 단위벡터가 

(0,1,0) 이면 저 파란선 위에 빨간색 공이 있는 것이니깐,

한바퀴를 돌았다고 할 수 있을 것이다.



여러가지 방법이 있겠지만,



저는 저 파란색 선을 지나칠때마다 

회전 수를 1증가시켰습니다.













이렇게 아무것도 안보이지만!





사실 저 흰색 박스가 보이시나요!

Collider에 Trigger을 체크 해주면, 물리적인 충돌이 일어나지 않습니다. 

하지만 충돌을 체크할 수 있습니다!

자세히 보면 

Rotation_Check라는 스크립트가 연결되어있는데 바로 밑에 사진이 내용입니다.





OnTriggerEnter2D()함수는 

Trigger와 Collider 또는 Trigger와  Trigger의 게임 오브젝트가 충돌, 그러니깐 스쳐지나간다? 시각적 충돌?

을 하게 되면 호출되는 유니티 내장함수입니다, 

OnTriggerEnter2D는 게임오브젝트가 들어올때 호출되고

OnTriggerExit2D는 게임오브젝트가 나갈때 호출됩니다.

그외에도 몇개 더 있는데, "유니티 개발 전" 카테고리에 올려놓았습니다,,(깨알 홍보 ㅎㅎㅎ~@!#)


1.  OnTriggerEnter2D()함수를 사용하여, 충돌시 이 함수를 호출한다.


2.   if문) 만약 충돌한 게임오브젝트의 태그가 "Red_Ball"이라면  Rot_Check에 1을 더해준다. 


간단하게 정리하자면,

회전수를 체크하기 위해 게임화면에 보이지 않는 게임오브젝트를 만들고, 콜라이더 추가,

트리거 체크를 하고나서, 스크립트 내에서 OnTriggerEnter2D함수를 사용, 

if문 게임오브젝트 태그를 사용해서 충돌체가 맞는지 확인,

맞으면 회전수를 1더한다.


그 밑에 PlayerPrefs는 회전수가 이전에 기록되어있는 것보다 크면, 

즉 베스트 스코어이면, 새롭게 저장을 한다. 라는 의미입니다.


PlayerPrefs는 쓰기가 편해서,,게임을 종료해도 삭제되지 않는 정보들을 저장할때 사용합니다!




https://play.google.com/store/apps/details?id=com.PaLab.STC

유니티 2D 게임 제작 2단계

(SLIDE TO CIRCLE)


#공의 점프




공의 점프는 버튼을 눌렀을 때, 

0.6초동안 중력 벡터를 반대로 설정하고 

0.6초 후에 다시 원래 대로 돌려 놓으면 자연스럽게 진행될 것입니다. 





빨간색 공의 Red_GM 스크립트에서 해결할 것입니다!





UI에서 Button을 생성하였고 Source Image에 png파일을 넣었습니다. 그리고 on click에 버튼 함수가 들어있는 스크립트인 Red_GM을 가지고 있는 오브젝트인 빨간색 공을 연결하고 오른쪽에 Red_Button함수를 연결시켜줍니다.





1. Red_Button()함수를 사용해 버튼이 눌리면 Red_Button_Constant에 1를 대입합니다.


2. if문에서 Red_Button_Constant를 체크하여 버튼이 눌린지 검사하고 눌렸다면 Jump_Time동안 Gravity_Vector에 -1.2를 곱한 값을 저장합니다. 즉 반대방향의 힘을 주기 위함입니다. 


3. else if문에서 1초가 지나면 버튼을 리셋시키고 Speed_Vector값을 -0.3f 해줍니다. 


4. else문을 통해서 버튼이 눌리지 않았을 때 Gravity_Vector을 유지시켜줍니다.


간단하게 정리하자면, 

점프를 구현하기 위해 버튼 함수를 만들고, 버튼이 눌리면 점프를 구현합니다.

점프는 일정시간 동안 Gravity_Vector에 -1.2f를 곱한 것으로 구현하고 시간이 지나면 원상태로 되돌립니다.


사실 점프를 이렇게 구현을 해보았는데, 

0.6초의 점프시간이라던지, -1.2라던지 약간 직관으로 계산을 해버려서 점프가 자연스럽지 못합니다.

이부분은 계속 테스트 해보면서 수정할 계획입니다. 

0.6초보단 0.4초가 더 낫다. -1.2보다 -1.4가 더 자연스럽다.

이런식으로요!




https://play.google.com/store/apps/details?id=com.PaLab.STC

유니티 2D 게임 제작 1단계

(SLIDE TO CIRCLE)

#공의 원 회전을 구현한다.







1. 빨간 공은 원의 중심을 향해 벡터 힘을 받는다.


2. 빨간 공은 원의 접선 방향으로 벡터 힘을 받는다.


3. 벡터 합은 빨간 선이 되고, 공은 원 회전을 한다.







그러니깐 빨간 공이 원을 미끄러지듯 움직이는 모습이다. 

이를 위해선 빨간 공과 원은 Collider과 RigidBody2D 컴포넌트를 추가해야한다.





위와 같이 원과 공의 컴포넌트를 추가하고 중력을 없애준다. 


또한 원은 RigidBody2D를 사용하면 오브젝트가 밀리는 현상이 발생하는데 원은 밀리면 안되기 때문에 위치를 고정시켜야한다.

Body Type을 Kinematic으로 바꿔주면 해결할 수 있다! 

(Kinematic == 운동학적인 && Dynamic == 역학적인)


그리고 공에 벡터 힘을 계산한 스크립트를 추가해주면 된다. 





1.  Gravity_Vector는 원의 중심과 공의 중심을 잇는 벡터이다. 하지만 원의 중심을 (0,0,0)으로 한다면 공의 중심이 바로 그 벡터가 되는 것이다. 따라서 중력벡터는 현재 자신의 위치벡터의 음의 값이다.


2.  Speed_Vector는 원의 접선의 벡터이다. 그것은 Gravity_Vector에 수직이면된다. 수직인 벡터를 구하기 위해선 '내적'이란 개념이 필요한데, C#에서 구하는 함수가 있지만,,어떻게 사용하는지 도저히 모르겠어서 저는 이렇게 구했습니다...수직인 벡터를 내적하면 0이 된다. 그건 내적의 정의에서 cos∂를 곱해야하는데 cos90˚는 0이기 때문입니다. (3,4,0) 과 (-4,3,0)을 내적하면 (3*-4) + (4*3) + (0*0) = 0 됩니다. 즉 벡터의 x성분을 y로, y성분을 x로 바꿔주면 수직벡터를 구할 수 있습니다. 


3.  Gravity_Vector에 Gravity_Constant를 곱해주고, Speed_Vector에 Speed_Constant를 곱해주어 손질합니다.


4.  Total_Vector은 단순히 Gravity_Vector와 Speed_Vector를 합한 벡터인 합벡터 입니다.


5.  이제 transform.position을 업데이트 시켜줍니다.


중간에 if문은 점프 버튼이 눌렸을 때이고 여기선 별 상관이 없습니다.


정리하자면 우리는 가속도를 구해야하는데, 그건 힘을 질량으로 나눠서 구할 수 있습니다. 

질량을 무시하면 가속도는 힘이고, 힘은 벡터입니다. 가속도도 벡터입니다.

두개의 힘 벡터를 구해야 하고 하나는 공의 중심에서 원의 중심을 잇는 벡터, 다른 하나는 원의 법선 벡터입니다.

그리고 그걸 구해 합벡터를 구하고, 이것은 힘이며 가속도이어서, 속도를 구하기 위해 가속도 * 시간을 하였고

위치를 구하기 위해 속도 * 시간을 한 것입니다.


사실 속도(Velocity)는 가속도(Vector) * 시간이고 

미래위치(Position)는 현재위치 + 속도(Velocity) * 시간입니다.


65번째 줄에서 Total_Vector에 시간을 곱해줘야하는데 곱하지 않았습니다...너무 작아져서,,




https://play.google.com/store/apps/details?id=com.PaLab.STC


유니티 2D 게임 구상

(SLIDE TO CIRCLE)



처음엔 하나의 공이 원을 회전하는 게임을 만들려고 했는데, 공이 두개면 어떨까 라는 생각을 해보았다..

그리고선 장애물들을 넣고 그걸 피하는 버튼을 만들자고 생각해서 이렇게 구상해 보았다..


필요한 코딩은


-공이 원을 돌게 하는 것

-버튼을 눌렀을 때 공이 점프를 하는 것

-장애물들이 바뀌는 것


-충돌하면 게임 오버

-점수 계산


지금은 다 만들고 이걸 작성하는 거여서, 

가장 힘들었던 건.. 공이 원을 돌게 하는거

아직 못 해결한 건 점프 입니다.



https://play.google.com/store/apps/details?id=com.PaLab.STC


+ Recent posts