전체 글 (197)

  • 2024.02.05
  • 2024.02.02
  • 2024.02.02
  • 2024.02.02
  • 2024.02.02
  • 2024.02.01
  • 2024.02.01
  • 2024.01.31
  • 2024.01.30
  • 2024.01.30
  • 2024.01.27
  • 2024.01.25
  • 02
    05

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

    전체진행도 : 30일차

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

    작성일자 : 2024.02.05(월)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    강의지급 및 개인과제 3/5일차, 서바이벌 3D게임 강의를 학습중이고, 개인과제는 UI구성까지만 마치고 스크립트 작성을 보류중이다. 슬슬 밤샘각이 보인다. 강의가 어렵지만 굉장히 멋진 구조의 디자인을 하고 있어 보이기 때문에 확실히 학습하면 굉장히 도움이 될 것이다.

    + 오후에 핫식스 도핑하면서 버티던 도중에 눈이 번득 뜨이는 이벤트가 발생했다.

    갑자기 분위기 우수 TIL(Today I Learned)

    일단 개발일지 목록이 업데이트가 덜 되어 있어서 호다닥 작성했지만 나머지는...

    더보기
    막 3일전 질러놓은 게임글
    NSFW 프로젝트
    유익하지만 음원소스가 서브컬쳐
    가장 최근 글이 방주 관리 프로젝트
    최고 인기글 제목도 부끄러움
    도메인도 부끄러움

    이쯤 되면 그냥 당당해지기로 했다

    2. 오늘 학습에 대해

    아침 CodeSolve

    바쁘더라도 한 문제 정도는 30분정도 써서 풀어도 괜찮은 것 같다.

    다른 날과 마찬가지로 주석으로 상세 내용을 정리한다.

    전엔 하지 않았던, 솔루션을 미리 작성해두고 과정을 따라가는 방식을 해 보니 괜찮은 것 같다.

    코딩테스트 연습 > 2022 KAKAO BLIND RECRUITMENT > 신고 결과 받기

    using System;
    using System.Collections.Generic;
    
    public class Solution {
        public int[] solution(string[] id_list, string[] report, int k) {
            int[] answer = new int[id_list.Length];
    
            // 솔루션
            // Dict1 : {"{ID}":{받을 메시지 수}}
            // Dict2 : {"{ID}":{List<string> ID를 신고한사람 목록}}
            // 1. Dict1과 Dict2를 초기화
            // 2. foreach문으로 report 순회하며 Dict2를 채운다
            // 3. Dict2를 순회하며 List의 길이가 k 이상일 경우,
            //    해당 리스트를 키로 갖는 Dict1의 값을 모두 1 더한다
    
            // 1. Dict1과 Dict2를 초기화
            // (Dictionary 안의 자료형으로 List<> 그냥 써 봤는데 왜 되는거야...)
            Dictionary<string, int> dict1 = new Dictionary<string, int>();
            Dictionary<string, List<string>> dict2 = new Dictionary<string, List<string>>();
            foreach(string id in id_list){
                dict1[id] = 0;
                dict2[id] = new List<string>(){};
                // Console.WriteLine(dict2[id].Count);
            }
    
            // 2. foreach문으로 report 순회하며 Dict2를 채운다
            foreach(string msg in report){
                string[] parts = msg.Split(' ');
                string userA = parts[0];
                string userB = parts[1];
                if(!dict2[userB].Contains(userA))
                    dict2[userB].Add(userA);
            }
    
            // 3. Dict2를 순회하며 List의 길이가 k 이상일 경우,
            //    해당 리스트를 키로 갖는 Dict1의 값을 모두 1 더한다
            // 찾아보기: 사전형의 순회
            foreach(var item in dict2){
                if(item.Value.Count>=k){
                    foreach(string user in item.Value){
                        dict1[user] += 1;
                    }
                }
            }
    
            // 4. answer 배열에 dict1의 내용 옮기기
            for(int i=0;i<id_list.Length;i++){
                answer[i] = dict1[id_list[i]];
            }
    
            return answer;
        }
    }
    • Dictionary 안의 자료형으로 제네릭 (예:List<>) 을 사용 해 보았다.
      • Dictionary<string, List<string>> dict2 = new Dictionary<string, List<string>>();
    • 사전형의 순회법
      • foreach(var item in dict2){ //Item.Key나 Item.Value로 접근 }

    3. 과제에 대해

    - 주어진 강의 흡수(남은 게 많다. 큰일. 오늘은 밤샘이다. 그래도 방향성은 잡힌 것 같다.)

    - 개인과제 스크립트 부분 이어서 착수

    반응형
    COMMENT
     
    02
    02

    FileFlattener-and-Restorer 깃허브 리포지토리

    방주 파일을 다루는데, 복잡한 디렉터리 구조에 퍼져있는 모든 파일을 하나의 앨범에 모으기 위해 작성한 스크립트이다.

    • flatten_and_move.py

    역으로, 모았던 파일을 다시 원래 위치로 복원하기 위한 스크립트도 마련하였다.

    • restore_files.py

    현재 폴더의 구분자를 _문자로 설정하여 새로운 파일 이름을 정하고 한 폴더에 몰아두는 방식을 채택했는데, 원래의 파일 이름에 _ 문자가 있었다면 원래 경로로 복원하는 과정에서 큰 문제가 된다.

    flatten_and_move 사용 시 로그 파일을 남기고, 그걸 사용하여 복원하는 방식으로 바꾸어야 해결 할 수 있을 것으로 보이고 조만간 업데이트 할 예정이다.

    아래는 2024.02.02 에 작성된 README.md 의 내용이다.

    FileFlattener-and-Restorer

    이 프로젝트는 다양한 경로에 퍼져있는 파일들을 단순화하고 정리하는 파이썬 스크립트를 제공합니다. 스크립트는 파일 이름을 변경하고, 지정된 출력 디렉토리로 파일을 이동시키며, 작업 로그를 기록합니다.
    또한 원래 경로로 복원이 가능한 스크립트를 제공합니다.

    기능

    • 파일 평탄화 및 이동: 지정된 디렉토리 내의 모든 파일을 스캔하고, 파일 이름을 변경하여 지정된 출력 디렉토리로 이동시킵니다.
    • 원본 경로 복원: 변경된 파일 이름을 사용하여 원본 디렉토리 구조로 파일을 복원합니다.
    • 로그 기록: 모든 작업에 대한 로그를 Logs 디렉토리에 기록합니다.

    사용 방법

    ▷ flatten,move ▷

    ▷ restore ▷

    이 리포지토리를 클론하거나 직접 압축파일을 다운로드 받아 파일정리를 원하는 곳으로 위치시킨 후에 실행시켜 사용합니다.

    폴더 경로의 구분자() 를 언더바(_)로 치환하여 바뀌어질 파일이름으로 사용하기 때문에

    원래의 파일명에 언더바가 섞여있는 경우 오작동을 일으킬 가능성이 매우 크며, 특히 원래 경로로 복원이 어렵습니다!!!

    (현재 구현하지는 않았으나, 로그파일을 참조하는 방식으로 위 문제를 해결 할 수 있을 것으로 보입니다)

    image

    위와 같이 해당 폴더의 주소창에 cmd를 입력하여 명령 프롬프트를 실행합니다.

    파일 평탄화 및 이동

    flatten_and_move.py 스크립트를 실행하여, 현재 디렉토리 및 하위 디렉토리에 있는 모든 파일을 flatten 디렉토리로 이동시키고 이름을 변경합니다.

    python flatten_and_move.py

    원본 경로로 파일 복원

    restore_files.py 스크립트를 실행하여, flatten 디렉토리에 있는 파일을 원래의 디렉토리 구조로 복원합니다.

    python restore_files.py

    기여 방법

    풀 리퀘스트 또는 이슈 등록 환영합니다.

    반응형
    COMMENT
     
    02
    02

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

    전체진행도 : 29일차

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

    작성일자 : 2024.02.02(금)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    오는 주 수요일까지, 개인과제 및 지급된 강의를 흡수하고 있다.

    우선 지급된 유티니 3D 프로젝트 강의를 보고 있는데, 보면서 조금이라도 의문이 드는 점은 매번 찾아 주석으로 남기고 있기 때문에, 무척 시간이 지체되고 있다.

    대충 이런 느낌으로 주석을 작성해가며 진행중

    주말도 열심히 활용해서 진도를 빼야 될 것 같다.

    2일차인데 과제까지 다 끝냈다는 수강생들도 많다는 게 괴담이다.

    2. 오늘 학습에 대해

    인코딩 문제 해결

    유니티 강의를 들으며 기능 구현을 할 때마다 커밋을 하며 진행중인데, 잠깐 지나치며 File Changes 내용을 보니 아래와 같이 인코딩 문제가 있었다.

    한글로 작성한 부분이 Github Desktop엥서 깨져보인다.

    유니티 프로젝트를 시작할 때 아래와 같은 에디터 설정을 해 주었어야 했는데 잊고있었다.

    프로젝트 루트 폴더에 .editorconfig 파일 마련

    .sin 폴더가 있는 루트 폴더에 위와 같은 내용을 가진 .editorconfig 파일을 마련해주고, 스크립트 파일을 다시 저장(Ctrl+S) 해 주면 해결이지만

    Github Desktop에서 파일 Changes를 확인 할 수 있다

    기능적으로 바뀐 부분 이외에 한글 주석을 달았던 부분이 모두 바뀌기 때문에 커밋 내용이 지저분해진다.

    가능한 빠른 시점에 .editorconfig 파일을 마련하도록 하는 것이 좋다.

    Code Solve

    매 문제를 풀 때마다 검색을 다섯 번 이상 한다. 마찬가지로 주석부분에 내용을 정리하였다.

    코딩테스트 연습 > 연습문제 > 공원 산책

    using System;
    using System.Collections.Generic;
    
    public class Solution {
        public int[] solution(string[] park, string[] routes) {
            int[] answer = new int[2];
    
            // 솔루션 : 주어진 내용을 정말 그대로 수행만 하면 될 것으로 보임
            // 0-1. NSWE의 튜플값 마련
            // 1. park를 한차례 순회하며(최대 2500회) S의 위치 찾기
            // 2. routes의 원소를 foreach순회
            // 2-1. 현재자리 기억
            // 2-2. for 반복문으로 0-1에서 마련한 튜플값만큼 이동 
            // 2-2-1. 이동 불가조건 만족 시 원래자리로 복귀
            // 3. 현재 자리(index) 반환
    
            // 0-1. NSWE의 튜플값 마련
            // 찾아보기. 제네릭으로 튜플값 사용하는 방법
            Dictionary<char,(int X,int Y)> going = new Dictionary<char, (int, int)>(){
                {'N',(0,-1)}, // (x, y) 순
                {'S',(0,1)},
                {'W',(-1,0)},
                {'E',(1,0)}
            };
            // 1. park를 한차례 순회하며(최대 2500회) S의 위치 찾기
            // 찾아보기. 튜플 변수 선언과 초기화
            // 찾아보기. (int, int) index와 같이 사용하는것은 불가능
            //          (int X, int Y) 로 수정
            //          이후 코드에서도 Item1 -> X, Item2 -> Y로 수정
            (int X, int Y) index = (-1,-1);
            for(int y=0;y<park.Length;y++)
                for(int x=0;x<park[0].Length;x++)
                    if(park[y][x]=='S'){
                        index = (x,y);
                        x = park[0].Length;
                        y = park.Length;
                    }
            // 2. routes의 원소를 foreach순회
            foreach(string directionInfo in routes){
                // 찾아보기: char로의 형변환
                char direction = char.Parse(directionInfo.Split(' ')[0]);
                int moveNum = int.Parse(directionInfo.Split(' ')[1]);
                // 2-1. 현재자리 기억
                // 찾아보기: 튜플값은 값 형식으로 다른 튜플에 할당하면 내용복사
                (int X, int Y) preIndex = index;
                // 2-2. for 반복문으로 0-1에서 마련한 튜플값만큼 이동 
                for(int i=0;i<moveNum;i++){
                    // 찾아보기: 튜플은 아래와 같이 +=는 사용 할 수 없다.
                    //index += going[direction];
                    index = (index.X + going[direction].X, index.Y + going[direction].Y);
                    // 2-2-1. 이동 불가조건 만족 시 원래자리로 복귀
                    // 찾아보기: 튜플 요소에 접근
                    if(
                        index.X<0 ||
                        index.Y<0 ||
                        index.X>= park[0].Length ||
                        index.Y>= park.Length
                    ){
                        index = preIndex;
                        break;
                    }
                    else if(park[index.Item2][index.Item1] == 'X'){
                        index = preIndex;
                        break;
                    }
                    // 이동 확인 구문
                    // Console.WriteLine($"{i}:({index.X}, {index.Y})");
                }
            }
    
            // 3. 현재 자리(index) 반환
            // 아래 구문은 왠지 잘 되지 않음.
            // (answer[0], answer[1]) = index;
            // 배열의 인덱스 위치에 직접 값을 할당하는 형태는 
            // 지원이 되는경우도 있고 안되는 경우도 있다는 듯
            answer[0] = index.Y;
            answer[1] = index.X;
    
            return answer;
        }
    }
    • 제네릭 형태로 튜플 값을 사용
      • 처음 시도 해 본 내용인데, 이게 되네 싶어서 놀랐다.
      • Dictionary<char,(int X,int Y)> going = new Dictionary<char, (int, int)>();
    • 튜플 변수
      • 마찬가지로 튜플 변수라는 것도 처음 사용 해 보았다. 배우거나 찾아본 거 없이 적은 구문이 맞는 구문이라 놀랐다..
      • (int, int) index = ... 형태는 C# 7.0 버전 이상에서 사용할 수 있다고 하고, 프로그래머스의 컴파일러도 이를 만족하지만 어째서인지 되지 않았다. 대신 아래와 같은 형태는 사용 가능했다.
      • (int X, int Y) index = ... 는 사용 가능. index.X 와 같이 사용 가능
      • 위와 같은 경우 값 참조이기 때문에, (int X, int Y) preIndex = index; 의 경우 값이 복사되며 두 튜플은 별개로 작동한다.
      • 튜플끼리 덧셈 연산자는 사용할 수 없다. index += going[direction];
      • 튜플 내부 변수명으로도 튜플 내의 값을 사용할 수도 있지만 .Item1, .Item2... 로도 사용 가능하다.
    • 한 글자로 된 문자열의 char 로의 형 변환
      • 왠지 사용했던 기억이 없어 익숙하지 않아 찾아보았다.
      • char direction = char.Parse("C"); 와 같이 char.Parse()를 사용 가능하고, "C"[0]과 같이 사용도 가능하다.

    3. 과제에 대해

    • 강의 흡수 및 개인 과제 수행(남은시간 주말 포함 5일)
    반응형
    COMMENT
     
    02
    02

    복귀 한 건 아니지만 오랫만에 통계 사이트에서 내 캐릭터를 확인 해 보았다.

    1년 반 정도 전 기점으로 활동을 하지 않아 업적점수는 동결중

    당시 시점에는 월드 랭킹 2위를 한동안 유지했었다.

    현재 15위 Ecchi Clone. 2년 전 당시에도 자주 보였던 닉네임들이 보인다.

    파판을 접은 동안 랭킹이 많이 떨어졌을 줄 알았는데, 아직 서버 15위에 위치중이다.

    Aegis 월드 순위권 점수

    현재 1위의 점수는 2만 6천점 정도이다. 모르는 닉네임인 걸 보면 단시간에 빡세게 올린 것으로 예상된다.

    당시 컨텐츠도 같이 했던 모모무츠상(전 1위), 타마토라상(전 3위), Kp상은 꾸준히 게임 하는 듯.

     

    복귀 하면 그 동안 쌓인 컨텐츠에서 뽑아먹을 게 정말 많아서 점수를 쭉쭉 올릴 수 있지 않을까 싶다.

    분신

    미코테 귀엽다

    반응형

    'Game > FFXIV' 카테고리의 다른 글

    [FFXIV] 탐험수첩 18번  (0) 2018.07.27
    [FFXIV] 탐험수첩 3번  (0) 2018.07.27
    [FFXIV] 탐험수첩 9번  (0) 2018.07.27
    [FFXIV] 탐험수첩 19번  (0) 2018.07.26
    [FFXIV] 탐험수첩 1번  (0) 2018.07.26
    COMMENT
     
    02
    02

    개요

    AlphaSlayer1964/kemono-dl

    kemono.su / coomer.su 에서 쉽게 일괄 다운로드를 할 수 있게 해 주는 파이썬 프로젝트이다.

    예전에 로컬에 Clone해서 사용하고 있었던 이게 오랫만에 쓰려고 보니 작동하지 않아, 여러번의 시행착오를 거쳐 잘 작동하도록 수정하였다.

    해당 깃허브 리파지토리의 Issues 목록을 확인 해 보니 역시 관련된 문제를 제기하는 유저들이 많았다.

    해결한 내용을 혼자만 알고 있기 아까워 처음으로 다른 개발자의 프로젝트를 손대보았다.

    사실 '오픈소스 프로젝트에 참여한다'는 게 어떤 행동까지 포함되는지 잘 알지 못하는데, 해당 프로젝트의 원래 개발자가 이 프로젝트를 public으로 던져두고 2년간 잠적한 상태라서 외부 개발자의 PR이 10개 가까이 쌓여있고, 마찬가지로 내가 수정한 코드가 반영 될 일은 없을 것 같다. 이걸 오픈소스 프로젝트라고 할 수 있을지는 잘 모르겠다.

    그래도 PR을 올려두면 다른 유저들도 수정사항을 조회 할 수 있기 때문에 의미없는 행동은 아닐 것이라고 생각한다.

    프로젝트 참여 과정

    해당 리포지토리의 우측 상단에서 Fork 기능을 사용하여 나의 리포지토리 목록에 해당 프로젝트를 복사 해 온다.

    정상적으로 Fork하였다면, 아래와 같이 새로운 리포지토리가 생성되어있고, forked from ... 의 메시지도 확인 할 수 있다.

    새로 생긴 리포지토리를 로컬에 Clone 하여 샥샥 수정 후 Commit과 Push를 하면 내 깃허브 리포지토리에 반영이 되는데

    내 리포지토리에서 Pull requests를 누르면 위와 같이 Fork했던 기존 프로젝트에 Pull Request를 할 수 있다.

    PR 메시지를 작성 후 올려두면, 아래와 같이 기존 프로젝트의 Pull Requests 목록에 올려진 것을 확인 할 수 있다.

    문제가 되었던 내용

    1. Kemono와 Coomer의 도메인 변경
    2. Kemono와 Coomer의 API 주소 변경
    3. Datetime - API 서버에서 반환되는 날짜 및 시간 문자열의 형식이 ISO 8601로 조정됨
    4. Datetime - 정수 및 문자열 데이터 타입의 혼동. 일부 데이터의 전달이 문자열(str) 타입에서 정수(int) 타입으로 바뀜

    Kemono와 Coomer의 도메인 변경

    두 서비스의 도메인이 kemono.party 와 coomer.party 에서 kemono.su 와 coomer.su 로 바뀌었다.

    세개 정도 소스코드에서 '.party'를 '.su' 문자열로 모두 찾아바꾸는 것으로 해결하였는데, 이 부분은 운좋게 해결 된 감이 없지않다.

    Kemono와 Coomer의 API 주소 변경

    기존에 요청을 보내던 API 주소가 제대로 응답을 주지 않았다. 설마 요청할 API의 주소가 바뀌었을 것이라고는 예상하기 어려울 것 같다.

    브라우저 창에서 기존에 요청하던 주소인 https://coomer.su/api/creators/ 에 접속 해 보아도 Not Found 를 받아오는 것을 보고 해당 주소의 파일구조를 확인 해 봐야겠다고 생각했다.

    루트 경로인 coomer.su 에서 개발자 도구를 통해 api로 향하는 파일구조를 확인할 수 있을 줄 알았지만, 찾아보니 이러한 방법으로는 구조를 확인 할 수 없다고 한다. coomer에서 api의 가이드를 해 주지 않는다면 알아내는 것은 어렵다고 한다.

    구글 검색을 하던 중, coomer가 제공하는 API 가이드를 발견하였다. 정말 기대조차 못했던 수확이다.

    확인 해 보니 아무래도 coomer는 API의 개선을 진행하고 있는 듯 하다. 기존 사용하던 API는 주소 뒤에 /v1라는 경로가 추가되어 있는 것을 확인했다.

    즉 소스코드에 쓰이고 있던 경로 문자열에서 .../api/....../api/v1/...로 바꿔주면 되었고, 모든 경로를 찾아 바꿔 해결 할 수 있었다.

    Datetime 관련 문제

    날짜에 관련된 문자열 처리 과정에서 문제가 있었다. 갈피를 잡지 못해 ChatGPT에게 소스코드를 보여주며 원인을 찾아보도록 하였는데, GPT는 API에 직접 접근하지 못하기 때문에 방향을 제시 해 주는 정도였다.

    입력으로 주는 날짜 정보 등을 출력해가며 이를 함께 제시 해 주니, 원인을 찾을 수 있었다.

    날짜 및 시간 형식의 불일치: API 서버의 주소 변경으로 인해 반환되는 날짜 및 시간 문자열의 형식이 ISO 8601로 조정되었습니다. 기존 코드는 특정 형식(예: %a, %d %b %Y %H:%M:%S %Z)을 기대했으나, API의 조정으로 인해 이 형식이 변경되었고, 이로 인해 기존의 datetime.strptime 함수로는 새로운 형식의 날짜 및 시간 문자열을 올바르게 파싱할 수 없게 되었습니다.

    즉 예전과는 다른 날짜형식을 API가 반환하고 있기 때문에 파싱 과정에서 문제가 되었다는 내용.

    coomer의 입장에서 보면 standard에 가까운 형식으로 반환하도록 수정한 것이기 때문에 방향성으로는 올바른 것 같다.

    그래서 문제가 된 것은, datetime.strptime함수로 새로운 형식의 날짜 및 시간 문자열을 올바르게 파싱할 수 없게 된 것이다.

    ISO 8601 형식에 대응 할 수 있도록 새로운 함수 parse_date_string를 마련하였다. 해당 함수는 여러 날짜 형식을 시도하고, 올바르게 파싱할 수 있는 형식을 찾으면 해당 형식으로 날짜 및 시간 문자열을 처리한다.

    또한, 문자열 타입(str)의 인자를 전달해야 하는 곳에 정수형 변수(int)가 전달되는 문제도 생겨, 필요한 경우 문자열로 변환하는 로직을 추가하였다.

    (위 작성한 내용의 절반 이상이 날아가 다시 작성함.. ㅠㅠ)

    Pull Request

    모든 것을 마치고 아래와 같이 PR을 작성하였다.

    다른 개발자의 프로젝트에 리퀘스트를 넣어보는 것은 처음이기도 하고 열심히 작성 해 보았다.

    반응형
    COMMENT
     
    02
    01

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

    전체진행도 : 28일차

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

    작성일자 : 2024.02.01(목)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    다시 개인과제 주차가 시작되었다.

    새로운 강의의 지급이 되어 해당 강의를 흡수하고, 개인 과제를 해결하여 제출하는 데에 주말 포함 7일이 주어졌다.

    또한 새로운 팀 편성을 하며 팀 스페이스를 새로 작성하였다.

    스크럼 로그
    트러블 슈팅 탬플릿
    오늘 먹은 거 자랑

    • 매일 회의를 간단하게 기록하거나, 세션에 대한 메모를 하는 스크럼 로그
    • 프로젝트 중 버그나 개선할 점 또는 추가하고 싶은 아이디어를 작성하는 데이터베이스
    • 쓸 사람만 쓰는 먹은 것 자랑 TIA

    팀에 활기를 불어넣어줄 것을 기대하며 위와 같은 내용들을 작성하였다.

    새롭게 주어진 강의는 3D 프로젝트에 관한 내용이었다. 오늘은 개발 환경을 구성하기 시작하는 단계까지만 진행하였다.

    저녁에는 메모리 관리에 관한 특강 세션이 있었고, 유익하게 배운 내용이 많았다.

    2. 오늘 학습에 대해

    Git Commit Message에 대해

    실제 여러 개발자들이 직접 개발에 참여하고 있는 프로젝트들을 살펴보았다.

    그 안에서도 많은 개발자들이 동시에 진행중인 PR(Pull Request) 목록들을 살펴보았는데, 기능 추가에 대한 내용을 예로 들면 소문자와 축약형의 단어인 feat보다 대문자로 시작하고 단어 전체의 Feature로 작성하는 개발자가 훨씬 많았다. 비율로 보면 1:5정도거나 그 이상 차이가 나는 정도.

    더 Standard한 메시지 형식을 사용하고 싶기 때문에, 이를 반영하여 앞으로 위와 같은 형식을 사용하도록 할 것이다.

    Unity3D - Skybox 만들기

    Material 생성

    • Project에서 Material로 생성

    Material의 Inspector에서 Shader 설정 및 파라미터 세팅

    • Inspector에서 Shader를 Skybox - Procedural(...etc)로 변경

    Lightning 윈도우. 에디터 우측 아래 아이콘 또는 Ctrl+9로도 열 수 있다.

    • Window - Rendering - lighting 의 Environment 탭에서 Skybox Material에 위에서 만든 Skybox를 사용

    특강: C# 메모리 관리

    메모리 : 프로그램/어플리케이션이 실행하는 데 필요한 데이터를 저장

    메모리는 아래와 같은 영역을 가진다.

    • Code : 프로그래머가 작성한 코드를 보관
    • Data : 어플리케이션 전반에 필요한 데이터 저장(static, const, 전역변수)
    • Heap : 참조 데이터(객체) 저장
    • Stack : 어플리케이션 실행 순서에 필요한 데이터 보관(로컬 변수, 매개 변수)

    일반적인 변수는 Stack에 저장된다.(int, bool, struct, enum ...)
    클래스 변수도 Stack에 저장된다. 다만 클래스 변수의 값은 '주소값'을 가진다.
    클래스 내부에 초기화된 데이터들은 Heap에 저장된다. 클래스 변수 내부의 주소값은, 이 데이터들이 저장된 Heap의 위치를 가리킨다.

    C#은 GC(Garbage Collection)라는 메모리 관리 기법으로 Heap에서 사용되지 않는 데이터들을 할당 해제하여 메모리의 공간을 확보한다.

    가비지를 가능한 적게 하는 프로그래밍이 좋은 개발이다.

    문자열을 계속 +=로 잇는 것은 해당 연산을 할 때마다 매번 새로운 공간을 할당받아 효율적이지 못하며 '문자열 파편화'라고 한다.
    문자열 파편화를 방지하는 방법은 아래와 같다.

    • string을 한번에 제작(보간문자열, 스트링 포맷, +연산자)
    • StringBuilder 사용
      • 처음부터 큰 메모리 공간을 할당받아 문자열을 채워나가는 방식
      • 물론 이 공간을 넘어가는 경우에는 더 큰 공간을 다시 할당받는다.

    만약 면접에서 구조체와 클래스의 차이를 물어보는 문제가 나온다면

    • 메모리 동작에 대한 잉해를 갖고 있는지를 물어보는 의도이다.
    • 구조체는 Stack에 데이터를 저장하는 '값형', 클래스는 Heap에 데이터를 저장하는 '레퍼런스형'이다.
    • 코드의 구조에 따라 어느 형태를 취하느냐에 따라 데이터 관리를 더 효율적으로 할 수 있다.

    static 데이터는 Data 영역에 저장

    • 프로그램 시작 시 생성
    • 프로그램 종료 시 삭제

    3. 과제에 대해

    • 이전 입문강의 및 이번 숙련강의 흡수
    • 개인과제 작성 및 제출
      • 내용 자체는 어렵지 않지만 디자인패턴을 활용하고 싶기 때문에 시간이 꽤 걸릴 것 같다
    반응형
    COMMENT
     
    02
    01

    대학교 자유 연구 때 다루던 Spleeter에 관한 내용이 생각나서 작성

    기본적으로 음원을 분리해주는 툴이라고 생각하며 좋다.

    2stems로 분리한다는 설정을 주면, 음원을 vocals.wavaccompaniment.wav로 분리해준다.

    엄청 깔끔하게 분리되는 편은 아니지만 나름 들을만 한 정도이다. (포스트 하단에 결과 첨부)

    로컬에서 Spleeter로 음원 분리 환경 구현

    python 환경에서 작동하며, 조금 검색을 해 보면 Google Colab에서도 사용할 수 있도록 마련된 환경이 보이는데, 어째서인지 런타임이 강제로 종료되는 등 잘 작동하지 않아 로컬 환경에서 구현하였다.

    선행하여 해 주어야 할 것이 두 가지 있다.

    1. pip install spleeter를 통한 spleeter 패키지의 설치
    2. FFmpeg의 설치. 공식 FFmpeg 웹사이트에서 운영체제에 맞는 설치 지침을 확인 할 수 있다.
      • Windows의 경우, FFmpeg의 바이너리(zip) 파일을 다운로드하고, 압축을 해제한 후, ffmpeg.exe가 포함된 디렉토리를 시스템의 환경 변수 Path에 추가한다.
      • 설치가 완료된 후, 터미널이나 커맨드 프롬프트에서 ffmpeg -version 명령어를 실행하여 FFmpeg가 올바르게 설치되었는지 확인할 수 있다.

    위 내용이 선행되었다면, 아래와 같은 내용의 Python 파일을 작성하여 실행한다.

    여러가지 옵션을 줄 수 있지만, 일단은 아래와 같이 작성하여 잘 작동하는지 확인한다.

    from spleeter.separator import Separator
    
    def main():
        # 오디오 파일 분리 설정
        separator = Separator('spleeter:2stems')
    
        # 분리할 오디오 파일 경로
        audio_path = './song.mp3'
    
        # 결과물을 저장할 디렉토리
        output_path = 'output/'
    
        # 오디오 파일 분리 실행
        separator.separate_to_file(audio_path, output_path)
    
        # 결과 확인 등의 추가 코드
    
    if __name__ == '__main__':
        main()

    if __name__ == '__main__':의 형식을 사용하지 않는다면 Python의 multiprocessing 모듈을 사용할 때 발생하는 문제를 겪게 된다.

    Windows 환경에서 multiprocessing을 사용할 때, 스크립트의 메인 블록이 올바르게 보호되지 않기 때문이라고 한다.

    다른 변수가 없다면, main.pysong.mp3 이외에 아래와 같은 파일구조가 만들어졌을 것이다.

    output/음원이름/ 위치에 결과물이 위치한다

    음원소스는 다음의 유튜브 동영상을 사용하였다.

    グッバイ宣言 / 百鬼あやめ cover

    song.mp3와 이로부터 생성된 vocals.wav(보컬), accompaniment.wav(반주)는 아래와 같다.

    성능을 보여주기 위해 첨부하는데, 출력된 파일 크기가 각각 20메가를 넘어, 부득이하게 크기를 줄여 첨부하였다.

    song.mp3
    2.64MB
    vocals_out.mp3
    6.57MB
    accompaniment_out.mp3
    6.57MB

    사용한다면 vocal쪽보다 반주쪽이 더 사용하기에 자연스러울 것 같다.

     

    Colab 에서 Spleeter로 음원 분리 환경 시도

    더하여, Colab에서 시도했던 내용은 아래와 같다.(공유)

    더보기

    # 스플리터와 ffmpeg 설치

    # 필요한 패키지들 설치
    !apt install ffmpeg
    !pip install spleeter

    # Spleeter 사용하여 음원 분리

    # 파일 업로드
    from google.colab import files
    uploaded = files.upload()

    # 업로드한 파일 목록 중 첫 번째 파일의 이름을 가져옵니다.
    file_name = next(iter(uploaded))

    # spleeter를 사용하여 파일 분리
    !spleeter separate -o output/ "$file_name"

    from IPython.display import Audio, display

    # 보컬 파일 재생
    vocals_path = 'output/song/vocals.wav'  # spleeter가 저장한 보컬 파일 경로
    display(Audio(vocals_path))

    # # 반주 파일 재생
    # accompaniment_path = 'output/song/accompaniment.wav'  # spleeter가 저장한 반주 파일 경로
    # display(Audio(accompaniment_path))

    마지막 블럭을 실행하면, 런타임 연결이 끊겨 실행할 수가 없게 된다. 아무래도 Colab 유료 플랜을 사용하고 있지 않은것이 문제라고 예상된다.

    반응형
    COMMENT
     
    01
    31

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

    전체진행도 : 27일차

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

    작성일자 : 2024.01.31(수)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 과제 '탕후루를 부탁해' 의 과제 발표날이다. 13분의 발표를 마치면 튜터진들의 피드백을 받는다.

    2. 오늘 학습에 대해

    발표 시 피드백 내용들

    <A조-탕후루>

    • 일정 등의 프로젝트 매니징 역할
    • 결과에 대해 더 볼륨을 주거나, 스토리가 있으면 더 좋았을 것

    <B조-펜타곤>

    • FPS 일정하도록
    • 게임 이펙트(파티클) 등을 주어 더 알아보기 쉽도록

    <C조-블록>

    • 깃 README.md도 잘 적어두면 포트폴리오 활용에도 좋을 것
    • 씬 한개로 구성된 게 아쉬웠다. 오브젝트가 너무 많음. 스크립트로 생성 삭제를 관리하면 좋을 것

    <D조-슈퍼마리오>

    • 소리가 전체적으로 일정치 않음
    • 전 조와 마찬가지로 오브젝트가 많이 쌓임. 탄막이 사라지게 하거나 필요.
    • 저장기능 있으면 더 좋을듯
    • 낮/밤 시간표기가 있으면 좋겠음

    <E조-고양이>

    • 리소스 폴더에 불필요한 파일 줄이기

    <F조-디버거코난>

    • 유니티 AI 몹 : 애네레이터? 나중에 과정중에 배울지도
    • mac/windows의 혼용으로 주석이 깨짐. 프로젝트 시작 때 인코딩 설정 utf-8로 설정하기.

    <G조-뱀서 느낌의 닷지>

    • (긍정)이동과 사운드의 싱크가 잘 맞춰져있음

    <참고하면 좋을 내용들>

    • 몰랐던 유니티 기능 : 마리오 블럭 관리하던 무언가(타일 룰?)
    • 몰랐던 유니티 기능 : 오디오 믹서 기능
    • 몰랐던 유니티 기능 : 유니티의 카메라 클램프로 일정 구역 벗어나지 않도록 하기
    • 몰랐던 유니티 기능 : 스테이지 맵 랜덤 생성
    • 도트 패널(또는 버튼)모양의 스프라이트, 유용해보임
    • 발표 시 모티브 게임 소개
    • 코...루틴? > GPT에게 물어보기
    • 발표자라면, 발표 전에 꼭 사운드 체크 리허설 하기
    • 발표 내용에서 TMI 줄이기
    • 개발자 게임이면, 탄막을 'Console.WriteLine("Shoot");' 같은 걸 쏘면 좋을지도
    • 데이터만 관리할 목적이면 Monobehavior를 상속받지 않는 것이 좋다는 의견
    • 유니티 모노비헤비어 라이프사이클
    • 플레이어 발사체가 몬스터 발사체를 삭제
    • draw.io라는 좋은 툴을 소개받았다. 와이어 프레임이나 순서도를 그리기 좋아 보이는 곳

    프로젝트를 마치며

    • 옵저버 패턴, 구독 패턴을 포함한 여러가지 스킬들을 흡수할 시간이 부족해 팀 프로젝트에서 활용하지 못한 게 아쉬웠다.
    • GameManager.cs의 역할을 맡으면서, 다른 스크립트에서 잘 활용할 수 있도록 구성을 했어야 했는데 그러지 못했다.
    • 짬 내서 디자인 패턴 및 관련한 내용은 다음 팀 프로젝트 전에 어서 흡수해야겠다.
    • 트러블슈팅 테이블 등 이전 조에서 작성했던 시스템들을 이번 조에 잘 끌어오지 못했다. 정말 아쉬움.

    특강 세션 : 연봉 1억 대기업 개발자 되는 법 - 어떻게 학습하고, 적응하고, 일해야 할까

    들으면서 도움이 될 만한 내용들

    • 내가 아는 것과 모르는 것을 알자(메타인지)
    • 키워드라도 남기면서, 요구사항/문제사항을 메모(TIL/WIL)
    • 채용공고를 점검하며 내 무기와 스킬을 계속 확인하기
    • 알고리즘 공부 시, DP, DFS/BFS 등 각각의 A4지(시트)에 문제 풀기 전에 한번씩 볼 수 있을 정도로 정리. 문제를 못풀면 관련 내용을 Develope 해간다.
    • 항상 기능 관점이 아닌 서비스 관점에서도 생각하기.

    3. 과제에 대해

    • 다시 돌아오는 개인 프로젝트 주차 잘 소화하기.
    반응형
    COMMENT
     
    01
    30

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

    전체진행도 : 26일차

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

    작성일자 : 2024.01.30(화)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 프로젝트 '탕후루를 부탁해'를 마무리 지었지만, 해상도 관련 대응이 되지 않는다는 내용이 있어 관련한 버그를 고치고, 개인 학습 시간을 가졌다.

    저녁에는 event와 Action에 대한 특강이 있어 강의와 질의시간을 가졌다.

    2. 오늘 학습에 대해

    팀 프로젝트 중 이슈

    변동 해상도에 대응되지 않음

    • 고해상도나 저해상도의 기기에서 실행할 경우
    • 게임 중 해상도의 변경이 있을 경우

    현재 개발을 계속 760x1280 해상도에 맞춰 해왔기 때문에, 더 고해상도가 된다면 UI배치가 전체적으로 화면의 가운데에 몰리고, 저해상도의 경우는 UI가 화면 밖으로 나가버리는 이슈가 있었다.

    이러한 경우, 아래와 같이 Canvas 오브젝트에서 UI Scale Moce를 기본값인 Constant Pixel Size 에서 Scale With Screen Size 로 수정해주면 해결할 수 있었다.

    설정을 적용할 경우, 어느 해상도에서도 Canvas가 스케일링되어 나오게 된다.

    목표 탕후루

    그러나 현재 스크립팅 된 내용으로는, 목표 탕후루를 Canvas의 position 기준으로 생성 위치를 선정했기 때문에, 해상도가 달라지면 목표 탕후루의 이미지도 엉뚱한 곳에 위치하는 문제가 있었다.

    목표 탕후루의 생성 로직 변경

    현재 목표 탕후루의 오브젝트는 Canvas의 하위에 생성되며, position을 설정하기 때문에, 해상도의 변경이 있을 경우, 목표 탕후루의 생성 지점도 유동적으로 움직일 수 없다.

    그러한 문제를 해결하기 위해, 목표 탕후루 오브젝트의 생성을 아래의 패널의 하위에 생성되도록 하여 부모의 위치를 기준으로 y값만 조절하여 과일을 배치할 수 있도록 하였다.

    부모 패널

    // 'TargetTanghulu' GameObject 생성 및 'Image'의 자식으로 설정
    GameObject targetTanghuluObject = new GameObject("TargetTanghulu");
    Transform imageTransform = GameObject.Find("TanghuluUI").transform.Find("Image");
    targetTanghuluObject.transform.SetParent(imageTransform, false);

    결과적으로 목표 탕후루 이미지의 생성되는 위치를 정상화 할 수 있었고, 해상도의 문제도 해결되어 아래와 같이 극단적으로 정사각형의 해상도를 가질 경우에도 게임 플레이를 할 수 있게 되었다.

    모든 이슈를 해결한 뒤의 스크린샷

    Code Solve

    코딩테스트 연습 > 연습문제 > 달리기 경주

    using System;
    using System.Linq;
    using System.Collections.Generic;
    
    public class Solution {
        public string[] solution(string[] players, string[] callings) {
            string[] answer = new string[] {};
    
            // 솔루션 1
            // 1. 선수이름으로 등수를, 등수로부터 선수이름을 아는 사전형 두개를 준비
            // 2. 선수 이름이 불리면
            // 2-1. 해당 선수의 등수 확인 (ex. 5등)
            // 2-2. 5등과 4등의 value값을 서로 바꾸며 이름 확인
            // 2-3. 해당 이름을 가진 두 선수의 등수를 바꾼다
            // 3. 모든 호명이 끝나면, 1등부터 answer에 입력
    
            // 솔루션 2
            // 다른 솔루션으로 생각해 볼 수 있는 건, ('5등이름','4등이름') 과 같이
            // ('선수','앞선선수')의 key,value값을 가지는 사전형을 사용해 볼 수도 있을 듯.
            // 이렇게 하면, 공간복잡도를 줄일 수 있다.
            // => 파기. 실제 해보니 ('앞선수','선수','뒷선수')의 리스트형이 필요한데, 공간복잡도도 낮지 않고 복잡함.
    
            // 1. 선수이름으로 등수를, 등수로부터 선수이름을 아는 사전형 두개를 준비
            Dictionary<string,int> playerToNum = new Dictionary<string,int>();
            Dictionary<int,string> numToPlayer = new Dictionary<int,string>();
            for(int i=0;i<players.Length;i++){
                playerToNum.Add(players[i],i);
                numToPlayer.Add(i,players[i]);
            }
    
            // 2. 선수 이름 호명
            foreach(string call in callings){
    
                // 2-1. 해당 선수의 등수 확인
                int num = playerToNum[call];
    
                // 2-2. 해당 등수 선수와 앞등수 선수의 value값을 서로 바꾸며 이름 확인
                string prePlayer = numToPlayer[num-1];
                numToPlayer[num] = prePlayer;
                numToPlayer[num-1] = call;
    
                // 2-3. 해당 이름을 가진 두 선수의 등수를 바꾼다
                playerToNum[prePlayer] = num;
                playerToNum[call] = num-1;
            }
    
            // 3. 모든 호명이 끝나면, 1등부터 answer에 입력
            // 검색 : Linq를 이용한 한줄 표현
            answer = numToPlayer.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value).ToArray();
    
            return answer;
        }
    }
    • 솔루션 2번을 시도하려다가, '뒷 선수'의 정보도 리스트에 필요하다는 것을 알게 되어 포기
    • Linq를 이용하여 한줄 코드
      • answer = numToPlayer.OrderBy(kvp => kvp.Key).Select(kvp => kvp.Value).ToArray();
      • 람다 표현식(kvp => kvp.Key) 사용
      • kvp는 KeyValuePair<int, string> 타입의 각 요소를 나타내며, NumToPlayer 딕셔너리의 각 항목을 순회하며 사용
      • =>는 람다 연산자라고 하며, 이를 기준으로 왼쪽은 입력 파라미터, 오른쪽은 함수 몸체를 나타냄
      • OrderBy(kvp => kvp.Key)를 호출하면, NumToPlayer의 각 KeyValuePair<int, string> 요소(kvp)를 kvp.Key (등수)를 기준으로 오름차순 정렬

    event와 Action 특강

    delegate, event, Action에 대해

    delegate : 함수에 대한 참조 타입. 함수를 변수처럼 저장하거나 매개변수로 전달 할 수 있음.

    delegate 반환형 델리게이트이름(매개변수);

    event : delegate의 한 종류. 옵저버 패턴으로 활용 할 수 있다.

    event 델리게이트이름 변수이름;
    // 델리게이트 변수 앞에 event 키워드를 붙인다

    Action : C#에서 제공하는 내장 delegate. 아래와 같이 두 줄을 한 줄로 줄여 쓸 수 있게 해 준다.

    public delegate void MoveDelegateFunc(Vector2 moveVector);
    public delegate void LookDelegateFunc(Vector2 lookVector);
    public delegate void ShootDelegateFunc(bool fire);
    
    public event MoveDelegateFunc OnMoveEvent;
    public event LookDelegateFunc OnLookEvent;
    public event ShootDelegateFunc OnFireEvent;
    public event Action<Vector2> OnMoveEvent;
    public event Action<Vector2> OnLookEvent;
    public event Action<bool> OnFireEvent;

    단, Action은 반환형이 void인 델리게이트에만 사용이 가능하다.

    구독 시스템

    1. 이렇게 event로 만든 변수에 여러 함수들을 추가 해 준 뒤
    2. 옵저버 역할을 하는 다른 클래스에서 특정 이벤트를 관측하여 해당 event 변수에 추가했던 메서드들을 모두 실행하도록 하는 패턴.
    // Observer.cs
    void OnMove(){
        Controller.CallMoveEvent(inputVector2);
    }
    // Controller.cs
    public event Action<Vector2> OnMoveEvent;
    public event Action<Vector2> OnLookEvent;
    public event Action<bool> OnFireEvent;
    
    public void CallMoveEvent(Vector2 direction)
    { OnMoveEvent?.Invoke(direction); }
    // Player.cs
    // 플레이어를 움직이는 메서드를, OnMoveEvent에 구독
    void Awake(){ Controller.OnMoveEvent += Move(); }
    void Move(Vector2 moveDirection){
        // 플레이어를 이동하는 구문들
    }

    플레이어 메서드에서 Move 메서드를 Controller의 OnMoveEvent에 구독해두고, 옵저버에서 키보드 입력을 관측하여 OnMove 메서드를 통해 CallMoveEvent 메서드를 실행하면 OnMoveEvent에 구독했던 모든 메서드들을 실행한다.

    특강 중 질의응답 내용

    델리게이트도 +=이 되나요?

    • Yes

    event가 존재하는 스크립트를 싱글톤으로 사용해도 되나요?

    • Yes, 오히려 그렇게 하게 될 듯 함.

    그래도 키가 눌렸는지 확인하려면 결국 Update는 써야겠죠?

    • Yes, 예) Player Input

    만약 등록된 모든 스크립트에 OnMove를 둔다면 다 따로동작하게도 할수 있나요?

    • Yes

    event들을 한군데로 모아서 이벤트 매니저를 만들어서 사용하는게 좀 더 효율적일까요?

    • 케바케겠지만 일단 Yes

    아까의 GameOver같은 경우는 자주 일어나는 상황이 아닌데, 옵저버로 매순간 관측하는 게 좋은가요?

    • Yes, Update()형태가 아니게 할 수도 있고, 한다고 해도 오버헤드가 나지는 않을 것

    델리게이트를 이벤트 처럼 쓸수있는데 반대는 못 하는거네요?

    • Yes, 델리게이트가 이벤트를 포함하는 관계

    혹시 이벤트를 너무 많이 사용하면 안 좋은 점도 있나요?

    • 비상식적으로 과한게 아니라면 No

    이벤트나 델리게이트를 하는일이 별로 없어도 습관적으로 모든곳에 쓰는게 낫다고 보면 될까요?

    • 개발적으로 권장되는 사항이며, 특히 공부 단계에서는 Yes.
    • 다만 단순한 프로그램에서는 효율이 좋지 않을 수 있음.

    이벤트를 호출할때 Invoke로 호출하는 이유가 있을까요?

    • ? (널러블)를 사용하기 위해.
      action?.Invoke() // 이와 같이 action이 null 일 경우에도 대응되도록 하기 위해 사용.
      위 구문은 if(action != null) action(); 과 같음

    3. 과제에 대해

    • 내용 정리 후 발표 준비
    반응형
    COMMENT
     
    01
    30

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

    전체진행도 : 26일차

    부분진행도 : Chapter3.2 - 4일차

    작성일자 : 2024.01.29(월)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 플젝을 마무리하고 개인 공부 시간을 가지기로 했다.

    버그 픽스까지 마치고 피로함에 뻗어서 오늘은 넉다운 상태..

    2. 오늘 학습에 대해

    • 코테 연습 한 문제

    코딩테스트 연습 > 2023 KAKAO BLIND RECRUITMENT > 개인정보 수집 유효기간

    using System;
    using System.Collections.Generic;
    
    public class Solution {
        public int[] solution(string today, string[] terms, string[] privacies) {
            int[] answer = new int[] {};
    
            // 프로그래머스에서 메서드 사용하는 법 및 출력메시지 띄우는 법 확인
            // hello();
    
            // 예상 솔루션
            // 1. terms는 사전형 변수에 저장
            // 2. 년도에는 365, 달에는 28, 일에는 1의 가중치를 두어 각 정보와 today를 비교
            // 3. privacy가 같거나 작을 경우, result에 추가
            // 3-1. result는 값 갯수의 변동이 있으므로 리스트로 관리
            List<int> result = new List<int>();
    
            // 1. terms를 사전형 변수에 저장
            Dictionary<char,int> termsDict = new Dictionary<char,int>();
            foreach(string term in terms){
                // 검색: split 사용법(파이썬의 term.split())
                // -> 마찬가지로 term.Split(' '); 과 같이 사용 가능
                // 검색: 특정 인덱스 이후 모든 문자열을 사용하는법(파이썬의 term[2:])
                // -> term.Substring(2); 과 같이 사용 가능
                char key = term[0];
                int value = int.Parse(term.Substring(2));
    
                // Console.WriteLine($"{key}: {value}"); // 테스트 구문
    
                // 검색: 사전형에 요소 추가
                // -> termsDict.Add(key, value);
    
                termsDict.Add(key, value);
            }
    
            // 2. 년은 365, 달은 28, 일은 1의 가중치를 두어 각 정보와 today를 비교
            int today_value = 0;
            today_value = 
                int.Parse(today.Substring(0,4)) * 28*12 +
                int.Parse(today.Substring(5,2)) * 28 +
                int.Parse(today.Substring(8,2)) * 1
                ;
            Console.WriteLine($"Today value: {today_value}"); // 테스트 구문
            for(int i=0; i<privacies.Length; i++){
                string privacy = privacies[i];
                int limitDay_value = 
                    int.Parse(privacy.Substring(0,4)) * 28*12 +
                    int.Parse(privacy.Substring(5,2)) * 28 +
                    int.Parse(privacy.Substring(8,2)) * 1 +
                    termsDict[privacy[11]] * 28
                    ;
                Console.WriteLine($"privacy index[{i+1}] value: {limitDay_value}"); // 테스트 구문
                // 3. privacy가 작을 경우, result에 추가
                if(limitDay_value <= today_value)
                    result.Add(i+1);
            }
    
            answer = result.ToArray();
    
            return answer;
        }
    
        public void hello(){
            Console.WriteLine("Hello");
        }
    }
    • 프로그래머스에서 메서드 사용하는 법 및 출력메시지 띄우는 법
    • 문자열에서 특정 문자를 기준으로 쪼개는 법 // .Split(' ')
    • 문자열 내에서 특정 인덱스 구간의 문자열을 떼어 사용하는 법 // .Substring(0,4)
    • 사전형에 요소 추가하기 // .Add(key, value)

    3. 과제에 대해

    • 의견 모아 제출과 발표 마치기
    반응형
    COMMENT
     
    01
    27

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

    전체진행도 : 25일차

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

    작성일자 : 2024.01.26(금)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    팀 과제 '탕후루 만들기' -> (개명)'탕후루를 부탁해!' 의 대부분 기능을 구현하였다. 사운드쪽만 월요일까지 작업하는 것으로 하고, 나머지 시간은 QA에 할애 할 것으로 보인다.

    팀 과제에 할애하느라 알고리즘 풀이를 거의 일주일만에 했는데 간단한 것도 생각이 안 나 찾아보며 진행했다.

    2. 오늘 학습에 대해

    오늘의 팀 프로젝트 커밋

    한 게 정말 많지만, 기록하기에 할애할 여유가 없어 매일 아쉽다.

    지금까지 진행하며 가장 아쉬운 점은 GameManager.cs에 너무 많은 량의 로직 코드를 작성했다는 것이다.

    보기에도 지저분하고, GameManager에 대한 의존도가 심각하게 높다.

    게임 전체를 총괄하는 역할은 맞지만, 세세한 공정까지 모두 직접 하는 애매한 포지션이 되어버렸다.

    이러한 생각을 하며 대화형 인공지능에게 코드리뷰를 맡겨 본 결과는 아래와 같다.


    유니티에서 GameManager와 같은 중앙 관리 클래스를 사용하는 것은 일반적인 패턴입니다. 이는 게임의 전반적인 상태 관리, 싱글톤 구현, 씬 전환, 점수 관리 등을 위해 사용될 수 있습니다. 하지만, 제공하신 코드를 보면 몇 가지 개선할 수 있는 부분이 보입니다:

    1. 책임 분리(Separation of Concerns): 현재 GameManager 클래스에는 여러 책임이 혼재되어 있습니다. 예를 들어, UI 관리, 게임 상태 관리, 씬 로딩, 점수 계산 및 고점수 관리, 사운드 관리 등이 모두 한 클래스 내에 구현되어 있습니다. 이러한 다양한 책임들을 별도의 클래스로 분리하여 각각의 관심사를 더 명확히 구분하는 것이 좋습니다. 예를 들어, UI 관련 로직은 UIManager, 점수 및 고점수 관리는 ScoreManager, 사운드 관리는 AudioManager와 같이 별도의 클래스로 분리할 수 있습니다.
    2. 긴 메소드 및 클래스: GameManager 클래스와 그 내부의 메소드들이 매우 길고 복잡합니다. 이는 코드를 읽고 이해하는 데 더 많은 시간이 소요되며, 유지 보수가 어려워질 수 있습니다. 각 메소드가 하나의 명확한 기능만 수행하도록 리팩토링하고, 필요하다면 복잡한 기능을 여러 개의 보조 메소드로 분리하는 것이 좋습니다.
    3. 정적 접근과 싱글톤: GameManager.Instance를 통해 전역적으로 접근 가능한 싱글톤 패턴을 사용하고 있습니다. 이는 편리할 수 있지만, 오버유즈는 전역 상태에 대한 의존성을 높이고 테스트와 유지보수를 어렵게 만듭니다. 싱글톤 사용을 최소화하고, 가능한 컴포넌트 간 의존성을 명시적으로 만드는 것이 좋습니다.
    4. 하드코딩된 값과 마법의 숫자: 코드 내에서 직접적으로 하드코딩된 값(예: "HighScore", "_MainScene" 등)과 마법의 숫자(예: 0.1f, 3 등)를 사용하고 있습니다. 이러한 값들은 상수로 정의하거나 구성 파일에서 불러오는 방식으로 관리하는 것이 좋습니다. 이렇게 하면 나중에 값을 변경할 때 한 곳에서만 수정하면 되므로 유지 보수가 용이해집니다.
    5. 리소스 관리: Resources.Load를 사용하여 리소스를 불러오는 방식은 유연하지만, 사용하지 않는 리소스가 메모리에 남아있을 수 있으므로 효율적인 리소스 관리를 위해 주의가 필요합니다. 가능하다면, 유니티의 Addressable Asset System 같은 더 효율적인 리소스 관리 시스템을 고려하는 것이 좋습니다.
    6. 게임 상태 관리: 게임 상태를 enum과 switch 문으로 관리하는 것은 간단한 프로젝트에는 적합할 수 있지만, 상태가 복잡해질수록 관리가 어려워집니다. 상태 패턴(State Pattern)을 적용하여 각 상태를 별도의 클래스로 구현하고, 이를 통해 상태 전환 로직을 더 명확하고 유지보수하기 쉽게 만들 수 있습니다.

    코드를 개선하려면, 위의 지점들을 고려하여 리팩토링을 진행하는 것이 좋습니다. 이는 코드의 가독성, 확장성, 유지 보수성을 향상시키고, 장기적으로 프로젝트의 품질을 높이는 데 도움이 됩니다.


    모든 씬에서 독립적인 클래스 상태를 가지는 싱글톤에 대해 배우며 적극적으로 활용하자고 생각했지만, 실제 프로젝트를 진행 해 보면 싱글톤이 마냥 좋은 것만은 아니었다. 특히 씬이 갱신 될 때마다 초기화를 해줘야 하는 번거로움을 싱글톤 스크립트에서 해결하려고 하면 코드가 무척 길어지고, 씬이 로드 되는 도중에 실행되지 않도록 별도의 작업도 하나하나 해 주어야 하는데 개발 과정에서 많은 버그를 일으키고 시간이 소비된다.

    프로젝트의 전체 스크립트 파일

    위와 같은 문제점들은 다른 스크립트에 책임분리를 함으로써 해결되는 부분이기 때문에, 스크립트 파일이 많아지는 것에 거부감을 갖지 않고 활용하면 좋겠다는 생각을 하였다.

    내가 맡은 파트에서 GameManager.csUIManager.cs 에서 대부분을 해결했는데, DisplayMainPanel.cs 스크립트를 하나 추가하여 사용한 것 만으로도 숨통이 트이는 듯한 기분을 느꼈다.

    알고리즘

    오늘은 가볍게 한 문제만 풀이하였다. 별도의 메모를 해 가며 풀이하는 건 너무 많은 에너지가 소모되어, 코드를 작성하며 생각하고 참고한 내용들을 주석에 적는 방식을 채택하였다.

    코딩테스트 연습 > 2022 KAKAO TECH INTERNSHIP > 성격 유형 검사하기

    using System;
    using System.Linq;
    using System.Collections.Generic;
    
    public class Solution {
        public string solution(string[] survey, int[] choices) {
            string answer = "";
    
            // 일주일 쉬어서 감 다 죽음
    
            // 찾아보기-1. choices의 각 원소를 3 빼서 새로운 배열에 담는 법(Linq)
            // int[] newChoices = choices.Select(choice => choice - 4).ToArray();
                // choices.Select(choice => choice - 4)를 사용했을 때의 결과 자료형은 IEnumerable<int>
                // LINQ의 Select 메서드는 컬렉션의 각 요소에 대해 지정된 변환 함수를 적용하고
                // 변환된 요소들을 포함하는 새로운 IEnumerable<T> 시퀀스를 반환
                // 변환 함수 choice => choice - 4는 정수를 반환하기 때문에, 결과 시퀀스의 타입은 IEnumerable<int>
    
            // 생각해보니, 위 방식은 설문결과의 분산(?)을 알 수 없게 만드는 요인이 되기 떄문에 사용하지 않도록 했다.
    
            // 찾아보기-2. 사전형식 자료형 초기화
            Dictionary<char, int> scores = new Dictionary<char, int>{
                {'R',0},{'T',0},{'C',0},{'F',0},
                {'J',0},{'M',0},{'A',0},{'N',0},
            };
                // var 사용과 Dictionary<char, int> 사용 중 자유롭게 선택. 장단점은 특별한 건 없고 상황이나 취향에 맞게.
    
            // 각 문항별 점수 집계
            int threshold = 4;
            for(int i = 0; i < choices.Length; i++){
                int selectedNum = choices[i];
                if(selectedNum<threshold)
                    scores[survey[i][0]] += (threshold - selectedNum);
                if(selectedNum>threshold)
                    scores[survey[i][1]] += (selectedNum - threshold);
            }
    
            // 성격유형 진단
            if(scores['R']>=scores['T'])    answer += 'R'.ToString();
            else                            answer += 'T'.ToString();
            if(scores['C']>=scores['F'])    answer += 'C'.ToString();
            else                            answer += 'F'.ToString();
            if(scores['J']>=scores['M'])    answer += 'J'.ToString();
            else                            answer += 'M'.ToString();
            if(scores['A']>=scores['N'])    answer += 'A'.ToString();
            else                            answer += 'N'.ToString();
    
            return answer;
        }
    }
    • 코드를 간결하게 하는 Linq, 사전형의 사용을 다시 찾아보았다.
    • 탭을 활용하여 반복되는 코드를 깔끔하게 정리했다.

    탭을 활용하는 건 처음 시도 한 방법인데, 이전 팀원 중 한명의 코드 작성 방식이 떠오른 겸 적용 해 보았는데 깔끔하게 작성된 것을 보고 흡족했다.

    3. 과제에 대해

    • 팀 과제 지속 리팩토링

    4. 참고자료

    • 없음 / ChatGPT-GPT4 는 잊어버렸던 내용이나 코드리뷰 등 적재적소에 활용중
    반응형
    COMMENT
     
    01
    25

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

    전체진행도 : 24일차

    부분진행도 : Chapter3.2 - 2일차

    작성일자 : 2024.01.25(목)

    개발일지 목록 : 클릭


    1. 진행중인 과정에 대해

    오늘의 악마, 절대 팝업되지 않는 설정 패널

    팀 과제 탕후루 만들기 2일차이다. 기본적인 게임진행 로직, 장면디자인과 상호작용을 오늘 대부분 완료한 것으로 보인다.

    다만 자잘한 버그가 정말 많고 하나하나 별 거 아닌 기능인데 해결이 안되어 아주 골칫거리이다.

    오는 수요일까지 과제제출 및 결과 발표라 생각보다 여유 시간이 될 지 모르겠다.

    오후에는 챌린지 클래스 세션이 있었다. 델리게이트에 관한 소개와 예시로 세션을 진행하였다. 전혀 몰랐던 내용이긴 하지만 파이썬에서도 비슷한 방식으로 함수를 사용했기 때문에 흡수하는 데에 무리는 없었다.

     

    2. 오늘 학습에 대해

    [Serialized] 에 대해 새로 배운 사용법

    팀원 한명이 에디터에서 플레이 버튼을 누르고 [Serialized]를 통해 변수가 바뀌는 걸 보며 디버깅을 한다는 얘기를 들었다. 생각 못 해본 방법이었는데 꽤나 괜찮다고 생각했다.

    싱글톤 오브젝트의 치명적인 단점

    예를 들어 ButtonManager.cs에서 '패널을 활성화하는 메서드'를 작성한 후, 다른 씬의 버튼에서 이걸 사용하려고 한다면 에디터의 버튼 이벤트로 작용하는 OnClick에 ButtonManager 오브젝트를 드래그 할 수가 없어, 스크립트로 할당하는 게 강제되는 것으로 보인다.

    구글링이랑 GPT를 통해 알아봐도 스크립트의 구성을 다르게 하는 것 말고 깔끔한 해결법은 없는 것으로 보인다.

    싱글톤 오브젝트를 사용 시 문제점 두번째

    씬 전환 등을 할 때 Start나 Awake가 실행되지 않기 때문에 다른 방안을 모색해야 했다.

    씬의 로딩이 완료되고 다른 오브젝트들이 만들어진 후 스크립트를 실행하는 방법의 예는 아래와 같다.

        private void Start()
        {
            SceneManager.sceneLoaded += OnSceneLoaded;
        }
    
        private void OnDestroy()
        {
            SceneManager.sceneLoaded -= OnSceneLoaded;
        }
    
        private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            FindAndAssignUIElements(scene);
        }
    
        private void FindAndAssignUIElements(Scene scene)
        {
            // 로드되는 씬 이름에 따라 UI를 로드
            if (scene.name == "_TitleScene")
            {
                GameObject canvasObject = GameObject.Find("Canvas");
                highscore = GameObject.Find("HighScore").GetComponent<Text>();
                settingsPanel = GameObject.Find("SettingUI");
            }
            else if (scene.name == "_MainScene")
            {
                GameObject canvasObject = GameObject.Find("Canvas");
                pausedPanel = canvasObject.transform.Find("PauseUI").gameObject;
                settingsPanel = GameObject.Find("SettingUI");
                score = canvasObject.transform.Find("ScoreUI").transform.Find("Score").GetComponent<Text>();
                resultPanel = canvasObject.transform.Find("ResultUI").gameObject;
                resultScore = canvasObject.transform.Find("ResultUI").transform.Find("ScoreText").GetComponent<Text>();
                resultHighScore = canvasObject.transform.Find("ResultUI").transform.Find("HighScoreText").GetComponent<Text>();
            }
    
            // 씬 로드 이벤트 발생할 때마다 초기 상태를 비활성화로 설정
            SetPanelActive(settingsPanel, false);
            SetPanelActive(pausedPanel, false);
            SetPanelActive(resultPanel, false);
        }

    씬의 이름에 따라 여러 UI 오브젝트들을 UIManager에 할당해준다. 다만 이런 방식은 UIManager가 처음 생성될 때는 작용하지 않는 것으로 보인다. SettingUI 오브젝트를 어째선지 다른 방법을 동원해도 UIManager에 할당하지 못하여 아직도 타이틀메뉴에서 세팅 창을 열지 못하는 상태이다. 정말 단순한 방법으로도 되지 않은 것으로 보아 무언가 알 수 없는 꼬임이 발생한 것 같다.

    3. 과제에 대해

    • 팀 프로젝트 버그픽스 위주로 계속 진행하기
    반응형
    COMMENT