오늘의 학습 키워드

재귀

 

공부한 내용

Flood_Fill 문제

구현 방법 : 알고리즘 해결을 위해 생각했던 것이 좌표를 리스트에 넣어놓고 상하좌우를 값이 같은지 체크하면서 반복하면서 뻗어가는 방식이었다.

문제 : 해당 과제에서의 문제가 발생한 부분은 리스트 안에 다음 타일의 중복체크를 안 해서 스택 오버 플로우가 발생했다. 해해결 : FloodFillNext 메서드에 중복체크하는 구문을 통해 이를 해결하였다.

* HashSet이 List보다 더 런타임 속도가 빨라서 이후에 수정하였다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sparta
{
    class Flood_Fill
    {
        // 2차원 배열이 주어지고 특정 좌표와 색이 주어진다.
        // 특정 좌표의 값을 바꾸는데 특정 좌표로부터 4차원(위,아래,왼,오른쪽)과
        // 연결되어있는 같은 값을 모두 color의 숫자로 바꾼다.
        static void Main(string[] args)
        {
            FloodFill(new int[][] { new int[] { 1, 1, 1 }, new int[] { 1, 1, 0 }, new int[] { 1, 0, 1 } }, 1, 1, 2);

        }

        private static void FloodFill(int[][] image, int sr, int sc, int color)
        {
            HashSet<(int, int)> posSet = new HashSet<(int, int)>();
            posSet.Add((sr, sc));
            FloodFillNext(image, posSet, sr, sc);

            foreach (var item in posSet)
            {
                image[item.Item1][item.Item2] = color;
            }

            //return image;
        }

        private static void FloodFillNext(int[][] image, HashSet<(int, int)> posSet, int sr, int sc)
        {
            int next = -1;

            if (sr > 0 && image[sr][sc] == image[sr - 1][sc])
            {
                next = sr - 1;
                if (!posSet.Contains((next, sc)))
                {
                    posSet.Add((next, sc));
                    FloodFillNext(image, posSet, next, sc);
                }

            }
            if (sr + 1 < image.Length && image[sr][sc] == image[sr + 1][sc])
            {
                next = sr + 1;
                if (!posSet.Contains((next, sc)))
                {
                    posSet.Add((next, sc));
                    FloodFillNext(image, posSet, next, sc);
                }
            }
            if (sc > 0 && image[sr][sc] == image[sr][sc - 1])
            {
                next = sc - 1;
                if (!posSet.Contains((sr, next)))
                {
                    posSet.Add((sr, next));
                    FloodFillNext(image, posSet, sr, next);
                }
            }
            if (sc + 1 < image[0].Length && image[sr][sc] == image[sr][sc + 1])
            {
                next = sc + 1;
                if (!posSet.Contains((sr, next)))
                {
                    posSet.Add((sr, next));
                    FloodFillNext(image, posSet, sr, next);
                }
            }
        }
    }
}

https://leetcode.com/problems/flood-fill/description/

 

Flood Fill - LeetCode

Can you solve this real interview question? Flood Fill - An image is represented by an m x n integer grid image where image[i][j] represents the pixel value of the image. You are also given three integers sr, sc, and color. You should perform a flood fill

leetcode.com

 

 

오늘의 회고

 오늘은 알고리즘 강의와 여러 알고리즘 문제들을 풀면서 공부하는 시간을 가졌다. 아직 많이 모자라고 부족한 점도 많지만 문제를 분석하고 정리해둔 후 코드로 바꾼다는 접근법을 알게된 것만으로 많이 배웠다고 생각한다. 알고리즘 풀 때는 비슷한 유형의 문제들을 연속적으로 풀어서 그 유형에 익숙해지도록 해야겠다.

 강의 과제로 문제들을 풀면서 힌트도 보고 했는데 내일은 힌트를 보지 않고 강의에 나온 문제들을 다시 복습하는 시간을 가져야겠다. 내일도 파이팅!

오늘의 학습 키워드

Task

 

공부한 내용

스네이크 게임

스네이크 게임 구현

