캐릭터 선택 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를 지원하지 않는다고 한다. 몇 푼되지도 않는 어셋이 개발을 방해해선 안된다.당장 구입하자.

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

스턴 추가

7.9

얼핏 보기엔 쉬울 것 같다. 그냥 많이 맞으면 별이 핑핑 돈다. 스트레스 변수를 추가하고 데미지를 입을 때마다 쌓이지만 시간이 흐르면 자연스레 깎인다. 하지만 일정 수치를 넘으면 다운되고 일어설 때 무방비 상태가 된다. 하지만 이 때 버튼을 마구 누르면 좀 더 빨리 풀려날 수 있다. 생각만큼 쉬울까. 한 번 해보자. 일단 모션부터 만들자.

그리고 스턴 이펙트

공용 이펙트이므로 스턴모션에 붙이는 것은 하드코딩으로 하는 것이 좋겠다.

구현완료. 변수가 몇 가지 있었지만 예상을 크게 벗어나지 않는 작업이었다.

다음은 캐릭터 선택 화면을 작업해 보자.

체력 바 처리

7.7

체력바는 2중 슬라이더가 되어야 하기 때문에 셰이더를 새로 짰다. 기본적인 체력차감 코드도 마무리했다. 콤보가 끊기지 않으려면 1초안에 데미지를 넣어야 하며, 그 이상 지연되면 콤보가 끊기며 데미지가 확정된다.

여기까지는 어렵지 않다. 이제 기획이 좀 필요하다. 데미지는 기본적으로 정해진 숫자는 있지만 상황에 따라 조금씩 보정을 받는다.

  • 카운터일 경우.
  • 연속기일 경우.
  • 타격형 잡기일 경우.

카운터를 판단하는 기준이 좀 어려워 보인다. 공격이 들어가는 타이밍은 이벤트에 의해 제어된다. 그렇다면 스테이트에 공격력이 붙어있고 아직 이벤트를 만나지 않았을 때를 카운터 유효시점이라고 보는 것이 맞는가? 그렇다면 발동이 느리다면 카운터가 쉽다는 이야기가 된다. 흐음. 말 되네?! 해보기로 하자.

7.8

카운터는 구현은 했으나 제대로 작동할 지 모르겠다. 어떤 구멍이 있을지 테스트를 하며 알아보자

콤보는 2콤보부터 10%씩 깎이는 데미지 보정이 있다. 이렇게 되면 짠손을 중간에 섞는 게 오히려 데미지를 깎아먹는 결과를 낸다. 하지만 SuperAttack은 보정하지 않는다. 달래의 경우 많이 때리긴 하지만, SuperAttack은 마지막의 회전차기 뿐이다. 일단은 괜찮아 보인다.

잡기의 데미지처리는 또 별도이다. 일단 비교적 간단한 히트형 잡기부터 처리.

타격형잡기는 버튼을 마구 누르면 좀 더 빨리 풀린다.

초필게이지 처리도 완료. 아. 그런데 때리는 애가 차야지…거꾸로 찬다.

체력바는 얼추 다 된 것 같다. 시간을 줄이는 건 일도 아니지만, 씬로딩과 승패가 들어가야 의미가 있을 것이다. 그 다음은 스턴을 추가해 보자.

전투 씬 UI

7.4

UI랄 게 별로 없다보니 폰트가 중요하다. 그래서 이리저리 알아본 결과 산돌폰트에 괜찮은 유료폰트를 발견했다. 그런데 저작권에 대한 해설이 불분명하다. 폰트란 건 한 번 받아 설치하면 영구히 사용할 수 있는 것인데, 그렇다면 다운로드 후 구독을 끊으면 어떻게 되는 거지? 게임 출시할 때 또 결제해야 하는 건가? 서비스하는 내내 폰트가 보일텐데, 그럼 계속 구독을 끊으면 안되나?

