DINO NET

[PPONGARI] 개발 일지 본문

GMI()/PPONGARI

[PPONGARI] 개발 일지

2023. 12. 7. 09:49

Git repository url

https://github.com/Gongju-Unity-Bootcamp/HANDSOMEDINO

 

GitHub - Gongju-Unity-Bootcamp/HANDSOMEDINO: 잘생김디노

잘생김디노. Contribute to Gongju-Unity-Bootcamp/HANDSOMEDINO development by creating an account on GitHub.

github.com

 

 

2023.11.15

11.11 ~ 11.12: 게임 <celeste>로 레퍼런스 연구 및 정리. 2D 횡스크롤 플랫포머 장르의 대략적인 구상을 시작.

11.15: 본격적인 협업 시작. 게임의 전체적인 스토리 라인 및 진행 방향을 기획. 그에 대한 Issue를 정리. 처음에는 게임을 크게 세 덩어리로 나누었다.

1. 타이틀 2. 메인 3.엔딩 (메인은 본격적인 게임을 진행하는 구간이다) player의 기본적인 기능에 대해서 정리.

1. 이동 2. 점프 3, 벽 잡기 4. 벽 타기 5. 바닥 판정 기능

 

[Player] 바닥 판정 기능 · Issue #15 · Gongju-Unity-Bootcamp/HANDSOMEDINO

설명 Player가 'ground' layer로 분류된 플랫폼에 도달하면 바닥에 있음을 알 수 있도록 한다. 참고자료 Youtube 플랫포머 플레이어 이동, 점(4:40) Unity Scripting API: Collider class Unity Manual: Collider 주의사항

github.com

 

 

2023.11.16

- 앞으로 진행할 내용에 대해서 issue로 정리를 진행하였다.

[Camera] 1. Camera Follow 기능

[title UI] 1. Title 화면 button 2. Start button 3. Option button 4. Quit button

[Game UI] 1. Loading Scene

[Tutorial Scene] 1. Tutorial Manager 2. Scene #1 3. Scene #2 4. Scene #3

[Enemy] 1. 고양이

 

[Game UI] Loading Scene · Issue #25 · Gongju-Unity-Bootcamp/HANDSOMEDINO

설명 다음 Scene이 load 되는 과정을 슬라이드 바를 이용해 player에게 시각화한다. 슬라이드 바 위에 무작위 tip 등이 함께 보이도록 한다. Scene Transition을 이용해 화면 전환을 매끄럽게 한다. 참고자

github.com

 

 

2023.11.19

- player Move 기능 구현

1. InputManager 내부의 virtual axis 값을 받아 움직일 수 있도록 만들었음.

2. Input.GetButtonDown("Horizontal") 사용.

3. Horizontal의 기본 alt 값에 a 키가 있는데 이는 점프 키와 중복이 되고, 화살표로만 player를 움직이고 싶었기 때문에 alt 값들을 수정함.

4. 동시에 "Jump" positive 값에 a 키 삽입

 

https://youtu.be/ZII8MrlLRRk?si=oT7XCJWWeOVmt_cc

 

 

 

2023.11.20

- Jump 기능 구현

1. InputManager 값 활용해보고자 Jump 역시 값 지정해서 받아봤으나, player가 그냥 상승하는 사태가 발생함.

2. y축으로 상승하는 사태를 막고자 if문 활용. GetAxisRaw로 받아오는 값은 반환값이 float이라 if문 내부에는 못 넣었다. 그래서 GetKeyDown("Jump")로 전환

3. GetKeyDown("Jump")는 InputManager 값을 받아오지 못해서 결국 KeyCode.A로 받아옴

4. move() 함수와 jump() 함수가 분리되어있고, if문 마저도 jump() 내부에 있으니까 jump key를 받는 데 제약이 생김. 움직임이 끝나고 나서야 jump 값을 받는 등 바로바로 값을 받을 수가 없어짐.

-> Debugging 과정
1. if 문을 Update() 내부에 넣었음. if(Input.GetKeyDown(KeyCode.A)) {Jump();} -> 동일한 현상 발생
2. FixedUpdate()를 Update로 바꿈 -> 동일 현상 발생
3. if(Input.GetKeyDown(KeyCode.A)) {Jump();를 Move()함수에 삽입. -> 현상 해결 +) FixedUpdate() 문제였음!! Update()로 바꾸면 정상적으로 실행이 됨. 이 상태에서 if(Input.GetKeyDown(KeyCode.A)) {Jump();}를 Update() 내부에 배치해도 원활하게 돌아간다.

 

