정렬하기

SELECT *
FROM players
WHERE birthYear IS NOT NULL
ORDER BY birthYear DESC, birthMonth DESC, birthDay DESC;

birthYear가 NULL값을 제외하고 내림차순으로 정렬한다. birthYear값이 같다면 birthMonth ,birthDay 까지 비교하여 내림차순 정렬처리한다.

 

SELECT TOP PERCENT*

데이터중 5퍼센트만 뽑아내기

 

SELECT TOP 100*

TOP 100개 뽑아내기

 

OFFSET 100 ROWS FETCH NEXT 100 ROWS ONLY;

100개행을 건너뛰고 그 다음 100개 행을 출력하기

'STUDY > 데이터베이스' 카테고리의 다른 글

[SQL] 인덱스 컬럼 순서  (0) 2022.07.20
[SQL] 연습 Clustered / NonClustered  (0) 2022.07.16
[SQL] 연습 복합인덱스 #4  (0) 2022.07.12
[SQL] 연습 DATABASE 작성 #3  (0) 2022.06.30
[SQL] 연습 SELECT,WHERE #1  (0) 2022.05.10

1.

SELECT  nameFirst, nameLast, birthYear

FROM players
WHERE birthYear = 1866

players파일에서 nameFirst, nameLast, birthYear중 birthYear가 1866인 데이터들만 추려내도록 하였다.

더보기
블록을 지정해서 실행하면 WHERE 문을 실행시키지 않음

 

2. WHERE 조건 조합하여 체크하기

 

3. 패턴 이용하기

- 'New Yor' 이후 한 글자 까지 찾기

- 'New York%'  New Yor으로 시작하는 모든 데이터 찾기

'STUDY > 데이터베이스' 카테고리의 다른 글

[SQL] 인덱스 컬럼 순서  (0) 2022.07.20
[SQL] 연습 Clustered / NonClustered  (0) 2022.07.16
[SQL] 연습 복합인덱스 #4  (0) 2022.07.12
[SQL] 연습 DATABASE 작성 #3  (0) 2022.06.30
[SQL] 연습 ORDERBY #2  (2) 2022.05.17

 

이 시점(현재 경력)에서 이런 글을 작성하는데 시간을 쓰는 것보다, 자료구조 공부 또는 알고리즘 문제를 한 문제 더 푸는 것이 더 좋다고 생각도 했지만 하지만 이렇게 나의 생각을 한 번쯤은 정리해보고 싶은 생각이 들었다. 

좋은 코드를 작성하는 방법은 무엇일까?

좋은 코드란? 좋은 코드는 빠르게 읽히고 유지보수가 쉬우며 수정사항이 있을 때 몇 개의 함수 호출로 원하는 기능을 수정/추가가 가능한 코드일 것이다.

하지만 기업에서 많은 사람들의 협업을 통해 업무가 진행되는 프로그램에서 이런 코드는 현실적으로는 힘들어 보인다.

 

버그를 수정할 때는 현재 수정하려는 부분에서 코드들의 한 줄 한 줄 의미를 파악해야 하며 수정했을 때 발생할 사이드 이펙트에 관해서도 꼼꼼히 살펴야 한다. 이 코드는 그럴 것이다~ 또는 누군가가 작성한 함수의 이름만 믿고 그런 기능을 하는 함수겠지 하는 생각으로 대충 리딩하고 버그를 수정하다 보면 나중에 크게 데일수 있다.. 그렇기 때문에 우리는 좋은 코드를 작성하려고 노력해야 한다.

 

이런 좋은 코드들이 나중에 프로그램이 실제로 서비스됐을 때 버그 수정에서도 빠르게 수정이 됐었고, 그 좋은 코 드위에서 작업을 할 때도 그렇지 않은 코드보다 훨씬 시간이 감축됐다. (어느 위치에, 어느 함수에 있어야 할 기능이 딱딱 있었다.)

 

좋은 코드를 작성하는 방법은 주어진 상황에 맞는 판단하여 작성하는 코드가 아닐까 한다. 

서비스하려는 프로그램을 만들다 보면 당연히 현재 코드가 실제 서비스됐을 때 새로운 기능은 쉽게 수정/추가/제거가 가능한지, 발생 가능한 이슈들을 예상하며 만들게 된다. 이러한 코드 작성은 당연히 시간이 많이 소요된다.

 

근데, 이 상황에서도 우리가 지향하는 좋은코드로 작성하는 게 맞을까?

예를 들어, 나에게 업무가 할당됐다. 그 업무는 이틀 안에 3가지 프로토타입을 발표하고 그중에서 선택받은 프로그램으로 본격적인 개발을 하기로 했다. 

 

그런데 미래 확작성을 고려한 베이스 코드부터 여러 기능을 미리 만들어 놓고 유지보수도 생각하면서 개발하다 설계과정에서부터 많은 시간이 소요돼 기한 내에 업무를 처리하지 못할 수 있다. 정작 결국에 사용하지 않을 코드나 기능을 만드는데 시간을 쓴 것이다. 그렇다면 코드로만 봤을 때 좋은 코드일 수는 있어도 좋은 코드 작성방법에서는 맞지 않는다고 생각한다. 

 

당연히 프로토타입을 만드는 과정에서도 수정사항과 여러 이슈가 발생할 수 있다.

말하고 싶은것은 작성하려는 상황에서 개발 속도와 그 상황에 어느 정도 예상되는 유지보수성까지만 살려 적절하게 작성하는 것이다.

시간이 돈으로 연관되는 이런 상황에서 이 두 개의 중점을 잡기는 힘들다. 업무를 하면서 이런 판단을 해야 할 일들이 많았고 또, 그 판단 결과가 만족스럽지 않은 적이 많았다. 이런 판단을 하려고 노력하다 보면 이 경험들이 점차 좋은 판단력을 얻어지지 않을까 생각한다.     

 

 

  

제한된 이미지 사이즈 안에서 들어가야 할 글자가 초과될 경우 사용하면 될듯하다.

좌우 방향 설정과 속도조절이 가능하다.

 

 

깃허브 링크

https://github.com/hahahohohun/PublicCode/blob/main/README.md#-cmovetextcs

 

GitHub - hahahohohun/PublicCode

Contribute to hahahohohun/PublicCode development by creating an account on GitHub.

github.com

+) 특정 상태에 따라 흐를 것인가 멈출 것인가를 기능을 넣으면 좋을 듯

선택한 오브젝트를 제외한 오브젝트의 SetActive를 false처리한다. 단축키는 Ctrl + Shift + Q

 

같은 부모 오브젝트 아래에서 작동

 

최상단 오브젝트에서 작동

 

-----------------------------------------------------------------------------------

2021.12.13

단축키를 사용하기전에 애초에 꺼져있는 오브젝트는 다시 켜지지 않도록 처리

1.Ctrl + Shift + Q

2.해당 오브젝트의 포지션값 변경

3.Ctrl + Shift + Q (꺼져있던 오브젝트 다시 활성화)

-----------------------------------------------------------------------------------

 

 

https://github.com/hahahohohun/PublicCode/blob/main/README.md#eccustomtoolscs

 

