
1. PlayerRotation (플레이어 방향)
2. TimeManager (레벨 디자인)
3. SceneryManager (씬 관리)
캐릭터의 회전을 담당하는 스크립트를 만들어서 플레이어의 입력에따라 캐릭터의 회전값을 변화시키는 코드를 작성할 것이다.

일단은 실행되는 것을 목표로 하기에 이 스크립트에서 if - else if문으로 입력을 받았다.
playingCoroutine과 Coroutine함수에 관해서는 나중에 알아보고 A,D,W,S에 따라 Quaternion.Euler로 바라볼 rotation값을 정해준뒤 Rotate라는 코루틴 함수의 매개변수로 전달한다.

Rotate코루틴 함수는 Quaternion형으로 최종적으로 바라볼 방향을 받아오고
while문을 이용하여 자연스럽게 해당 방향을 바라보도록 작성하였다.
transform.rotation값을 Quaternion.Lerp를 사용하여 정해주는데
Lerp의 시작점으로 Quaternion.Euler를 사용하여 현재 회전값을 eulerAngles.y를 통해 사용하고
Lerp의 도착점으로 end (아까 받은 최종방향)을 잡고,
속도 (비율)은 대충 0.3f로 잡아주었다.

Quaternion.Lerp와 Quaternion.Euler이 조금 어려웠다..
도착방향 y값의 절댓값 - 현재방향 y값의 절댓값이 0.1보다 클때와,
도착방향 y값의 절댓값 - 현재방향 y값의 절댓값이 -0.1보다 작을때,
while문을 반복하게 되는데, 이 조건문의 의미는
현재 회전값.y의 크기가 도착회전값.y보다 클 때와 작을 때를 판단하여,
클 때는 0.1차이가 날 때까지 회전값을 줄이고,
작을 때는 0.1차이가 날 때까지 회전값을 늘리게끔 되어있다.

도착방향 - 현재방향이 양수일때와 음수일때로 구분되어 처리된다.

위 코드에선 A를 눌렸을 때, if문을 지나서, playingCoroutine이라는 변수에 Coroutine을 넣으며, 함수를 호출한다.
만약 함수가 실행중일떄 입력을 받게되면 if문에 걸리며, 실행중인 Coroutine을 정지하고 다시 코루틴을 실행되게끔 한다.

하지만 이렇게 대충 작성만 하게되면, 전에 만들었던 시스템인, '게임오버'를 했을 때, 캐릭터의 회전값이 돌아갈수 있는 것을 볼수 있다

때문에, PlayerInput에서 입력을 받아 게임오버가 되면 입력을 받지 못해 회전하지 못하도록 하겠다.

Quaternion형의 프로퍼티를 만들고,
OnRotate라는 함수를 만들어 플레이어의 입력에 따라 rotate의 값이 바뀐다.

PlayerInput에서 Quatertion을 가져와 함수를 호출하게끔 되어있다.
(코드가 매우 간결해졌다.)

(살짝 아쉬운 점은 두개의 키를 동시에 눌렀을때, 대각선을 바라보지 않는다는점..?)

해당 스크립트는 체력 UI에 넣어줄 스크립트로 시작할때의 방향값을 계속해서 유지하게끔 작성하였다.
이건 왜 이런지 잘 모르겠어서 이정도로 만족한다.
'음식이 떨어질 속도', '감소하는 Hp의 양', 'hp감소 빈도' 정도가 존재하고,
'음식이 떨어질 빈도', '캐릭터의 이동속도' 등이 존재한다.

먼저 TimeManager에 프로퍼티를 선언하여,
TimeManger안에서 값을 수정하고 밖에서 읽을 수 있게끔 하였다.
초깃값은 현재 난이도 정보를 넣어주었다.

먼저 이 플레이되는 씬이 시작될 때 부터 플레이시간을 판단해야 하기 때문에,
씬이 시작될 때 실행될 함수를 만들고, OnEnable과 OnDisable에
씬이 실행될때 브로드캐스팅을 하는 sceneLoaded 이벤트 함수에 넣어주었다.

기본적으로 증감계수를 이렇게 잡아주고, 테스트를 하면서 적절하게 값을 잡는다.



음식의 떨어지는 속도가 순식간에 너무 빨라져서 먹기가 힘들다던가, 음식이 떨어지는 빈도가 낮아서 재미가 없는 것 같다. 그렇기에 계속해서 테스트를 해가며 플레이어의 흥미를 유발할 수 있는 레벨디자인을 만든다.