5. collider 추가 배치. player에 component box collider2D를 추가적으로 배치하였다. circle collider가 box collider가 붙어있는 platform의 모서리에 밀려 올라가는 현상이 조금 완화되었다. +) Input.GetButton("Jump") vs. Input.GetKeyDown(KeyCode.A) GetButton을 하면 계속 눌린다. jump를 위해 Vector2.up을 했더니, 그냥 위로만 상승한다. 부스트 팩같은거 만들 때 사용하면 좋을 듯 Input.GetKeyDown(KeyCode.A) key 값이 눌린 순간만 받아와서 up을 통해 jump는 하지만 동시에 방향키를 받을 수 있어서 포물선 운동이 가능하다.

6. keyCode로 조건을 두면, 하드 코딩인 것 같아서 Input Manager 상의 Jump를 이용하려고 노력한 결과 결국 코드를 알아냈다!

https://docs.unity3d.com/ScriptReference/Input.html (input|Scripting API)

https://docs.unity3d.com/Manual/class-InputManager.html (input manager|Unity Manual )

GetButtonDown: Returns true during the frame the user pressed down the virtual button identified by buttonName. -> 가상 축 버튼 이름을 받고 bool로 반환. GetKeyDown: Returns true during the frame the user starts pressing down the key identified by name. -> keycode에 저장된 이름으로 받고 bool로 반환. 그래서 GetKeyDown으로 가상축 이름을 넣었을 때, error가 났던 것임.

7. LongJump vs. ShortJump 버튼을 누르고 있는 동안 rigid2D의 중력계수가 바뀌었다가 버튼을 떼면, 중력계수의 값을 다시 크게 만드는 방법을 사용. (이 때부터 controller와 movement script를 나누기 시작함.) 누르는 동안 isLongJump의 값을 true로 반환. 누르는 동안이므로 GetButton 사용. (가상축 사용을 위해) GetButtonUp을 이용해 버튼을 떼자마자 false로 반환. -> 그래서 else로 처리하지 않고 else if로 처리하여 GetButtonUp 경우를 만들어주었다. if (isLongJump && rigid2D.velocity.y > 0) { rigid2D.gravityScale = longJumpGravityScale; } else { rigid2D.gravityScale = shortJumpGravityScale; } velocity.y > 0 이라는 것은 jump key를 누르고 공중에 뜨게 되면 자연스럽게 가능함. (isLongJump 또한 true 값으로 변하므로) 그 반대 또한 동일하다. -> 이 때는 jump key를 누르지 않으면 자연스럽게 isLongJump = false 이므로 자연스럽게 gravityscale이 커진다. +) commit message 쓸 때 전부 영어로 써서 뿌듯했음.

 

https://youtu.be/RsiqecedIYM?si=I_hv3WkXCBhs_Cdw

 

 

 

2023.11.22

- collider 맵 끼임 버그 원 형태의 collider를 사용하면 platform 모서리를 미끄러지는 현상이 발생해 이를 수정하고자 box collider 추가. collider의 갯수가 두 개가 되자 각각 충돌이 감지되어 tile map에 끼이는 버그가 간헐적으로 발생. 이를 고치기 위해서 box collider에서 'Used By Composite' 항목을 체크하고 Composite collider component를 추가하여 collider들을 한 덩어리로 만들었다. 그 이후로 맵 끼임 버그가 나타나지 않았다.

- 바닥 판정 기능 해당 프로젝트에서 player의 점프 횟수는 1로 제한된다. player가 gorund에서 jump를 하면 다시 ground에 닿기 전까지 점프를 할 수 없다는 뜻이다. 그러므로 ground라는 gameobject를 명명해주고 ground로 인식 되는 map에 닿을 때 true를 반환해주는 판정 시스템을 만들었다.

1. hierachy 창에서 GameObject 'Ground'로 분류되는 prototype tile map이 'Ground'가 될 것이므로 layer에 Ground를 추가하여 분류를 해놓았다.

2. 판정을 하려면 판정을 하는 판정 주체와 판정을 당하는 대상이 있어야 하는데, 판정을 당하는 대상은 'Ground'라는 layer로 분류 되어 LayerMask에 의해 판정이 될 것이고 판정 주체는 collider.bounds를 이용했다. 판정 위치는 player를 이루는 circle collider 2D가 가장 바닥과 부딪히므로 CircleCollider.2D의 omponent를 가져와 collider bound의 x축의 가장 center 값, y축의 최소값을 집어주었다. 그리고 이를 footPrint라는 변수에 Vector2 형식으로 저장하였다.

