추가 스킬의 애니메이션 제작

8.11

일단 앨리스의 맞고 찌르기. 데미지상태로 볼 때 크게 이득인 것 같진 않다…

원안보다 많이 변형되어 별 것아니라고 생각했던 일이 커졌다. 일단 쳐올리기 데미지상태를 추가하면서 피격자의 애니메이션이 필요해서 4캐릭터 모두에게 추가했고, 기본 애니도 좀처럼 예쁜 모양이 나오지 않아 꽤 많은 수정을 거쳤다. 원래 구현은 처올리기 후 어류겐으로 추가타를 넣는 것이었지만, 체공시간이 짧아서 작은 기술밖에 들어가지 않는다. 원했던 방향이다.

이펙트 작업은 심호흡을 하고 한 번에 처리하는 편이 좋기 때문에 나중에 한 번에 처리하기로 하고, 닐리로 넘어가도록 하자.

8.12

닐리의 추가스킬 계획은 바닥에서 커다랗고 뜨거운 손이 나와 캐릭터를 때리는 것이다. 그리고 그것은 2연타이고 싶다.

사실 닐리의 추가 스킬은 그렇게 어렵게 생각하지 않았다. 근접기 하나 더 추가하는 것인데 뭐 그리 어려울까? 하지만 작업하며 깨닫고 말았다. 이것은 새로운 유형의 작업이었구나…근접기인듯 보이지만, 근접기는 아닌 게, 공격을 하는 이펙트가 제자리에 남는 것이다. 캐릭터와 다르게 그자리에 남아 확실한 타격을 준다. 하지만 발사체와는 다르게 타격은 또 일반 히트로 분류되어야 한다. 콤보 시 데미지 보정이 들어간단 이야기다.

타격을 2번 연속 때리는 것이므로 캐릭터에게 Attack이벤트를 2번 넣어도 된다. 실제로 앨리스가 땅을 밟는 스킬을 시전할 때 10프레임과 12프레임에 각각 Attack이벤트를 심었었다. 하지만 이제 와서 왜 타격을 캐릭터와 분리했냐면 대전 테스트를 할 때, 그 2프레임(=약 0.066초)사이에 앨리스가 얻어맞으면 모션이 캔슬되며 1타만 일어나는 현상이 있었다. 정말 짧은 시간인데, 프레임 사이를 다투는 격투게임에선 이런 일이 제법 자주 일어난다.

발사체는 생각보다 많은 정보가 필요하다. 공격자, 충돌을 검사할 적, 데미지, 발사체 속도, 원소타입, 공격타입, 데미지타입, 타격타이밍 등등. 때문에 이것들을 발사체클래스 내에 주루룩 변수로 만들었었는데, Bomb이 발사체를 상속받게 만들면서 정리를 할 필요가 생겼다. 하는 김에 코드를 리팩토링하자. 짧다고 좋은 코드는 아니지만, 좋은 코드는 짧다. 개발이 진행되면서 코드가 길어지는 것은 어쩔 수 없지만, 이렇게 틈나는대로 정리를 해주면 보기에도 좋고 무엇보다 나중에 편하다.

뿅뿅뿅. 윙샷도 구현완료. 데미지는 약한데, 많이 맞으니 이것도 아프다.

여기까지 구현된 걸 기록해 놓자.

오늘의 깨달음. 블렌더 애니메이션을 할 때 원하는 채널을 잠궈놓으면 해당 채널엔 애니메이션 키가 잡하지 않는다!

루트의 애니메이션은 모든 관절부 애니메이션을 통틀어 가장 중요한데, 때문에 채널별로 키를 다르게 줄 때가 많다. 개인적으로 많이 사용하는 기능이 블렌드 애니메이션(alt+e)기능인데, 이건 다 좋은데 모든 채널에 키를 만드는 것이 문제다. 이럴 때 Location을 잠궈놓으면 로테이션만 키를 줄 수 있다. 물론스케일 채널에도 키를 만들겠지만, 그건 버리는 키니까 상관이 없다.

8.13

러프한 구현완료. 손만 빠르면 점프킥부터 시작해서 초필까지 연계할 수 있다. 아무리 데미지 보정을 받아도 그렇게 되면 거의 절명기 수준인데, 문제는 내 손이 그걸 못한다…

애니메이션도 완료. 빙그르르- 이 짧은 씬을 구성하는데도 4개의 애니메이션이 필요하다. 잡기는 참 번거로운 기술이다.

8.14

그 번거로운 잡기를 또 해야 한다. 이번엔 오로라의 스킬이다.

구현과정을 녹화해 보았다. 사실 이건 스크립팅이라 구현이랄 것도 없다. 기존에 되어있던걸 추가했을 뿐인데, 그럼에도 새로운 트러블이 발생해서 한참 삽질을 했다. 그런데도 타임랩스로 보니 뭐 대단한 전문가가 아주 빠르게 코드를 쓰고 있는 것처럼 보인다. 사실은 ‘이게 맞나…’ 싶어서 코드를 썼다가, ‘아까 했던 게 맞네’하고 되돌리고 나니 ‘앗. 고친 게 맞네’싶어서 허둥대는 모습이다. 그걸 지우고 다시 쓰고하는 삽질도 삽질이지만, 오늘따라 유니티의 못보던 버그도 끼어들어 한층 더 삽질이 심했다. 그런데 이 모든 것이…! 타임랩스로 돌리니 마냥 일잘하는 직원같아 보인다!?! 약은 이렇게 파는 거구나.한 수 배웠다.

본디 이 스킬은 KOF블루 마리의 태클잡기에서 따온 것이다. 테스트할 때 지인이 아이디어를 줬는데, 이걸 그대로 차용한 것이다. “하단인데도 발동이 빠른 기술이예요”. 그걸 듣고 만든건데, 어제 구현하고도 뭔가 좀 이상했다. 잡기에 무슨 하단이 있어… 그래서 생각해보니 막히면 연계되지 않아야 한다. 즉 1타는 타격기다. 그래서 오늘 코드를 수정. 막히면 잡기는 발동되지 않는다. (그리고 얻어맞겠지)