오브젝트에 인덱스를 추가하여 이름을 변경이 필요할때!

ctrl + D 를 통해 생성한 오브젝트는 (1),(2),(3)~~~ 이름 마지막에 이렇게 숫자가 붙는다. 하나 하나 바꾸기 번거롭기때문에 이것을 한꺼번에 변경이 가능한 간단한 툴을 만들었다.

 

https://github.com/hahahohohun/PublicCode/blob/main/README.md#ecnamingcs

 

파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀점

문제

상근이가 가지고있는 숫자중 제시된 숫자의 갯수를 구하는 문제이다.

풀이 : 내가 작성한 코드

            int[] arry = new int[20000001];
            int n = int.Parse(Console.ReadLine());
            string[] nstr = Console.ReadLine().Split(' ');

            int m = int.Parse(Console.ReadLine());
            string[] mstr = Console.ReadLine().Split(' ');
            int nIdx = 10000000;
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < nstr.Length; i++)
            {
                arry[nIdx + int.Parse(nstr[i])]++;
            }
            for (int i = 0; i < mstr.Length; i++)
            {
                stringBuilder.Append(arry[nIdx + int.Parse(mstr[i])] + " ");
            }
            Console.WriteLine(stringBuilder.ToString());

단순히 이중for문으로 풀수도있지만 시간초과가 날수 있기 때문에 카운트소팅으로 문제를 해결했다. 카운트할 갯수를 저장한 배열을 선언하다 범위가  [-10,000,000보다 크거나 같고, 10,000,000보다 작거나 같다] 이기 때문에 배열크기를 20,000,001로 할당했다. 

예를들어 -9000000 이 값이 나오면 10000000을 더해 1000000인덱스에 접근하여 1을 더해준다.

 

그렇게 하면 다른 조건처리 없이 배열의 값을 할당할 수 있기 때문이다. 20,000,000이 아닌 20,000,001인 이유는 0의 카드도 나올수 -10000000인 카드가 나오면 배열의 인덱스는 0을 가리키기 때문이다. 

 

https://www.acmicpc.net/problem/10816

 

10816번: 숫자 카드 2

첫째 줄에 상근이가 가지고 있는 숫자 카드의 개수 N(1 ≤ N ≤ 500,000)이 주어진다. 둘째 줄에는 숫자 카드에 적혀있는 정수가 주어진다. 숫자 카드에 적혀있는 수는 -10,000,000보다 크거나 같고, 10,

www.acmicpc.net

 

파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀 점

문제

스택을 활용하는 문제이다.

풀이 : 내가 작성한 코드

            int length = int.Parse(Console.ReadLine());
            int[] nArray = new int[length];
            for (int i = 0; i < length; i++)
            {
                nArray[i] = int.Parse(Console.ReadLine());
            }

            int nCount = 0;
            int nIdx = 1;
            StringBuilder stringBuilder = new StringBuilder();
            Stack<int> stack = new Stack<int>();
            stack.Push(nIdx);
            stringBuilder.Append("+\n");

            while (nCount != length)
            {
                while (true)
                {
                    if (stack.Count != 0 && nArray[nCount] == stack.Peek())
                    {
                        int nPop = stack.Pop();
                        stringBuilder.Append("-\n");
                        nIdx = nPop > nIdx ? nPop : nIdx;
                        break;
                    }
                    else
                    {
                        nIdx++;
                        stack.Push(nIdx);
                        stringBuilder.Append("+\n");
                    }
                    if (nIdx > length)
                    {
                        Console.WriteLine("NO");
                        return;
                    }
                }

                nCount++;
            }
            Console.WriteLine(stringBuilder.ToString());

신경 써야 할부분은 스택에서 pop 된 후 바로 다음으로 나열되어야 하는 수가 peek에 있지 않으면 답은 "NO"가 출력되어야 한다.

처음에는 Idx로 push 할 때 +1해 주고 pop 할 때 -1을 했지만 그렇게 되면 pop 됐던 값이 다시 push 되기 때문에 nPop을 통해 방금 꺼낸 pop 된 값이 크면 nIdx값을 변경해줬다. 

 

예상된 수열로 불가능한 예시

 

https://www.acmicpc.net/problem/1874

문제

BFS문제로 인접한 배추들의 갯수를 그룹이 몇개있는지 체크하는 문제이다.  

풀이 : 내가 작성한 코드

public static void Main()
    {
            int length = int.Parse(Console.ReadLine());
            for (int i = 0; i < length; i++)
            {
                string[] str = Console.ReadLine().Split(' ');
                int x = int.Parse(str[0]);
                int y = int.Parse(str[1]);
                int count = int.Parse(str[2]);
                Queue<(int, int)> queue = new Queue<(int, int)>();

                int[,] arry = new int[x, y];
                int[,] vis = new int[x, y];

                for (int k = 0; k < count; k++)
                {
                    string[] strpos = Console.ReadLine().Split(' ');
                    arry[int.Parse(strpos[0]), int.Parse(strpos[1])] = 1;
                }

                int[] dx = new int[] { 1, 0, -1, 0 };
                int[] dy = new int[] { 0, 1, 0, -1 }; // 상하좌우 네 방향을 의미
                int nG = 0;
                for (int z = 0; z < x; z++)
                {
                    for (int v = 0; v < y; v++)
                    {
                        if (vis[z, v] == 1 || arry[z, v] == 0)
                        {
                            continue;
                        }

                        queue.Enqueue((z, v));
                        nG++;
                        while (queue.Count != 0)
                        {
                            var cur = queue.Dequeue();
                            int curx = cur.Item1;
                            int cury = cur.Item2;
                            //시작 확인

                            // 상하좌우 칸을 살펴볼 것이다.
                            for (int dir = 0; dir < 4; dir++)
                            {
                                int nx = cur.Item1 + dx[dir];
                                int ny = cur.Item2 + dy[dir]; // nx, ny에 dir에서 정한 방향의 인접한 칸의 좌표가 들어감
                                if (nx < 0 || nx >= x || ny < 0 || ny >= y)
                                    continue; // 범위 밖일 경우 넘어감
                                if (vis[nx, ny] == 1 || arry[nx, ny] != 1)
                                    continue; // 이미 방문한 칸이거나 배추칸이 아닐경우

                                vis[nx, ny] = 1; // (nx, ny)를 방문했다고 명시
                                queue.Enqueue((nx, ny));
                            }
                        }
                    }
                }
                Console.WriteLine(nG);
            }

              
    }

BFS문제에서는 칸의 어떤 상태(배추:1,빈공간:0)인지 체크하는 부분과 큐에 들어간 칸(배추 가 심어져있는)이 어디까지 방문할수 있는지, 이미 방문한 위치인지 체크한다.

이문제는 흰지렁이가 포함할수 있는 범위를 묻는 문제는 아니고 몇개의 범위를 폼할수 있는 문제이기 때문에 큐에 입력될때 마다 카운팅을 했다. 

 

 

문제

https://www.acmicpc.net/problem/1012

파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀 점

문제

https://www.acmicpc.net/problem/10808

 

10808번: 알파벳 개수

단어에 포함되어 있는 a의 개수, b의 개수, …, z의 개수를 공백으로 구분해서 출력한다.