스파르타 동료분의 코드를 참고하여 진행하였습니다.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SnakeGame
{
    class Program
    {
        static int score = 0;
        static int highScore = 0;
        static int foodCount = -1;

        static Map map;
        static SnakeHead player;
        static ConsoleKeyInfo input;
        static Vector2Int direction = new Vector2Int(1, 0);

        static bool applicationQuit = false;
        static bool isEnterPressed = false;

        static void Main(string[] args)
        {
            Init();
            Route();
            while (!applicationQuit);
        }

        private static async void Route()
        {
            bool isCrashed = false;

            Task task = Task.Run(() =>
            {
                while (true)
                {
                    input = Console.ReadKey(true);
                    if (input.Key == ConsoleKey.Escape)
                    {
                        applicationQuit = true;
                        break;
                    }
                    else if (input.Key == ConsoleKey.Enter)
                    {
                        isEnterPressed = true;
                    }
                }
            });

            while (!applicationQuit)
            {
                await Task.Delay(100);

                if (isCrashed)
                {
                    Console.Clear();
                    map.DrawMap();
                    Console.WriteLine("===============================================================");
                    Console.WriteLine($"점수 : {score}  최고 점수 : {highScore}  먹은 수 : {foodCount}");
                    Console.WriteLine("===============================================================");
                    Console.WriteLine("죽었습니다. 다시 시작 : Enter");

                    if (isEnterPressed)
                    {
                        Reset();
                        Init();
                        isEnterPressed = false;
                        isCrashed = false;
                    }
                }
                else
                {
                    switch (input.Key)
                    {
                        case ConsoleKey.RightArrow:
                            direction = new Vector2Int(1, 0);
                            break;
                        case ConsoleKey.LeftArrow:
                            direction = new Vector2Int(-1, 0);
                            break;
                        case ConsoleKey.UpArrow:
                            direction = new Vector2Int(0, -1);
                            break;
                        case ConsoleKey.DownArrow:
                            direction = new Vector2Int(0, 1);
                            break;
                    }

                    isCrashed = Update();
                }
            }
        }

        private static void Reset()
        {
            score = 0;
            foodCount = -1;

            map = null;
            player = null;
            direction = new Vector2Int(1, 0);
        }

        static bool Update()
        {
            Console.SetCursorPosition(0,0);
            if (!map.IsFoodExist())
            {
                foodCount++;
                map.SpawnRandomFood();
            }

            bool isCrashed = player.TryMove(map, direction);
            map.DrawMap();
            Console.WriteLine("===============================================================");
            Console.WriteLine($"점수 : {score}  최고 점수 : {highScore}  먹은 수 : {foodCount}");
            Console.WriteLine("===============================================================");
            Console.WriteLine("ESC를 누르면 종료");
            score++;
            if (score > highScore)
            {
                highScore = score;
            }

            isEnterPressed = false;
            return isCrashed;
        }

        private static void Init()
        {
            int mapX = 50, mapY = 25;
            map = new Map(mapX, mapY);
            player = new SnakeHead(new Vector2(mapX / 2, mapY / 2));
        }

        public enum ObjectType
        {
            BLANK,
            WALL,
            FOOD,
            SNAKE_BODY,
            SNAKE_HEAD,
        }

        public struct Vector2
        {
            public int X { get; set; }
            public int Y { get; set; }

            public Vector2(int x, int y)
            {
                X = x;
                Y = y;
            }
        }

        public struct Vector2Int
        {
            private int x, y;

            public int X 
            {   
                get => x; 
                set
                {
                    if (value > 1)
                    {
                        x = 1;
                    }
                    else if (value < -1)
                    {
                        x = -1;
                    }
                    else
                    {
                        x = value;
                    }
                } 
            }

            public int Y
            {
                get => y;
                set
                {
                    if (value > 1)
                    {
                        y = 1;
                    }
                    else if (value < -1)
                    {
                        y = -1;
                    }
                    else
                    {
                        y = value;
                    }
                }
            }
            
            public Vector2Int(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        public class Transform
        {
            public Vector2 position = new Vector2(0, 0);
            public Vector2Int rotation = new Vector2Int(0, 0);
        }

        class Object
        {
            public Transform trans = new Transform();
            public ObjectType objType;

            public Object(Vector2 pos, Vector2Int rot, ObjectType type)
            {
                trans.position = pos;
                trans.rotation = rot;

                objType = type;
            }
        }

        class Map
        {
            public int X { get; private set; }
            public int Y { get; private set; }

            public Object[][] tiles { get; private set; }

            public Map(int x, int y)
            {
                X = x;
                Y = y;

                // 세로줄이 y개인 2차원 배열
                tiles = new Object[Y][];

                for (int i = 0; i < Y; i++)
                {
                    // 안에 가로줄의 요소가 x개인 배열
                    tiles[i] = new Object[X];
                }

                for (int i = 0; i < Y; i++)
                {
                    for (int j = 0; j < X; j++)
                    {
                        Vector2 pos = new Vector2(X, Y);
                        Vector2Int rot = new Vector2Int(0, 0);
                        Object tile;

                        // 벽 만들기
                        if (i == 0 || i + 1 == Y || j == 0 || j + 1 == X) // 경계 부분이라면
                        {
                            if (i == 0)
                            {
                                if (j == 0)
                                {
                                    rot = new Vector2Int(-1, 1);
                                }
                                else if (j + 1 == X)
                                {
                                    rot = new Vector2Int(1, 1);
                                }
                                else
                                {
                                    rot = new Vector2Int(0, 1);
                                }
                            }
                            // -1
                            else if (i + 1 == Y)
                            {
                                if (j == 0)
                                {
                                    rot = new Vector2Int(-1, -1);
                                }
                                else if (j + 1 == X)
                                {
                                    rot = new Vector2Int(1, -1);
                                }
                                else
                                {
                                    rot = new Vector2Int(0, -1);
                                }
                            }
                            else
                            {
                                if (j == 0)
                                {
                                    rot = new Vector2Int(-1, 0);
                                }
                                else if (j + 1 == X)
                                {
                                    rot = new Vector2Int(1, 0);
                                }
                            }

                            tile = new Object(pos, rot, ObjectType.WALL);
                        }
                        else // 경계 부분이 아니라면 기본 Vector2Int(0,0);
                        {
                            tile = new Object(pos, rot, ObjectType.BLANK);
                        }

                        tiles[i][j] = tile;
                    }
                }
            }

            public void DrawMap()
            {
                for (int i = 0; i < Y; i++)
                {
                    for (int j = 0; j < X; j++)
                    {
                        if (tiles[i][j].objType == ObjectType.BLANK)
                        {
                            Console.Write(' ');
                        }
                        else if (tiles[i][j].objType == ObjectType.FOOD)
                        {
                            Console.Write('$');
                        }
                        else if (tiles[i][j].objType == ObjectType.SNAKE_HEAD)
                        {
                            Console.Write('@');
                        }
                        else if (tiles[i][j].objType == ObjectType.SNAKE_BODY)
                        {
                            Console.Write('#');
                        }
                        else if (tiles[i][j].objType == ObjectType.WALL)
                        {
                            if (tiles[i][j].trans.rotation.X == -1 && tiles[i][j].trans.rotation.Y == 0
                                || tiles[i][j].trans.rotation.X == 1 && tiles[i][j].trans.rotation.Y == 0)
                            {
                                Console.Write('|');
                            }
                            else if (tiles[i][j].trans.rotation.Y == -1 && tiles[i][j].trans.rotation.X == 0
                                || tiles[i][j].trans.rotation.Y == 1 && tiles[i][j].trans.rotation.X == 0)
                            {
                                Console.Write('-');
                            }
                            else
                            {
                                Console.Write('+');
                            }
                        }
                    }
                    Console.WriteLine();
                }
            }

            public void SpawnRandomFood()
            {
                Random rand = new Random();

                while (true)
                {
                    int ranX = rand.Next(0, X);
                    int ranY = rand.Next(0, Y);

                    if (tiles[ranY][ranX].objType == ObjectType.BLANK)
                    {
                        tiles[ranY][ranX].objType = ObjectType.FOOD;
                        break;
                    }
                }
            }

            public bool IsFoodExist()
            {
                for (int i = 0; i < Y; i++)
                {
                    for (int j = 0; j < X; j++)
                    {
                        if (tiles[i][j].objType == ObjectType.FOOD)
                        {
                            return true;
                        }
                    }
                }

                return false;
            }
        }

        class SnakeHead : Object
        {
            List<Object> body = new List<Object>();

            public SnakeHead(Vector2 startPos) : base(startPos, new Vector2Int(1, 0), ObjectType.SNAKE_HEAD)
            {
                body.Add(this);
                for (int i = 1; i < 4; i++)
                {
                    body.Add(new Object(new Vector2(trans.position.X - i, trans.position.Y), 
                        trans.rotation, ObjectType.SNAKE_BODY));
                }
            }

            public bool TryMove(Map map, Vector2Int dir)
            {
                bool isCrashed = false;

                Vector2 lastBodyPosition = body[body.Count - 1].trans.position;

                // 머리 다음 부분을 제외한 몸통 이동
                for (int i = body.Count - 1; i > 1; i--)
                {
                    body[i].trans.rotation = body[i - 1].trans.rotation;

                    body[i].trans.position = 
                        new Vector2(body[i].trans.position.X + body[i].trans.rotation.X,
                        body[i].trans.position.Y + body[i].trans.rotation.Y);

                    map.tiles[body[i].trans.position.Y][body[i].trans.position.X].objType = 
                        body[i].objType;
                }

                body[1].trans.rotation = trans.rotation;
                body[1].trans.position =
                    new Vector2(body[1].trans.position.X + body[1].trans.rotation.X,
                    body[1].trans.position.Y + body[1].trans.rotation.Y);

                map.tiles[body[1].trans.position.Y][body[1].trans.position.X].objType = 
                    body[1].objType;

                trans.rotation = dir;
                trans.position.X += trans.rotation.X;
                trans.position.Y += trans.rotation.Y;

                if (map.tiles[trans.position.Y + dir.Y][trans.position.X + dir.X].objType == 
                    ObjectType.WALL)
                {
                    isCrashed = true;
                }
                else if (map.tiles[trans.position.Y + dir.Y][trans.position.X + dir.X].objType ==
                    ObjectType.SNAKE_BODY)
                {
                    isCrashed = true;
                }
                else if (map.tiles[trans.position.Y + dir.Y][trans.position.X + dir.X].objType ==
                    ObjectType.FOOD)
                {
                    Object tail = body[body.Count - 1];
                    body.Add(new Object(new Vector2(tail.trans.position.X - tail.trans.rotation.X, tail.trans.position.Y - tail.trans.rotation.Y),
                        body[body.Count - 1].trans.rotation, ObjectType.SNAKE_BODY));
                    lastBodyPosition = body[body.Count - 1].trans.position;
                }

                map.tiles[trans.position.Y][trans.position.X].objType = objType;

                map.tiles[lastBodyPosition.Y][lastBodyPosition.X].objType = ObjectType.BLANK;

                return isCrashed;
            }
        }
    }
}

블랙잭

딜러와 겨루는 블랙잭 게임을 구현했습니다.

나의 조건 : 21이 목표, 21을 넘어가면 짐, 1~13의 카드뭉치 4개 => 52장

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sparta
{
    class 블랙잭
    {
        static int cardCount = 3;
        static int targetSum = 21;

        static void Main(string[] args)
        {
            List<int> cardSet = new List<int>();
            int[] cards = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 };
            for (int i = 0; i < 4; i++)
            {
                cardSet.AddRange(cards);
            }

            // 1 ~ 13
            List<int> playerCards = new List<int>();
            int playerSum = 0;

            List<int> dealerCards = new List<int>();
            int dealerSum = 0;

            Random rand = new Random();

            for (int i = 0; i < cardCount; i++)
            {
                int randomIndex = rand.Next(0, cardSet.Count);
                playerCards.Add(cardSet[randomIndex]);
                cardSet.RemoveAt(randomIndex);
                WriteCards(playerCards);

                randomIndex = rand.Next(0, cardSet.Count);
                dealerCards.Add(cardSet[randomIndex]);
                cardSet.RemoveAt(randomIndex);
                WriteCards(dealerCards);

                playerSum = CalculateSum(playerCards);
                dealerSum = CalculateSum(dealerCards);

                if (playerSum > targetSum && dealerSum > targetSum)
                {
                    Console.WriteLine("둘 다 합이 목표 숫자를 넘었습니다. 무승부입니다.");
                    break;
                }
                else if (dealerSum > targetSum)
                {
                    Console.WriteLine("플레이어의 승입니다.");
                    break;
                }
                else if (playerSum > targetSum)
                {
                    Console.WriteLine("딜러의 승입니다.");
                    break;
                }

                if (i == cardCount - 1)
                {
                    if (playerSum == dealerSum)
                    {
                        Console.WriteLine("합이 같습니다. 무승부입니다.");
                    }
                    else if (playerSum > dealerSum)
                    {
                        Console.WriteLine("플레이어의 승입니다.");
                    }
                    else
                    {
                        Console.WriteLine("딜러의 승입니다.");
                    }
                }
            }
        }

        private static void WriteCards(List<int> cards)
        {
            foreach (var card in cards)
            {
                Console.Write($"{card} ");
            }
            Console.WriteLine();
        }

        private static int CalculateSum(List<int> cards)
        {
            int sum = 0;
            foreach (var card in cards)
            {
                sum += card;
            }

            return sum;
        }
    }
}

 

오늘의 회고

 오늘은 강의 듣느랴 기능 구현 해보느랴 코드 이해하느랴 정신이 없었다. 하지만 하나하나 문제가 풀려가는 것을 보고 꽤나 보람을 느낀 하루였던 것 같다. 이렇게만 꾸준히 노력하면 나도 언젠가 코딩 고수가 되지 않을까 하는 기대를 품으며 내일도 달려보도록 하겠다.

 내일은 알고리즘 강의 듣는 것이 목표인데 먼저 알고리즘 분야 별로 접근법이나 빅오 표기법을 위주로 공부하도록 해야겠다. 내일도 파이팅!

 

 

참고 :

https://potatopotatopotato.tistory.com/15

 

C# 콘솔로 스네이크 게임 찍어내기

숙제라서 만들었다 만들고 보니까 껌뻑껌뻑 거리는데 예전에 WINAPI 할때 더블 버퍼링 생각이 났다 어렵진 않았는데 시간 박치기 구현 노가다라서 피곤했다.. 저녁까지 밖에 나갔다 왔는데 집에

potatopotatopotato.tistory.com

 

한 주 회고

깃 데스크탑만 쓰다가 깃 배쉬도 써보고

카드 게임 리소스를 위해 팀원들과 사진 공유도 해보고

연출적인 부분을 맡아 여러가지 시도를 해보았다. 씬 페이딩이나 원의 방정식을 이용한 카드 세팅이라던지 처음엔 이해하기 어려웠지만 구현하고 나서는 다음에 또 써먹어야지 하는 생각이 들었다.

ppt 작업은 정민님이 해주셔서 부담감 없이 발표를 할 수 있게 되었다. 압도적 감사!

 

다음 주 목표

C# 과제들이 많던데 최대한 이해하면서 구현하는 방향으로 진행해야겠다. C# 과제 구현이 끝난다면 개인 공부(최적화, 쉐이더, 게임 수학)도 간간히 진행할 생각이다.  

오늘의 학습 키워드

문자열

 

공부한 내용

문자열 형식 지정

string.format(string format, string args[]);

문자열 대소 비교

string str1 = "Apple";
string str2 = "Banana";
// compare < 0 일 땐 str1 < str2, compare == 0 일 땐 str1 == str2, compare > 0 일 땐 str1 > str2
// A가 B보다 작으므로 음수 나옴
int compare = string.Compare(str1, str2);

문자열 갱신

boardUI가 갱신이 되지 않는 버그 수정

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sparta
{
    class 틱택토_게임
    {
        static void Main(string[] args)
        {
            int[] board = new int[9];
            int player = 1;
            int enemy = 2;
            Random random = new Random();

            string boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";

            Console.WriteLine(boardUI);
            int user = 0;
            bool result = false;

            while (true)
            {
                int tempNum = -1;
                tempNum = ChooseNum(board);
                // 1 : 012 036 048
                // 2 : 012 147
                // 3 : 012 258 246
                // 4 : 345 036
                // 5 : 345 147 048 246
                // 6 : 345 258
                // 7 : 678 036 246
                // 8 : 678 147
                // 9 : 678 258 048

                // 012 345 678 036 147 258 048 246
                board[tempNum] = player;
                boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";
                Console.WriteLine(boardUI);
                result = CheckBingo(board, tempNum);

                if (result)
                {
                    user = player;
                    break;
                }

                tempNum = SelectRandomNum(random, board);
                board[tempNum] = enemy;
                boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";
                Console.WriteLine(boardUI);
                result = CheckBingo(board, tempNum);

                if (result)
                {
                    user = enemy;
                    break;
                }
            }

            WriteResult(user);
        }
    }
}

바보 같이 string을 갱신해주지 않아서 생긴 문제였다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sparta
{
    class 틱택토_게임
    {
        static void Main(string[] args)
        {
            int[] board = new int[9];
            int player = 1;
            int enemy = 2;
            Random random = new Random();

            string boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";

            Console.WriteLine(boardUI);
            int user = 0;
            bool result = false;

            while (true)
            {
                int tempNum = -1;
                tempNum = ChooseNum(board);
                // 1 : 012 036 048
                // 2 : 012 147
                // 3 : 012 258 246
                // 4 : 345 036
                // 5 : 345 147 048 246
                // 6 : 345 258
                // 7 : 678 036 246
                // 8 : 678 147
                // 9 : 678 258 048

                // 012 345 678 036 147 258 048 246
                board[tempNum] = player;
                boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";
                Console.WriteLine(boardUI);
                result = CheckBingo(board, tempNum);

                if (result)
                {
                    user = player;
                    break;
                }

                tempNum = SelectRandomNum(random, board);
                board[tempNum] = enemy;
                boardUI = $"-------\n-{board[0]}-{board[1]}-{board[2]}-\n-{board[3]}-{board[4]}-{board[5]}-\n-{board[6]}-{board[7]}-{board[8]}-\n-------";
                Console.WriteLine(boardUI);
                result = CheckBingo(board, tempNum);

                if (result)
                {
                    user = enemy;
                    break;
                }
            }

            WriteResult(user);
        }
    }
}

 

오늘의 회고

 오늘은 12시간 중 10~11시간 정도는 몰입한 것 같다. 구현이 오래 걸리는 문제나 내가 모르는 부분이 있어 정보를 찾는데 시간이 오래 걸렸는데 그 과정이 순탄치는 않았지만 새로 알게된 내용이 많아 재밌었던 것 같다.

 내일은 과제로 있는 스네이크 게임과 블랙잭 구현을 마무리 할 것이다.

오늘 한 것

오늘은 4일동안 진행한 미니 프로젝트를 발표하는 시간을 가졌다.

다른 사람들의 작업물들도 보게 되었는데 기발한 생각들도 많고 잘 구현한 것 같았다.

간단한 프로젝트였지만 까먹었던 내용도 있고 새로 배웠던 내용들도 많아서 재밌었던 것 같다.

또한 팀원들도 좋은 사람들이라 재미있게 개발할 수 있었다.

https://github.com/j-miiin/WhereIsMyTeam

 

GitHub - j-miiin/WhereIsMyTeam: 뿔뿔이 흩어진 팀원을 모아서 팀을 완성시켜라!

뿔뿔이 흩어진 팀원을 모아서 팀을 완성시켜라! Contribute to j-miiin/WhereIsMyTeam development by creating an account on GitHub.

github.com

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

 

배운 것들

깃 허브 데스크탑

Git 작업을 GUI상에서 할 수 있는 깃 데스크탑 사용법을 배웠다.

Readme 파일에 이미지 업로드하기

깃 허브의 Issues - New Issue의 Write창에 이미지를 드래그 드랍을 해서 url을 얻어온 뒤 readme 파일에 복사하면 된다.

#if를 통한 전처리

전처리를 통해 안드로이드와 데스크탑 분기를 나눌 수 있다.

void Update()
{
// 안드로이드
#if UNITY_ANDROID
        if (Input.touchCount > 0)
        {
            touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                TouchParticle particle = ObjectPooler.I.touchPool.GetObject();
                Vector3 touchPos = mainCam.ScreenToWorldPoint(touch.position);
                touchPos.z = 0;
                particle.transform.position = touchPos;
            }
        }
#endif
// 데스크탑
#if UNITY_STANDALONE
        if (Input.GetMouseButtonDown(0))
        {
            TouchParticle particle = ObjectPooler.I.touchPool.GetObject();
            Vector3 mousePos = mainCam.ScreenToWorldPoint(Input.mousePosition);
            mousePos.z = 0;
            particle.transform.position = mousePos;
        }
#endif

    }

 

 