먼저 플레이어를 기준으로 나오는 음식의 범위를 좁혀서 플레이어가 더 많이 먹을 수 있도록 하였다.

이렇게 수정한 결과 대략적으로 최대 2분 정도의 플레이 타임을 가졌다.

음식이 떨어지는 빈도를 너무 낮추면 게임이 음식에 가득차게되고,
HP감소시간을 너무 줄이면 게임이 너무 어려워 지기에 둘다 한계값을 지니게 해주었다.
다른 변수들도 값이 크게 변화하면 정상적인 플레이의 지장을 주지만, 정상적인 게임플레이 시간에는 나타나지 않기때문에 따로 한계값을 지정해주진 않았다.
그래서 열심히 찾아보니, Coroutine안에 변수의 값을 지정해줄 때, 프로퍼티를 통해 값을 할당하지 않고, 변수에 직접적으로 할당을 하니 set함수를 거쳐 저장되지 않았던 것이였다.

이렇게 작성하였다. 이제 프로퍼티의 set을 작성하게 되면 변수로 할당하지 않고 프로퍼티를 통해 값을 할당해야 한다는 것을 알게된 것 같다.

그리고 이렇게 한계값을 정해주니, 생각보다 플레이 타임이 길어지기에 음식의 떨어지는 속도와 캐릭터의 속도의 한계값도 정해주었다.

이렇게 TimeManager의 코드를 작성하였다.
플레이타임 1분30초 이후부터는 값의 변경점은 없었고, 실력만 좋다면 계속해서 살아남을 수 있었다.
(결론적으로 여기서는 씬 전환만 작성하였다.)

기본적으로 게임을 시작하는 Start버튼과, 떨어지는 음식의 종류를 보여주는 Foods 버튼 그리고 게임을 나갈수 있는 Quit 버튼을 만들어 줄 것이다.

먼저 이렇게 버튼을 구성하고, SceneryManager라는 스크립트와 그 안에 씬을 호출할 함수를 미리 만들어 버튼을 클릭 했을때, 해당 씬으로 넘어간다. (Start만 씬이 존재하다..)


button같은경우 Panel에 Vertical Layout Group과 Content size Fitter를 통해 버튼의 규칙적인 간격을 잡아주었으며, 이미지의 경우 ChatGPT를 이용하여 가져왔다.

이렇게 작성하고 Start 버튼을 누르게 되면 맵의 Direction Light가 제대로 작동하지 않은채로 실행된다.

아마 이러한 이유는 맵의 로딩이 전부 되지 않고 씬을 불러오기에 연산 시간이 부족한 것이다.

정확히 비동기 씬 이동 시스템인데 간단하게 검은 화면이나, 로딩 바같은 건 생략하고 씬이 준비되었을때 이동하는 함수만 작성해 주었다.
AsyncOperation asyncOperation = SceneManager.LoadSceneAsync(index);
이 코드는 이동할 씬을 백그라운드에서 로딩시작하고, 변수에 담는다.
asyncOperation.allowSceneActivation = false;
이 코드는 장면이 준비된 즉시 장면을 활성화하는 것을 허용하는 변수는 꺼둔건데,
내가 작성한 코드처럼 로딩화면이나, 로딩 바를 사용하지 않는다면 작성하지 않아도 될 것이다.
while(asyncOperation.isDone == false)
이 코드는 해당 동작이 준비 되었는지를 반환하는데,
완료가 되면 true를 반환하는 변수로 while 조건문을 사용했다.
if(asyncOperation.progress >= 0.9f)
마지막으로 이 코드는 작업의 진행정도를 0 ~ 1(float)로 나타내는 코드는 0.9이상일때로 조건문을 사용했는데, 왜 0.9이상일때 씬을 불러오냐면,
0.9가 되었을 때, 씬의 거의 모든 것을 불러오고 이제 씬을 불러오면 되는 진행정도를 가지게 된다.
그리고 씬이 넘어갔을때, 1이라는 값을 가지게 됨으로 씬을 불러올때는 0.9이상으로 잡아주었다.


그렇게 찾아보다가 이러한 현상이 빛(조명)관련 문제라는 것을 알았고,

(모든 맵에다가 조명 생성을 해주었다.)