www.acmicpc.net

알파벳 소문자로만 이루어진 단어 S가 주어진다. 각 알파벳이 단어에 몇 개가 포함되어 있는지 구하는 작성하는 문제이다. 

풀이 : 내가 작성한 코드

 

 처음 생각했을 때는 입력된 string값을 기준으로 또 다른 a~z을 체크하여 입력된 string요소의 알파벳이 해당하는 수를 카운팅 해서 추 출력하려고 했다. 그러면 총 O(N^2) 시간 복잡도였다. 다른 방법으로는 미리 a~z의 해당하는 26개 크기를 가지는 배열을 미리 생성했다. 그렇게 하면 a~z까지 각각의  string요소의 해당하는 알파벳에 위치가 정해 지므로, 입력값의 요소로 for문을 돌리면서 해당하는 알파벳에 1씩 증가해 주면 된다. 

느낀 점

단순히 정답을 출력하는 풀이식보다는 좀 더 최적화여 값을 구하는 연습을 해야겠다. 

 

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀 점


문제

여러 장소에서 작업하기 위해 코드를 깃에 올려놓고 다운로드하였는데 아래와 같은 오류가 발생했다. 무엇인가 연결이 끊겨서 파일 요소를 로드할 수 없는 상태인 것 같다.

 

sln파일을 메모장으로 열어봤다.

Server.sln

sln안에는 내가 생성한 cs파일들의 프로젝트 파일들의 경로가 적혀있다.  sln은 경로 된 파일들을 로드하는데, 그중에 ServerCore에 는 csproj파일이 없었다.  우선 이 파일이 어떤 형태인지 알아보기 위해 얘도 메모장으로 열어봤다. 

Server.csproj

csproj 설명https://docs.microsoft.com/ko-kr/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2019


오류 해결방법

ServerCore에 는 csproj파일을 만들어주면 된다. 안에 내용은 다른 프로젝트와 다르지 않기 때문에 복사해서 이름만 바꿔주도록 한다. 

사실 작업을 하다 보면 콘텐츠에 구현하는데만 신경 썼었다. 프로젝트 파일이 어떤 식으로 구성되고 어떤 확장자가 어떤 기능을 하는지 알아볼 생각도 하지 못했다. 지금도 정확하게 알지는 못하지만 이런 오류를 해결함으로써 빌드 파일의 구성에 대해 조금이나마 알게 되었다.

 

 

파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀 점

문제

https://programmers.co.kr/learn/courses/30/lessons/42587

우선순위를 정하여 먼저 출력할 인쇄물의 대한 처리를 하는 문제이다. 

풀이 : 내가 작성한 코드

우선순위와 인덱스를 갖는 Task라는 클래스를 만들어서 Queue에 넣도록 했다.  그런 후 foreach로 하나씩 검사하여 자기보다 뒤 에이 있는 값이 우선순위가 높다면 반복문을 빠져나와 맨뒤로 넣도록 했다. 선입선출이기 때문에 큐를 사용했다. 모든 값들을 정렬할 필요가 없기 때문에 nNow인 순서를 체크하는 변수와 매개변수 location이 같으면 값을 출력해준다. 

풀이 : 다른 사람이 작성한 코드

나는 큐에 우선순위와 인덱스 값을 저장할 클래스를 따로 만들었지만 이 코드에서는 KeyValuePair를 사용했다. While문을 돌리면서 큐에 들어가 있는 값들 중에 가장 높은 우선순위의 값이 큐의 맨 처음에 입력된 값과 우선순위를 비교한다. 가장 큰 값이 현재 입력 순서라면 매개변수 location과 비교한다.  그 값이 location도 아닌데 가장 큰 값이면 answer를 하나씩 증가한다. 출력 순서가 아니란 얘기다. 

파란색 : 풀면서 어려웠던 부분 or 고려해야 될 부분

빨간색 : 해결방법

초록색 : 느낀 점

문제

k값만큼 알파벳 순서인 뒤로 이동.

 

1. 대소문자 구별

2. 알파벳 외에 다른 문자는 변경하지 않는다.

3.'z' , 'Z'를 넘어가면 다시 'a', 'A' 순서로

풀이 : 내가 작성한 코드

            public static string caesarCipher(string s, int k)
            {
                string strAnswer = string.Empty;
                for (int i = 0; i < s.Length; i++)
                {
                    if (s[i] >= 'a' && s[i] <= 'z' )
                    {
                        int a = s[i] - (97 - k);
                        strAnswer += ((char)((a % 26) + 97));
    
                    }
                    else if(s[i] >= 'A' && s[i] <= 'Z')
                    {
                        int a = s[i] - (65 - k);
                        strAnswer += ((char)((a % 26) + 65));
                    }
                    else
                    {
                        strAnswer += s[i];
                    }
                }
                return strAnswer;
            }

고려할 부분은 대소문자 체크와 그 외 문자 체크 정도였다.  z이면 다시 a로 이동하도록 하는 부분을 어떻게 할지는 고민했었다. 알파벳 개수인 26을 % 연산을 통해서 체크했다.  

 

Caesar Cipher | HackerRank

Encrypt a string by rotating the alphabets by a fixed value in the string.

www.hackerrank.com

알고리즘 문제는 풀면 풀수록 자주 등장하거나 디테일한 값을 구하는 패턴이 있는 거 같다. 많이 풀어봐야겠다. 

Cache

자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 원래의 데이터를 접근하는 시간이 오래 걸리거나 반복적으로 동일한 결과를 돌려주는 경우 데이터를 직접적인 접근으로 인한 병목현상을 막기 위해 사용되는 저장소이다. 

 


캐시 철학

 

Temporal locality

시간적으로 보면, 최근에 사용했던 기억 장소들이 집중적으로 액세스 되는 경향이 있다. 접근되었던 적이 있는 곳에는 다 시 접근할 가능성이 높다는 판단

 

Spacial locality

프로그램 실행 시 접근하는 메모리의 영역은 이미 접근이 이루어진 영역의 근처일 확률이 높다.

 

테스트 코드

            int[,] arr = new int[10000, 10000];

            {
                long now = DateTime.Now.Ticks;
                for (int i = 0; i < 10000; i++)
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        arr[i, j] = 1;
                    }
                }
                long end = DateTime.Now.Ticks;
                Console.WriteLine($"(i,j) 순서 걸린 시간{end - now}");
            }

            {
                long now = DateTime.Now.Ticks;

                for (int i = 0; i < 10000; i++)
                {
                    for (int j = 0; j < 10000; j++)
                    {
                        arr[j, i] = 1;
                    }
                }
                long end = DateTime.Now.Ticks;
                Console.WriteLine($"(j,i) 순서 걸린 시간{end - now}");
            }

 테스트 결과로 봤을 때 같은 동작이지만 두 개의 결과가 많은 시간이 차이가 보이는 걸 알 수 있다. Spacial locality관점으로 근접한 공간적 메모리 영역을 접근했을 때 좀 더 짧게 수행되기 때문이다. 

 


https://talkingaboutme.tistory.com/entry/Study-Memory-Hierarchy-1

 