참고 : 

https://worthpreading.tistory.com/83

 

Github Readme에 이미지 올리기

약간의 트릭을 이용해 편리하게 Github Readme에 이미지를 삽입할 수 있다. 1. Github 프로젝트의 Issue 탭으로 들어간다. 2. New Issue 버튼을 눌러 이슈 추가 화면으로 들어간다. 3. 업로드하고자 하는 이

worthpreading.tistory.com

 

구현한 것들

간단한 오브젝트 풀링을 구현하여 터치 이펙트와 카드 파괴 이펙트를 구현하였다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPooler : MonoBehaviour
{
    public static ObjectPooler I;

    [Header("Pools")]
    public ObjectPool<TouchParticle> touchPool;
    public ObjectPool<Explosion> explosionPool;


    void Awake()
    {
        I = this;

        // 터치 이펙트 풀
        TouchParticle touchPrefab = Resources.Load<TouchParticle>("TouchParticle");
        touchPool = new ObjectPool<TouchParticle>(touchPrefab, transform, 10);

        // 카드 폭발 풀
        // 폭발은 그냥 생성 파괴 해도 될 수도? - 갯수가 얼마 없음
        Explosion explosionPrefab = Resources.Load<Explosion>("Explosion");
        explosionPool = new ObjectPool<Explosion>(explosionPrefab, transform, 4);

    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPool<T> where T : MonoBehaviour, IPoolable<T>
{
    [Header("풀링 프리펩")]
    T tPrefab;

    [Header("스택 풀")]
    Stack<T> objectPool = new Stack<T>();

    Transform parentTrans;

    public ObjectPool(T t, Transform trans, int initCount)
    {
        tPrefab = t;
        parentTrans = trans;

        for (int i = 0; i < initCount; i++)
        {
            CreateNewObject();
        }
    }

    public T GetObject()
    {
        T t = null;

        if (objectPool.Count > 0)
        {
            t = objectPool.Pop();
            t.gameObject.SetActive(true);
        }
        else
        {
            t = CreateNewObject();
        }

        return t;
    }

    void ReturnObject(T t)
    {
        objectPool.Push(t);
    }

    T CreateNewObject()
    {
        T t = Object.Instantiate(tPrefab, parentTrans);
        t.SetReturnObject(ReturnObject);
        t.gameObject.SetActive(false);
        return t;
    }
}

씬 전환 효과 구현

간단한 쉐이더를 이용해 씬 전환 효과를 구현하였다.

Shader "Unlit/Fade"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _StepValue ("FadeValue", Range(0,1)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue" = "Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha


        Pass
        {
            CGPROGRAM

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _StepValue;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed dist = distance(i.uv, float2(0.5, 0.5));
                col.a = step(dist, _StepValue);
                return col;
            }
            ENDCG
        }
    }
}