슈웅-!여기서 추가타를 넣으면 잡는다. 닐리나 달래의 장풍 또한 피할 수 있기 때문에 헛점을 찌르는 용도로 사용할 수 있지 않을까

시스템 보완 작업

8.7

지인 사무실에서 테스트를 진행했다. 이는 사실 처음이 아닌데, 저번 테스트는 버그가 많았고 방어 딜레이가 너무 길어 치고받기가 매끄럽지 않았기에 굳이 영상을 올리지는 않았다. 오늘은 저번에 비해서는 게임이 되는 편이었다. 버그가 일부 발견됐고, 보완할 점이 많았다. 테스트는 역시 중요하다.

영상을 보면 모든 문제가 드러난다. 대충 정리하면 다음과 같다.

  • 오로라는 벽에 몰아넣고 공격할 때 마땅히 할 게 없다.
  • 전체적으로 스킬 3개가 적은 느낌이었다. 하나 더 늘려야 한다.
  • 평타가 대공에 너무 강했다. 덕분에 점프 공격이 봉쇄되는 효과가 있었다. 공격판정을 줄일 필요가 있다.
  • 구르기를 추가하자.
  • SP를 사용한 카운터를 추가하자.
  • HP바에 딸피임을 알 수 있는 깜빡임을 추가하자.

구르기와 SP사용 카운터까지 추가하면 꽤나 킹오파에 가까워진다. 버그부터 처리해 보자.

버튼 2개누르기는 피하고 싶으니. 도발 버튼을 사용하자. 이젠 사실상 액션버튼으로 사용된다.

구르기를 추가할 때의 고민은 ‘뒤구르기’를 넣는가였다. 킹오파는 이게 있다. 그런데 백대시도 있다. 그럼 뒤구르기가 왜 필요하지? 쓰임새야 있겠지만, 물량을 하나라도 줄여야 하는 입장에선 고민이 된다. 그래서 우선 모션이름을DodgeForward로 정했다. 하지만 이내 생각을 고쳤다. 자연스럽지도 않고.. 꼭 따라해야 할 필요가 있을까? 싶어서 이름을 그냥 Dodge로 고쳤다. 정답은 테스트가 알려줄 것이다.

다음은 막고 치기. 상대방의 몰아치는 콤보를 끊을 때 사용한다. 고급사용자용. 발동은 방어했을 때 막은 상태에서 P를 누르면 된다.

8.8

오로라 차징까지 완료.

데이터 테이블은 구조체에 직접 써놓고 있다.

지금까진 평타의 판정이 지나치게 넓었기 때문에, 점프 공격을 평타로 막아내는 현상이 있었다. 검을 저렇게 휘두르면 당연히 위까지 다 맞는게 맞지 않을까? 싶었는데… 아.. 그러면 안되는 것이었다. 수정하자.

점프 공격은 판정 상 유리해야 한다. 방어를 할 수 없는데다 이동루트가 예상이 되기 때문에 발동이 빠르다. 그리고 이를 이기는 것이 대공기이다. 모든 게 계산된 것들이었구나. 새삼 격투게임을 창시한 이들이 대단해 보인다.

평타수정은 됐고, 이제 스킬을 하나씩 더 추가해보자. 계획은 다음과 같다.

  • 앨리스(←A or B, Thrust) – 뒤로 뺐다가 찌르기. 소울칼리버의 황성경이나 샹화, 미츠루기등이 사용했던 느낌으로. B는 2연계할 수 있다.
  • 닐리(→A or B, FlameClaw) – 근접 폭발. 바닥에서 불꽃의 손을 소환. 수비형 캐릭터인만큼 공격적인 연계기가 부족했기 때문에 추가하자.
  • 달래(← B, SpinMove ) – 잡아채 돌리며 뒤로 돌아가기. 이오리나 블루마리,각성야시로등이 사용하던 잡아채기. 공격능력은 없고 콤보 연계용이다.
  • 오로라(→K, AnkleHook) – 슬라이딩으로 발목 잡기. 장풍 혹은 상단공격을 피하면서 아래쪽으로 파고 드는 잡기형 기술이다.

몇가지 수정사항이 더 필요해보인다.

  • 닐리(하늘을 나는 동안 A or B) – 폭탄떨어뜨리기를 추가.
  • 달래(플라잉킥 중 K) – 연계기 추가
  • 달래 – 반격을 상단/중단 구분없이 하나로 합침. 하단 반격은 삭제
  • 오로라 – 폭탄안기 발동딜레이를 없애고 A, B의 성능을 분화

8.9

러프하게 구현은 했으나, 예상했던 것만큼 모양이 예쁘지가 않다. 캐릭터들의 강베기 리치가 생각보다 긴데, 이를 회피하려면 앨리스는 더욱 더 멀리 뒤로 갔다와야 하는 문제가 있다. 그냥 써도 문제지만, 1타 후 쓰려면 반발력 때문에 정말 멀리 떨어지게 되어 캐릭터가 지나치게 가벼워 보인다. 다른 기술을 생각해보는 편이 좋겠다.

다른 기술을 생각하다가, 문득 회피를 공격초반에 넣어보면 어떨까 싶어서 넣어봤는데 느낌이 괜찮았다. 문제는 이것이 직관적이지 않고, 성능이 너무 좋다. 그렇다면 반격기로 넣을까..싶었는데, 앨리스는 단순명쾌한 컨셉이므로 섬세한 반격기가 잘 안어울린다. 그렇다면 슈퍼 아머는 어떨까! 싶어서 구현. 슈퍼 아머란 데미지는 고스란히 들어가지만 이를 견딘 채 공격을 수행하는 시스템을 말한다. 내 기억엔 스파3에서 처음 도입된 걸로 알고 있다. 느낌도 좋고 앨리스의 컨셉과도 어울린다. 어떻게 해서든 데미지를 넣겠다는 기술이다. 마음에 든다. 이제 애니메이션을 다듬어 보자.