'STUDY > 운영체제' 카테고리의 다른 글

운영체제) 교착상태 (deadlock)  (0) 2019.08.16

멀티스레드

둘 이상의 스레드가 하나의 프로세스에서 동시에 작업하는 것을 의미, 하나의 프로세스 내의 여러 스레드들은 프로세스에게 할당받은 자원을 공유한다. (스택 영역은 공유 X)

 

스레드 스케줄링

스레드를 어떤 순서로 동시성을 실행할 것인지 결정하는 것, (우선순위, 순환 할당 방식이 있다.)

스레드가 많다고 좋은 건 아니다.

컴퓨터가 동시에 작업 수는 CPU의 코어 수와 같다. CPU의 코어 수보다 스레드를 많이 사용하게 되면 문맥 교환이 발생하는데 오히려 효율은 저하된다. 코어 수만큼 스레드를 실행시키는 것이 좋다.

 

 


https://tcpschool.com/java/java_thread_multi

'STUDY > 네트워크' 카테고리의 다른 글

[네트워크] 유니티 네트워크 공부#1  (0) 2021.08.09
네트워크) TPC/IP 프로토콜  (0) 2019.11.17
네트워크) 네트워크 구성  (0) 2019.11.13

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀 점

문제

Google sheet to Json이라는 에셋을 추가했을 때 발생 Newtonsoft.Json.dll이 중복으로 있어 문제인 거 같았다.

오류 해결방법

 

추가한 GSpreadSheets 폴더

 

 

Library\PackageCache에 있는

Library\PackageCache\com.unity.nuget.newtonsoft-json@2.0.0\Runtime에 있는 Newtonsoft.Json.dll를 제거해준다.

느낀 점

내가 사용하는 유니티 프로젝트 버전은 2021 버전으로 최신을 낮은 버전으로 Google sheet to Json에셋을 추가했을 때는 아무 문제가 없었다. 검색한 본 걸로는 2020? 버전부터 패키지에 com.unity.nuget.newtonsoft-이 추가되어있는 것 같다.  최신 버전의 유니티에 에셋들은 아직 버전 대응이 되지 않았던 것 같다. 유니티 버전을 바꾸면서  고려야 해야 할 사항을 체크가 필요할 것 같다.

 

 

참고 

https://www.reddit.com/r/Unity3D/comments/o18fet/multiple_precompiled_assemblies_with_the_same_name/

네트워크 프로그래밍 관련 공부를 하면서 간단히 정리해보았다. TCP

 

ServerCore(서버) /  Client(클라이언트) 두 개의 스크립트를 작성한다.

 

ServerCore

string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

host : ip주소가 아닌 도메인주소로 사용하도록 한다. 

AddressList : IP주소 리턴

IPEndPoint : IP주소와 포트번호를 통해 생성

 

Listener.class

매개변수 OnAcceptHandler : Accept이 성공했을 때 불리는 콜백

_listenSocket.Bind(endPoint); : 넘겨 받아온 IPEndPoint의 바인드 처리

_listenSocket.Listen(10); : Listen 시작 클라이언트의 연결 요청을 대기상태

SocketAsyncEventArgs : 비동기 소켓의 관련한 패턴을 사용할 때? 쓰임

args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted); : 작업이 완료되면 OnAcceptCompleted을 호출

RegisterAccept(args); : 최조 한번 등록

 

args.AcceptSocket = null; : 전에 사용했던 args이기 때문에 null 초기화 작업

bool pending = _listenSocket.AcceptAsync(args); : 완료되면 false를 리턴

즉시 완료되면 OnAcceptCompleted 실행

 

_OnAcceptHandler.Invoke(args.AcceptSocket); : 연결 성공시 송수신 처리

RegisterAccept(args); : 다시 Accept 처리.

 

Init()에서 최초 Accept 처리 -> Accept 성공 완료 콜백 OnAcceptCompleted 에서 Accept 처리 -> Accept처리 ...

로 계속 반복해서 Accept을 시도한다.


 

인프런 유니티로 만드는 MMORPG 게임 개발 시리즈를 학습 예제

'STUDY > 네트워크' 카테고리의 다른 글

[네트워크] 멀티스레드 #1  (0) 2021.09.23
네트워크) TPC/IP 프로토콜  (0) 2019.11.17
네트워크) 네트워크 구성  (0) 2019.11.13

유니티 커스텀 에디터로 맵툴 만들기 두 번째 포스팅

오늘 공부할 에디터 뷰


            GUILayout.BeginVertical("box");

            GUILayout.Label("Map Name");
            _strMapName = EditorGUILayout.TextField(_strMapName/*, GUILayout.ExpandWidth(true)*/);
            GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;
            if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
            {
                Debug.LogError("create " + _strMapName);
                CreateMap();
                _bEditOn = true;
            }

            GUILayout.Space(12);

1. GUILayout.BeginVertical("box"); 박스로 묶는 범위(진한 회색으로)를 지정하게 된다.

2. Create New Map 버튼 : EditorGUILayout.TextField()에 string값이 존재 시 활성화.

3. CreateMap()함수 : 새로운 맵을 생성함

 

 


            GUI.enabled = true;
            GUILayout.Label("Load MapData");
            _txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));
            GUI.enabled = _txtAssetMapContent != null ? true : false;
            if (GUILayout.Button("Edit Map", GUILayout.ExpandWidth(true)))
            {
                LoadMap(_txtAssetMapContent.text);
                _bEditOn = true;
            }
            GUILayout.EndVertical();

1. EditorGUILayout.ObjectField() : 끌어다 붙여도 되고, 동그라미 눌러서 프로젝트 리소시스에 텍스트 파일을 선택해도 됨

2. Edit Map 버튼 : _txtAssetMapContent에 값이 들어와야 활성화.

3. LoadMap() 함수 : 선택한 텍스트 파일을 가지고 하이어라키에 맵 파일을 로드시킨다.

커스텀 에디터를 이용한 맵툴 제작

 

작업 결과물

 

GUILayout.Label(string text );

 GUILayout.Label("Map Name");

=> 윈도 류창에 텍스트 라벨에 원하는 string을 표시할 수 있다.

 

EditorGUILayout.TextField( )

_strMapName = EditorGUILayout.TextField(_strMapName);
 GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 텍스트 입력필드, GUI.enabled을 통해 입력값이 있어야 

 

GUI.enabled

GUI.enabled = (_strMapName == string.Empty || _strMapName == null) ? false : true;

=> 해당 코드 아래에 버튼이나 입력 칸을 활성화 처리 여부를 정한다.

 

GUILayout.Button( )

if (GUILayout.Button("Create New Map", GUILayout.ExpandWidth(true)))
{
    CreateMap();
    _bEditOn = true;
}

=> 버튼 , 클릭하면 if안에 함수들이 실행된다.

 

GUILayout.Space()

GUILayout.Space(12);

=> 여백을 준다.

 

EditorGUILayout.ObjectField()

_txtAssetMapContent = (TextAsset)EditorGUILayout.ObjectField(_txtAssetMapContent, typeof(TextAsset), GUILayout.ExpandWidth(true));