3. 판정 주체와 판정을 받는 물체가 정해졌다면, 그 둘이 접촉을 했는지를 판단해줄 무언가가 필요하다. 그 역할을 해주는 것이 Physics2D.OverlapCircle이다. 해당 함수는 정해진 circle 영역에 collider가 속해져 있는지 확인하는 함수로, point, radius, layerMask 값을 이용할 것이다. point는 circle의 중심부를 나타내며 이는 우리가 미리 판정 주체로 정한 footPrint로 설정하였다. 원형의 radius는 1.0f로 정하였고 layermask는 판정을 당하는 groundLayer로 설정하였다. (layer 지정은 unity engine 내부에서 정함) 그리고 이를 bool 형의 isGround 변수에 저장하였다.

4. isGround == true 일 때만 jump가 가능하도록 설정하였다. jump를 하고 다시 바닥에 닿을 때까지 jump가 불가능함을 확인하였다.

- 점프 횟수 제한 기능 위 바닥 판정기능만으로도 점프 횟수가 제한이 됨을 확인하였지만 점프 횟수의 한계점을 가시화 해놓는 것이 좋을 것이라 판단되어 점프 횟수 제한 기능을 추가하였다.

1. isGround가 true일 때 jump를 가능하게 했으므로, 기존의 점프 가능 횟수를 미리 정해놓고 해당 횟수보다 현재 점프 횟수가 작을 때, 혹은 현재 점프에 최대 점프 횟수를 할당하여 0이 될때까지 횟수를 감소되는 형식을 사용할 수가 있다.

2. 전자의 방식을 채택하면 점프 횟수가 +1 된다. 그래서 후자의 방식을 채택

3. 점프가 정상 작동되는 것을 확인할 수 있었음.

 

 

 

2023.11.23

- camera follow system camera에 부착할 CameraFollow.cs 생성. 해당 스크립트에서 GameObject 'Player'의 Transform을 가져와야 하므로 Transform class를 선언하고 GameObject.Find("Player").GetComponent<Transform>()을 이용해 player라는 gameobject를 찾은 다음 transform을 받아오게 하였다. camera는 수직 방향으로 화면을 송출해준다. 깊이 (z축 값)에 따라 비춰지는 모양도 다르게 되는데, 그런 일이 없도록 -10 값을 넣어서 위치를 보정해주었다. 카메라 이동에 player transform 자체를 붙이면 바로바로 따라다니는 코드가 되지만, Lerp 함수를 사용하면 훨씬 부드러운 카메라 이동을 구현할 수 있다. Lerp은 선형보간으로 Mathf.Lerp(), Vector.Lerp(), Vector2.Lerp(), Vector3.Lerp(), Quatanion.Lerp()와 같이 사용된다. 그 중에 이 코드에서 사용된 lerp는 Vector3.Lerp()로 다음과 같이 쓰인다. public static Vector3 Lerp(Vector3 a, Vector3 b, float t); a와 b 두 끝점 사이에 위치한 값 t를 추정하기 위해 직선 거리에 따라 선형적으로 계산하는 방법이다. 계산 식은 a + (b - a) * t이므로 t = 0이면 a 값이 반환되고, t = 1이면 b 값이 반환된다. 내가 쓴 lerp 식은 다음과 같다. transform.position = Vector3.Lerp(transform.position, playerTransform.position + cameraPosition, Time.deltaTime * cameraMoveSpeed); Camera의 위치에 선형 보간법을 사용한 값을 계속해서 넣어서 점점 player의 위치로 옮겨간다. a와 위치와 동일한 transform.position의 값은 계속해서 b의 위치와 가까워질 것이므로 그 둘의 차이를 일정한 비율로 좁혀질 것이다. t값에 고정된 인자 값을 넣게 되면 프레임에 따른 간격의 오차가 생길 가능성이 높기 때문에 frame당으로 시간을 세어주는 Time.deltaTime을 넣어주고 camera이동 스피드를 넣어서 좁혀지는 속도를 조절할 수가 있다. - position reset button 처음에는 player가 시작되는 위치를 받아서 그 위치로 돌아오는 코드를 짰었다. 하지만 게임의 볼륨이 늘어나면 세이브 포인트가 각각 존재할 것이고 그 세이브 포인트에서 다시 시작되는 것을 고려하면 spawn point를 설정해놓고 해당 포인트에서 리젠되는 것이 장기적으로 봤을 때 더 유용할 것 같아서 spawn point를 만들었다. (spawn point의 transform 값을 찾는 방법은 camera follow system 응용함) input manager의 virtual axis에 "PositionReset" key를 추가하고 r 값을 할당하였다. 이번에도 movement에 PositionReset()을 controller에 key를 눌렀을 때 해당 함수를 실행하도록 하였다.

 