컨트롤 재고

8.2

어제는 지인의 사무실에서 대략적으로 완성된 클라이언트를 가지고 대전테스트를 했다. 역시 혼자할 때와는 다르다! 생각보다 많은 부분에서 문제가 일어났는데, 특히 방어 후 반격을 할 때 조작감이 상당히 좋지 않았다. 게다가 장풍은 왜 이리 안나가는지! 대시가 들어가 템포는 빨라졌는데, 맞고 누워있을 때의 시간은 그대로라 이것도 꽤 길게 느껴졌다. 다행인 점은 버그가 생각보다 적었다는 점이다. 대부분 시스템적 보완사항이다.

조이스틱으로 게임을 하니 커맨드가 생각보다 어려웠다. 난 어릴 적 오락실에서 스틱 깨나 가지고 놀았음에도 장풍이 어려운데, 초심자들은 더 하겠구나 싶다. 이래서 격겜이 고인물 소리를 듣나 보다. 좀 더 간결해야 한다. 2개 이상의 화살표커맨드를 최대한 1개로 줄여보는 작업을 해보자.

디지털 방식 스틱에서 대각선 이동이 안되는 현상이 있다는 제보가 들어왔다. 이것은 사실 한 번 테스트한 것이고, 스틱형 조이스틱을 사서 테스트를 한 결과 패드의 이상으로 결론 지었었다. 그런데도 제보가 들어온 것이 이상해서 한 번 더 면밀하게 검사를 해보니, 맙소사! GetAxis()에서 대각선 입력을 할 경우 0.7이 들어온다.

이는 0과 1로만 값을 받는 GetAxisRaw()도 마찬가지다. 때문에 반올림해주어야 한다. 테스트를 해보니 이제야 잘된다. 또 하나 깨달음을 얻었다.

앨리스의 커맨드를 단순화했다. 이제 →A를 누르면 기술이 나간다. 마찬가지로 어류겐은 ↑A이다. 다만 이렇게 되면 일반 공격과 키가 충돌하기 때문에 커맨드를 누른 후 0.2초안에 다음공격을 넣어야 한다. 이미 달래의 반격기가 이렇게 처리되고 있었지만, 좀 더 범용적으로 사용하기 위해 시스템을 개편했다. 커맨드표의 쩜은 누른지 0.2초가 되지 않은 상태라는 이야기이다.

일단 기술 넣기는 한결 쉽다.

닐리는 모으기가 관건인데, 모으기 후 방향을 반대로 꺾고 버튼을 눌러야 했다. ←(모으고) → A하면 파이어볼이 나간다. 모으기 캐릭터는 대체로 모으는 과정이 번거로운만큼 발동이 빠르다는 장점이 있다. 때문에 모으기는 유지하되, 이후의 커맨드를 없앤다. ←(모으고) (레버중립) A이다. 희안한 커맨드지만, 꽤 많이 고민한 것이다.

훨씬 쉽다!

달래와 오로라도 각자에 맞게 커맨드를 단순화했다. 이제 충돌만 나지 않으면 되는데… 이건 테스트를 해보기 전까지는 알 수가 없다.

8.3

공방의 주고받기를 위해 방어시스템을 손보던 중 카운터시스템의 결함을 발견했다. 지금까지 카운터는 상대가 공격이 끝난 후라도 데미지를 입을 당시의 스테이트가 공격형이면 무조건 카운터로 계산되게 되어있었다. 어쩐지 자주 뜨더라!

지금까지는 방어 후 딜레이가 너무 커서 공격을 자주 주고 받을 수가 없었다. 이 수치가 얼마나 컸냐면 기본 0.5초 후, 데미지에 따라 모두 밀릴 때까지(…) 방어한 사람은 아무 것도 할 수 없었다. 코드를 찾아보니 //임시라고 써있는 허망한 주석이. 아… 과거의 난 무슨 생각이었던 걸까.

작은 데미지는 0.1초, 큰 데미지라고 해도 0.3초후엔 반격할 수 있도록 수정했다. 일반 공격이 딜레이가 길기 때문에 아무 짓도 안하면 반드시 반격을 당하며, 연속기를 넣어야 커버가 된다. 그렇다면 이 연속기를 막는 상대쪽은 상대의 연속기가 끝날 때 쯤 반격을 펼쳐야 하는데, 그렇다면 또 이에 대한 훼이크로 앨리스의 3연타가 딜레이를 줄 수 있게끔 설계되었다.

8.4

리팩토링…리팩토링…리팩토링…내일 테스트 해보자.

자잘한 버그가 참 많았다. 대체로 커맨드 방식을 바꾼 데 대한 사이드 이펙트지만, 원래부터 문제를 품고 있던 버그들도 많았다. 다 된 것 같아도 테스트하면 문제가 나온다. 참 신기하다.

하는 김에 해상도 대응. 노트북에선 화면 비율이 양쪽 벽은 해상도에 따라 달라진다. 그런데 이렇게 되면 온라인 대전에 변수가 생긴다. ..음. 생각해볼 문제다.

8.6

테스트 플레이를 위해 간단한 AI를 구현. 수비적으로 방어하지만 공격하면 맞고 반격을 한다. 그리고 달래는 그 공격을 받아서 또 반격할 수 있다. 예상하지 못한 빠른 공격들이 생각보다 많은 버그를 쏟아냈고, 이것들을 고치면서 시간을 보냈다. 하다 보니 주 전략은 달려간 후 약손으로 공격을 잇거나, 장풍을 점프로 피해 위로부터 공격하거나…의 킹오파스타일이 됐는데, 일단은 마음에 든다. 실제 대전에서도 예상했던 그림이 나와줄 지 궁금해진다.