=> 설정한 타입으로 입력받을수있는 링크 필드가 생긴다. 나는 TextAsset으로 텍스트 관련 오브젝트만 입력받을 수 있도록 했지만 해당 타입을 그냥 object라고 하면 어떠한 오브젝트도 받을 수 있는 상태가 된다.

Cpaacity를 먼저 설정했을 때와 그냥 사용했을 때에 차이점을 테스트해봤다.

첫 번째 테스트

리스트에 같은 수의 데이터를 넣고 지우 고를 반복했을 때 걸리는 시간 체크하기. 여기 포인트는 미리 정해놓은 Cpaacity의 크기를 넘지 않을때이다.

 

테스트 코드

	long lPre = System.GC.GetTotalMemory(false);
        int tick1 = Environment.TickCount;
        List<int> list = null;
        if (bCapacity)
            list = new List<int>(10000000);
        else
            list = new List<int>();

        for (int k = 0; k < 10; k++)
        {
            for (int i = 0; i < nCount; i++)
            {
                for (int j = 0; j < nCount; j++)
                {
                    list.Add(i + j);
                }
            }
        }
        int tick2 = Environment.TickCount;
        long lAfter = System.GC.GetTotalMemory(false);

        Debug.LogError(string.Format("Capacity Use : {0} | list Count : {1} / Tick : {2}/ Memory : {3}", 
        			bCapacity, list.Count, tick2 - tick1, lAfter - lPre));

1. Capacity을 사용 안 했을 때.

list Count : 10000000 / Tick : 172/ Memory : 134279168

2. Capacity을 사용했을 때.

 list Count : 10000000 / Tick : 109/ Memory : 40001536


테스트 결과

Capacity를 크기를 미리 설정한 케이스가 시간이나 메모리가 더 적게 발생하는 걸 알 수 있다. 

 


 

두 번째 테스트

재 할당될 때 List의 Capacity변화를 테스트해봤다.

 

테스트 코드

        List<int> list = new List<int>();
        Debug.LogError(list.Capacity);
        list.AddRange(new int[4] { 1, 2, 3, 4 });
        Debug.LogError(list.Capacity);
        list.Add(1);
        Debug.LogError(list.Capacity);

처음에 4개의 데이터를 넣고 그 범위를 초과할 때 Capacity를 로그 찍어봤다.

 

테스트 결과

데이터의 수는 4개에서 5개로 1개 증가했지만 현재 사이즈의 2배만큼 Capacity를 할당된 것을 알 수 있다.

 


오늘 테스트 최종 결과

List에 사용하는 최대 크기를 안다면 Capacity를 미리 설정해두는 것이 좋다. List는 현재 가지고 있는 Count수가 초과될 때 기존의 데이터를 복사한 후 다시 2배 사이즈로 재할당하게 된다(기존의 있던 데이터는 가비지 해제 대상으로 잡히게 된다.) 만약 할당된 Capacity를 현재 데이터의 수만큼 변경하고 싶으면 TrimExcess 을 이용한다.

 

 

'프로그래밍언어 > C#' 카테고리의 다른 글

C#) Async, Await  (0) 2022.08.15
C#) Enum.Parse  (0) 2020.04.17
C#) String null 검사,체크 (널처리)  (0) 2020.04.16
C#) 전처리기 지시자  (0) 2020.04.12
C#) 동적바인딩 / 정적바인딩  (0) 2020.04.08

#1에 이어 #2이다. 이번에는 Client코드를 살펴보자.

 

먼저 소켓에 접속하는 함수

 

ConnectToServer()

    public void ConnectToServer()
    {
        // 이미 연결되었다면 함수 무시
        if (_bSoketReady) return;

        // 기본 호스트/ 포트번호
        string ip = ins_inputIPNumber.text == "" ? "127.0.0.1" : ins_inputIPNumber.text;
        int port = ins_inputPort.text == "" ? 9999 : int.Parse(ins_inputPort.text);

        // 소켓 생성
        try
        {
            _tcpSoket = new TcpClient(ip, port);
            _NetworkStream = _tcpSoket.GetStream();
            _writer = new StreamWriter(_NetworkStream);
            _reader = new StreamReader(_NetworkStream);
            _bSoketReady = true;
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"소켓에러 : {e.Message}");
        }
    }

 

 

OnIncomingData

    void OnIncomingData(string data)
    {
        if (data == "%NAME")
        {
            _strClientName = ins_inputNickName.text == "" ? "Guest" + UnityEngine.Random.Range(1000, 10000) : ins_inputNickName.text;
            Send($"&NAME|{_strClientName}");
            return;
        }

        Chat.instance.ShowMessage(data);
    }

if(data = "%NAME") : 으로 들어오는 %,&인 특수문자로 체크하는데 영상 댓글에 보니 들어오기 전 이름, 나갈 이름을 구분하는 용도라 한다. 여기서는 % 들어오기 전에 이름인거같다.

사용자가 입력한 string이 없으면 Guest + 숫자로 임의로 만들어준다.

send : &NAME으로 서버에 보낸다.

 

연결되는 부분이 Server클래스에 OnIncomingData()에서

//Server.cs.

void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

연결된 클라이언트의 이름을 접속한 클라이언트들에게 보내는 내용이다.

 

OnSendButton

    public void OnSendButton(InputField SendInput)
    {
#if (UNITY_EDITOR || UNITY_STANDALONE)
        if (!Input.GetButtonDown("Submit")) return;
        SendInput.ActivateInputField();
#endif
        if (SendInput.text.Trim() == "") return;

        string message = SendInput.text;
        SendInput.text = "";
        Send(message);
    }

메시지 보내기 버튼으로 입력된 내용을 send 해주는 과정이다.

 

 

OnApplicationQuit

    void OnApplicationQuit()
    {
        CloseSocket();
    }
    
    void CloseSocket()
    {
        if (!_bSoketReady) return;

        _writer.Close();
        _reader.Close();
        _tcpSoket.Close();
        _bSoketReady = false;
    }

앱 종료하면 연결된 소켓들의 정보를 끊어준다.

 

tcp에 대한 간단한 개념잡기에는 괜찮은거 같다.

 

참고:

https://www.youtube.com/watch?v=y3FU6d_BpjI

유튜브에 c# tcp소켓통신 채팅 관련 영상이 있었다 그 영상의 코드를 공부해봤다.

 

먼저 ServerClinet 

public class ServerClient
{
    public TcpClient tcp;
    public string clientName;

    public ServerClient(TcpClient clientSocket)
    {
        clientName = "Guest";
        tcp = clientSocket;
    }
}

TcpClient : TCP 네트워크 서비스에 대한 클라이언트 연결을 제공한다.

 

ServerCreate

    public void ServerCreate()
    {
        _listClients = new List<ServerClient>();
        _listDisconnect = new List<ServerClient>();

        try
        {
            int port = ins_PortInput.text == "" ? 9999 : int.Parse(ins_PortInput.text);
            _server = new TcpListener(IPAddress.Any, port);
            _server.Start(); //바인드 처리.

            StartListening();
            _bserverStarted = true;
            Chat.instance.ShowMessage($"서버가 {port}에서 시작되었습니다.");
        }
        catch (Exception e)
        {
            Chat.instance.ShowMessage($"Socket error: {e.Message}");
        }
    }