폰트만큼 딴지를 많이 거는 분야도 드물다. 그러다 보니 심지어 이걸 빌미로 사기를 치는 집단도 있다. 마치 공인된 기관인 것처럼 속여 돈을 뜯으려 하는 것이다. 그러니 속편하게 그냥 무료폰트를 쓰도록 하자. 요새는 무료폰트도 예쁜 것이 참 많다.

https://fonts.google.com/share?selection.family=Pirata+One

https://fonts.google.com/share?selection.family=Hahmlet:wght@100..900

게임의 분위기는 적당히 앤틱스럽게 가져갈 예정이다. 잘 어울릴 것 같으니 이걸 사용하자. 여담이지만 이렇게 삐죽삐죽하고 각진 중세풍 폰트를 블랙 레터라 부른다. 처음 알았다.

7.5

이미지가 영 마음에 들게 나오지 않는다. 오늘은 여기까지.

다듬어 보자.

7.6

디자인은 끝났으나, 몇가지 확인해야 할 사항들이 있다. 언리얼은 UI용 셰이더가 구분되어 있다. 유니티도 그럴까?

다행스러운 점은, 유니티는 별도로 구분되어 있지 않은 모양이다. UI를 카메라 기준으로 찍으면 HDR까지 모두 지원된다. 훌륭하다! 캐릭터를 HP바가 가리는 것이 싫기 때문에 캔버스를 뒤에 두고 싶고, 글씨나 몇몇 UI에 블룸을 적용하고 싶다. 모두 가능하다.

빌드하다가 셰이더 경고가 한가득 떴다. 대체로 float3와 float4를 잘못쓴 경우이다. 요새 빌드를 게을리 했더니 이런 선물을 주었다.

셰이더 수정은 간단하니 빠르게 처리하고, 다음으로 알아야 할 것은 폰트의 디자인 확장성이다. TMPro로 텍스트를 어디까지 꾸밀 수 있는가? 어..일단 구워야겠지. 이건 할 줄 모르는데… 인터넷을 찾아보자.

https://taekhyeong.tistory.com/350

요기에 잘 설명되어 있다. 그대로 따라해보자.

오. TMPro… 생각보다 확장성이 더 좋다. 확인이 끝났으니 붙여보자.

한글도 예쁘게 잘 나온다.

머티리얼 프리셋을 만드는 방법은 기본적으로 생성되는 머티리얼을 선택하고 복사하면 된다.

그러면 이렇게 머티리얼 프리셋에 등록된다. 폰트의 모양은 유지한 채, 효과만 다르게 준 글씨를 만들 때 사용한다. 머티리얼만 바꾸면 되겠지 싶어서 한참 찾았네!

실제 게임에 붙여보는 중.

7.7

이것저것 적용해 보니 카메라 기준으로 정렬된 UI는 폴리곤으로 치부된다. 이는 즉 매핑에 필터링이 적용된다는 이야기고, 이는 곧 원본보다는 조금 부옇게 표시된다는 걸 뜻한다.

위의 캔버스가 ScreenSpace, 아래쪽이 Overlay방식이다. 확실히 품질이 떨어져 보인다. 역시나. 렌더파이프라인 상 UI는 블룸을 적용할 수 없는데다가, Z Write계산도 안할텐데, 이것이 모두 적용된다는 게 좀 이상하긴 했다.

하지만 이것이 HDR을 포기할만큼의 퀄리티 저하가 있는 것은 아니니, Clip을 통한 알파테스트를 써보자. UI용으로 셰이더를 만들어야한다.

어..형편없군…?! 그냥 반투명 쓰자.

HP게이지는 3단계. 붉은 색은 콤보가 끊기지 않았을 때의 데미지 누적량이다. 데미지 확정의 중간단계이며 콤보가 끊기면 사라진다.

데미지 처리는 조금 더 복잡한 단계를 거칠 것 같다. 글도 길어졌으니 포스트를 새로 파는 편이 좋겠다.