https://youtu.be/FThkItrT4Og?si=gmH6-LzISQGQprKa

 

 

 

2023.11.24

- wall jump wallcheck gameobject를 만들고 wallcheck가 wall layer를 감지하면 sliding을 할 수 있도록 하였다. rigidbody의 velocity 값을 조정하였다. Jump의 gravityscale을 변화시켜주는 코드에 else if를 만들고 iswall가 감지되면 walljump로 이동한다. 문제 발생: vector2.right에 Horizontal 값 곱해서 왼쪽 오른쪽 모두 raycast로 감지할 수 있도록 is wall를 만듦. 문제는 오른쪽은 감지가 되는데 왼쪽이 감지가 되지 않았다! 무엇이 문제였는가?: WallCheck object가 player sprite의 앞 부분에 위치해 있었음. sprite가 flip 될 때, 함께 flip 될 줄 알았지만, wall check object는 그렇지 않았다...! 해결: wall check object의 위치를 player의 가운데로 위치시킨 후 distance를 player sprite 크기의 반으로 설정하였다. 그리고 drawline으로 해당 ray를 가시화 했다. 왼쪽 벽 감지 성공.

- Respawn System GameObject 'Player'가 닿으면 '죽음' 판정이 되어 특정 Spawn Point에 respawn 되는 system. OnCollisionEnter(Collider collision)을 이용하였다. 이를 위해 PlayerMovement Script의 PositionReset()을 이용하기로 했다. 변수 지정해주고 GetComponent를 사용했는데, Object Reference not set to an instance of an object 오류가 발생. 즉, 참조하려는 오브젝트의 형식이 잘못되었거나 참조하려는 오브젝트가 없다는 뜻이었다. https://docs.unity3d.com/ScriptReference/Component.GetComponent.html (Component.GetComponent Scripting API)

이는 GetComponent에 대한 이해도가 낮아서 일어난 오류였다! GetComponent가 단순히 PlayerMovement Script를 가져온다는 뜻이 아니라 해당 Script를 부착한 GameObject를 가져와 사용한다는 뜻으로, 관련이 있는 GameObject를 찾아 해당 object의 script를 가져와야 했다. 내 코드의 경우에는 PlayerRespawn Script가 DeathZone에 붙어있고, 충돌되는 대상이 'Player'인 셈이므로 Awake()를 통해 받아올 때에는 단순히 GetComponent가 아닌 GameObject.Find("Player").GetConponent();를 사용했어야 했다. 이는 CameraFollow에서 사용한 코드였다. 이를 단순하게 줄인 코드가 존재하는데, PlayerRespawn에 사용되었다. FindObejctOfType(); <>내부의 스크립트가 들어간 오브젝트를 찾는 방법이다.

https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html (Object.FindObjectOfType Scripting API)

 

https://youtu.be/IPgeHVB2Mvc?si=rM6bBVxs5zshFUau

https://youtu.be/s_K0kbFROkA?si=B6sjbeq0USHCkt7F

 

 

 

2023.11.25

- Spawn Point (Save Point) 결국엔 답을 찾았다! OnTriggerEnter()에 모든 충돌체를 감지하게 만드는 것이 아니라 'Player'라는 Tag 를 지닌 객체의 충돌만 받아서 마지막 저장 포인트(lastCheckPointPos)를 해당 포인트로 전환을 하게 만드는 것이다. Reload 될 때에는 미리 만들어 놓았던 PlayerRespawn.cs와 PlayerMovement의 ResetPositin()을 재활용하였다. (이후에 PlayerRespawn.cs는 DeathZone.cs로 이름을 바꾸었다.)

 

https://youtu.be/kOnRZAWYsZw?si=WOqvjGgKaL7Nvbja

 

https://youtu.be/1rl5zQWpREg?si=wXcHADJw_rxrcgeG

 

 

 

2023.11.26

1. overlap 함수 동적으로 collider 할당. 만들어진 범위 안의 collider를 감지

2. Empty Object를 생성하면 좋지 않을까?

3. 함수들의 경우에는 순서가 정해져 있지 않기 때문에 임의로 스크립트들을 이용해서 순서 조정이 가능함. 내가 불러오는 순서대로 움직이기 때문에

4. Lerp() -> 이동에서 많이 사용되는 개념 y = a + (b - a)t

5. Raycast -> 2D 게임은 forward가 없음 (z축 밖에) 그래서 임의로 만들어줘야 하고 그것이 Vector2.right 이용해 준 것이었음

6. 아직 좋은 코드가 뭔지 모르겠다... 레퍼런스를 잘 참고해보자...