이렇게 메인 화면에서 게임 화면으로 이동할 수 있게 만들었다.
이건 쉽다. 그냥 게임 오버가 되었을 때, 다시시작 버튼과, 메인 메뉴 버튼을 둔 이후,
다시시작 버튼을 눌렀을 땐, 게임씬 다시시작, 메인메뉴 버튼을 눌렀을 땐, 메인 씬 시작을 하면 된다.


buttonText로 Text형의 자식 오브젝트를 가져오고 textFontSize로 기존의 폰트크기를 가져온다.
여기서의 이 코드의 목적은 버튼에 마우스를 올릴때, 내릴때, 클릭할때 폰트의 크기를 변경시키는 코드이다.

살짝 번거로웠던 점은 프리팹을 통해 ButtonManager, EventTrigger,
코드를 통해 자식 오브젝트 가져오기, 폰트크기 가져오기는 한번에 가능했지만,
Event Trigger에 자신의 오브젝트를 모두 하나하나씩 드래그하고 각 실행 조건을 맞춰야 한다는점?




Action이벤트 두개를 통해, 게임오버창에서 버튼을 눌렀을 때, 브로드캐스팅 방식으로 함수를 호출한다.
gameOverImage는 버튼 두개를 포함한 게임오버 창을 뜻한다.



SceneryManager는 MainScene에 있고, GameManager는 GameScene에 있다.
SceneryManager에서 GameManager에 있는 이벤트에 구독하려면 GameManager 또한 MainScene에 있어야 하는데, GameManager 같은 경우 게임 오버창에 있는 버튼 두개의 구독을 받고 있기 때문에,
MainScene으로 이동하게 된다면, 게임 오버창의 버튼 두개를 어떻게 관리 해야하는지 문제이다.

근데 GameManager의 경우 SceneryManager처럼 Main화면 부터 있으면 좋기에 버튼을 어떻게 연결할 것인가에 대해 생각해 보았다.

ReStart버튼에 부착시킨후 버튼 클릭시 GameManager의 함수가 실행되도록 하게끔 만들어 주었다.

GameScene에서의 UI를 담당하는 GameSceneUIManager를 만들어서 모든것을 통제하면 어떨까 생각 했다.
이렇게 선택한 이유중 하나는 너무 세부적으로 들어감과 동시에 Canvas또한 제어해줄 스크립트가 필요하다는 점에서 이러한 방법을 최종적으로 선택하게 되었다.



각각의 버튼을 Inspector에서 넣어준후 gameOverImage에도 Canvas를 넣어준다.

이후 게임씬에 있는 GameManager를 삭제 시키고 메인씬에 GameManager를 넣어준다.
이후




뭔가 코드간의 진행, 게임의 흐름이 이상하지만 되기만 하면 일단은..? 그만이다...

Hp가 0 이하로 내려갔을때, GameManager의 게임오버와 GameSceneUIManager의 게임오버 함수를 둘다 실행시킨다....(음...)


이 오류는 캐릭터에 있는 PlayerInput 스크립트가 싱글톤으로 작성되어있기 때문이다.
(결국 한번 발목을 잡는구나)

PlayerInput의 싱글톤을 빼준후,

다시 실행해 보면

또 문제가 발생하는데 다시 Start버튼을 눌러도 실행이 안된다는 점이다 다른 버튼도 그렇다.
그래서 왜 이런지 찾아보니,

클릭 시 호출될 오브젝트가 존재하지 않는다. 내 예상으로는 아마 SceneryManager가 여기 있다가 다른씬으로 가고 이때 연결이 끊어진 것으로 보인다.



화면과 같이 다시 실행했을 때,
게임이 멈춘 이유는 GameManager의 state를 변화시켜주지 않았기 때문이다.

Game씬으로 갈때 GameManager의 시작 함수를 호출시켜 state를 제어한다.


이때, ReStart버튼을 눌렀을 때,GameManager의 State가 false인채로 실행된 이후, 다시 true로 변환되어 게임이 멈추기 때문에 PlayerHealth 에서 Update로 GameState를 제어하던 코드를 Hp가 닳았을 때만 호출하게끔 Die함수를 만들고 HealthUpdate함수에 넣어 State를 제어해준다.

이거 때문에 꽤나 골치아팠다... (앞으로 게임 state을 제어하는 코드는 Update같은 코드에서 하지 말자..)