게임을 재시작 하거나 다음 단계로 넘어갈 때 Time.timeScale = 0 이 되어서 Time.deltaTime이 0이 된다. 그래서 이에 영향을 받지 않고자 Time.unscaledDeltaTime을 사용하였다.

IEnumerator CoFade(string sceneName)
    {
        float time = 0;
        while (time < fadeValue)
        {
            time += Time.unscaledDeltaTime / fadeTime;
            fadeImage.material.SetFloat("_StepValue", time);
            yield return null;
        }

        var op = SceneManager.LoadSceneAsync(sceneName);

        while (op.isDone)
        {
            yield return null;
        }

        time = fadeValue;
        while (time > 0)
        {
            time -= Time.unscaledDeltaTime / fadeTime;
            fadeImage.material.SetFloat("_StepValue", time);
            yield return null;
        }
    }

애니메이터와 파티클 또한 UnscaledTime 옵션이 존재한다.

 

시연 영상

화면 전환과 카드 파괴 풀링이 적용된 모습이다.

 

 

참고 : 

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

https://koreanfoodie.me/983

 

유니티에서 일시정지 및 특정 물체 시간 정지 구현하기

Udemy 관련 개념 정리 및 Dev Log 를 기록하고 있습니다! 유니티에서 일시정지하기 유니티에서는 일시 정지 기능을 어떻게 구현하면 될까? 먼저 결론만 말하자면, Time.timeScale 값을 조절하여 Time.deltaT