대시와 백스텝

6.30

이 2개의 상태를 처리하기 위해선 3개의 모션이 필요하다.

  • Rush – 앞으로 치닫는 Dash와 구분하기 위해 달리기는 Rush라는 코드명을 쓴다.
  • Backstep
  • BackstepEnd

Backstep은 뒤로 폴짝 뛰는 모션이지만 SubState를 Jumping으로 두면 안된다. 비주얼에 따라 백덤플링같은 것이 더 어울릴 수 있기 때문.

당연한 이야기지만, 일단 모션이 필요하다. 만들자.

모션 데이터는 액션매니저에 의해 그룹화해서 관리하고 있다. 기존까지는 점프중이 아닐 경우의 이동이 걷기뿐이었으므로 Stand에 포함되어 있었지만, Moving으로 새 카테고리를 만들어 관리하는 편이 좋겠다. 이렇게 모션별로 짤라 카테고리를 만드는 이유는 익스포트 속도때문이다. 데이터가 크다보니 각 그룹별로 익스포트하는데 꽤나 오랜 시간이 걸리는데, 이걸 자주 해야 한다. 데이터가 단순하면 하나에 묶는 편이 여러모로 좋다.

한 캐릭터에게 필요한 모션은 이렇게나 많다. 나도 몰랐다. 이렇게까지 많아질 줄은.

이제 코드에 대시를 추가해야 하는데, 코드가 다소 엉망이라 정리를 했다. 그리고 정리 수납하고 흐뭇해하는 가정주부의 마음을 이해하게 되었다.

7.2

세상에! 단순히 대시를 넣으려 했던 계획은 감속부분의 문제로 인해 코드 리팩토링으로 번졌다. 솔직히, 쉬울 줄 알았다. 그냥 커맨드 앞앞, 뒤뒤만 추가하면 되는 일 아닌가?! 그런데 기존 코드가 이를 허락하지 않았고 이동관련 코드를 상당 부분 뒤엎어야 했다.

여전히 수정 중이다. 하지만 뜻이 있는 곳에 길이 있다고 했던가? 기존까지 sin함수로 처리하던 코드를 Lerp로 바꾸니 훨씬 간결하고 깔끔해졌다. 처음부터 이랬어야 했지만, 원래 인생이란 시행착오의 연속 아니던가. 허허.

야호.. 빠르다. 버그도 함께 찍혔네.

지금까지는 캐릭터가 그렇게 빠르게 움직일 필요가 없었기 때문에 타격판정을 실제 충돌에도 사용하고 있었다. 이것이 문제가 있던 것은 아니지만 컬리전이 계속 형태가 변하다보니 다소 불안한 느낌은 있었는데, 이동속도가 빠른 모션을 넣으니 확연히 드러난다. 아무래도 이동형 컬리전을 추가하는 편이 좋겠다. 이 쪽이 속도도 더 빠르기 때문에 최적화에도 도움이 된다.

7.3

그런데 이게 쉬운 일이 아니었다. 이번에도 진행하며 여러가지 일을 알게 됐는데, 박스컬라이더가 단순히 트랜스폼을 바꾼다고 업데이트되지 않는다는 사실이다. 컬라이더의 업데이트 주기는 fixedUpdate()를 따른다. 당연하지. 물리니까! 이걸 가지고 반나절을 헤맸는데… 어째 날이 갈 수록 버그 하나 잡는데 걸리는 시간이 늘어나는 것 같다.

여튼 수정완료. 이동형 컬라이더는 이제 단순한 사각박스를 따른다. 충돌검사 부하가 줄었기 때문에 퍼포먼스가 약간 더 좋아졌을 것이다.

퍼포먼스도 좋아졌겠다, 컬라이더 클래스를 수정하는 김에 컬라이더를 팔에 분배했다.

