본문 바로가기

졸프-오만과 편경

[기술블로그] 타격에 대한 피드백 애니메이션 구현

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

 

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

 

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값을 조절하여 원하는 피드백이 나오도록 하면 됐지만 play씬은 딜레이를 정말 쥐어짜서라도 줄이고 싶었기 때문에 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: 현재 연주되어야 하는 정답 돌

 

다음과 같이 완성하였다.

 

전체 코드:

https://github.com/imHyejinPark/OMGPG