조이패드 대응

7.30

인풋매니저의 시스템은 각종 입력체계의 호환성이 좋다. 단, 이것은 방향키를 positive와 negative로 사용했을 경우다. 버튼이야 상관이 없지만, 이런 방향 입력 체계는 이제까지 써왔던 시스템과 충돌을 일으킨다. 지금까진 커맨드 입력을 위해 버튼을 누르는 방식을 사용했는데, 이걸 수정해야 할 것 같다. 이와 같은 정보는 아래블로그에 자세히 기술되어 있다.

https://gamecurry.tistory.com/27

방향키는 GetAxis()를 사용하고, 버튼은 조이스틱 버튼을 사용해야 한다. 일단 입력체계를 버튼이 아니라 Axis형태로 바꿔보자

요걸

요렇게

일단 버튼식에서 Axis형태로 바꾸는 건 크게 오래 걸리지 않았다. 그런데… 바꿔도 어쩐지 작동이 안된다. 무엇이 문제일까.

싶어서 살펴봤더니 이 프로젝트를 처음 시작할 때 InputManager를 설정하며 민감도를 0으로 맞춰놓았다. 일단 모르는 숫자는 전부 0으로 바꿔보았는데, 그러고도 잘 되니 이대로 고장난 채 있었던 것 같다. 이걸 0이상으로 돌려놓으면 된다.

조이패드 버튼은 A,B,X,Y 순으로 joystick button 0~3에 대응시키면 된다. A는 약펀치, B는 강펀치, X는 킥에 대응된다.

다 된 것 같으니 아내와 테스트. 이 짧은 영상을 찍는데도 버그가 3개나 나왔다.

7.31

조이패드 대응은 끝났지만 몇 가지 다듬을 것들이 있다. 현재 KO시 적용되는 줌블러는 멋있긴 한데, 피아식별이 힘들다는 단점이 있다.

줌은 히트영역이 중심이다. 따라서 가운데 부분은 줌이 약하다. 약한 김에 좀 더 약하면 안될까?length를 이용해 원을 만들고, 이를 적용치에 곱해주어 가운데 부분엔 왜곡이 덜하도록 수정해 준다.

박진감은 조금 떨어지지만, 그래도 이 쪽이 낫다. 더 선명하게 보일 수도 있으나 이 정도로 하자.

그래도 안보이는 건.. 뭐 어쩔 수 없다.

이제 빌드해보자. 에러가 없기를.

잘 돌아간다.

8.1

패드의 방향키를 아날로그 대응만 해놓았더니 디지털 방식의 방향키(옛날 방식)가 작동되지 않는다는 제보가 있었다. 게임 패드의 십자키가 이에 해당하는데, 문제는 격투게임의 조이스틱이 이 방식을 사용한다는 것이다. 시대가 흘렀어도 이 방식이 여전히 사용되고 있다니! 어쩐지 반가운 느낌이 들었다.

조이스틱은 아날로그만 구현했었는데, 그나마도 2P한정이었다. 이 참에 싹 정비를 해보자.

…버그 없네… 이상하다..

어쨌거나 버그는 없는데, 패드의 아날로그 스틱이 2개의 방향을 동시에 입력받지 못하는 문제가 있다. 이거 왜 이러지..

그래서 급하게 조이스틱 구입. 어제 오후에 주문했는데 오늘 아침에 배송이 왔다. 와..정말 빠르다. 예상대로 조이스틱은 디지털 방식이고, 0과 1밖에 받지 못한다. 대각선 입력이 잘 안되는 것은 패드의 문제였다. 스틱으로 하니 잘 된다.

오늘의 깨달음. 조이스틱 버튼은 joystick button 0~으로 된 키 값을 가지고 있다. 하지만 특정 조이스틱에 매핑하려면 아랫부분의 JoyNum을 지정하는 것으론 부족하고(애초에 저건 Type이 버튼이면 대응되지 않는다는 것을 뒤늦게 깨달았다.) 특정 조이스틱 번호를 기재해 주어야 한다. 때문에 joystick 1 button 0~이 된다. 2P는 자연스레 joystick 2 button 0~ 이 된다.

버그 픽스

7.26

타이틀은 이전부터 생각해둔 것이지만, 내 의지를 믿지 못해서(=언제 그만둘지 모르므로) 프로젝트A라는 TF명을 쓰고 있었다. 이젠 어느 정도 골조가 완성되었으므로 표면화해도 될 것 같다.

이름하야 배틀 퀸! 촌스러운 것은 내가 극복할 수 있는 게 아니므로 생각하지 말도록 하자.

Hits작업은 전에 염두에 두고 있었는데, 그냥 까먹었었다. 일단 이것부터 처리.

이번 버그는 역대급으로 많다. 하나하나 처리해 보자.

꽤 많이 처리했다고 생각했는데… 이제 10개 했네!?

가즈아! (는 1%확률로 출현한다.) 스파2에서 장풍이 가끔 금색으로 나가는 류의 이스터에그. 난 사실 그 전부터 이런 걸 게임에 넣는 걸 좋아했는데, 게임이 흥행에 실패하며 아무도 찾아내질 못했다.(슬픔)

7.28

카운터를 맞았을 경우에 화면효과를 추가하고 싶은데, 줌블러같은 게 기본사양으로 없다. 순간적인 집중효과를 위해 필요하다. 이걸 만들자. 이런 걸 만드는 것이 내 전생의 일이었다. 쉽지!

어.. 음.. 침착하자. 어떻게 하는 거였더라…

아… 근데 만들다 보니 이거 아니었던 듯.

하지만 일단 적용해 보자.

..음. 별론데?

그냥 고전적인 방식이 나은 듯…

포스트프로세싱은 셰이더 그래프가 편하다. 스크립트로 추가하기가 아주 어렵게 돼있다.

코드까지 적용완료. 이제 카운터를 맞았을 땐 히트위치를 중심으로 줌블러가 적용된다.