koreanfoodie.me

 

'스파르타 Unity 1기' 카테고리의 다른 글

스파르타 Unity 8기 5일차 TIL  (0) 2023.08.11
스파르타 Unity 8기 4일차 TIL  (0) 2023.08.10
스파르타 Unity 8기 2일차 TIL  (0) 2023.08.08
스파르타 Unity 8기 1일차 TIL  (1) 2023.08.07
사전캠프 4일차  (0) 2023.08.04

구현한 것들

원의 방정식을 사용한 카드 세팅 연출

작은 원을 기준으로 현재 위치에서 원과 만나는 y좌표를 찾아 직선으로 움직이는 것이 아닌 원을 따라 곡선으로 움직이게 만들어준다.

IEnumerator CoMoveOffsetPosition(Transform cardTrans, Vector3 destination)
    {
        Vector3 offsetPos = cardTrans.position;
        Vector3 targetPos = Vector3.zero;
        float ratio = 0f;
        while (ratio < cardSettingTime)
        {
            ratio += Time.deltaTime;
            targetPos = Vector3.Lerp(offsetPos, destination, ratio / cardSettingTime);

            // 원의 방정식 (x-a)^2 + (y-b)^2 = r^2
            float halfRadius = radius * 0.5f;
            // 반지름의 제곱
            float powRadius = Mathf.Pow(halfRadius, 2);
            // 현재 x위치가 목표의 왼쪽인지 오른쪽인지
            bool isDestinationXLow = targetPos.x > destination.x;
            // 오른쪽이라면 반지름 빼주기 왼쪽이라면 반지름 더해주기 (원의 센터 x좌표가 반지름만큼 차이나니까)
            float powXPos = isDestinationXLow ? Mathf.Pow(targetPos.x - destination.x - halfRadius, 2) 
                : Mathf.Pow(targetPos.x - destination.x + halfRadius, 2);
            // y좌표
            float yPos = Mathf.Sqrt(Mathf.Abs(powRadius - powXPos));

            // 현재 위치에서 목표지점까지의 선분(원의 지름) 위의 점 + 원 중심으로부터 y좌표
            targetPos.y += yPos;
            cardTrans.position = targetPos;

            yield return null;
        }
    }