port로 포트번호를 만들어준다. (현재 사용하지 않는 포트번호로 지정)

IPAddress.Any : 모든 클라이언트에서 오는 요청을 받겠다는 의미

TcpListener.Start : 들어오는 연결 요청의 수신을 시작. (바인드)

 

StartListening

    private void StartListening()
    {
        _server.BeginAcceptTcpClient(AcceptTcpClient, _server);
    }

BeginAcceptTcpClient(AsyncCallback? callback, object? state) : 들어오는 연결 시도를 받아들이는 비동기 작업을 시작한다.

AsyncCallback은 작업이 완료됐을때 호출한다.

state는 연결을 받아들이는 작업자에 대한 정보가 들어 있는 정의 개체이다. 작업이 완료되면 callback대리자에게 전달한다.

 

AcceptTcpClient

    private void AcceptTcpClient(IAsyncResult ar)
    {
        TcpListener listener = (TcpListener)ar.AsyncState;
        _listClients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
        StartListening();

        // 메시지를 연결된 모두에게 보냄
        Broadcast("%NAME", new List<ServerClient>() { _listClients[_listClients.Count - 1] });
    }

매개변수

IAsyncResult : 비동기 작업의 상태를 나타낸다.

IAsyncResult 

TcpListener.EndAcceptTcpClient() : 들어오는 연결 시도를 비동기적으로 받아들이고 원격 호스트 통신을 처리할 새로운 TcpClient을 만든다. (리턴 TcpListener)

 

AcceptTcpClient이 실행 된후에 다시 StartListening()를 실행시켜 새로운 Client를 받을 수 있도록 처리한다.

 

Update()

	priavate void Update()
    {
        if (!_bserverStarted)
        { 
            return;
        }

        foreach (ServerClient c in _listClients)
        {
            // 클라이언트가 여전히 연결되있나?
            if (!IsConnected(c.tcp))
            {
                c.tcp.Close();
                _listDisconnect.Add(c);
                continue;
            }
            // 클라이언트로부터 체크 메시지를 받는다
            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }
        }

        for (int i = 0; i < _listDisconnect.Count - 1; i++)
        {
            Broadcast($"{_listDisconnect[i].clientName} 연결이 끊어졌습니다", _listClients);

            _listClients.Remove(_listDisconnect[i]);
            _listDisconnect.RemoveAt(i);
        }
    }

Update에서 클라이언트들의 접속여부와 채팅 내용을 체크하도록 한다.

 

IsConnected

   bool IsConnected(TcpClient c)
    {
        try
        {
            if (c != null && c.Client != null && c.Client.Connected)
            {
                if (c.Client.Poll(0, SelectMode.SelectRead))
                    return !(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

                return true;
            }
            else
                return false;
        }
        catch
        {
            return false;
        }
    }

 

if(c.Client.Poll(0, SelectMode.SelectRead))

TcpClient.Poll(int microseconds, SelectMode mode); : 메소드는 간단히 말하자면 하려는 행동이 완료할 수 있는 상태면 true를 리턴한다. 

매개변수

microseconds : 응답을 기다리는 시간

SelectMode : 오류상태모드, 읽기 상태 모드, 쓰기 상태 모드를 선택한다.

=> 데이터를 읽을 수 있다면 true를 반환한다.

 

!(c.Client.Receive(new byte[1], SocketFlags.Peek) == 0);

socket.Receive(Byte [], Int32, Int32, SocketFlags, SocketError)

매개변수

byte[] 수신된 데이터에 대한 스토리지 위치인 Byte형식의 배열

SoketFlags.Peek : 소켓 전송 및 수신 동작을 지정(Peek:들어오는 메시지를 미리 본다)

=> 1바이트를 보내고 실제 수신된 바이트를 확인하여 연결여부를 확인한다.

 

다시 위쪽에 else부분을 살펴보면,

            else
            {
                NetworkStream s = c.tcp.GetStream();
                if (s.DataAvailable)
                {
                    string data = new StreamReader(s, true).ReadLine();
                    if (data != null)
                        OnIncomingData(c, data);
                }
            }

TcpClient.GetStream() : 데이터를 보내고 받는 데 사용되는 NetworkStream을 반환한다.

NetworkStream.DataAvailable() : 데이터를 읽을 수 있는지 여부를 나타내는 값을 가져온다.( 읽을 수 있으면 true, 그렇지 않으면 false)

 

이제 NetworkStream.DataAvailable() 까지 성공했다면 밑에 함수를 통해

    void OnIncomingData(ServerClient c, string data)
    {
        if (data.Contains("&NAME"))
        {
            c.clientName = data.Split('|')[1];
            Broadcast($"{c.clientName}이 연결되었습니다", _listClients);
            return;
        }

        Broadcast($"{c.clientName} : {data}", _listClients);
    }

 

Broadcast()

    void Broadcast(string data, List<ServerClient> cl)
    {
        foreach (var c in cl)
        {
            try
            {
                StreamWriter writer = new StreamWriter(c.tcp.GetStream());
                writer.WriteLine(data);
                writer.Flush();
            }
            catch (Exception e)
            {
                Chat.instance.ShowMessage($"쓰기 에러 : {e.Message}를 클라이언트에게 {c.clientName}");
            }
        }
    }

리스트로 받은 모두 ServerClient에 메세지를 전달하는 과정이다.

StreamWriter.Flush : 현재writer의 모든 버퍼를 지우면 버퍼링된 모든 데이터가 내부 스트림에 쓰여진다.

 

참고

https://www.youtube.com/watch?v=y3FU6d_BpjI 

https://docs.microsoft.com/ko-kr/dotnet/api/system.net.sockets

파란색 : 문제점

빨간색 : 해결방법

초록색 : 느낀점

문제

빌드된 프로젝트를 돌렸을때 데이터와 이미지들이 빠진 상태로 진행되지 않는 현상이 있었다. 번들이 다운로드가 완료되지 않은 상태에서 이미지를 가져오려고 하는 경우였다. 유니티 에디터에서는 번들을 다운로드 하는 과정이 없이 진행되기에 오류가 발생되지 않아서 놓치고 있던 부분이었다. 

오류 해결방법

번들을 모두 완료됐다고 됐을 경우에 게임이 진행될수 있도록 셋팅한다. 

수정 전

우선 문제가 되는 부분은 LoadAssetAsyncGameObject이라는 위 함수안에 Addressables.LoadAssetsAsync()가 타입별로 한번만 실행되는줄 알았다. 하지만 EmAssetType타입의 해당하는 모든 객체가 로드될때마다 동작된다. 

 

 

수정 후

AsyncOperationHandle<IList<Object>> handle의 IsDone을 현재 에셋이 모두 불러와졌는지 체크할수 있었다.

+) 어드레서블에는 유니티 에디터에서 번들을 불러오는 것처럼 테스트할수 있는 기능이 있다

 

느낀점

느낀점은 어드레서블의 기능을 완벽히 숙지 못하고 코드를 설계했다. 코드 한줄 한줄 모두 이해하고 적용해야한다는것을 다시 깨닫게 되었다. 

 

 

 