KO때는 색수차가 적용된다. 색수차 효과는 URP에서 기본제공된다.

덕분에 작업은 별로 못함.

7.29

영차 영차

쉽진 않겠지만, 초필살기는 동시에 사용할 수 있다. 이걸 고려해서 코드를 수정한다. 음.. 그런데 2P에게 SP가 차네…?!수정하자.

대시 후 점프는 좀 더 길게 뛸 수 있도록 하자.

거의 끝나간다!

7.30

드디어 끝! 이제 조이패드 지원을 해보자.

승리와 패배 애니메이션 제작

7.22

일반적인 조이패드의 버튼은 4개다. 요새는 트리거와 아날로그등이 추가되어 그보단 훨씬 많아지긴 했지만, 어쌔신 크리드의 튜토리얼을 진행하다가 손이 꼬이는 벽을 넘지 못한 후로 콘솔 게임은 그만두었다. 내게 있어 트리거는 단지 X+Y등의 겹버튼 숏컷용이며, 아날로그는 그냥 방향키의 다른 버전일 뿐이다. 따라서 조작은 4개의 버튼을 넘지 않게 기획되었다. 그것이 약손, 강손, 킥, 도발 버튼이다.

첫 계획은 도발을 추가하는 것이었지만, 작업이 너무 길어지고 있다. 따라서 이번 빌드에서는 도발은 제외하기로 하자. 게다가 도발은 캐릭터성을 상당부분 반영하는 것이어서 캐릭터 설정이 완전해진 후에 제작하는 편이 좋을 것 같기도 하다. 설정을 따지자면 승리와 패배도 마찬가지인데, 어쨌거나 이긴 티는 나야 하지 않을까. 하나는 제작하는 편이 좋겠다.

패배는 정확히 말하면 타임오버 시의 패배모션이다. 일반적인 경우의 KO패배라면 그냥 쓰러지고 끝난다.

늘 그렇듯 앨리스부터 시작. 대사는 “자, 또 한 건 끝났고!” 정도로 계획 중이다. 즉홍적으로 지은 대사가 용병 같은 느낌인데, 괜찮을지도 모르겠다.

손바닥을 치는 게 예상외로 어려웠다. 저건 브레이브 카노를 제작할 때도 어려웠던 기억이다.

패배 모션. 앨리스의 패배모션은 구상이 확실했기 때문에 제작은 쉬운 편이었다.

닐리의 승리 모션도 완료

완성은 했지만, 승리도 패배도 딱히 떠오르는 것이 없어 어정쩡한 구상 상태에서 진행했던 지라 크게 마음에 들지는 않는다.

달래는 인사하기 재활용.

패배모션도 완료

7.23

오로라 승리. 최신 유행 고속버스 춤.

패배모션도 완료. 이제 코드를 짜보자.

7.24

죽음을 처리하기 위해 데미지 처리 코드를 리팩토링 중. 이제 없을 줄 알았는데!!

오늘의 깨달음. 함수에 구조체를 인자로 전달하면 Value로 넘어간다. 난 당연히 데이터의 집합체니까 배열과 비슷하고, 그렇다면 당연히 Reference일 줄 알았는데!! 이것 때문에 한참을 헤멨다.

데미지 리팩토링이 생각보다 오래 걸렸다. 기존에 짜놓았던 코드들이 워낙 중구남방이었던 탓도 있지만, 스탯의 비주얼표현을 위한 hp변수를 그냥 체력처럼 써놓아서 엉뚱한 곳을 뒤지느라 시간을 써버린 탓도 크다. 급조한 코드는 반드시 벌을 받게 되어 있다! 주석을 잘 달아놓도록 하자.

죽음을 처리하는 것도 생각보단 복잡한 과정이었다. 딸피일 경우에 칼처럼 데미지를 처리하면 좀 인정없어보여서, 조금이라도 여유가 있다면 있는 피를 모두 반납하고 한 방정도는 버티도록 설계했다. 이로서 데미지는 75%, 방어는 50%의 피가 남아있다면 막타는 죽지 않고 견딜 수 있다.

그리고 이제야 비로소… 승리포즈를 처리할 수 있다. 아휴. 길었다. 다시 하던 걸 해보자.

7.25

일단은 KO처리. 완전한 승리는 퍼펙트를 의미한다. 한글로 쓸까 영어로 쓸까 고민을 많이 했는데, 긴 문장은 한글이 낫겠다는 결론을 내렸다. 이제 다음라운드로 넘어가야 한다.

7.26

다음 라운드로 넘어가기 위해선 먼저 승리를 기록해야 한다. 이를 위해 승리 라운드 정보를 게임데이터에 저장해야 한다. 그리고 이걸 명시적으로 표시하기 위해선 이긴 라운드를 알 수 있는 UI가 필요하다.

이는 러프 초창기부터 구상했던 모양이 있었으므로 디자인해서 배치해 둔다. 모델링은 어렵지 않은데 별도의 셰이더가 필요하다. 이겼을 때 깜빡거리며 그 존재를 과시할 필요가 있고, 돌아가며 보석느낌을 내려면 쬐그만한 게 빛벡터가 최소 2개는 필요하다. 하지만 스펙에 비해 셰이더 자체는 그리 어렵지 않을 것이다. 단순한 dot연산과 _Time을 이용한 깜빡임 정도면 충분하다.

다이아는 범용적으로 사용될 셰이더는 아니므로, 이렇게 모든 걸 정한 후엔 프로퍼티 정보는 코드 안 쪽으로 넣은 후 삭제해 주는 편이 최적화면에서 좋다.

오늘의 깨달음. StopAllCoroutine()으로 소멸되는 코루틴은 해당 Behaviour가 호출한 코루틴에 한한다. 이는 타클래스에서 코루틴을 public으로 설정해 호출하면 다소 귀찮아질 수 있다는 이야기가 된다.

