전체 글 (204)

  • 2024.03.08
  • 2024.03.06
  • 2024.03.06
  • 2024.03.06
  • 2024.03.04
  • 2024.02.29
  • 2024.02.26
  • 2024.02.25
  • 2024.02.23
  • 2024.02.20
  • 2024.02.20
  • 2024.02.19
  • 03
    08

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 51일차

    부분진행도 : ChapterF - 3일차

    작성일자 : 2024.03.08(금)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    3D 탄막게임을 최종 과제의 주제로 두어, 나는 업적 시스템과 탄막 시스템의 개발을 맡았다.

    먼저 탄막 시스템을 개발하여 게임의 스타일을 잡은 후, 업적 시스템을 개발하는 것으로 정했다.

    2. 현재까지 진행 한 작업

    다이어그램

    도전과제와 탄막 매니저의 모습에 대한 구상은 아래와 같다.

    도전과제 확인 화면의 와이어 프레임
    탄막 매니저가 탄막을 생성하고 관리하는 대략적인 로직
    Todo 대략적인 다이어그램

    탄막 매니저 기술적 요소

    탄막 매니저를 구성하기 위한 기술적인 내용은 아래와 같다.

    1. Pool Manager

    탄막 오브젝트 풀을 이용하여 Instantiate와 Destroy의 사용을 줄여 성능을 최적화

    2. Coroutine

    탄막 로직 중 Delay가 필요한 곳에 사용. 불필요한 연산을 줄여 성능 최적화에 좋을 것으로 판단하여 사용.

    3. Scriptable Object

    몹들의 공격 페이즈 별 한 개의 파일로 저장.
    Gen() 호출 시, 경과 시간 별 호출할 탄막 패턴과 그 상세 내용에 대해 모두 작성.
    단순한 몹은 하나의 파일을, 보스몹은 페이즈 당 하나의 파일을 가지게 될 것.
    (추가내용) 탄막 패턴별로 각 디폴트값을 저장할 SO도 구성한다.

    기타 메모한 팁

    Pool에 이미 반환한 오브젝트를 또 반환하려고 하면 에러를 표시한다.

    // 탄막위치가 y > 5가 될 경우 삭제
    if (this.transform.position.y > 5)
    {
        // 오브젝트 풀에 반환
        Pool.Release(this.gameObject);
        Pool.Release(this.gameObject); // 이미 없는 오브젝트를 반환하려고 할 시, 에러 발생.
        Pool.Release(this.gameObject);
        Pool.Release(this.gameObject);
        Pool.Release(this.gameObject);
    }

    Invoke와 코루틴 성능차이

    https://wonsang98.tistory.com/entry/Unity-C-인보크Invoke

    https://velog.io/@livelyjuseok/유니티-코루틴과-Invoke의-차이

    • 어느쪽이든 사용해도 괜찮다면, 적게나마 코루틴이 성능적으로 앞섬.

    int와 float의 차이(기술면접 참고)

    https://velog.io/@ysh096/float과-부동소수점

    코루틴 사용 시 new 반복사용 x

    Awake, Start 대신 OnEnable 사용

    setActive로 재사용할 경우에도, Active 될 때 마다 구문을 사용하도록 할 수 있음

    탄막 여러가지 정보를 초기화한다.

    랜덤 Vector

    // 반지름이 1인 원 안에서 위치를 Vector2로 가져옴
    Vector2 randCirclePos = Random.insideUnitCircle;
    // 반지름이 1인 구 안에서 위치를 Vector3로 가져옴
    Vector3 randSpherePos = Random.insideUnitSphere;
    // 반지름이 1인 구 위의 점 위치를 Vector3로 가져옴
    Vector3 randOnSpherePos = Random.onUnitSphere;

    3. 과제에 대해

    • 탄막 매니저의 틀을 잡기에 아직 생각이 정리가 되지 않아 더 고민해야한다.

    4. 참고자료

    반응형
    COMMENT
     
    03
    06

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 48일차

    부분진행도 : Chapter5.2 - 6일차

    작성일자 : 2024.03.05(화)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 과제를 마치고, 금일 2시부터 과제발표 세션을 시작하였다. 이번 팀은 다른 팀원이 발표를 맡았다.

    2. 오늘자 발표에 대해

    발표에 대해

    발표는 팀당 13분이 주어지며

    • 게임의 소개
    • 게임시연
    • 각 팀원당 개발 중 문제 해결에 관한 영상(트러블슈팅)
    • 회고 등

    과 같은 구성을 갖는다.

    피해야 할 점

    • 발표 13분 전체를 영상으로 송출
      • 발표와 동시에 재생되는 BGM. 발표 집중도에 방해가 된다.
    • 문제해결 영상에 본인의 목소리가 아닌 TTS를 사용
    • 기술 발표 목적의 세션에서 과한 감성적 요소와 인트로. 이후 회고 파트에서도 영양가 없는 내용은 피하도록.

    괜찮았던 점

    • 각자 맡은 역할에 대한 흥미로울 정도로 심도있는 문제 해결
      • 개발 도중 이에 대한 메모를 차근차근 쌓아놓았다면 자연스레 가능할 것
    • 그리고 문제해결 영상에 자막을 넣어 보기 좋게 하는 점
    • 일부러 발표 시간을 살짝 남겨 가볍게 게임 플레이를 보여주는 진행방식

    3. 회고

    이번 팀 프로젝트에 대해

    매번 느끼지만, 무척 짧은 팀 프로젝트의 기간은 매 번 느끼지만 장단점이 극명하다. 밀도있게 개발의 모든 단계를 진행하여 경험치를 쌓을 수 있지만, 심도있게 개발하기는 힘든 점.

    하지만 이번 팀 프로젝트는 기획 초반에 게임의 볼륨을 크게 잡지 않아 완성도를 높이고 코드 리뷰와 함께 정제를 할 수 있었다. 되는대로 기능을 때려박고 기억에도 남지 않던 이전 프로젝트들과 비교하면 무척 생산적이었던 시간이었다.

    개인적으로는 몬스터 파트를 하며 상속과 애니메이터, 그리고 몬스터 스폰에 관하여 ScriptableObject를 사용했는데, 아직도 잘 모르겠으면서도 나름 경험치를 쌓았다.

    내일배움캠프 중간 만족도 평가

    매 도중 과정을 마칠 때마다, 캠프에서 만족도 평가를 한다.

    캠프 커리큘럼 자체가 아침 9시부터 밤 9시까지 매일 진행하는 일정 등 호불호가 갈릴 요소가 굉장히 많기 때문에, 타인에게 추천할 것인지에 대해서 매번 8/10점 정도를 매겼었는데, 오늘은 조금 볼륨있게 상세 내용을 작성했기 때문에 살짝 적어본다.

    힘들었던 점만 적어보자면 / 
    너무 자주 바뀌는 팀원, 짧은 개발기간, 호불호가 심하게 갈릴 듯한 일부 즉흥적으로 보이는 세션 구성, 알고리즘의 대비가 되어있다는 초반 설명에 비해 문제풀이에 대한 심화적인 알고리즘 강의 세션이 없음.(프로젝트 활동만으로도 바쁘기 때문에 사실상 알고리즘까지 신경 쓸 여유가 없기는 하지만, 그렇다면 이에 대해 캠프 초반에 설명이 있었어야 했다고 생각한다)
    개인학습 시간이 압도적으로 부족한 데에 비해, 제공되는 강의는 갑자기 난이도가 상승하여 네다섯번을 봐도 이해하기 어려운 내용으로 꽉 차 있는 경우도 있어 시간이 낭비된다.

    현재의 유니티 3기 캠프는 내가 이렇다고 느끼고 있지만, 앞으로 더 나은 캠프를 위한 방향성을 위한 설문이기 때문에 나아질 것으로 생각된다.

    그리고 교육과 이에 관한 관리를 하는 기관이라는 이미지에 비해 튜터진과 매니저들의 마이크 이슈(마이크 볼륨, 음질. 특히 오늘자 선배와의 만남 세션은 이어폰 마이크를 사용하시던데 말하는 내용이 식별이 안 될 정도로 음질이 끔찍했다)가 개인적으로는 무척 신경쓰이는데 회사 차원에서 질 좋은 마이크를 1인 1개씩 지원해주면 좋겠다고 잠깐 생각했다.

    4. 앞으로의 과제

    내일부터 최종프로젝트의 시작이다. 3D 탄막게임에서 나는 도전과제와 탄막패턴(+최적화)를 메인으로 개발을 진행한다.
    일단 아래의 강의를 학습하며 도전과제 구현에 대한 아이디어를 얻을 예정이고, 탄막의 패턴에 관해서는 이전에 받아뒀던 탄막 패턴 에셋으로부터 작동 과정과 최적화 방식을 확인하여 참고가 된다면 할 생각이다. 탄막패턴 에셋은 2D 기반으로 작성되어있기 때문에 이를 참고한다고 하더라도 난항을 겪지 않을까 예상된다.

    [유니티 레벨 업!] 모듈식으로 개발하는 퀘스트&업적 시스템

    또한, 지금 기획은 대략 작성을 거의 끝냈다고 생각하는데, 이에 관한 와이어프레임과 게임 플레이 사이클에 대한 순서도 등은 아직 마련되어있지 않아 이에 대한 작성도 배워보고, 클래스図 등에 관해서도 다시 배워 개발 과정에서 잘 써먹고 싶다.

    반응형
    COMMENT
     
    03
    06

    혹시나 해서 작성하는데, 가이드는 구글검색해서 나오는 티스토리 블로그(https://growy.tistory.com/57) 말고 아래 DC글을 참고하자.

    티스토리 내용이 아래 글을 복붙으로 퍼간 내용인데, 최신화가 안되어 중간에 놓치는 부분(수수료)이 치명적이다.

    현재 나는 티스토리 글을 따라해서 게임 수익의 30%의 수수료를 내는 걸로 등록이 되었는데, 아래 DC글의 가이드처럼 ENI를 제대로 기입하는 방식으로 한다면 더 낮은 10%의 수수료를 내는 것으로 보인다.

    30%의 수수료로 등록을 해버린 이 건 관해서는 나중에 업데이트를 하여 10%로 바꿀 수 있을지 미지수.

    그 외에, 댓글에서도 여러가지 팁을 얻을 수 있다. 스팀 출시 강좌 -4-까지 잘 따라하여 성공적인 출시를 노려보자.

    https://gall.dcinside.com/mgallery/board/view/?id=game_dev&no=56289&_rk=zx3&page=1

     

    스팀출시 강좌 -1- - 인디 게임 개발 마이너 갤러리

    좋은 게임을 만들었는데 첫 출시를 하는 것에 막연함이 있는 인붕이들망설이지 말고 스팀출시 함 해무라! 겁먹을 필요 없어이거 보고 따라하기만 하면 스팀 그까이꺼 대단한 것도 아니야!영알

    gall.dcinside.com

     

    참고로 본인은 영어 페이지가 아닌 한글 페이지를 사용했고, 입력할 필드가 영어 페이지와 비교하여 하나 더 있었지만 적당히 뭔가 적었던 것으로 기억하는데 크게 문제되진 않았다.

    위 강좌의 가이드에 따라, 성공적으로 서류제출 단계까지 가면, 이메일로 관련한 증빙 자료를 제출하라는 요청이 온다.

    파일업로드를 할 수 있는 드롭박스 링크를 같이 주는데, 정상적인 국민이라면 서랍을 열면 바로 나올 서류들이라 잘 찍어서 해당 링크에 업로드를 해 주었다.

    약 한 달 전에 위 작업을 한 후, Valve의 답장을 계속 기다리고 있었는데... 회신이 오지 않아 답답한 마음에 스팀웍스에 로그인을 해서 확인 해 보니 이미 승인이 되어있는 상태였다.

    세금 정보의 승인으로 보이는 페이지의 스크린샷

    세금 정보가 확인되었다는 녹색 테두리의 업데이트 날짜를 보면, 제출하고 약 20시간만에 승인이 되었다는 것을 알 수 있었다. 이 내용은 당연히 이메일로 날아올 줄 알았는데, 역시 다시 확인 해 봐도 없다.

    아무튼, 세금정보를 제출하고 하루도 안되어 승인되었는데 참고가 될까 싶어 적어둔다.(이메일 회신 x)

    이어서 '계속' 버튼을 눌러보니, 스팀 파트너 등록이 되었다는 메시지를 볼 수 있었다.

    파트너 등록 완료 메시지
    사용자 대시보드

    이후 사용자 대시보드를 확인하거나 할 수 있다. 나는 당장 여기서 게임 출시를 할 수 있는 직접적인 무언가를 찾지 못했기 때문에, 스팀 출시 가이드 -2- 이후를 이제 참고하며 진행해야겠다.

     

    + 추가내용

    새로운 앱 만들기

    대시보드에서 스크롤바를 쭈우우우우우우욱 밑으로 내려보면 우측 메뉴에서 게임 출시를 위한 '새로운 앱 만들기' 버튼을 찾을 수 있다.

    반응형
    COMMENT
     
    03
    06

    1. 문제

    찜 목록(1) 눌러보면 표시되는 게임이 없음

    찜 목록에 (1) 표시가 붙어 확인 해 보았지만, 목록에 표시되는 내용이 없다.

    2. 해결

    찜했던 게임이 상점에서 비공개되거나 하는 경우 발생한다는 듯. 이것을 찜 목록에서 삭제 해 보자.

    크롬 등의 브라우저로 스팀에 접속하여, 찜 목록에 들어가 F12를 눌러 개발자도구를 연다.

    웹브라우저로 접속 후 개발자도구 사용

    Elements 탭에서 Ctrl+F로 찾기기능을 사용하여 아래 구문을 검색

    var g\_rgWishlistData =

    바로 오른쪽에 표시되는 appid를 확인한다. 비공개 된 게임의 번호이다.

    콘솔 탭으로 이동 후, 구문 사용

    Console탭으로 이동하여 아래 구문의 appid를 수정하여 입력 후 실행(엔터)

    $J.post( g_strWishlistBaseURL + 'remove/', {
                    'appid' : 000000,
                    'sessionid' : g_sessionID
    });

    정상적으로 구문이 동작한 후, ▶버튼을 눌러 열어보면 위와 같은 내용을 볼 수 있다
    새로고침 후 찜 목록에 (1)이 사라진 모습

    구문을 실행 후, 새로고침을 하면 찜 록록이 정상화 된 것을 볼 수 있다.

    여담으로 숨겨졌던 게임이 무엇이었는지 궁금해서 확인을 해 보았는데

    https://store.steampowered.com/app/746470

    찜 목록에 숨어있던 아이템

    이와 같이 상점 목록에서 제외된 아이템이었다는 것을 확인 할 수 있었다.

    여담으로, 이런 아이템을 찜 해제한 이후에도 다시 찜 할 수 있다.

    반응형
    COMMENT
     
    03
    04

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 47일차

    부분진행도 : Chapter5.2 - 5일차

    작성일자 : 2024.03.04(월)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    4일차에 필수 구현 목표를 대부분 완료하고 마무리 단계.

    금일 예정은, Monster 코드의 상속 구조 리팩토링 후, 몬스터 로밍 자동화 실패했던 내용에 대해 해결 해 보기.

    2. 오늘 학습에 대해

    오늘 오전에는 몬스터 종류에 관한 코드를 리팩토링 하고, 오후에는 아래와 같은 사장되었던 기능을 살려보았다.

    몬스터의 자동 배치 및 로밍에 대해 Fix

    문제

    구현 초기에, 몬스터를 소환 할 때 몬스터 바로 아래의 바닥 콜라이더( Layer 중 'Ground', 'Passthrough' 에 대해)를 찾아 그 콜라이더의 표면으로 몬스터의 y값을 조정하고, 콜라이더의 좌우 끝값과 몬스터의 콜라이더 폭에 따라, 몬스터가 좌우로 이동할 수 있는 범위를 동적으로 지정해 주었다.

    슬라임을 스폰하면, 스폰한 위치의 하단의 콜라이더를 찾아 Y위치를 조정하고, 콜라이더 폭 만큼 X범위를 갖는다

    이걸 파기 한 이유는, 실제 MainScene에 팀원이 구현해 놓은 것을 보면 Ground 그리고 Passthrough가 각각 타일맵으로 한 개의 덩어리로 만들어져 있어 슬라임을 이 스테이지 안에 대충 배치해 놓는다고 해도 실제 게임을 실행 해 보면 스테이지의 y최상단에 슬라임이 올라가 있고 스테이지 전체의 좌우폭을 슬라임의 이동거리로 갖게 되는 문제가 있었기 때문이다.

    MainScene에서 적용할 경우, 스테이지가 하나의 콜라이더이기였기 때문에, 최상단에 슬라임이 생겼다. X이동범위도 스테이지 폭과 같음.

    아래 스크립트는, 위 알고리즘을 파기하여 주석으로 둔 후, 임시로 x의 범위를 직접 SO에서 설정하도록 하였다. y위치도 역시 SO에서 정확한 수치를 지정해주어야 한다.

    private void SpawnMonstersFromList(List<SpawnInfo> spawnData)
    {
        foreach (var spawnInfo in spawnData)
        {
            string prefabPath = $"Prefabs/Monsters/{spawnInfo.monsterType}";
            GameObject monsterPrefab = Resources.Load<GameObject>(prefabPath);
            if (monsterPrefab != null)
            {
                GameObject monsterObj = Instantiate(monsterPrefab, spawnInfo.spawnPosition, Quaternion.identity);
                SpawnedMonsters.Add(monsterObj);
                // "Ground"와 "Passthrough" 레이어에만 있는 물체를 감지하기 위한 LayerMask 생성
                int layerMask = LayerMask.GetMask("Ground", "Passthrough");
                // ray 쏘기
                RaycastHit2D hit = Physics2D.Raycast(spawnInfo.spawnPosition, Vector2.down, 10f, layerMask);
                if (hit.collider != null)
                {
                    // 하단 콜라이더의 경계를 기반으로 MinX와 MaxX 계산
                    BoxCollider2D monsterCollider = monsterObj.GetComponent<BoxCollider2D>();
                    float colliderHalfWidth = monsterCollider != null ? monsterCollider.size.x * monsterObj.transform.localScale.x / 2f : 0;
                    float minX = hit.collider.bounds.min.x + colliderHalfWidth;
                    float maxX = hit.collider.bounds.max.x - colliderHalfWidth;
                    float genY = hit.collider.bounds.max.y;
                    // 해결 될 때 까지 임시조치
                    //monsterObj.transform.position = new Vector3(monsterObj.transform.position.x, genY, monsterObj.transform.position.z);
                    // MonsterStat MinX, MaxX 설정, bool 관련 설정
                    MonsterStat monsterStat = monsterObj.GetComponent<MonsterStat>();
                    if (monsterStat != null)
                    {
                        //monsterStat.MinX = minX; // 해결 될 때 까지 임시조치
                        //monsterStat.MaxX = maxX;
                        // 임시조치
                        monsterStat.MinX = spawnInfo.spawnPosition.x - spawnInfo.tempMinX;
                        monsterStat.MaxX = spawnInfo.spawnPosition.x + spawnInfo.tempMaxX;
                        monsterStat.IsStopOnIdle = spawnInfo.isStopOnIdle;
                        monsterStat.IsStopOnTrack = spawnInfo.isStopOnTrack;
                    }
                }
            }
            else
            {
                Debug.LogError($"NotFound : MonsterPrefab {spawnInfo.monsterType} | path {prefabPath}");
            }
        }
    }

    또한, 위와 같은 몬스터 배치 스크립트는 하단의 바닥 콜라이더가 가로로 평평한 직사각형이 아닐 경우 몬스터가 엉뚱한 위치에 배치되거나 엉뚱한 이동범위를 가질 수 있다. 예로 아래와 같은 느낌의 계단 모양의 콜라이더에 슬라임을 배치 할 경우 아래와 같은 모양을 하게 된다.

    그림. 기대한 슬라임의 위치와 범위

    ■■■
    □□■ ← slime →
    □□■■■■■■■
    □□□□□□□□□ ㅇ

    그림. 실제 스크립트를 적용한 슬라임의 y위치와 이동범위

    ← slime →
    ■■■
    □□■
    □□■■■■■■■
    □□□□□□□□□

    이러한 계단이나 언덕 모양에서도 잘 작동하도록 하기 위해, 스크립트를 다시 구성할 필요가 있다.

    해결

    위 두 가지의 문제를 동시에 해결하기 위한 솔루션으로 아래와 같은 방법을 사용하였다.

    1. 몬스터의 하단에 Raycast를 사용하는 방법은 동일하다.
    2. 단, Raycast가 인식되는 지점을 y위치로 지정한다. 이전 스크립트에서는, 콜라이더의 최상단 지점이 몬스터의 y좌표가 되었었다.
    3. 몬스터를 좌우로 이동시키며, 상단 및 하단으로 Ray를 쏴 동일한 플랫폼인지 확인한다.

    2번 과정으로 몬스터가 스폰되는 y위치의 조정을 해결하고, 3번 과정을 통해 계단 및 언덕 모양 등 높이가 다른 바닥으로는 몬스터가 이동하지 않도록 할 수 있게 된다.

    3번 과정의 구현에서 꽤 시간이 걸렸는데, 자세히 작성하면 아래와 같다.

    좌측 범위에 대한 탐색을 먼저 시작한다. 상단(몬스터의 하단을 기준으로 레이를 발사하여, 몬스터의 상단 지점까지)은 플랫폼 Collider가 탐색되지 않아야 하고, 하단은 아주 짧은 거리에 플랫폼 Collider가 탐색되어야 하며, 이 두 조건을 만족할 경우, 이동 가능한 X 범위에 포함시키며 반복문을 통해 계속 확장해 나간다. 두 조건 중 하나라도 만족하지 않게 된다면 확장을 멈추고 좌측으로의 X범위를 확정한다. 이를 우측 범위에 대해서도 똑같이 탐색한다.

    자세한 스크립트는 아래와 같으며, 주석으로 각 구문에 대한 설명을 하였다.

    FindBoundary 메서드에서 Ray를 통한 X범위 탐색을 구현하였다.

        private void SpawnMonstersFromList(List<SpawnInfo> spawnData)
        {
            foreach (var spawnInfo in spawnData)
            {
                string prefabPath = $"Prefabs/Monsters/{spawnInfo.monsterType}";
                GameObject monsterPrefab = Resources.Load<GameObject>(prefabPath);
                if (monsterPrefab != null)
                {
                    GameObject monsterObj = Instantiate(monsterPrefab, spawnInfo.spawnPosition, Quaternion.identity);
                    SpawnedMonsters.Add(monsterObj);
    
                    // 현재 몬스터 Stat의 MinX, MaxX 등을 재설정하기 위해 준비
                    MonsterStat monsterStat = monsterObj.GetComponent<MonsterStat>();
    
                    // 스폰 시 Y값 및, X범위를 바로 아래 플랫폼의 모양에 따라 재할당
                    if (spawnInfo.autoAdjustPosition)
                    {
                        // "Ground"와 "Passthrough" 레이어에만 있는 물체를 감지하기 위한 LayerMask 생성
                        int layerMask = LayerMask.GetMask("Ground", "Passthrough");
    
                        // ray 쏘기
                        RaycastHit2D hit = Physics2D.Raycast(spawnInfo.spawnPosition, Vector2.down, Mathf.Infinity, layerMask);
    
                        if (hit.collider != null)
                        {
                            Vector3 monsterPosition = spawnInfo.spawnPosition;
    
                            // 몬스터의 Y 위치 설정
                            monsterPosition.y = hit.point.y;
                            monsterObj.transform.position = monsterPosition;
    
                            // 이동 범위 설정
                            float leftBoundary = FindBoundary(monsterObj, Vector2.left, layerMask);
                            float rightBoundary = FindBoundary(monsterObj, Vector2.right, layerMask);
    
                            BoxCollider2D monsterCollider = monsterObj.GetComponent<BoxCollider2D>();
                            float colliderHalfWidth = monsterCollider != null ? monsterCollider.size.x * monsterObj.transform.localScale.x / 2f : 0;
                            monsterStat.MinX = Mathf.Min(spawnInfo.spawnPosition.x, leftBoundary + colliderHalfWidth);
                            monsterStat.MaxX = Mathf.Max(rightBoundary - colliderHalfWidth, spawnInfo.spawnPosition.x);
    
                        }
                    }
                    // 스폰 시 위치 및 X범위를 수동으로 할당
                    else
                    {
                        // x 이동 범위 수동 지정
                        monsterStat.MinX = spawnInfo.spawnPosition.x - spawnInfo.tempMinX;
                        monsterStat.MaxX = spawnInfo.spawnPosition.x + spawnInfo.tempMaxX;
                    }
    
                    monsterStat.IsStopOnIdle = spawnInfo.isStopOnIdle;
                    monsterStat.IsStopOnTrack = spawnInfo.isStopOnTrack;
    
                }
                else
                {
                    Debug.LogError($"프리팹({spawnInfo.monsterType})을 경로({prefabPath})에서 찾을 수 없음");
                }
            }
        }
    
        // 몬스터의 X이동범위 자동으로 할당할 경우 탐색
        float FindBoundary(GameObject monster, Vector2 direction, int layerMask)
        {
            float step = 0.05f; // Raycast를 발사할 간격
            float maxDistance = 20f; // 최대 탐색 거리
            float rayLength = monster.GetComponent<BoxCollider2D>().size.y; // 몬스터 콜라이더의 높이
    
            // 몬스터 하단에서 시작
            Vector2 basePosition = new Vector2(monster.transform.position.x, monster.transform.position.y);
            float goalPositionX = basePosition.x;
    
            for (float distance = 0; distance <= maxDistance; distance += step)
            {
                Vector2 origin = basePosition + (direction * distance);
                Vector2 rayStartBelow = origin + new Vector2(0, 0.01f); // 아래쪽 Ray 시작점
                Vector2 rayStartAbove = origin + new Vector2(0, 0.1f); // 위쪽 Ray 시작점
                // 시각화 FOR DEBUG
                Debug.DrawRay(rayStartBelow, Vector2.down * 0.05f, Color.red, 60f);
                Debug.DrawRay(rayStartAbove, Vector2.up * rayLength, Color.blue, 60f);
    
                // 아랫방향으로 짧은 Raycast 발사
                RaycastHit2D hitBelow = Physics2D.Raycast(rayStartBelow, Vector2.down, 0.05f, layerMask);
    
                // 몬스터 위치보다 약간 위에서 위쪽으로 Raycast 발사
                RaycastHit2D hitAbove = Physics2D.Raycast(rayStartAbove + new Vector2(0, 0.05f), Vector2.up, rayLength, layerMask);
    
                // 아래에는 플랫폼이 있고, 위에는 플랫폼이 없는 경우
                if (hitBelow.collider != null && !hitAbove.collider)
                {
                    // 이동 가능한 경계를 찾음
                    goalPositionX = basePosition.x + direction.x * distance;
                }
                else break;
            }
    
            return goalPositionX;
        }

    상단(파랑)과 하단(빨강)의 탐색 시각화

    X탐색에 관한 Ray를 시각화하면 위와 같다.

    3. 과제에 대해

    • 이번 프로젝트에 대한 과제는 일단 더 없고, 생각했던 리팩토링도 끝냈기 때문에, 발표에 쓰일 트러블슈팅(개발 중 문제 해결 경험)에 대한 영상을 찍어 발표자에게 전해주는 것, 기타 프로젝트에 관한 정리를 하면 되겠다.
    • (추가)오후 중 영상까지 완료하여 일단 종료

     

    반응형
    COMMENT
     
    02
    29

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 45일차

    부분진행도 : Chapter5.2 - 3일차

    작성일자 : 2024.02.28(수)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 프로젝트로 2D 횡스크롤 게임을 제작중이다. 스펠렁키라는 게임이 크게 모티브가 되어있는데, 모르는 게임이라 살짝 찾아본 게 끝. 스펠렁키를 모른다면 슈퍼마리오 정도라고 생각해도 괜찮다.
    현재까지의 완성도는 아래와 같다.

    씬 뷰
    시작 지점에서의 게임 뷰

    사실 위 스크린샷에 내가 담당한 부분은 없고, 나는 몬스터 파트를 맡았기 때문에 그 부분은 아래에서 정리한다.

    2. 몬스터 파트에서의 개발

    첫 2일간은 감을 잡기가 힘들었다. 그렇다고 지금도 매끄럽게 되고있는 건 아니다.

    이틀 간 감을 잡는다고 다른 강의를 찾아보며 베이스를 구축하기는 했지만, 전혀 다른 타입의 게임을 예로 든 강의였기 때문에 프로젝트에 도움이 거의 되지 않았다. 많은 시간을 사용했지만 얻은 건 거의 없는 느낌

    팀 프로젝트이고 남은 시간이 충분치 않아, 그 정도만 하고 개발에 착수하여 일단 제시된 필수구현이 돌아가도록 하는 것만을 목표로 삼아 깡으로 코드를 작성하였다.

    업계 스탠다드의 구조를 가진 코드를 작성하고 싶었는데, 그걸 찾는다고 시간을 허비하는 게 상당한 민폐인지라 어쩔 수가 없는 상황이다.

    현재까지 구현 한 내용을 큰 부분만 정리하면 아래와 같다.

    • 몬스터의 구현. 스폰되어 이동을 하고, 플레이어에게 공격을 한다.
    • 상태(Idle, Move(Track to Player), Attack, Die)에 따른 여러가지 처리.
    • 데미지 계산. 플레이어에게 데미지를 주고, 플레이어에게 공격받는다.
    • 공격주기의 반영 등 은근 까다로운 요소.
    • 몬스터의 스폰. 미리 설정한 지점에 몬스터를 소환한다. 한 스테이지에 여러 구역이 있어, 다른 구역으로 넘어가면 이전 구역의 몬스터를 모두 지우고, 새 구역에 풀피의 몬스터를 새로 생성한다.
    • 여러가지 몬스터. 근접공격을 하는 몬스터와 원거리 공격을 하는 몬스터를 마련한다.
    • 위 내용에 따른 발사체 구현.
    • 아이템 드랍. 체력 회복을 하는 포션을 일정 확률로 드랍한다.

    현재로써는 몬스터 종류의 확장과, 개발중 지속적으로 고쳐주는 상태 처리이다. 상태처리에 관해서는 뼈대가 잘 잡히지 않은 채로 구현을 시작하다 보니 그런 듯 하다.

    의외로 가장 쉬웠던 것이 아이템 드랍이다. 따로 아이템을 카테고리화하여 관리 할 정도가 아닌 게임이기 때문에, 몬스터가 죽는 시점에, 일정 확률로 포션을 던지고, 포션은 플레이어와 충돌이 일어나면 플레이어에게 준비되어 있는 회복 메서드를 사용한다. 사실 플레이어에게 회복 기능이 구현되어있지 않아, 본인이 작성하기는 했다.

    더 상세한 내용은 아래와 같다.

    가장 먼저 만든, 근접 공격 타입의 몬스터.

    범위 내에 플레이어가 감지되면 플레이어를 추적하고, 공격을 한다.

    플레이어에게 공격당하면 일정거리 넉백이 되며, 체력이 0이 될 경우 DIE 상태가 되어 애니메이션을 보여주며 사라진다.

    플레이어에의 피격을 발생시키는 시점의 경우, 애니메이션 중에 이벤트를 추가할 수 있어 정확한 시점에 메서드를 실행 할 수 있다.

    애니메이션 뷰에서 우클릭으로 이벤트를 추가할 수 있다. 피격시점과 애니메이션 종료 시점 사용할 이벤트를 마련했다.

    새로운 몬스터인 원거리 공격형을 만드는 데에는 많은 시간을 소비했다.

    상속구조에 대해 아직 익숙해지지 않아 MonsterMelee와 MonsterRange 클래스는 거의 비어있고, 두 클래스가 상속받는 MonsterBasController라는 클래스에 모든 것을 작성 해 놓은 상태이다.

    완전히 빈 Melee와 Range. Init도 만약의 경우를 위한 코드이다.
    몬스터에 대한 거의 모든 코드가 몰려있는 부모 클래스

    처음에 Melee와 Range를 구분할 여유가 없이 힘들게 코드를 작성했기 때문에, Range의 작성을 시작하며 MonsterController라는 클래스의 이름을 Melee로 바꾸고, 그의 부모였던 MonsterBaseController에 모든 코드를 옮겼으며, Range의 코드를 생성했기 때문에 벌어진 일이다.

    이 부분에 대해서는 현 프로젝트의 내 파트에 있어 가장 리팩토링이 필요한 부분이다.

    몬스터에게 필요한 여러 수치들은 MonsterStat에 모아 프리팹의 컴포넌트로 사용하였다

    몬스터에게 필요한 여러 능력치들을 저장하는 MonsterStat이다. MonsterManager에서 몬스터를 생성할 때 설정하는 내용도 있고, MonsterBaseController에서 hp 등을 변화시켜주기도 한다.

    원거리 몬스터는 투사체를 발사한다.

    원거리 몬스터의 투사체는 플레이어의 콜라이더와 접촉 시, 데미지를 준다. 플레이어측에서 데미지를 받는 메서드를 마련 해 두었기 때문에, 그것을 사용하였다. 접촉데미지의 경우도 마찬가지이다.

    일정 확률로 포션 아이템을 드랍한다.

    몬스터는 Rigidbody를 사용하지 않고, Collider도 트리거로서만 작동하도록 하였는데, 드랍되는 포션은 물리법칙을 받도록 작성했다.

    현재 포션이 얌전히 몬스터의 위치에 떨어지는데, 스폰 시 랜덤한 방향으로 아주 살짝 velocity를 주면 좋을까 생각하고 있다.

    드랍된 HP포션

    HP 포션을 먹으면 체력을 채운다.

    유용한 기능이었지만 지워버린 기능

    개발 초기의 기능 중 하나로 몬스터의 좌표를 대충 설정하고 스폰하게 되면, 몬스터의 바로 아래에 있는 플랫폼의 콜라이더를 확인하여 바로 위에 안착하고(y좌표), 콜라이더의 좌우 끝 위치를 확인하여, 몬스터가 그 범위만을 순찰하도록 하였다(x범위).

    테스트 시에는 가로로 긴 직사각형 플랫폼을 여럿 사용하여 개발을 하였는데, 실제 개발중인 메인 씬에서 확인해보니 이걸 활용할 수가 없는 환경이었다.

    한 덩어리로 이루어진 Ground 콜라이더
    모든 스테이지의 정점에 소환된 슬라임

    플랫폼이 각자의 콜라이더를 가진 상황이 아니라, 모든 스테이지가 하나의 덩어리로 이루어져 있어 스테이지의 상단에 슬라임이 소환되며 위와 같이 스테이지의 폭만큼 X범위를 가지게 된 것.

    아쉽지만 직접 몬스터의 좌표를 정확히 지정하고, 좌우로 이동할 거리를 수동으로 입력 해 주게 되었다.

    MonsterSpawnData(SO)

    위의 문제를 해결할 때 까지 사용한다는 의미로 Temp 를 붙여놓았지만, 아무래도 해결이 어렵지 않을까 생각한다.

    3. 앞으로 구현할 내용

    • 몬스터를 두 종류 더 만든다. 둘 다 근접공격이고, 하나는 빠르고 약한 '생쥐', 하나는 느리고 단단한 '골렘'이다. 또한 기존 슬라임의 크기를 키워 한 종류를 더 만든다는 듯 하는데, 이는 밸런스 담당에서 건드릴 것 같다.
    • 몬스터 스폰 시 y와 x정찰범위 기능의 수리는 도전과제로.
    • 투사체 모양의 구체화. 현재는 흰색의 circle과 square만을 합쳐놓은 모양이다. 사실 이렇게 만들어도 위화감은 별로 없었다는 게 킬포인트.
    • 코드 자체의 리팩토링. 상기 작성한 내용처럼 코드가 무척 정리되지 않았다.

    개발 기간은 현재 3일을 진행했으며, 목요일~월요일의 휴일 포함 5일가량이 남았고, QA를 할 시간이 충분하다. 세세한 부분도 부족하다면 차차 수리하거나 추가개발을 진행해도 될 정도이다. 다른 팀원의 코드도 확인 해 나가며 배울 점을 찾을 수 있을 것 같다.

    4. 참고자료

    반응형
    COMMENT
     
    02
    26

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 43일차

    부분진행도 : Chapter5.2 - 1일차

    작성일자 : 2024.02.26(월)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    다시 팀 과제의 시작이다. 개발할 게임은 2D 횡스크롤 게임이고, 슈퍼마리오와 비슷한 느낌으로 생각하면 될 것 같다.
    기획에 관해서는 개인 과제 주차에서도 얘기가 되었던 내용이 많아, 점심시간 전에 세부적인 기획도 거의 마치고 각자 개발을 시작했다.

    역할분배는 '플레이어', '맵', '몬스터', 'UI와 씬 등 기타 관리' 이렇게 넷으로 나누었다.
    나는 지금 스크립팅 구조의 전체적인 설계에 있어 그림이 그려지지 않는 상태인 것 같아, 영향을 덜 받을 듯한 '몬스터' 설계의 역할을 맡았다.

    개발 중 문제와 여러가지 생각을 작성할 표

    팀 노션에 각자의 기록을 작성할 수 있도록 공간을 마련했다.
    여담으로 본인은 '트러블 슈팅'이라는 용어가 너무 보여주기식으로 느껴진다고 해야 하나... 거부감이 심해서 잘 쓰지 않는 편.

    다른 팀원들이 굉장히 구현을 빠르게 잘 하고 있는 것 같아 내 파트가 뒤쳐지고 있는 느낌이다.

    2. 오늘 학습에 대해

    Monster에 관한 스크립트의 구현

    감이 안 잡혀서 관련 강의를 빠르게 확인하고 구현에 참고를 하고있다.

    MonsterController에서 몬스터의 상태 관리와 행동에 대해 모두 컨트롤을 하고있다. 몬스터의 타입(투척형, 근접형 등)에 따라 이 스크립트를 세분화 할 것 같다. 상태에 따른 애니메이션을 마련하였고, 이것의 관리도 이 스크립트에서 행한다.

    MonsterStat에서는, HP, 공격력, 속도, 스폰과 이동범위 등을 관리하려고 한다.

    오늘의 구현. 몬스터의 인지 범위 내에 플레이어가 들어오면, 플레이어를 추적하여 공격한다.

    3. 과제에 대해

    • 몬스터 파트 힘내서 계속 구현하기

    4. 참고자료

    반응형
    COMMENT
     
    02
    25

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 42일차

    부분진행도 : Chapter5.1 - 5일차

    작성일자 : 2024.02.23(금)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    개인 과제가 완성되지는 않았지만 제출을 마치고, 오는 주차는 팀 과제 주차이다.

    팀 과제 주차가 지나면, 두 달의 최종 프로젝트를 시작하게 되는데, 팀원을 모두 구했기 때문에 여러 번 회의를 하고있다.

    24일 토요일 까지 모아 본 최종 프로젝트에 참고할 레퍼런스를 모아보거나 한다.

    2. 레퍼런스

    탄막 스크립팅에 관한 에셋(구매 완료, 이후 분석)

    탄막 비쥬얼에 관한 플레이리스트

    사운드

    전투 씬 플레이 중

    • 후보 였던 것 - 90000% 취향저격이긴 한데 볼륨이 크다는 느낌. 리듬게임 스타일 너무 좋은데, 탄막슈팅게임이라 뭔가 핀트가 안 맞는다는 그런 느낌?) tn-shi - Synthesis. - Arcaea 스타일
    • 후보 진 - 장르와 프로젝트 규모 생각하면 이 정도 볼륨이 딱 적당할지도?) 'New Game'

    메인메뉴 등, 비전투 (둠칫둠칫)

    3. 그 외

    움직이는 Emit 머테리얼 약 3000개 + Bloom포스트 프로세싱, 실행시작에 약 2분, 실행중에는 20FPS정도가 된다.

    4. 최종프로젝트를 위해 공부해야 할 것

    • 오브젝트 풀링 실습
    • 탄막 관련 알고리즘
    • 업적 시스템을 위한 모듈화
    • 파티클 시스템에 관해
    반응형
    COMMENT
     
    02
    23

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 33일차

    부분진행도 : Chapter4.2 - 1일차

    작성일자 : 2024.02.08(목)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해(개인과제)

    이번 주엔 피로도가 너무 쌓여서 그런지 힘내기가 무척 힘들었다.

    지급된 심화 강의를 학습하고, 주제는 자유에 가까운 개인과제를 제출하는 한 주 였다.

    강의는 거의 소화를 못 했고, 개인 과제를 시작하자 생각하여 하루이틀 간 생각한 것을 구현하였다.

    일단 주제는, 네트워크(API)를 활용하여 게임에서 사용할 수 있는 여러가지 기능을 시험 해 볼 수 있는 시뮬레이터이다.

    유니티 에디터에서 만든 시뮬레이터

    간단히 소개를 하면, Sign Up 을 통해 회원가입을 하고, 로그인을 하여 프로필과 스코어 등의 점수를 받아올 수 있다.

    ChangeName을 통해 유저의 이름을 설정할 수 있고, 일정 시간마다 1씩 회복되는 AP를 사용하여 경험치나 스테이지의 점수를 얻을 수 있다.

    AP가 차는 시간에 대해서는, 유저에게 보여지는 남은 시간(스크린샷에서는 01:08 의 부분)에 오차가 발생할 가능성이 있기 때문에, 'Refresh'버튼으로 서버와 동기화 해 줄 수 있다.

    Watch Rank Board 버튼을 눌러 스테이지 당 상위 5명의 점수와 이름을 얻어 반영한다.

    개발자용으로, 화면 좌측에서는 모든 유저의 UID 및 name 정보를, 화면 우측에서는 지금까지 행동한 로그를 보여주고있다.

    Unity Client, Server, BackupDrive

    네트워크 구조는 위와 같다. 

    유니티로 만든 클라이언트에서 회원가입과 로그인, 여러 행동을 하면, API에 데이터를 전달하거나 요청을 보내 현재 상태를 서버쪽 데이터에 업데이트하거나 불러올 수 있다.

    현재, 서버 배포 환경이 닫힐 경우, 데이터가 그대로 사라지기 때문에 Google Drive에 백업을 만들어놓는 방법을 선택했다.

    구글 서비스계정을 새로 만들어 구글 드라이브에 수동 인증 없이 코드만으로 접근 할 수 있도록 하였고, 이 과정에서 많은 시간을 보내다가 결국 사용하던 패키지를 포기하고 최종적으로는 pydrive2라는 패키지를 사용하여 성공하였다.

    현재 시점에서, 코드를 만들 재료는 모두 갖추어졌고, 열심히 기능을 구현하는 일만 남았다.

    2023.02.23, 0시 시점, 회원가입 및 로그인 기능 구현 완료하였다.

     

    시간이 다 되어 이미 리포지토리를 제출하기는 했는데, 부족한 내용이 많아 열심히 보충중이다.

    반응형
    COMMENT
     
    02
    20

    0. 개요

    유튜브 보다가 흥미로워 보이는 프로그램을 발견해서 바로 찾아보았다. 해당 영상의 설명란에서 Domino에서 열 수 있는 파일(큐우쿠라링.dms)을 배포하고 있다.

    1. 설치

    다운로드 버튼

    • 영어(비공식 번역) : Github: Hans5958 - Domino-English-Translation
      • 영어 번역이 대부분 되어있지만 인코딩 문제로 한글이 표시되어야 할 곳은 여전히 깨져 보인다. 어플로케일 등으로 실행하여 해결하면 될 듯 하지만, 당장 시연에는 문제가 없으므로 그냥 사용

    압축파일을 해제하여, Domino.exe 파일실행

    2. 간단 사용법

    아래와 같은 사용법을 순서대로 정리한다.

    • dms 파일을 여는 방법
    • 모든 트랙을 보는 방법
    • 재생 시, 스피커에서 들리도록 설정

    압축 해제 된 모습

    압축 해제 후, Domino.exe를 실행하여 에디터를 열 수 있다.

    Domino.exe를 실행하면 보이는 모습

    영어 버전의 경우, Defender의 보안 설정을 통해 해당 파일을 수동으로 예외처리해야 실행할 수 있을지도 모르지만, 여기에는 정리하지 않는다.

    정상적으로 열렸다면 위와 같은 모습. 준비된 파일(위 영상의 설명란에서 곡의 dms 파일을 배포 중)을 불러오자.

    `File - Open`을 통해 준비된 dms 파일을 열어본다

    파일을 정상적으로 열면, 아래와 같은 모습을 확인 할 수 있다.

    파일을 연 직후의 상태

    좌측 패널에서 여러가지 이벤트(각 음에 대한 정보, 곡의 세팅, 곡의 시작과 종료 등)에 대한 내용을 확인 할 수 있고(현재 선택된 트랙에 따라서는 아무것도 적혀있지 않을 수 있음),

    노트를 시각화 한 정보는 우측 패널에 아직 보이지 않을 수 있다. Tab(View - Track List, 표 모양의 아이콘 형태로도 있음)을 눌러 곡의 모든 트랙을 확인 할 수 있다.

    트랙 리스트로 토글 한 모습. 표시한 부분에서 줌 기능 사용 가능

    스크린샷에 표시한 아이콘으로 X축과 Y축의 줌인 줌아웃을 할 수 있다. 물론 단축키로도 사용 할 수 있다.

    상하 이동은 마우스 휠을, 좌우 이동은 Shift를 누른 상태에서 마우스 휠을 이용해 이동 할 수 있다.

    여러 트랙이 잘 열린 것을 확인하였으니, 다시 Tab을 눌러 원래 모드로 돌아온다.

    아이콘을 눌러 Onion Skin을 사용한다

    먼저 모여준 영상과 같이, 모든 트랙의 노트를 한번에 시각화 할 수 있도록 하고 싶은데, 이는 Onion Skin이라는 것을 사용한다. Overlay Specific Track(s)를 선택하여, 내가 선택한 트랙이 모두 보여지도록 할 수 있다.

    Shift를 누른 채로, 처음과 끝을 선택 한 모습
    Ctrl을 누른 채로 원하는 트랙을 선택 한 모습

    여러 트랙을 선택하기 위해서는 아무래도 Shift키나 Ctrl 키를 사용할 수 밖에 없어 보인다. 아무튼 이렇게 보여줄 트랙을 모두 고른 후 OK를 눌러본다.

    Onion Skin을 통해 모든 트랙을 오버레이 한 모습. 작성자가 세팅한 곡의 Start 이벤트 지점등으로 이동할 수 있고, SpaceBar로 재생 할 수 있다.

    현재 선택한 트랙 이외에도 다른 트랙이 오버레이되어 보여지게 된다.

    파일 작성자가 세팅 해 둔 Setup 지점이나 Start 지점으로 버튼 클릭을 통해 이동 할 수 있고, 스페이스바 또는 재생 아이콘을 눌러 재생을 할 수 있다.

    Domino를 설치한 직후라면, 소리가 들리지 않을 텐데, 이에 대한 설정이 필요하다.

    `File - Preferences...`로 환경설정에 진입
    `MIDI-OUT - MIDI OUT 장치선택(으로 추정)`에서 장치 선택

    위와 같이 설정에 진입하여 MIDI-OUT 탭에서 Microsoft GS Wavetable Synth 장치를 선택하고 OK 버튼을 클릭한다.

    재생 시 정상적으로 스피커를 통해 재생되는 곡을 들을 수 있다.

    99. 참고자료

    모든 트랙을 표시(Onion Skin) - https://tounderlinedk.blogspot.com/2011/02/domino_25.html

    반응형
    COMMENT
     
    02
    20

    개인 연구와 미니 프로젝트에 관한 내용은 한 게시글에 작성중이며, 업데이트할 내용시 포스트를 수정합니다.
    모든 연구 페이지(현재 미작성)에서 진행중인 모든 주제를 확인 할 수 있습니다.

    모든 연구 포스트는 시간 흐름대로 작성중이며, 연구주제는 업데이트가 있을 수 있습니다.
    현재 진행도(2024.02.20 ~ 2024.02.20(끝))

    0. 주제

    midi 음계를 상하반전 시켜주거나 음계 일괄조정(Inverting MIDI Scales Vertically or Bulk Adjusting Pitch)

    1. 사용하고 싶은 기능

    2024.02.20
    현재 부분을 작성하는 시점, 전문 DAW를 사용하는 방법 외에 다른 수단을 찾아보지 않았습니다.

    인터넷에서 여러가지 음악과 노래의 midi파일을 구할 수 있고, 이를 바탕으로 다른 악기로의 연주, 여러가지 이펙트를 넣어 사용할 수 있지만, 그 전에 원본 midi를 높낮이 반전 또는 shift pitch만을 간단하게 하여 새로운 느낌의 소스로 선행작업을 해 줄 수 있다고 생각하였습니다.(물론 DAW에서는 정말 간단하게 두 가지 작업을 할 수 있으나, 여기서는 DAW를 사용하지 않고 선행작업을 하는 방법을 생각합니다.)

    먼저 사용 가능한 소프트웨어와 프로젝트가 있는지 찾아보았습니다.

    위 프로젝트의 프로그램을 사용 해 보았습니다.

    CarlosManuelRodr/Midi-Transform
    Unwelcome_School.mid
    0.01MB

    File: Orig

    Invert.mid
    0.01MB

    File:  Invert

    Revert.mid
    0.01MB

    File:  Revert

    ChangePitch+2.mid
    0.01MB

    File:  ChangePitch+2

    Invert+Revert.mid
    0.01MB

    File:  Invert+Revert

     

    Invert(상하반전), Revert(곡의 처음과 끝 뒤집기), Change Pitch 셋 다 모두 정상적으로 작동하는 것을 확인했습니다.

    개인적으로 원본이 괜찮다면 Invert+Revert가 의외로 괜찮게 나오지 않을까? 라고 생각했지만, 그렇지 않았습니다.

    2. 종료

    이미 같은 기능을 구현 해 놓은 프로젝트가 있는 것을 확인했기 때문에 종료, 저장 해 두고 유용하게 쓸 수 있을지도 모르겠습니다.

    반응형
    COMMENT
     
    02
    19

    과정명 : 내일배움캠프 Unity 게임개발 3기

    전체진행도 : 38일차

    부분진행도 : Chapter5.1 - 1일차

    작성일자 : 2024.02.19(월)

    개발일지 목록 : 클릭


    입문과 숙련을 넘어 심화주차(개인 1주, 팀 1주)에 진입했다. 밀린 세션과 강의도 있고, 최종프로젝트 관련하여 깊게 생각할 일도 있고(스테미너 소모량 높음), 새로운 팀과 소통하는 일도 피로도가 누적되어 있어 힘든 것 같다.

    이번 개인 주차와 팀 주차의 과제는 거의 자유작성이다. 개인 주차도 그렇지만, 팀 주차는 되도록 가볍게 하면 좋겠다고 생각했다. 컨텐츠적인 요소를 너무 많이 만들다보니 압박감도 심하고 좋은 코드를 고민할 여유가 없어진다.

    반응형
    COMMENT