피격 컬라이더는 공격시에만 영향을 받는다. 이전엔 퍼포먼스를 위해 팔은 충돌에서 제외했었지만, 이제는 맞는다. 이렇게 되면 마법사이면서 팔로 장타를 날리는 닐리가 조금 불리하다. 무기는 피격 충돌에서 제외되기 때문.

대기자세도 커서 맞기 쉽다.

점프 후 바로 백스텝하면 반대로 뛰는 버그도 수정했다.

7.4

닐리의 대시모션을 작업하던 중, 여차저차해서 오로라의 데이터를 살펴보게 되었는데 무언가 크게 잘못돼 있었다. 이게 지금까지 돌아갔다고…?! 그래서 원인을 수정하고 블렌더로부터 내리 수정. 이걸 살펴보려던 건 아닌데, 덕분에 큰 버그를 잡았다. 시간은 버렸지만 보람은 있었다. 가만, 그럼 시간을 버린 게 아닌가?

달래까지 완료. 달리기가 있으니 확실히 공격패턴이 늘어난다.

완료. 다음엔 UI를 붙여보자.

대전 준비

6.30

▲ IX.(좌)와 임모씨(우), 사진 오모씨(View)

얼마 전, 앨리스의 스킬이 완성되던 시점에 지인 사무실에서 간이 대전(?)을 해본 적이 있다. 원래는 혼자서 할 수 없는 몇가지 사항을 확인하기 위해 진행한 테스트였지만, 한 번씩 키보드에 손을 댄 후엔 ‘왜 대시 없어?’라는 반응이 가장 먼저 자연스레 튀어나왔다.

처음부터 대시를 넣을 생각은 아니었지만, 참여자 모두가 대시를 원했으므로 어느 정도 궤도를 수정할 예정이다. 오로라의 작업이 끝난 이 시점의 다음 작업은 버그픽스 후 신캐제작이었지만, 이를 잠시 미뤄두고 실제 대전에 적합한 게임의 형태를 만드는 편이 더 좋다고 생각되었으므로 체력바를 달자. 승리포즈도 필요할 것이다. 좀 이르지만 캐릭터 선택화면도 필요하다.

필요한 모션은 상당히 많다. 이젠 인게임 모션과 아웃게임 모션을 분리해야 한다. 일러스트를 3D가 대체할 것이므로, 포트레이트 애니메이션이라고 하자. 이번엔 캐릭터 선택 정도에 사용되지만, 이후엔 대화씬 등에서 사용된다.

인게임

  • 대시(Dash)
  • 백스텝(Backstep)
  • 스턴(Stun)
  • 승리1(Victory_01)
  • 승리2(Victory_02)
  • 도발(Provoke)
  • 타임오버(TimeOver)

포트레이트

  • 캐릭터 선택대기(Portrait_Idle)
  • 캐릭터 선택확정(Portrait_Confirm)
  • 등장 애니메이션(Portrait_Ready)

씬이 하나 더 필요하다. 미루고 미뤄왔던 씬로딩. 허허. 이젠 할 때가 됐다.

  • Battle(현재)
  • Select(캐릭터 선택화면)

이제 체력바 그려야 된다. 배틀씬에 필요한 UI를 정리해보자.

  • 체력 게이지
  • 이름
  • 라운드 승리횟수
  • 시간
  • 초필 게이지
  • 라운드 텍스트
  • 파이트! 텍스트
  • XXX 윈! 텍스트(승리)
  • 더블 KO 텍스트
  • 타임오버 텍스트
  • 퍼펙트 텍스트

체력 처리를 위한 시스템 추가도 필요하다.

  • 타격형 잡기는 상대방의 저항이 있을 경우 좀 덜 때려야 한다.(닐리의 궁디팡팡, 오로라의 꿀밤)
  • 장풍은 서로에게 맞을 경우 상쇄된다.
  • 대시, 백스텝 처리
  • 대시할 때 점프하면 더 높이 점프
  • 조이스틱 지원
  • 데미지 처리
  • 연계 데미지 처리
  • 카운터 데미지 처리
  • 공격 우선순위 결정(판정)
  • 데미지가 누적될 경우 스턴처리
  • 공격할 때마다 초필 게이지 처리