무승부 처리. 이것도 조금 난이도가 있었다.

아직 불완전한 모습이긴 하지만, 일단락됐다. 이후의 작업은 버그로 간주하고 작업하기로 하자.

캐릭터 선택 UI #4

7.20

2P를 표현하는 가장 좋은 방법은 모델링을 별도로 하는 것인데, 물론 내겐 그럴만한 시간도 의지도 없다. 따라서 고전적인 방법으로 처리하기로 하자. 별 게 아닌 것에 고민이 많았다. 예를 들면 머리카락색을 유지할 것인지, 아니면 바꿀 것인지에 대해. 머리 속으로는 ‘머리카락이 바뀌면 캐릭터가 바뀌어버리는 게 아닌가?’라고 생각했지만, 정작 복장의 색을 바꾸고 나니 머리카락의 색깔이 복식과 어울리지 않는 것이다. 2P의 색은 캐릭터가 확연히 구분되도록 많이 바꿔야 하니, 애초에 1P에 맞게 설정된 머리카락색이 잘 어울릴 리가 없다. 그렇게 머리카락 색깔을 바꾸고 나니, 이번엔 눈동자 색이 또 거슬린다. 결국은 모두 바꿔는 편이 좋겠다는 결론을 내렸다.

다음 고민은 로딩방법에 관한 것이다. 색을 바꾸는 것은 머티리얼을 바꾸는 것이다.수정해 놓고 보니, 캐릭터에게 사용되는 9개의 머티리얼 중, 6개를 바꿔야 한다. 셰이더의 컬러를 바꾸는 것도 있지만, 대체로 텍스처를 통해 색이 바뀐다. 이것을 초반에 로딩해 놓을까? 아니면 중복 캐릭터를 고를 때 실시간 로딩할까? 캐릭터의 텍스처는 비교적 큰 편이니, 분명히 버벅임이 있을 것이다. 그렇다고 2P컬러까지 미리 로딩해 놓자니, 메모리에 불필요한 리소스를 너무 많이 올려놓는 감이 있다. 지금이야 캐릭터가 4명이기 때문에 별 문제가 없지만, 최후엔 13인까지 늘어날 것이고, 행여나 확장이라도 하게 된다면… 그냥 버벅임을 감수하고 실시간 로딩하는 편이 좋을 것 같다.

2P캐릭터 전체를 프리팹으로 만들어놓고 대체하는 편이 편할 것이나 파일이 많아지는 것이 싫으므로 셰이더 드라이버에서 머티리얼만 바꾸는 방법을 택하기로 하자.

파일명 뒤의 숫자를 붙인 건 2P를 고려했던 것은 아니었다. 그냥 리소스엔 전부 넘버링을 붙이는 오래된 습관이다. 좋은 습관이라고 생각한다. 후반으로 갈 수록 일이 편해진다.

오늘의 깨달음. 인스턴싱된 오브젝트의 머티리얼을 바꾸려면 SetMaterials()를 사용해야 한다. 그냥 materials[n]을 바꾼다고 되지 않는다.

…아.. 보호색이 따로 없네. 너무 묻혀서 피아식별이 좀 힘들 것 같다. 채도를 좀만 올리자.

음. 여전히.. 안보인다.. 격투게임의 캐릭터들이 왜 오색찬란한지 좀 알 것도 같다.

그렇군. 뭐 하나는 눈에 띄어야 한다. 다음 캐릭터 세팅할 때 참고해보자.

닐리도 완료

달래도 완료.

오로라도 완료

캐릭터 선택 UI #3

7.17

이제 인트로는 완전해졌다. 아랫쪽에 스킬게이지는 위쪽으로 옮길 것이다.

바쁜 한국인들을 위해 스킵 기능도 제작. 아무 키나 누르면 등장씬은 스킵할 수 있다.

이제 캐릭터를 선택했을 때와 등장씬의 애니메이션을 만들차례다. 그런데 이걸 하기 전에 애니메이션을 UI에서 좀 더 쉽게 사용할 수 있도록 코드를 리팩토링 해야 했다. 늘 그렇듯이 버그가 잔뜩 나왔고, 이걸 수정하느라 반나절이 걸렸다.

캐릭터를 선택했을 때의 모션은 확실한 피드백을 위해 필요하다. 이 때 음성도 나와야 하지만 그것은 먼 훗날의 일이다. 그보단 입의 셰이더가 당면한 문제를 해결하여야 한다. 지금까지는 작아서 보이지 않던 입이 캐릭터를 확대해 보니 블렌딩되며 문제가 생긴다. 세상에! 블렌딩이 문제가 되다니! 이걸 어떻게 해결하지…

이걸 해결하며 커브는 애니메이션 데이터에 포함되어 있다는 걸 처음 알았다.

챗GPT는 천재야!

이 방법은 일시적인 것이 아니라 유니티의 데이터를 건드는 것이다. 때문에 빌드가 잘될지는 테스트가 좀 필요해보인다.

빌드 에러가 난다. 예상하지 못한 문제지만, 해결책은 대충 예상이 된다. 스크린샷을 찍을 때와 마찬가지로, UnityEditor네임스페이스를 사용한 코드는 에디터 전용이다. 따라서 스크립트가 Editor폴더에 포함되어야 한다. 이는 곧 게임코드에서 파일을 분리해야 함을 의미하는데, 데이터를 한 번만 베이킹 해주면 되는 것이라 어차피 그럴 생각이었으므로 이 참에 분리하기로 하자.

#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Animations;
#endif

…였는데 완전히 있고 있었다. 이런 방법이 있었지…하지만 여전히 분리하는 것이 효율적이므로 분리하자.

어…음… 그런데 이렇게 하니 Animator를 변수로 받을 수가 없네…?!

따로 뗐더니 되긴 했는데, 이거 빌드가 될까…몇가지 문제가 있었지만 잘된다.