카드 뒤집기 연출

카드가 뒤집어지는 연출을 구현하였다.

앞면과 뒷면을 나누어 y축을 0~90도까지 회전 90~0도까지 회전하는 방식을 사용하였다.

IEnumerator CoFlip()
    {
        // 애니메이션 꺼주기 -> rotate 애니메이션 끄기
        anim.SetBool("isOpen", true);

        // 뒷면의 y축 회전을 0 ~ 90까지 증가
        float ratio = 0f;
        Vector3 backRotation = backTransform.rotation.eulerAngles;
        while (ratio < flipRotation)
        {
            ratio += Time.deltaTime * flipSpeed;
            backRotation.y = ratio;
            backTransform.rotation = Quaternion.Euler(backRotation);
            yield return null;
        }

        // 뒷면 -> 앞면 스위치
        frontTransform.gameObject.SetActive(true);
        backTransform.gameObject.SetActive(false);

        // 앞면의 y축 회전을 90 ~ 0까지 감소
        Vector3 frontRotation = frontTransform.rotation.eulerAngles;
        while (ratio > 0)
        {
            ratio -= Time.deltaTime * flipSpeed;
            frontRotation.y = ratio;
            frontTransform.rotation = Quaternion.Euler(frontRotation);
            yield return null;
        }

    }