캐릭터 선택화면에 필요한 UI작업

  • 처음엔 8캐릭터, 한 번 보스를 클리어한 후엔 12캐릭터가 선택되어야 한다.
    • 히든 캐릭터는 조건만족하면 튀어나오는 식이니까 UI엔 없어도 된다.
  • 인게임 스테이지는 양쪽 모두 캐릭터를 선택하면 랜덤하게 선택된다.
  • 뒤쪽으로 뭔가 움직이는 배경을 깔아야 한다.

시스템 작업

  • 씬 전환
  • 각 캐릭터에게 딸린 이펙트 프리로딩.

주저리주저리 길게 적어놨지만, 결국 끝나는 작업들이다. 하나하나 해보자.

일단 버그리포트에 적어둔 사항을 처리하자. 장풍을 맞으면 서로 상쇄되어야 한다.

오로라의 2기 애니메이션 #2

6.27

오로라의 필살기는 주로 잡기다. 이것만으로 할 일이 좀 더 많은 편인데, 이걸 한 이후에도 각 캐릭터에게 잡기에 해당하는 애니메이션을 추가해 주어야 한다. 많이 한 것 같은데, 아직 먼 길 가야 된다. 차근차근 가자.

팔돌리기에 트레일을 추가. 달리기 속도 높이니 뜻밖에 연계기가.. 되네?!

초필을 만들자.

팔돌리기만 반나절이 걸렸다. 힘든 애니메이션이었어. 그런데 아직 안끝났다는 것이…

6.28

와. 요근래 가장 힘든 작업이었다. 그나저나 화면이 좁네!

6.29

공중잡기와 초필살기에서 위치가 어긋나는 문제가 있었다. 현상은 같지만 원인이 달랐는데, 버그추적을 하다보니 결국 같은 문제였다는 것이 밝혀져서 허탈.. 덕분에 코드의 구멍을 좀 더 메울 수 있어서 완전히 헛된 시간은 아니었다.

오로라의 이펙트는 별 게 없다. 잡기 캐릭터이다 보니 거의 먼지투성이다. 별도의 포스트를 거치지 않고 빠르게 완료하자.

와… 어려웠다. 이제 정리 작업을 해보자.

오로라는 앨리스밖에 잡질 못한다. 왜냐면 앨리스에게만 잡기 모션이 포함되어 있기 때문이다. 이제 이것들을 다른 캐릭터들에게도 포함시켜 주어야 한다.

내가 게임을 개발하며 정말 많이 놀랐던 것이 이런 부분이다. 우리가 당연하다고 생각되는 것들은, 누군가의 손을 거친다. IT업계라고 하면 아주 놀라운 최첨단의 업무처리방식을 갖추고 있을 것 같지만, 내막을 살펴보면 대부분 누군가의 그림자 노동으로 이루어져 있다.. 자동으로 되는 건… 유감스럽지만, 거의 없다. 개발자란, 그냥 최신식 기술을 사용하는 일꾼일 뿐이다.

오로라도 오로라에게 잡힌다. 뭔가 귀엽네.

물론 폭탄 엔딩은 똑같지만.

오로라의 2기 애니메이션

6.20

4번째 캐릭터쯤 되면 시행착오가 많이 사라진다. 모델링을 오로라까지 했던 이유도 마찬가지였다. 작업이 편해지는 단계다. 시작해 보자.

