본문 바로가기

졸프-오만과 편경

[기술블로그] VR 리듬게임 구현

**졸업프로젝트 수업 과제 제출을 위한 합본입니다.**

그로쓰 07팀 (오만과 편경) 유예원

 

원래의 포스팅:

딜레이에도 적절히 작동하는 리듬게임 채점 구현

타격에 대한 피드백 애니메이션 구현

좁은 시야를 보완하기 위한 indicator 구현

 

프로젝트 :  '편경'이라는 전통 타악기를 이용한 리듬게임 구현

<1편> 딜레이에도 적절히 작동하는 리듬게임 채점 구현

 

배경 : 초기에는 채점을 delta time 값을 이용하여, 음악의 속도(bpm)과 비교해 올바른 시간 간격으로 타격이 이루어지는지를 기준으로 채점을 구현하였다. 하지만 매번 씬에 작고 랜덤한 딜레이가 생겨서 unity play mode에서 돌려볼 때 마다 각각 다른 딜레이가 생겼고 (음악이 뒤로 갈수록 실제 시간에 비해 점점 느려지거나 빨라짐) 그래서 실제 시간 값(초 단위)이나 프레임 시간 단위인 deltaTime값을 가지고는 정확한 채점을 구현할 수 없었다.

 

목표 : 사용자가 들리는 음악에 맞추어 정확하게 연주하였을 때, 딜레이와 무관하게 그에 맞는 점수 부여하기.

 

먼저, 가장 중요한 포인트는 audio source의 time samples값을 활용하는 것이다.

- 음악이 느려지거나 빨라질 때에 실제 시간을 랜덤한 그 딜레이에 맞춰 적용하는 것은 불가능하므로 현재 음악이 얼만큼 재생되었는가를 나타내는 필드인 AudioSource.timeSamples 값을 이용하였다.

 

1. SoundSystem.cs의 update 함수.

 