IEnumerator CoReverseFlip()
    {
        float ratio = 0f;

        // 앞면의 y축 회전을 0 ~ 90까지 증가
        Vector3 frontRotation = frontTransform.rotation.eulerAngles;
        while (ratio < flipRotation)
        {
            ratio += Time.deltaTime * flipSpeed;
            frontRotation.y = ratio;
            frontTransform.rotation = Quaternion.Euler(frontRotation);
            yield return null;
        }

        // 앞면 -> 뒷면 스위치
        frontTransform.gameObject.SetActive(false);
        backTransform.gameObject.SetActive(true);

        // 뒷면의 y축 회전을 90 ~ 0까지 감소
        Vector3 backRotation = backTransform.rotation.eulerAngles;
        while (ratio > 0)
        {
            ratio -= Time.deltaTime * flipSpeed;
            backRotation.y = ratio;
            backTransform.rotation = Quaternion.Euler(backRotation);
            yield return null;
        }

        // 애니메이션 켜주기 -> rotate 애니메이션 키기
        anim.SetBool("isOpen", false);
    }

터치 이펙트 구현

간단한 파티클과 풀링으로 터치 이펙트를 구현하였다.

입력 감지

void Update()
    {
#if UNITY_ANDROID
        if (Input.touchCount > 0)
        {
            touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Began)
            {
                GameObject particle = touchPool.GetObject();
                particle.transform.position = mainCam.ScreenToWorldPoint(touch.position);
            }
        }
#endif
#if UNITY_STANDALONE
        if (Input.GetMouseButtonDown(0))
        {
            ParticleSystem particle = touchPool.GetObject();
            Vector3 mousePos = mainCam.ScreenToWorldPoint(Input.mousePosition);
            mousePos.z = 0;
            particle.transform.position = mousePos;
        }
#endif

    }

스택으로 간단한 풀링 구현

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TouchPool
{
    ParticleSystem touchParticle;
    Stack<ParticleSystem> particlePool = new Stack<ParticleSystem>();

    Transform parentTrans;

    public TouchPool(ParticleSystem particle, Transform trans)
    {
        touchParticle = particle;
        parentTrans = trans;
    }

    public ParticleSystem GetObject()
    {
        ParticleSystem particle = null;

        if (particlePool.Count > 0)
        {
            particle = particlePool.Pop();
            particle.gameObject.SetActive(true);
        }
        else
        {
            particle = CreateNewObject();
            particlePool.Push(particle);
        }

        return particle;
    }

    void ReturnObject(ParticleSystem particle)
    {
        particle.gameObject.SetActive(false);
        particlePool.Push(particle);
    }

    ParticleSystem CreateNewObject()
    {
        ParticleSystem particle = Object.Instantiate(touchParticle, parentTrans);
        particle.GetComponent<TouchParticle>().SetReturnCallback(ReturnObject);
        return particle;
    }
}

 

중간 점검

오늘 개발한 것들을 포함한 플레이 영상이다.

 

 

참고 :

https://youtu.be/Xo7EEegTUfE

 

'스파르타 Unity 1기' 카테고리의 다른 글

스파르타 Unity 8기 4일차 TIL  (0) 2023.08.10
스파르타 Unity 8기 3일차 TIL  (0) 2023.08.09
스파르타 Unity 8기 1일차 TIL  (1) 2023.08.07
사전캠프 4일차  (0) 2023.08.04
사전캠프 3일차  (0) 2023.08.03

구현 내용

스프라이트 참조와 스케일 세팅

Resources.LoadAll로 Sprite를 다 부른 후 특정 스프라이트 기준으로 스케일을 세팅하였다.

void Start()
    {
        Time.timeScale = 1f;

        int[] teams = { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7 };
        teams = teams.OrderBy(item => Random.Range(-1.0f, 1.0f)).ToArray();

        // 폴더의 스프라이트 모두 부르기
        Sprite[] sprites = Resources.LoadAll<Sprite>(CARD_PATH);

        for (int i = 0; i < 16; i++)
        {
            GameObject newCard = Instantiate(card);
            // newCard를 cards 안으로 옮겨줘
            newCard.transform.parent = GameObject.Find("cards").transform;
            float x = (i / 4) * 1.4f - 2.1f;
            float y = (i % 4) * 1.4f - 3.0f;
            newCard.transform.position = new Vector3(x, y, 0);

            Transform frontTrans = newCard.transform.Find("front");
            SpriteRenderer cardRenderer = frontTrans.GetComponent<SpriteRenderer>();

            cardRenderer.sprite = sprites[teams[i]];

            // 스케일 세팅
            Vector3 tempScale = frontTrans.transform.localScale;
            tempScale.x *= rtanSpriteSize / cardRenderer.sprite.rect.width;
            tempScale.y *= rtanSpriteSize / cardRenderer.sprite.rect.height;
            frontTrans.localScale = tempScale;
        }
    }

맞췄을 때와 아닐 때 구분하기

정해진 시간을 기준으로 맞췄을 때와 아닐 때를 구분하였다.

private IEnumerator CoVerifyMatching(string cardName, bool isCorrect = false)
    {
        // 맞췄을 때
        if (isCorrect)
        {
            matchText.text = cardName.Split('_')[0];
            matchText.color = correctColor;
        }
        // 아닐 때
        else
        {
            matchText.text = unCorrectMessage;
            matchText.color = unCorrectColor;
        }

        matchText.gameObject.SetActive(true);
        yield return new WaitForSeconds(matchTextTime);
        matchText.gameObject.SetActive(false);
    }

 

플레이 장면

인게임 플레이

 