베이킹할 때만 저걸 켜주도록 하자. 별로 세련된 방법은 아니지만, 모로 가도 서울만 가면 되니까.

눈썹이 왜 이렇지?!

요런색이어야 되는데! 셰이더가 이상하다.

결국 큐문제이긴 한데, 과거의 나는 큐를 왜 이렇게 구성했었을까…

각 캐릭터의 선택 모션들을 제작. 다음은 등장 모션들이 필요하다.

7.19

Awake()는 스크립트가 실행되자마자, 혹은 enable이 켜지자마자 실행된다. Start()는 한 프레임 늦다. 시간으로 따지면 1/60초다. 컴퓨터는 이렇게 짧은 시간동안 엄청나게 많은 명령을 수행한다. 이 때문에 컴포넌트 대입식이 Start()에 있었다는 사실은, 많은 버그를 빚어내기에 충분한 원인이 됐다. 개발은 늘 시행착오의 연속이다. 어디 개발뿐이랴. 인생 또한 그렇다. 하지만 지금은 설익은 철학 이야기를 적을 땐 아닌 것 같고, 지금까지의 코드를 리팩토링하는 데 시간을 쓰는 게 더 적절해 보인다.

사실 지금까진 Awake()를 쓰는 일을 가급적 피했는데, 이는 에러가 날 때 Start()에 있는 내용을 Awake()로 옮기면 해결되는 경우가 많았기에 일종의 보험으로 남겨두고 싶은 알랑한 마음이었다. 컴포넌트의 대입과 새로운 변수의 선언은 Awake()로 옮기고, 타클래스를 참조해야 하는 요소들은 Start()에서 처리하도록 바꾸자. 기초를 좀 더 탄탄히 다지고 가는 편이 좋겠다. 그리고 경험상, 며칠 걸릴 것 같은 이런 일들은 작정하고 덤비면 의외로 빠르게 끝난다.

Dotween도 코루틴이다. 하지만 StopAllCoroutine()으로 모든 코루틴을 제거했음에도 Dotween이 자꾸 오류를 냈다. 수많은 삽질 끝에 이는 Dotween이 독자적인 코루틴을 사용하기 때문으로 밝혀졌다. Dotween의 코루틴을 제거하려면

DOTween.KillAll();

을 사용해야 한다.

휴. 리팩토링이 끝났다. 모든 일의 시작은 저 실루엣이었다. 실루엣은 별도의 메쉬를 사용하는데, 얘는 UI의 영역이라 실제 게임까지는 가지고 오면 안된다. 때문에 게임FSM이 가동되기 전 얘를 정리해주어야 했는데, 이것이 실루엣을 별도로 관리하지 않는 코드설계 상 쉽지 않았다.

7.20

길었던 캐릭터 선택이 거의 끝나간다. 이제 같은 캐릭터를 골랐을 경우, 색을 바꿔주는 처리만이 남아있다. 이건 내일 하도록 하자.

캐릭터 선택 UI #2

7.14

기본 틀은 완성이 됐다. 이제 이걸 Battle로 넘겨야 한다. 싱글턴 인스턴스에 선택정보를 저장하고 로딩 씬에서 사용할 리소스들을 로드하여야 한다. 같은 캐릭터를 선택했을 땐 나중에 선택한 플레이어의 캐릭터 색도 달라야 하고, 시작 시 등장모션도 출력해야 한다. 모두 안해본 것들이다. 공부를 해보자.

싱글턴 인스턴스 게임 오브젝트를 하이라키에 포함시키고, 이를 다시 로드하면 해당오브젝트는 중복이 될까? 아니면 그 정도는 알아서 거를까? 테스트를 해보자.

알아서 거른다. 이것은 복사가 아니기 때문인 것으로 보인다.

싱글턴패턴으로 제작된 GameData와 PoolManager를 제작해서 장착

이제 캐릭터는 Select화면이 아니라 로딩프로세스에서 로드해서 들어간다. 메모리에 올린 후엔 확실히 버벅임이 없다.

그리고 그와 별개로 싱글턴은 굉장히 편하다! 배우기 싫어서 미적미적 미뤄놨었는데, 좀 더 빨리 배워둘걸.

씬간에 게임의 정보를 공유하고 이를 처리. 1/3정도 된 것 같다. 아직 할 일이 많다.

7.15

이펙트도 기존의 매니저를 사용하고 싱글턴화 시킨다. 매니저는 선택 때는 공용이펙트를, 전투진입 때엔 캐릭터 이펙트를 로드해 둔다. 2번에 나누어 로드하는 이유는 속도때문이다. 물론 한 번에 로드해봤자 1초도 안되긴 하지만, 눈깜짝할 새에 로드되면 좋겠다. 이펙트를 몇 개를 사용할지는 알 수 없지만, 하나만 로드해두어도 어쨌거나 메모리에 올리는 것이라 버벅임이 훨씬 적을 거라 기대한다.

이 쯤에서 빌드 테스트

잘 된다. 야호.

게임매니저의 1P, 2P의 변수를 따로 만들어둔 것을 배열로 정리했다. 이제 FSM이나 게임매니저는 정말로 손 댈일이 많지 않을 것 같다. 물론 미래는 알 수 없지만..부디 그렇게 되기를

다음은 게임진입 연출씬이다. 본격적인 게임에 앞서 캐릭터가 준비대사를 하는 구간이다. 이 부분에선 캐릭터를 돋보이게 하고 싶은 마음이 있기 때문에, 별도의 배경이 필요하다.

속도선 셰이더는 전에도 한 번 만들어본 적이 있기 때문에, 쉽게 만들 수 있다. 이제 이 앞으로 캐릭터나 나와야 한다.

7.16

캐릭터 등장 러프. 홍코너~!! 앨리스! 청코너~!!도 앨리스!!

그 다음은 화면이 걷히며 캐릭터가 달려나오고 라운드1 – 파이트로 이어져야 한다.