void Update(){
        if(Data.answers_tsample[Data.selected_song][NoteManager.noteCnt]-bgm.timeSamples<=note_term
                                    && NoteManager.noteCnt < Data.answers[Data.selected_song].Length){
            NoteManager.noteCnt++;
            AnsManager.CurrentAns=Data.answers[Data.selected_song][NoteManager.noteCnt-1];
        }
    

 

- 모든 '시간'값은 실제 시간과는 무관하게 timeSample값 사용하여 돌려볼 때 마다 딜레이에 함께 영향을 받는다. 딜레이가 길어져도 서로 엇갈리지 않고 함께 미뤄지기 때문에 최소한의 플레이가 가능한 환경을 유지한다. 이를 위해 직접 편경 돌을 연주하여, 타격 시의 timeSamples값을 측정하고, 여러 트라이얼의 값을 평균 내어 활용하였다.

 

- 모든 음의 타이밍이 지나갈 때 까지, 음이 연주되어야 할 차례 (완전한 정답 시간으로부터 약 +-0.5초 가량, timeSample값이기 때문에 정확한 초단위 시간으로 환산할 수는 없음)가 오면 정답값에 현재 연주되어야 하는 돌의 인덱스 값을 넣어주고 정답 순서를 1 증가해준다.

 

Data: 여러 스크립트에서 사용되는 변수들을 모아서 저장해놓은 스크립트

answers_tsample: 각 곡, 각 음의 완전 정확한 정답 기준 timeSamples값을 저장해둔 배열

selected_song: 현재 연주하고 있는 곡의 인덱스

bgm: 배경 음악 오디오 소스

note_term: 정답 음이 업데이트 되는 시간 간격

NoteManager: 전체적인 play씬 진행을 담당하는 스크립트

noteCnt: 현재 연주되어야 하는 정답 값의 순서를 저장하는 변수

CurrentAns: 현재 연주되어야 하는 돌의 인덱스 값

answers: 각 곡의 연주되어야 하는 각 음을 순서대로 돌의 인덱스 번호로 저장해놓은 배열

 

2. 타격시 호출되는 함수

 

public void OnCollisionEnter(Collision col){
        for (int i = 0; i < 16; i++){
            if (col.collider.CompareTag(i)){
                hitCheck = i;
                break;
            }
        }
        Data.hit_tsample=SoundSystem.bgm.timeSamples;
}

 

- 타격 시의 timeSamples값을 저장해준다.

 

- 추후 옳은 음이 연주되었는지를 채점에 반영하기 위해 타격된 돌의 번호를 저장한다.

 

hit_tsample: 가장 최근에 타격이 이루어진 시점의 timeSample값을 저장하는 변수

hitCheck: 타격된 돌의 번호를 저장하는 변수

 

3. PlayerInput.cs의 타격 점수를 계산하는 함수

 

public int CheckTiming()
    {
        double interval=Math.Abs(Data.hit_tsample-Data.answers_tsample[Data.selected_song][NoteManager.noteCnt-1]);
        if(interval<=1000){
            return 0;
        }
        if(interval<=3000){
            return 1;
        }
        if(interval<=7000){
            return 2;
        }
        if(interval<=13000){
            return 3;
        }
        return 5;//miss
    }

 

-  5 단계의 기준을 두어 더 좋은 타이밍, 즉 더 정답 기준값에 가까운 타이밍에 타격이 이루어질수록 작은 값을 리턴한다. (점수 계산에 이용한다.)

 

- 정답값 앞 뒤로 1000 timeSamples 이내에 타격이 이루어졌다면 perfect, 3000 timeSamples 이내라면 cool, 7000 timeSamples 이내라면 good, 13000 timeSamples이내라면 bad로 판정하고 값을 리턴시켰다. 만약 그렇지 못했을 경우에는 miss로 간주하고 5를 리턴하여 점수 계산에 반영하지 않았다.

 

- 이 때, 1000, 3000, 7000, 13000의 간격은 실험적으로 여러번 플레이 하여 perfect 하게 연주할 수 있지만 매번 잘했다고만 판정이 나오진 않도록 임의로 설정하였다. 점점 채점의 간격이 넓어지도록 하여 bad 판정은 실수를 한 경우가 아니라면 웬만하면 나오지 않도록 조절하였다.

 

interval: 정답 기준 값과 타격이 이루어진 시점의 timeSamples값의 차의 절댓값

 

4. PlayerInput.cs에서 사용자의 점수를 계산하는 부분

 

if (collision.hitCheck!=-1&&NoteManager.noteCnt>=1)
        {
            timeVal=CheckTiming();//가장 맞는 것 부터 0 1 2 3
            if (AnsManager.CurrentAns == collision.hitCheck)
            {
                playScore += (10 - timeVal * 2);//점수를 더해서 넣어준다.
            }
                collision.hitCheck = -1;//검사 완료 후 돌 상태 -1로 돌려놓기
        }

 

- 해당 스크립트의 update함수 안에 위치하여 매 프래임마다 호출된다. hitCheck값은 타격된 돌의 값을 저장하는 동시에 타격이 이루어지지 않는 동안은 -1로 해두어 타격이 이루어졌는지 여부를 판단하는 용도로도 사용하였다.

 

- 먼저 앞서 설명한 CheckTiming 함수의 리턴값을 timeVal에 넣는다.

 

- 현재 연주되어야 하는 음과 연주된 음이 일치하는지 확인한다. 일치할 경우 점수를 계산한다. 하나의 음 당 만점은 10점이다. 정답 기준값에서 멀리 연주하였다면 timeVal값이 1, 2, 3으로 커지기 때문여 timeVal*2 만큼씩 10에서 빼주면 정확도에 따라 차등 점수를 부여할 수 있다.

 

- 실수 타격에 의해 CheckTiming함수에서 5를 리턴 받았다면 10-5*2의 값이 점수에 추가되므로 채점에 반영되지 않는 셈이다.

 

- 정답음과 일치하지 않는 돌을 무작위로 연주하거나 실수하였을 경우에는 아무 일도 일어나지 않는다.

 

playScore: 해당 플레이 씬 동안 사용자가 획득한 점수를 누적 저장하는 변수

timeVal: 타이밍 채점 값을 담는 변수

 

4. 결과화면을 세팅하는 ResultManager.cs의 점수반영 별 세팅 부분

 

 if(PlayerInput.playScore > 0.5 * Data.max_scores[Data.selected_song])
        {
            left.isOn = true;
            if(PlayerInput.playScore > 0.7 * Data.max_scores[Data.selected_song])
            {
                right.isOn = true;
                if (PlayerInput.playScore > 0.9 * Data.max_scores[Data.selected_song])
                {
                    center.isOn = true;
                }
            }
        }

 

- 사용자가 획득한 최종 점수 값이 곡 당 만점의 50퍼센트를 넘을 경우 별 1개, 70퍼센트를 넘을 경우 1개를 더 켜서 총 2개, 90퍼센트를 넘을 경우 3개의 별이 켜지도록 하였다.

 

- 각 곡당 연주되는 음의 개수가 다르기 때문에 곡 마다 획득 가능 최대 점수가 다르다. 그러므로 만점에 얼마나 가까운지를 상대적으로 계산하여 별로 표시하였다.

 

max_scores: 각 곡의 만점을 저장해놓은 배열

left, right, center: 결과 화면에 위치한 별(toggle) 객체

 

다음과 같은 결과화면을 출력한다.

 

다음과 같은 결과화면을 출력한다.

 

 

참고하면 좋을 문서:

유니티 timeSamples 도큐먼트

https://docs.unity3d.com/kr/530/ScriptReference/AudioSource-timeSamples.html

 

<2편> 타격에 대한 피드백 애니메이션 구현

 

목표 : 사용자가 플레이하는 동안, 적절한 피드백을 제공하여 사용자의 게임 효능감 높이기.

 

perfect , cool 등의 텍스트 피드백은 text나 textmeshpro를 이용하여 구현할 수도 있었지만, 시각적으로 더욱 입체적이고 색이 예쁜 판정 애니메이션을 구현하기 위해 3D 오브젝트로 만들어 이용하였다.

 

1. 3D 그림판을 이용하여 적절한 피드백 객체 만들기

 

다음과 같은 순서로 진행하면 된다.

 

--->

 

- 텍스트-3D텍스트를 클릭하고 적절한 크기와 폰트, 색상을 선택한다.

- 원하는 텍스트를 작성한다.

- 저장하기를 누른 후 3D모델 저장을 클릭한다. 확장자는 색을 포함하여 저장하기 위해 fbx로 하였다.

- 이후, fbx파일을 유니티 창으로 드래그&드롭하여 임포트할 수 있다. 오만과 편경에는 4가지 판정 범위가 있으므로 아래 4개의 객체를 만들었다.

 

색상과 폰트를 적절히 고르는 것이 중요하다.

 

- 유니티에서 다시 한번 크기와 로테이션을 적절히 조절한다. 

 

프리펩 안에 알맹이인 node_id50이 객체가 있다.

 

- 이때 중요한 것은, fbx는 프리팹 형식으로 들어가는데 프리팹과 이미지의 축이 동일한 곳에 있도록 해야 한다. 즉, 프리펩에 대한 이미지의 상대위치가 0,0,0 이어야 한다. 이 두 위치가 엇갈려있으면 이후 애니메이션 컴포넌트를 추가하고 코드 작성하는데에 어려움이 있을 수 있다. 

 

- 꼭 상대 위치값이 0인 것 보다 각 객체를 클릭해 보았을 때, 두 객체의 축이 같은 위치에 있는 것이 중요하다. 글씨의 모양에 따라서 상대 위치가 0,0,0으로 같아도 눈으로 확인했을 때는 축의 위치가 약간 다를 수 있다.

 

2. 애니메이션 만들기

 

scale값과 rotation값을 이용하여 빙글~돌아서 나타나고 계속 같은 방향으로 돌며 사라지는 애니메이션을 만들었다. 사용자가 시각적으로 피드백을 확실히 인식할 수 있는 동시에 너무 거슬리지 않도록 나타날 때에는 천천히 나타나고 사라질 때에는 빠르게 휙! 사라지도록 간격을 조절하였다.

 

- 먼저, 프리펩의 알맹이(이미지)에 animator 컴포넌트를 추가해준다. (하단의 add component 버튼 클릭 후 animation을 검색하여 클릭)

 

 

- 이때 유의할 점은 animator와 animation은 다르다.

 

- 이후, 다시 객체를 클릭하면 다음과 같이 해당 객체에 대한 애니메이션을 만드는 윈도우가 보인다. create버튼을 누른 후, 만들 애니메이션의 이름을 정해준다. (오만과 편경에서는 show라고 지었다.)

 

 

- 위와 같이 animation 탭이 뜨지 않을 경우에는 상단의 window에서 animation을 찾아 클릭해서 열어주면 된다.

 

 

- 애니메이션 윈도우의 왼쪽 상단의 빨간 동그라미 버튼을 눌러 애니메이션 만들기를 시작할 수 있다.

 

 

- 먼저, 원하는 시간 값 부분을 클릭하여 흰색 시간 막대를 위치시킨다.

- 그 다음, 그 시간에 변경시키고 싶은 부분을 inspector 탭에서 입력해준다. 그러면 해당 필드가 빨간색으로 보인다. 그리고 애니메이션 property 리스트에(왼쪽 중간) 그 요소가 자동으로 추가된다.

- 시간 값을 옮겨서 클릭해가면서 애니메이션을 만들면 된다.

- 필요한 일을 다 했으면 다시 빨간 동그라미를 눌러 녹화를 마친다. (언제든 덧붙이기 가능)

- 빨간 동그라미 줄에 있는 재생 버튼을 누르면 애니메이션을 미리보기 할 수 있다.

- 재생 버튼 양 옆에 있는 버튼으로 저 흰 동그라미들이 있는 프래임으로 옮겨갈 수 도 있고, 그 양 옆에 있는 버튼으로는 애니메이션의 맨 처음, 맨 끝으로 이동할 수 있다.

 

- 이런 방식으로 위에 서술한대로 빙글~ 도는 애니메이션을 만들었다.

 

왼쪽 위 - 오른쪽 위 - 왼쪽 아래 - 오른쪽 아래

 

- 초기 스케일을 0으로 하고, rotation은 -145, 이후 0:10까지 스케일을 0.2로, rotation을 -45까지 돌렸다. 0:45가 될 때 까지는 스케일이 1로 증가하고 ratation은 수평방향인 0까지 줄어들도록 하였다. 마지막으로 사라질 때 휙 사라지도록 0:50까지 스케일을 0으로 감소시키고 rotation은 145도로 휙 돌도록 하였다.

 

- 매 순간의 값을 설정해줄 필요 없이 2개의 시점에 대해서 값을 설정하면 그 사이 시간 동안은 연속적인 값으로 애니메이션이 그려진다.

 

- 애니메이션은 값을 조금씩 조절해가면서 원하는 느낌이 나도록 연출해주면 된다. 애니메이션의 러닝 타임이나 빠르기를 원하는 용도에 맞게 조절하는 것이 중요하다.

 

- 각각 객체에 같은 애니메이션을 모두 부여해준다. (Add component하여 animation추가 후, animation 필드 끝의 동그라미 안에 네모.. 를 클릭한 다음, 애니메이션 이름으로 검색하여 찾은 후 클릭해주면 된다.)

 

 

- 이 때, 애니메이션을 프리펩에 대해서 제작했다면 모든 객체에 대하여 프리펩에 컴포넌트로 주고, 프리펩 내부의 이미지에 대해서 제작했다면 이미지에 컴포넌트로 주는 것이 중요하다. 상대적인 값인 scale값을 사용하였기 때문이다.

 

- 여기까지 완성하면 다음과 같은 모습이 된다. 노력에 비해 초라해보일 수 있지만 아직 실망하기엔 이르다.

 

 

4. 적절한 타이밍에, 적절한 위치에 나타나도록 스크립트 작성하기

 

먼저 오만과 편경의 경우 편경의 돌이 16개이므로 16가지의 위치 경우의 수가 있었고, 돌의 연주 타이밍에 따라 4가지 피드백 중 하나의 적절한 피드백을 노출해야 했다. 가장 단순한 방법으로는 모든 돌 앞에 4개의 피드백을 각각 위치시켜두고 SetActive값을 조절하여 원하는 피드백이 나오도록 해도 되지만, 오만과 편경에서는 4개의 객체를 하나씩만 두고 원하는 타이밍에 원하는 transform값을 갖게 한 뒤 SetActive값을 조절하여 나타나도록 하였다. 전자처럼 구현할 경우에는 애니메이션이 나와야 하는 시점마다 16*4개의 애니메이션을 다 SetActive(false)를 하고 원하는 객체만 true를 주면 된다.

 

1. PlayerInput.cs에 위치 배열을 만드는 함수 작성

 

void set_effect_positions()
    {
        effect_positions=new Vector3[16];

        effect_positions[15] = new Vector3(-1.123f,0.296f,-0.228f);            
        effect_positions[14] = new Vector3(-0.818f,0.296f,-0.228f);
        effect_positions[13] = new Vector3(-0.473f,0.296f,-0.228f);
        effect_positions[12] = new Vector3(-0.155f,0.296f,-0.228f);
        effect_positions[11] = new Vector3( 0.136f,0.296f,-0.228f);
        effect_positions[10] = new Vector3( 0.428f,0.296f,-0.228f);
        effect_positions[9]  = new Vector3( 0.806f,0.296f,-0.228f);
        effect_positions[8]  = new Vector3( 1.105f,0.296f,-0.228f);

        effect_positions[7]  = new Vector3( 1.105f, -0.472f, -0.472f);
        effect_positions[6]  = new Vector3( 0.806f, -0.472f, -0.472f);
        effect_positions[5]  = new Vector3( 0.428f, -0.472f, -0.472f);
        effect_positions[4]  = new Vector3( 0.136f, -0.472f, -0.472f);
        effect_positions[3]  = new Vector3( -0.155f, -0.472f, -0.472f);
        effect_positions[2]  = new Vector3( -0.473f, -0.472f, -0.472f);
        effect_positions[1]  = new Vector3( -0.818f, -0.472f, -0.472f);
        effect_positions[0]  = new Vector3( -1.123f, -0.472f, -0.472f);
    }

 

- 코드를 정리하기 위해 start함수에서 호출되는(최초 1회만 실행) 초기화 함수를 작성하였다. 이 값들은 16개의 편경 돌 앞에 애니메이션을 직접 위치시켜보며 적절한 x,y,z값을 수동으로 다 측정하여 적은 것이다.

 

- 하지만 더욱 깔끔한 코드를 작성하고 싶다면 16개의 empty object를 만들어 적절한 위치에 놓고, 스크립트에서 game object의 serialized field array를 만든 후 그 객체들을 스크립트가 들어있는 객체 inspector에서 드래그앤드롭으로 넣어주는 방식을 사용할 수 있다.

 

2. PlayerInput.cs 에서 피드백 애니메이션을 보여주는 feedbackAnim 함수

 

[SerializeField] GameObject[] judgePrefab = null;//판정 이미지

void feedbackAnim()
    {
        judgePrefab[0].SetActive(false);//숨겼다가
        judgePrefab[1].SetActive(false);//숨겼다가
        judgePrefab[2].SetActive(false);//숨겼다가
        judgePrefab[3].SetActive(false);//숨겼다가
        judgePos[timeVal].transform.localPosition = effect_positions[AnsManager.CurrentAns];//옮기고
        judgePrefab[timeVal].SetActive(true);//나타나기.
    }

 

- serializedField를 선언해준 뒤, 스크립트를 넣은 객체의 inspector창에서 만들어둔 4가지 객체들을 드래그앤드롭 해주면, 스크립트를 이용해서 객체에 접근할 수 있다.

 

- 먼저, 4가지 피드백 객체의 inspector창에서 애니메이션의 playOnAwake 체크박스를 체크 해뒀다. 이렇게 하면 setActive(true)가 되면 animation이 1회 플레이 된다. 이 에니메이션은 끝나는 시점에서 이미지의 스케일이 0, 즉 사라지도록 했기 때문에 나타났다가 사라지는 것 처럼 보인다.

 

- 이미 나타나있는(setActive(true)한 객체) 객체를 또 다시 setActive(true)하면 애니메이션이 나타나지 않는다. 그래서 위치를 옮기기 전에 모든 피드백 객체를 setActive(false)해주었다.

 

timeVal: 정답 기준 값과 연주된 시점이 얼마나 가까운지에 따라 가장 0,1,2,3 의 값을 가지는 변수

CurrentAns: 현재 연주되어야 하는 정답 돌

 

다음과 같이 완성하였다.

 

<3편> 좁은 시야를 보완하기 위한 indicator 구현

 

배경 : 편경은 크기가 크면서도 연주하는 채인 각퇴는 길지 않아 VR상에서 편경 객체가 사용자 가까이에 위치해야 한다. 그렇기 때문에 편경이 아주 크게 느껴지고 사용자의 시야가 한정적이어서, 다음 연주할 음을 보여주는 애니메이션이 사용자 시야 밖에 위치하게 되기도 하여 연주에 불편함이 있었다. 이를 극복하기 위해 사용자가 애니메이션이 나타나는 곳을 보고 있지 않을 때 경고를 주는 기능을 추가하기로 하였다.

 

목표 : 사용자의 시야 밖에서 이벤트가 발생하면 화살표를 이용하여 알려주기.

 

먼저, 단순하고 직관적인 푸른 화살표 이미지를 만들었다. 다운받아도 되지만 피피티에서 기본 도형 화살표를 그린 후 png파일로 저장하였다.

 

 

1. hierarchy상 적절한 위치에 화살표 넣기

 

 

- 화살표는 브이알 세계에서 절대적인 위치를 갖는 것이 아니라 사람의 머리인 헤드셋의 위치를 따라가야 한다. 그러므로 hierarchy상 카메라에서 가운데 눈의 자식 객체로 두개의 화살표(왼,오)를 넣어주었다.

 

- png는 이미지이므로, 3D 공간에 두기 위해서는 캔버스가 필요하다. tmp_SI (sight indicator)라는 캔버스를 만들어주었다.

 

- 화살표는 z값 로테이션을 주면 하나의 이미지로 왼쪽 오른쪽 화살표를 만들 수 있다.

 

2. indicator를 보여주는 함수 작성

 

public Transform head;


public void indicatorL(){
        if(head.localRotation.y<-0.1){//왼쪽으로 가야 할 때
            left.SetActive(true);
        }
        else
            left.SetActive(false);   
    }
    
public void indicatorR(){
        if(head.transform.localRotation.y>0.1){//오른쪽으로 가야 할 때
            right.SetActive(true);
        }
        else
            right.SetActive(false);
    }
    
public void indicatorOff(){
        left.SetActive(false);
        right.SetActive(false);
    }

 

- head 필드에는 헤드셋의 transform값, right에는 오른쪽 화살표 이미지, left에는 왼쪽 화살표 이미지를 넣었다. (스크립트를 넣은 객체의 inspector창에 드래그앤드롭)

 

- 직접 헤드셋을 들고 왼쪽 오른쪽으로 움직여봐서, 왼쪽이 보이지 않을 만큼 헤드셋을 돌렸을 때 transform의 값이 -0.1 이하라는 것을 확인했다. 그러므로 이 값보다 작은 transform.localRotation.y값을 갖는다면 화살표를 보이도록 setActive(true)를 해주고 아닐 경우 false를 해준다. 오른쪽도 동일하게 구현하였다.

 

head: 헤드셋의 transform값

right: 오른쪽 화살표 이미지

left: 왼쪽 화살표 이미지

 

3. indicator가 필요한 시점에 따라 호출해주는 조건 작성

 

 if((AnsManager.CurrentAns>=12||AnsManager.CurrentAns<=3)&&AnsManager.CurrentAns!=16)
      indicatorL();
else if(AnsManager.CurrentAns!=16)
      indicatorR();
else
      indicatorOff();

 

- 편경의 돌 위치 상 왼쪽에 있는 돌이 현재 연주해야하는 정답일 경우에는 indicatorL() 오른쪽일 경우에는 indicatorR()을 호출하였다. 이후 이미지를 보이게 할지 말지는 현재 연주해야 하는 돌이 사용자의 시야에 있는지를 indicatorL, indicatorR 함수 내부에서 확인해서 결정하였다. 

 

CurrentAns: 현재 연주해야하는 정답음

 

4. 애니메이션을 추가하여 고개를 돌리도록 유도하기

 

 

- 애니메이션에는 position 값, 그 중에도 x값만 사용하여 좌우로 움직이는 화살표를 만들었다. 

 

- 초기 상황에 position.x 값을 10, 0:15 까지 0으로 가도록 하고, 0:30 까지 다시 10으로 돌아오도록 하였다.

 

애니메이션이 적용된 모습

 

 

- 마지막으로, 이름 옆의 체크박스를 해제해주어 setActive(false)의 상태로 만들어준다. 이렇게 해야 초기 화면에 화살표가 노출되지 않고 필요에 의해 호출될 때만 나타난다.

 

다음과 같이 완성하였다.

 

 

 

<4편> 스크립트의 오버헤드를 줄이는 방법 몇가지와 디버깅 팁.

 

 1. 아무 일도 하지 않는 Update()함수는 지워준다.

 

  - 스크립트를 만들 때, 자동으로 update함수가 생기거나 어떤 일을 수행하기 위해 만들었다가도 추후 수정하며 딱히 할 일이 없어지기도 한다. 그러나 update 함수는 매 프레임마다 호출되는 것 만으로도 꽤 오버헤드를 일으키므로 지워주는 것이 좋다.

 

  - 그렇다고 모든 update함수를 쓰는 함수를 다 하나로 모아 하나의 스크립트로 만들 만큼 큰 효과가 있는 것은 아니다. 그저, 불필요한 경우 지워주면 된다.

 

 2. 3D 게임의 경우 배경 객체의 쉐도우, 콜라이더를 지워준다. 

 

  1) inspector 창에서 배경과 사용자는 인터렉션 할 일이 없으므로 Mesh Collider는 통째로 끈다.

  2) Mesh Randerer에서 Lighting - Cast Shadows를 Off로 하고, Receive Shadows도 체크를 꺼준다.

 

 

  - 오만과 편경에서는 사용자가 편경 이외의 배경 객체와 인터렉션 할 필요도, 머리를 돌리는 정도 이상의 이동도 없기 때문에 그림자나 콜라이더가 전혀 필요하지 않았다. 하지만, 다른 케이스의 경우라면 필요성을 따져봐서 결정해야 한다.

 

  - 배경의 리얼함이 중요하지만, 시점 이동은 없는 게임과 같은 경우에는 shadow를 땅이라든지, 그 그림자를 맞는 객체라든지,, 의 material에 각각 그림자를 그림처럼 반영하여 입히기도 한다. VR게임의 경우 그림자는 사용자의 시점에 맞게 변화하여야 하기 때문에 프레임마다 계속 그 모양을 새로 랜더링 하기 때문에 오버헤드가 큰 편이기 때문이다.

 

 3. Mesh Collider는 되도록 절대 사용하지 않는다. 

 

  - 콜라이더는 실제 세상의 객체가 서로 부딛히는 것과 같이 유니티 상의 객체들이 서로 부딛힘을 인식하게 하는 기능으로, 유니티에는 다음과 같이 다양한 콜라이더가 있다. 

 

 

  - 그 중에서도  mesh collider는 아주 특이한 콜라이더다. 다른 콜라이더의 경우에는 ppt에서의 기본 도형처럼 정해진 모양이 있다. (box, capsule 등) 하지만 mesh collider는 마우스로 직선을 그려 직접 그리는 도형처럼 디테일하게 선을 하나하나 그리는 것을 유니티가 자동으로 해주는.. 콜라이더라고 생각하면 된다.

 

  - mesh 콜라이더의 원래의 용도를 생각하면 엄청 그 객체의 모양에 딱 맞는 콜라이더를 만드는 것이 목적이므로 (사용자와 리얼하게 인터렉션하기 위해서), 이 자동 그려주기 기능이 아주 디테일하고 오버헤드가 매우매우매우 크다. 그리고, 그렇게까지 디테일할 필요까지는 없는 경우가 많을 것이다.

 

  - mesh 콜라이더를 사용하는 대신, 물체와 꼭 맞지 않고 조금 튀어나오고 들어가더라도 다른 닮은 형태의 콜라이더를 사용하거나 몇개의 콜라이더를 조합하여 사용하는 것이 이롭다.

 

  - 정말 여러 팁 중에서도 mesh collider 사용하지 않기는 별 5개 짜리 만큼 중요한 항목이다. 정말 가시적일만큼 큰 딜레이를 유발한다.

 

 4. Debug.Log를 적극 활용하기.

 

  - 급작스럽게, 조금 기초적인 내용이지만 아주 유용하다.

  

  - 스크립트를 디버깅하다보면 간단한 프린트문 같은 것이 아주 유용하게 쓰일 때가 있는데, 유니티에서는 Debug.Log가 print문에 해당한다. 적절한 곳에 다음과 같이 작성하면 된다.

 

Debug.Log("Hello World");

Debug.Log("Time here  "+ CurTime);

 

  - play mode에서 콘솔 탭을 클릭하면 다음과 같이 볼 수 있다.

 

 

  - 디버깅 할 때는 콘솔 뿐만 아니라, Scene 뷰를 적극 활용하여.. 원하는 오브젝트가 어디에서 생겼다가 사라지는지.. 생기긴 하지만 너무 작아 안보이는지, 카메라 뒤에서 생긴다든지, 땅 아래에 있다든지.. 하는 보통은 상상하기 어려운 기상천외한 이상한 일들이 생기는 것을 잘 파악해야 한다. 특히나, 객체가 분명 있는데 없다면 Scale을 아주아주 크게 키워보기도 하고 아주아주 작게 줄여보기도 해야한다. 너무 커서 보이지 않는다는 것이 말이 안되게 들릴 수 있지만 유니티 세상에선 그런 일이 자주 일어난다.

 

 

 

 

전체 코드:

https://github.com/yeawon-you/OMGPG