참고 

docs.unity3d.com/Packages/com.unity.addressables@1.13/manual/LoadingAddressableAssets.html

번들관련 공부를하다가 동적으로 생성되는 파일이나 저장되는 데이터들이 어디로 저장되는지 알아보았다.

 

1. PlayerPrefs 저장위치

단말기에 저장되는 PlayerPrefs 저장경로

 

[레지스트리 편집기] -> [HKEY_CURRENT_USER] -> [SOFTWARE] -> [Unity] -> [UnityEditor] -> [DefaultCompany] ->["ProductName"] 

(DefaultCompany 와 ProductName은 ProjectSettings에서 볼수 있음)

Int형
string형

 


2. 앱 저장 경로 Application클래스

빌드된 패키지에는 에디터에서 폴더에 접근할수 없다. 

 

1) Application.dataPath

[C:\Users\사용자이름\AppData\LocalLow\회사이름]

읽기 전용


2) Application.persistentDataPath

[해당 프로젝트폴더 경로\Assets]

 

3) Application.streamingAssetsPath
[해당 프로젝트 폴더]

읽기 전용이다.

 

 

테스트 결과.

1) 에디터

2) 안드로이드

 


유니티에서 지정한 폴더들의 특징 정리

 

wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder

 

오랜만에 글을 작성한다.

 

사내 스터디 프로젝트에는 어드레서블을 사용한다. 그렇기 때문에 어드레서블에 학습이 필요했다.! 알아야 쓰지

 

리소스 폴더 사용

장점은 사용하기에 편리하다가 있다. 하지만, 리소스폴더에 메모리는 최대한 줄이도록 한다. 시작 시 최소한의 에셋만 남겨두어야겠지만? 리소스 폴더를 경계해야 한다.

단점으로는

1. apk사이즈가 커진다.

2. 앱 시작 시간이 길어진다.

3. 앱이나 폴더 변경 시 재 빌드를 해야 한다. (apk빌드에 묶이기 때문에 에셋 변경 시 무조건 재 빌드해야 한다.)

4. 에셋 이름 변경이 힘들다. (에셋을 로드할 때 경로를 바꿔줘야 하기 때문)

 

이런 문제점들을 보완할 수 있는 에셋 번들이 있다.

에셋 번들

장점은 에셋을 묶음 단위로 관리할 수 있다. 빌드 사이즈 절감과, 앱 시작 시간을 단축시킨다. 있지만

단점으로는

1. 번들의 종속성 문제.

같은 이미지를 Asset A와 Asset B에 묶여있다면 같은 이미지 1개로 처리될 것 같지만 2개의 이미지로 인식된다.

 

이러한 에셋 번들(빌드와 번들의 분리)의 장점과 리소스 폴더의 장점(비교적 편리함)을 가진 어드레 서블 에셋 시스템입니다. 


어드레 서블 에셋

어드레서블 에셋이란 어드레스가 할당된 에셋이다. 어드레스를 이용하여 에셋 위치에 상관없이 참조가 가능하다.

어드레서블 에셋 시스템

어드레스를 이용하여 에셋의 관리, 로딩, 빌드가 통합된 시스템이다.

 

어드레 서블 에셋 시스템. 특징만 봐도 편리해 보인다. 장점을 자세하게 보면

1. 에셋 빌드와 배포의 단순화 : 직접 레퍼런스, 리소스 폴더, 에셋 번들 분리가 단순화되고 편리해진다.

2. 효율적인 에셋 관리 : 종속성 파악 및 메모리 로드/언로드 현황을 볼 수 있다. (중복된 에셋으로 메모리를 낭비가 되는 것을 막는다.)


 

이제 어드레 서블 기능에 대해서 알아보자!

 

1. 해당 프리 팹에 어드레스 부여 하기.

어드레 서블 체크박스에 체크하고 어드레스를 부여하면된다. 그리고 잘 되어있는지 어드레서블 현황 대시보드를 통해 확인한다.

2. 에셋 로드하기.

1) Lables 으로 로드하기.

		Addressables.LoadAssetsAsync(type.ToString(), (Object obj) =>
		{
			if (!m_dicObject.ContainsKey(type))
				m_dicObject.Add(type, new Dictionary<string, Object>());

			m_dicObject[type].Add(obj.name, obj);

		});

위 코드에 type이 어드레서블에 Lables이다. 

 

2) 레퍼런스로 로드하기.

이런 식으로 인스펙터에 레퍼런스를 지정하여 로드하는 방식이다.

 

3) 어드레 서블 이름으로 로드하기.

    public void LoadAsset(string strAddressableName)
    {
       Addressables.InstantiateAsync(strAddressableName);
    }

2),3) 번은 현재 프로젝트에서는 사용하지 않는 방법이지만 어떤 식으로 로드하는지 찾아봤다. 

유니티가 제공하는 인앱결제 IAP에 대한 학습.

간단하게 인앱컨트롤러를 만들어서 게임시작과 동시에 인앱초기화작업이 되도록 한다.

 

1. var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());

AddProduct ~~~...

builder를 통해 스토어에 등록된 상품의 아이디와 같은 데이터를 입력한다.

 

2. UnityPurchasing.Initialize(this, builder);

등록된 builder와 Store클래스를 초기화 한다.

 

초기화

1. IStoreListener.OnInitialized(IStoreController controller, IExtensionProvider extensions)

초기화가 성공하면 IStoreController 과 IExtensionProvider 을받습니다.

 

2. IStoreController 

Unity IAP를 제어하는데 사용하며 데이터및 구매 영수증을 포함한 제품을 저장하고 있다.

3. IExtensionProvider

상점 별 확장 기능에 대한 엑세스를 제공 (extensionProvider.GetExtension<IAppleExtensions>()) 등 특정 스토어 관련한 

 

초기화 실패

1. OnInitializeFailed

초기화 실패 시InitializationFailureReason를 통해 원인을 볼 수 있음 

 

구매실패

1. Product

구매 시도한 상품의 대한 정보.

2. PurchaseFailureReason

구매 실패한 원인

 

구현결과

인앱 초기화의 성공하면 Purchase함수를 통해 각각 상품 구매 테스트 진행

 

사실 실제 상용게임에서는 여러가지 이유로 구매실패가 많이 발생한다. 게임 구매 시도중에 단말기가 종료되거나 통신이 끊기는 경우가 있기에 거기에 대응이 되는 코드와 조건이 많이 필요하다. 

타이머를 간단하게 만들어봤습니다. 

 

타이머 시작 부분

    public void StartTimer(int nRemain, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        ClearTimer();
        _CorTimer = StartCoroutine(CorStartTime(nRemain, txtTimer, EndCallBack));
    }

 

nRemain : 파라미터로 타이머를 동작시킬 시간

txtTimer : 남은 시간을 표기할 Text UI

EndCallBack : 타이머가 완료 후 실행시킬 함수.

 

코 루틴으로 작동되기 때문에 Clear 하는 부분을 꼭 넣어주도록 합니다. 

    private void ClearTimer()
    {
        if (_CorTimer != null)
        {
            StopCoroutine(_CorTimer);
            _CorTimer = null;
        }
        _bPause = false;
    }