우선 데미지 모션을 붙이고 수정하자. 달래의 초필을 구현하며 HeavyLong스테이트가 추가로 필요했다. 나머지는 닐리와 같다.

  • 방어 (Block) – 엔진에선 3분할 된다.
  • 앉아서 방어(CrouchBlock) – 마찬가지로 엔진에선 3분할 된다.
  • 소점프킥(SmallJumpKick)
  • 소점프킥 착지(SmallJumpKickLanding)
  • 일직선 날아가기(DamageFlying)
  • 날아가 벽에 부딪히기(DamageFlyingWall)
  • 바닥에 엎드려 떨어지기(KnockDownFront)
  • 공중에서 바닥에 꽂히기(DamageJumpSlam)
  • SlamAir의 넉다운 모션(KnockdownSlam) : DamageSlam에서 짤라쓴다.
  • 회전하며 맞기(DamageScrewCW)
  • 회전하며 맞기(DamageScrewCCW)
  • 맞고 날아가기(DamageKnockback) : DamageJumpHeavy와 같다.
  • 길게 맞기(DamageHeavyLong) : DamageHeavyHead와 같다. 연출상 딜레이를 두고 맞아야 할 때 사용한다.

커맨드기는 아직 구상 중이다. 잡기 캐릭터이니 초필은 커맨드잡기 확정이고, 필살기 중에도 하나는 있어야 한다. 그리고 버튼 연타기술도 구현돼야 한다. 버튼 연타는 구상한 기술입력 시스템의 마지막 퍼즐이다. 이 외에 버튼 2개이상 동시에 누르기 등이 있지만, 난 버튼 동시 발동을 별로 좋아하지 않으므로 패스하기로 하자.

  • 허그(Hug) – ←↓→ A or B (구상 중)
  • 대공잡기(AirCatch) – 공중에서 ←↓→ A or B (구상 중)
  • 팔 휘두르기(Swing) – A연타 or B연타.
  • 정신없이 쿵쾅쾅(SmashDown) – ↓←↓→ B 초필.

필살기 말고도 만들 모션은 많다. 차차 생각해보자.

방어부터 시작

이야야야아아아-ㅇ아-=

소점프킥. 얘는 연계기로 연결이 안됨.

야야야얍

방어 후 바로 반격을 할 수 있는 버그가 있어서 추적을 시작했는데, 해결까지 무려 4시간이 걸렸다.[…] 이게 전부터 재현이 안되던 버그였는데, 이번에 재현이 되어 추적 끝에 결국 해결. 지금까지 개발하며 어려운 상황을 많이 맞이해봤지만, 이번 버그는 역대급이었다. 아휴… 이제 안정될 때도 됐건만…

다시 애니메이션으로 돌아가자.

커맨드잡기 – Hug는 상대를 끌어안은 다음, 폭탄을 쥐어주고 본인은 떨어진다. 뱀파이어 세이버의 바레타와 같다. 원래는 끌어안는 척 하면서 뒤로 물러서 기계손으로 머리를 친 다음 쓰러진 상대를 덮치면 심판이 튀어나와 1,2,3을 외치려고 했는데…너무 길다!

폴짝. 이렇게 끌어안는다.실패 시 딜레이가 큼.

구현 중… 생각보다 손이 많이 간다.

6.24

작업이 하기 싫을 때가 있다. 모든 일을 의욕적으로 처리하기는 당연히 어렵다. 그럴 때 난 느릿느릿~하는 편을 택한다. 머리를 비우고 일을 천천히 진행하는 것이다. 클릭도, 타이핑도 느리게. 또박또박. 재미있게도 목표점에 다다르는 시간이 크게 차이가 나질 않는다. 아니, 오히려 더 빠를 때도 있다. 그리고 무엇을 했든, 그 결과가 의지를 충전해 준다. 글씨를 또박또박 쓰는 것과 같다. 하고 나면 뿌듯하다. 작업을 이어갈 수 있는 원동력이 된다.

그래서 일단…음. 폭탄을 만들었다.오로라가 이걸 앨리스에게 주도록 해보자.

요로케 붙여야지.