저 번개가 파지직.하고 빛나려면 … 또 번개 셰이더가 필요하겠다. 기본은 cutout과 같고, UV를 빠르게 흘리면 될 것이다. 만들어 보자.

러프한 구현. 기본틀은 됐고, 이제 좀 더 꾸며 보자.

캐릭터 선택 UI

7.10

일단 러프를 해보자. 처음엔 플레이어블 8명이지만, 나중에 사천왕 포함해서 12명으로 확장될 것이다. 하지만 1회 클리어까진 8명까지만 보여줄 예정이므로 이에 맞춰서 디자인을 한다.

그런데… 음.. 넣을 게 뭐 별 게 없다…. 느무 심심한 느낌인데..뭘 넣지

디자인하다보니 모노톤이 더 좋아보인다. 역시 디자인에 대해 뭘 모르면 흑백쓰라는 선인의 말을 되새겨야 할 때.

캐릭터 선택창은 이 쯤에서 마무리. 그 다음 절차는 싸울 배경을 소개하고, 화면 전환한 후에 캐릭터들이 등장하고…같은 과정을 거칠 예정이다.

7.11

언리얼은 뷰포트 스샷 기능이 제공된다. 언리얼은 이미 실시간 렌더링을 영화에 사용할만큼 뷰포트 렌더링이 뛰어나기 때문이리라. 하지만 유니티는 이게 없다. 배경을 찍어야 되는데, 뷰포트 렌더링이 없다… 난감한걸.

혹시 내가 못찾는 것은 아닐까? 싶어서 챗GPT에게 물어보니 렌더타깃을 이용해 찍으라는 조언을 해준다. 물론 코드와 함께 친절한 사용설명서까지 덧붙여줬다.

using UnityEngine;
using UnityEditor;
using System.IO;

public class ViewportScreenshot : MonoBehaviour
{
    public Camera cameraToUse; // 스크린샷을 찍을 카메라
    public int resWidth = 3840; // 원하는 해상도 너비
    public int resHeight = 2160; // 원하는 해상도 높이

    [MenuItem("Tools/Take Screenshot")]
    public static void TakeScreenshot()
    {
        ViewportScreenshot instance = new GameObject("ViewportScreenshot").AddComponent<ViewportScreenshot>();
        instance.CaptureScreenshot();
        DestroyImmediate(instance.gameObject);
    }

    public void CaptureScreenshot()
    {
        if (cameraToUse == null)
        {
            cameraToUse = Camera.main; // 기본 카메라 설정
        }

        RenderTexture rt = new RenderTexture(resWidth, resHeight, 24);
        cameraToUse.targetTexture = rt;
        Texture2D screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
        cameraToUse.Render();
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
        cameraToUse.targetTexture = null;
        RenderTexture.active = null; // 메모리 해제
        Destroy(rt);

        byte[] bytes = screenShot.EncodeToPNG();
        string filename = ScreenShotName(resWidth, resHeight);
        
        // 디렉토리 생성
        string directoryPath = Path.GetDirectoryName(filename);
        if (!Directory.Exists(directoryPath))
        {
            Directory.CreateDirectory(directoryPath);
        }

        File.WriteAllBytes(filename, bytes);
        Debug.Log($"Screenshot saved to: {filename}");

        // 에디터 씬에서 이미지 확인
        AssetDatabase.Refresh();
    }

    public static string ScreenShotName(int width, int height)
    {
        return string.Format("{0}/Screenshots/screen_{1}x{2}_{3}.png",
                             Application.dataPath,
                             width, height,
                             System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"));
    }
}

이걸 그대로 유니티에 넣고 작동시키니… 와. 정말 된다.

전이라면 이걸 손으로 찍어서 이어붙이고 난리도 아녔을텐데…와. 고마워라.

캐릭터를 고르면 이렇게 배경이 나오고 화면을 왼쪽에서 오른쪽으로 쓸고 간다.

구상은 끝났고, 할 일이 남았다. 포트레이트 애니메이션을 만들어 보자.

이걸 제작하고 있노라니 의문이 들었다. 이게 게임에 잘 맞을까?…

더미 작업 없이 바로 실제 리소스 제작을 들어가는 것이 장님 코끼리 만지는 기분이다. 그럼 구현이 먼저다. 구현부터 하자.

새로운 씬을 세팅 중.

뒤로 흐르는 배경과 커서는 별도의 셰이더가 필요하다. 틀은 잡았고, 이제 코드를 짜보자.

DoTween은 UI프로그램을 할 때 가장 먼저 친해져야 할 애드온이다. 코드기반은 무료고 비쥬얼 스크립팅은 유료인 듯?

https://dotween.demigiant.com/

일단 UI트랜스폼은 RectTransform을 사용한다. 이것이 익숙치가 않다. 배워보자.

7.12

UI의 트랜스폼은 RectTransform을 사용하나, 해상도가 고정되어 있기 때문에 그냥 position을 직접사용하는 게 편하다. 음… 이래도 되겠지? 빌드해보자.

잘된다. 연출은 됐다. 이제 커서를 움직이는 처리를 해야 한다.

빌드하면서 알게 된 사실인데, 스크린샷을 찍는 툴이 빌드에서 에러가 났다. 이는 Editor에서만 사용하는 기능인데 빌드에 포함시켜 에러가 나는 모양이다.

이렇게 하면 된다. 챗GPT가 없었다면 난 아직도 사경을 헤메고 있었겠지! 오류가 두렵지 않다.

캐릭터 커서 이동의 처리

캐릭터 선택의 처리. 동시 선택작업이 꽤 번거로웠다.

7.13

배경 설명에 글씨가 타이핑쳐지는 효과를 내고 싶었는데 DoTween무료버전에선 TMPro를 지원하지 않는다고 한다. 몇 푼되지도 않는 어셋이 개발을 방해해선 안된다.당장 구입하자.

화면 한 켠이 점점 복잡해진다.