느낀 점

 나는 게임 개발자로서 성장하기 위해 이번 부트캠프에 도전하게 되었다. 꾸준함과 노력은 결과를 배신하지 않는다고 믿고있다. 단순히 시간을 버리는 것이 아닌 주어진 시간에 집중하여 내 자신을 가꿔 한 발 더 성장하는 시간이 되었으면 좋겠다. 오늘 보다 나은 내일의 내가 될 것을 다짐하며 오늘 하루를 보냈다. 게으름과 싸워 지지말자 파이팅!

'스파르타 Unity 1기' 카테고리의 다른 글

스파르타 Unity 8기 3일차 TIL  (0) 2023.08.09
스파르타 Unity 8기 2일차 TIL  (0) 2023.08.08
사전캠프 4일차  (0) 2023.08.04
사전캠프 3일차  (0) 2023.08.03
사전캠프 2일차  (0) 2023.08.02

배운 것들

스플래시 이미지 : 앱을 켰을 때 떴다가 사라지는 이미지

Edit - Project Settings - Player - Splash Image

Splash Image

Splash Screen - Preview (기본은 Made With Unity)

Splash Style

- Light on Dark : 검은 화면에 빛

- Dark on Light : 빛에 검은 화면

Animation

Static - 그대로

Dolly - 커졌다 사라짐

Light on Dark
Dark on Light

Draw Mode

- Unity Logo Below

- All Sequential

Unity Logo Below
All Sequential

로고 : MeshType - FullRect

로고 이미지는 짤리는 경우가 있어 Mesh Type을 Full Rect로 해주면 좋다.

Full Rect - 이미지 전체 영역 렌더링

Tight - 투명영역을 제외한 RGBA 영역 렌더링

안드로이드 배포

안드로이드 마켓에 빌드하기 위해서는 64bit 지원이 필수

Configuration - Scripting Backend : IL2CPP

Target Architectures - ARM64 체크

publishing Settings : 앱에 대한 권한 설정

Keystore Manager 눌러서 만듬

Keystore Manager

유니티 광고

Project Settings - Services General Settings에 계정 연결하기

Organizations 선택 후 Create project ID

13세 이하 광고 - NO

Window - General - Services

Advertisement 설치

만약 이게 안 뜰 시 에디터 껐다 키기

Leran More - Unity Monetize 대시보드 이동

Unity Monetize 대시보드
프로젝트 연결

광고 세팅

끝 버튼 누르면 광고를 보여주고 광고를 다 봐야만 재시작을 할 수 있도록 함

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class endTxt : MonoBehaviour
{
    public void ReGame()
    {
        adsManager.I.ShowRewardAd();
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Advertisements;

public class adsManager : MonoBehaviour, IUnityAdsShowListener, IUnityAdsInitializationListener, IUnityAdsLoadListener
{
    public static adsManager I;

    string adType;
    string gameId;
    void Awake()
    {
        I = this;

        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            adType = "Rewarded_iOS";
            gameId = "5369710";
        }
        else
        {
            adType = "Rewarded_Android";
            gameId = "5369711";
        }

        Advertisement.Initialize(gameId, true, this);
    }

    public void ShowRewardAd()
    {
        if (Advertisement.isSupported)
        {
            Advertisement.Show(adType, this);
        }
    }

    /*void ResultAds(ShowResult result)
    {
        switch (result)
        {
            case ShowResult.Failed:
                Debug.LogError("광고 보기에 실패했습니다.");
                break;
            case ShowResult.Skipped:
                Debug.Log("광고를 스킵했습니다.");
                break;
            case ShowResult.Finished:
                // 광고 보기 보상 기능 
                Debug.Log("광고 보기를 완료했습니다.");
                break;
        }
    }*/

    public void OnUnityAdsShowFailure(string placementId, UnityAdsShowError error, string message)
    {
        Debug.LogError("광고 보기에 실패했습니다.");
    }

    public void OnUnityAdsShowStart(string placementId)
    {
        Debug.Log("광고 보기를 시작했습니다.");
    }

    public void OnUnityAdsShowClick(string placementId)
    {
        Debug.Log("광고 보기를 클릭했습니다.");
    }

    public void OnUnityAdsShowComplete(string placementId, UnityAdsShowCompletionState showCompletionState)
    {
        Debug.Log("광고 보기를 완료했습니다.");
        gameManager.I.retryGame();
    }

    public void OnInitializationComplete()
    {
        Debug.Log("Init Success");
    }

    public void OnInitializationFailed(UnityAdsInitializationError error, string message)
    {
        Debug.Log($"Init Failed: [{error}]: {message}");
    }

    public void OnUnityAdsAdLoaded(string placementId)
    {
        Debug.Log($"Load Success: {placementId}");
    }

    public void OnUnityAdsFailedToLoad(string placementId, UnityAdsLoadError error, string message)
    {
        Debug.Log($"Load Failed: [{error}:{placementId}] {message}");
    }
}

광고를 적용하자.

참고 : 

https://notyu.tistory.com/43

 

유니티 스프라이트 (Sprite)

1. 스프라이트 ( Sprite ) 스프라이트는 텍스쳐이며, 2D 그래픽 오브젝트이다. 스프라이트는 2D 그래픽에 사용된다. 스프라이트는 PNG, JPG와 같은 이미지 파일이 아니다. UI에 그림파일을 등록하고, Sce

notyu.tistory.com

 

'스파르타 Unity 1기' 카테고리의 다른 글

스파르타 Unity 8기 2일차 TIL  (0) 2023.08.08
스파르타 Unity 8기 1일차 TIL  (1) 2023.08.07
사전캠프 3일차  (0) 2023.08.03
사전캠프 2일차  (0) 2023.08.02
사전캠프 1일차  (0) 2023.08.01

+ Recent posts