타이머 동작 부분

    private IEnumerator CorStartTime(int nRemain = 5, Text txtTimer = null, UnityAction EndCallBack = null)
    {
        while (nRemain >= 0)
        {
            txtTimer.text = nRemain + " 초 남음";

            while (_bPause)
            {
                yield return null;
            }

            yield return new WaitForSeconds(1f);
            nRemain--;
        }
        EndCallBack?.Invoke();
    }

 

이 타이머스크립트에는 코 루틴으로 작동되며, WaitForSeconds(1f)로 1초마다 남은 시간을 감소하도록 했습니다. 저는 EndCallBack으로 델리게이트로 넘겨받도록 했는데 이유는 다른 곳에서도 쓰일 수 있도록 했습니다. 

 

 

그리고 매프 레임 텍스트만 바꾸지만 매 프레임 단위로 실행시키는 함수가 필요하면 EndCallBack처럼 받고 매프 레임 실행시켜주면 되겠죠. 


일시정지

타이머를 잠깐 멈추는 일시정지를 하는 부분입니다. 버튼 부분에 달아주고 OnPointerDown과 OnPointerUp을 통해 제어하도록 해줬습니다.

public class EventButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
    private TimerUI _TimerUI = null;
    public void SetData(TimerUI TimerUI)
    {
        _TimerUI = TimerUI;
    }
    //클릭 누름.
    public void OnPointerDown(PointerEventData eventData)
    {
        Debug.LogError("퍼즈");
        _TimerUI.ins_Timer.SetPause(true);
    }
    //클릭 뗌.
    public void OnPointerUp(PointerEventData eventData)
    {
        Debug.LogError("퍼즈 취소");
        _TimerUI.ins_Timer.SetPause(false);
    }

}

 ins_Timer는 타이머 동작부분이 구현되어있는 스크립트입니다. SetPause를 통해 코루틴을 잠시 멈추도록합니다.

 


구현결과

스칼라

벡터가 크기와 방향이 있다면 스칼라는 크기만을 갖는 물리량이라고 말한다. (길이,넓이,시간,온도,질량,속력,에너지)

스칼라 왜 필요할까?

벡터 크기를 조정하는 데 사용한다. 스칼라는 방향이 없기 때문에 벡터에 스칼라를 곱하면 벡터의 크기만 바꿀 수 있는 것이다. 좌표계로 보면 아래 그림과 같다.

게임에서는 어떻게 이용하는지 살펴보자. 우선 저번 포스팅에 벡터의 뺄셈을 통해 이동할 방향벡터를 구할 수 있었다. 이 방향벡터에 스칼라를 곱하면 이동 벡터가 나오게 된다.

방향벡터(단위 벡터) x 스칼라(이동할 거리) = 이동 벡터

2020/10/23 - [StUdY/게임 수학] - 게임 수학) 벡터 뺄셈

캐릭터를 원하는 위치로 이동시키는 방법은 캐릭터 현재 위치 벡터 + 이동 벡터

 

'STUDY > 게임수학' 카테고리의 다른 글

게임수학)벡터 뺄셈  (0) 2020.10.23
게임수학) 벡터란, 벡터의 덧셈  (0) 2020.10.19

벡터의 뺄셈

벡터의 뺄셈은 덧셈과 같다. 우리가 알고 있는 3 - 2 = 1인 것처럼.  (3,0) - (2,0) = (1,0)이 된다. 음수인 좌표에 있는 값은 (3,2) - (-2,2) = (5,0)이 된다. 좌표로 보면 아래 그림과 같다.

뺄셈을 덧셈으로 바꿔보자. (3,2) - (-2,2) = (3,2) + (2,-2) = (5,0)

(-2,0)이 (2,-2)로 바뀌면서 화살표 머리 방향이 반대로 바뀌었다. 두 벡터의 뺀 값 (5,0)도 좌표로 표시해 보자

벡터의 덧셈에서 공부했던 것처럼 꼬리에서 머리로 이으면 두 벡터의 뺄셈 결과인 (5,0)을 알게 되었다. 여기서 주의해서 봐야 할 것은 (5,0)의 방향입니다. (3,2)를 을 가리키고 있습니다.

벡터의 뺄셈 언제 쓸까?

벡터의 뺄셈은 특정 위치에서 다른 위치를 바라보는 벡터의 방향을 알 수 있다.(방향벡터)  

유니티로 몬스터 위치에서 히어로 위치로 이동하도록 해봤다.

 

'STUDY > 게임수학' 카테고리의 다른 글

게임수학) 벡터와 스칼라의 곱셈  (0) 2020.10.30
게임수학) 벡터란, 벡터의 덧셈  (0) 2020.10.19

게임프로그래밍에서 벡터는 물체의 위치, 이동 등 다양하게 이용된다. 그렇기 때문에 지식이 필요하다. 책과 구글링, 서치를 통해 정리하면서 공부를 하려고 한다.

벡터의 사전적 의미

 

벡터의 표시법

 

벡터의 기본적인 성질에 대해서 알아보자

동등성 :  사전적 의미로 벡터는 크기방향만을 가지고 있다. 그렇기 때문에 원점의 특정한 위치는 아무런 의미를 갖지 않는다. 이게 무슨 말이냐??

내가 그림

위 4개의 벡터는 각각 다른 위치에 있지만, 크기와 방향은 같다. 그러니까 동일한 벡터이다. 

 

벡터는 왜 필요할까?

x,y나 특 정 위치를 표시하는 방법으로 표현할 수도 있지만, 벡터의 성질을 게임에서 활용하기 위해서이다.( 더 많은 것들을 표현할 수 있음)

예를 들어 지속적으로 맵 안에서 움직이는 캐릭터가  있다. 이 캐릭터는 일정한 속도로 움직인다. 이 상황에서 크기와 방향이 같은 벡터를 통해 어디에 놓아도 동일한 계산 결과가 나오게 된다.

벡터가 크기랑 방향을 갖고 있는건 알겠는데,  이걸로 뭘 어떻게  움직이는 건데..??

 

벡터의 덧셈

위 2개의 벡터의 덧셈 결과는? 어느 위치하냐는 의미가 없다고 했으니까, 각 좌표의 x끼리, y끼리 더하면 된다. 결괏값은 벡터(7,0)이다.  몇 가지 더 풀어보자

첫 번째 그림은 (5,0)  두번째 그림은 (5,1)

마지막으로 이 두 벡터를 더해보자.

위에서 봤던 문제처럼 (5,3)이라는 답을 쉽게 찾을 수 있다. 그러면 이 (5,3)을 벡터로 표시하면 어떻게 될까?

 두 벡터 덧셈은 시작점과 끝점을 연결하면 된다는 것을 알 수 있다.

이 말은 즉, 특정 위치에서 특정 거리만큼 떨어진 위치를 찾을 수 있다. 

'STUDY > 게임수학' 카테고리의 다른 글

게임수학) 벡터와 스칼라의 곱셈  (0) 2020.10.30
게임수학)벡터 뺄셈  (0) 2020.10.23

+ Recent posts