오늘의 깨달음. 폭발 후 어깨에 불이 계속 붙어있는 문제가 있어서 추적을 해보니 particle의 입자를 생성중인가의 조건식의 particle.IsAlive()가 문제였다. 이것은 파티클의 입자가 아직 살아있는가?를 조사하는 게 맞기는 한데, looping이 켜져있을 경우 여전히 살아있는 것으로 인식한다. 야이… Stop()으로 파티클을 껐으면 당연히 looping도 끄는거지! 결국 수동으로 라이프타임을 재계산해서 파괴하는 방식을 썼다. 뜻밖에 멍청한 구석이 있네. 내가 못찾은 건가…

세상에서 가장 뜨거운 포옹이다. 퍼엉이겠지

6.26

어제 작업을 건너뛴 건 아닌데, 소득이 없었다. 공중 잡기의 컨셉이 정말 어려웠는데, 팔이 너무 크다 보니 리치가 지나치게 길어져서 OP가 된다. 안그래도 상대하기 까다로운 게 잡기 캐릭인데, 공격범위까지 넓을 수는 없다보니 구상에 많은 시간이 걸렸다.

그래서 최종 구상은 파리잡기!

잡히면 요래 땅에 박힌다. 애니메이션은 아직 완성이 덜 됐다.

땅에 박히는 모션을 복사하기 위해 데미지 액션 중 DamageSlam의 전체 액션을 복사해야 했다. 그런데 잘 안된다. 원인을 찾아보니 다양하다.

  • 다리의 본 애니메이션이 Euler로 만들어져 있다.
    • 쿼터니온으로 변경해서 해결할 수 있다.
  • 채널이 없다.
    • alt+r등으로 fcurve를 생성해 주어야 한다. 블렌더는 애니메이션을 붙일 때 fcurve가 없으면 붙이지 않는다.
  • 그룹 외 커브이다.
    • 이는 스크립트를 사용하다 생기는 오류인데, 몇몇 애니메이션은 본의 그룹을 벗어난다. 정상적인 상황이라면 커브는 모두 본의 이름을 그룹으로 갖는다. 하지만 이것이 바깥으로 빠져나와있는 경우가 있다. 이럴 경우, 복사가 되질 않는다. AutoRig Pro의 쿼터니온 변환이 이런 오류를 일으킨다. 그룹에서 벗어나 있어도 애니메이션은 잘 되기 때문에 큰 문제삼지 않았는데, 이런 문제가 있을 줄은.
  • 키를 복사할 때 그룹이 선택되어 있다.
    • 특정 채널이 선택되어 있을 경우, 전체 선택(A)를 눌러도 해당 채널만 복사되는 현상이 있다. 이건 버그가 아니고 블렌더에서 그렇게 설정되도록 한 것이다. 불편해!

이 때문에 오랫만에 스크립트를 업데이트하고, 정리하다 보니 타임오버. 쿼터니온 문제는 첫단추를 잘못 꿴 댓가이다. 첫 데이터가 말썽이면 개발 내내 고통받는다. 레퍼런스 모델은 필요한 관절은 모두 쿼터니온으로 변경해 놓았다. (다시 한 번 체크해보자.) 5번째 캐릭터부터는 이런 고통이 좀 줄어들 것이다.

이게 실무에서는 어떨까… 작업자마다 오일러에 익숙한 사람(=커브 사용자)이 있고, 쿼터니온에 익숙한 사람(=직관 사용자)이 있을텐데.. 그야말로 혼파망일 듯.

공중잡기 완료. 그런데 이걸 테스트하면서 잡기 위치가 어긋나는 현상을 발견했다. 이건… 음. 과거에 프레임이 튈 때 위치가 어긋나는 문제가 있어 방어코드를 넣은 것이 오히려 버그를 내고 있었다. 이를 삭제하니 잘되는 것 같아 보이긴 하는데, 좀 더 정확한 건 테스트를 해보아야 알겠다.