스파르타코딩클럽 게임개발
오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 9일차(스네이크 게임 발표 준비하기)
코드천자문
2023. 11. 10. 01:58
반응형
스네이크 게임은 오늘은 스파르타 코딩 클럽 unity 게임 개발 과정 6일차! (tistory.com) 에서 만들었던 적이 있는데 10일 오후 2시에 그 코드를 공개하며 발표를 하는 날이다.
왜 그렇게 하냐면..
다수의 사람들이 저 게임을 만드는 과제가 어려워서 잘 모르겠다! 라고 하는 것이 큰 이유였다.
매니저님 께서 혹시 해볼생각이 없냐 해서 한다고 했는데..
첫날의 틱택토의 발표가 아주 프로같이 잘 하시는 바람에 나는 좀 더 준비를 해야했다..
코드에 주석도 새로 달고 리팩토링도 하면서.. 나름 열심히 준비해봤다.
그런데 신기한게 이렇게 발표 준비를 해보니 나의 코드가 좀더 명확히 이해된다는 점이다.
참 신기하다..
내가 짠 코드인데도 어라 이부분은 왜 이렇게 되었을까 하는 점이 있었는데
그런 부분이 발표를 준비하면서 사라져갔다.
오늘의 느낀점!
자신의 코드를 흐름대로 이해하면 더욱 좋은 코드를 작성할 수 있다!
아래는 내가 다시 작성한 스네이크 게임 코드다.
SnakeGame.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
|
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
// 뱀 게임을 위한 클래스
class SnakeGame
{
// 메인 함수
static void Main(string[] args)
{
// 필요한 변수들을 선언합니다.
ConsoleKeyInfo consoleKeyInfo; // 키보드 입력을 처리하기 위한 변수
Snake snake; // 뱀 객체
FoodCreator foodCreator; // 먹이 생성기 객체
// 게임을 시작합니다.
StartGame(out consoleKeyInfo, out snake, out foodCreator);
// 게임 화면을 그립니다.
gameDraw();
// 게임 루프를 시작합니다.
SnakeGameLoop(consoleKeyInfo, snake, foodCreator);
}
// 게임을 시작하는 함수입니다.
private static void StartGame(out ConsoleKeyInfo consoleKeyInfo, out Snake snake, out FoodCreator foodCreator)
{
Console.WriteLine("게임을 시작하려면 엔터키를 누르세요.");
// 엔터키를 누를 때까지 기다립니다.
while (true)
{
consoleKeyInfo = Console.ReadKey();
if (consoleKeyInfo.Key == ConsoleKey.Enter)
{
break;
}
}
// 게임 화면을 초기화합니다.
Console.Clear();
// 뱀을 생성합니다.
Point p = new Point(4, 5, '*');
snake = new Snake(p, 4, Direction.RIGHT);
// 먹이를 생성합니다.
foodCreator = new FoodCreator(5, 10, '$');
}
// 게임 화면을 그리는 함수입니다.
private static void gameDraw()
{
int x = 0;
int y = 0;
// 게임판의 가로 선을 그립니다.
for (int j = 0; j < 2; j++)
{
for (int i = 0; i < 30; i++)
{
Console.SetCursorPosition(i, y);
Console.Write("-");
}
y = 20;
}
// 게임판의 세로 선을 그립니다.
for (int j = 0; j < 2; j++)
{
for (int i = 0; i < 25; i++)
{
Console.SetCursorPosition(x, i);
Console.Write("|");
}
x = 30;
}
}
// 게임 루프를 담당하는 함수입니다.
private static void SnakeGameLoop(ConsoleKeyInfo consoleKeyInfo, Snake snake, FoodCreator foodCreator)
{
// 게임이 계속 진행되는 동안 반복합니다.
while (true)
{
// 게임 상태를 화면에 표시합니다.
Console.SetCursorPosition(1, 21);
Console.WriteLine($"게임판의 범위는 X:0~30,Y:0~20");
Console.SetCursorPosition(1, 22);
Console.WriteLine($"현재 뱀의 위치는 X:{snake.GetPoint().x}, Y:{snake.GetPoint().y}");
Console.SetCursorPosition(1, 23);
Console.WriteLine($"뱀의 현재 길이는 :{snake.GetWidth()}");
Console.SetCursorPosition(1, 24);
Console.WriteLine($"지금까지 먹은 먹이의 수는 :{snake.GetEatCount()}");
// 키보드 입력이 있으면 처리합니다.
if (Console.KeyAvailable)
{
consoleKeyInfo = Console.ReadKey();
}
// 뱀이 먹이를 먹은 경우 처리합니다.
// 뱀의 머리가 푸드에 닿거나
if (foodCreator.IsHit(snake.GetPoint()) || snake.IsEatHit(foodCreator.GetPoint()))
{
snake.Eat(); // 뱀이 먹이를 먹었으므로 뱀의 길이를 늘립니다.
foodCreator.Rnadom(); // 먹이를 위치를 새로 정합니다.
foodCreator.Draw(); // 먹이를 그립니다.
}
// 키보드 입력에 따라 뱀의 이동 방향을 바꿉니다.
switch (consoleKeyInfo.Key)
{
case ConsoleKey.Enter: //일시 정지
break;
case ConsoleKey.LeftArrow:
snake.Clear();
snake.Move(Direction.LEFT);
snake.Draw();
break;
case ConsoleKey.UpArrow:
snake.Clear();
snake.Move(Direction.UP);
snake.Draw();
break;
case ConsoleKey.RightArrow:
snake.Clear();
snake.Move(Direction.RIGHT);
snake.Draw();
break;
case ConsoleKey.DownArrow:
snake.Clear();
snake.Move(Direction.DOWN);
snake.Draw();
break;
default:
break;
}
// 뱀의 머리가 자신의 몸이나 벽에 부딪힌 경우 게임을 종료합니다.
if (snake.IsBodyHit(snake.GetPoint()) || snake.Lose())
{
break;
}
// 게임의 속도를 조절합니다.
Thread.Sleep(50);
}
Console.WriteLine("게임이 끝났습니다. 다시 도전해보세요!");
}
}
// 뱀의 이동 방향을 표현하는 열거형입니다.
public enum Direction
{
LEFT, // 왼쪽
RIGHT, // 오른쪽
UP, // 위
DOWN // 아래
}
|
cs |
Snake.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
// 뱀을 표현하는 클래스입니다.
using System.Collections.Generic;
public class Snake
{
List<int[]> body = new List<int[]>(); // 뱀의 몸통을 저장하는 리스트입니다.
Point point; // 뱀의 머리 위치를 저장하는 변수입니다.
int width; // 뱀의 길이를 저장하는 변수입니다.
int eatCount = 0; // 뱀이 먹이를 먹은 횟수를 저장하는 변수입니다.
Direction direction; // 뱀의 이동 방향을 저장하는 변수입니다.
// Snake 클래스 생성자입니다.
public Snake(Point p, int width, Direction direction)
{
this.point = p;
this.width = width;
this.direction = direction;
// 뱀의 초기 길이만큼 몸통을 생성합니다.
for (int i = 0; i < width; i++)
{
Move(this.direction);
Draw();
}
}
// 뱀의 이동을 처리하는 메서드입니다.
public void Move(Direction direction)
{
// 이동 방향에 따라 뱀의 머리 위치를 변경합니다.
switch (direction)
{
case Direction.LEFT:
point.x--;
break;
case Direction.RIGHT:
point.x++;
break;
case Direction.UP:
point.y--;
break;
case Direction.DOWN:
point.y++;
break;
default:
break;
}
// 뱀의 새로운 머리 위치를 몸통 리스트에 추가합니다.
body.Add(new int[] { point.x, point.y });
}
// 뱀의 꼬리를 지우는 메서드입니다.
public void Clear()
{
// 몸통을 뱀의 길이만큼만 유지하고, 그 이상이 되면 가장 오래된 몸통(꼬리)을 지웁니다.
if (body.Count >= width)
{
//리스트에선 First가 제일 오래된 요소이고 Last가 제일 최근에 만들어진 요소입니다.
Console.SetCursorPosition(body.First()[0], body.First()[1]);
Console.Write(' ');
body.RemoveAt(0); // 리스트의 첫번째 요소를 삭제합니다.
}
}
// 뱀을 화면에 그리는 메서드입니다.
public void Draw()
{
Console.SetCursorPosition(point.x, point.y);
Console.Write(point.sym);
}
// 뱀의 머리가 몸통에 부딪혔는지 확인하는 메서드입니다.
public bool IsHit(Point p) { return body.Where(x => (p.x == x[0]) && (p.y == x[1])).Count() > 1; }
public bool IsBodyHit(Point p)
{
List<int[]> list = new List<int[]>();
foreach (var x in body)
{
if (x[0] == p.x && x[1] == p.y) list.Add(x);
}
return list.Count > 1;
// 바디의 마지막요소는 항상 현재 머리의 포인트와
// 동일하니 카운트는 1개가 초과할때로 지정
}
// 뱀의 몸이 먹이에 닿았는지 확인하는 메서드입니다.
//public bool IsEatHit(Point p) { return body.Where(x => (p.x == x[0]) && (p.y == x[1])).Count() > 0; }
public bool IsEatHit(Point p)
{
List<int[]> list = new List<int[]>();
foreach (var x in body)
{
if (x[0] == p.x && x[1] == p.y) list.Add(x);
}
return list.Count > 0;
}
// 뱀이 먹이를 먹었을 때 처리하는 메서드입니다.
public void Eat()
{
eatCount++;
width++;
}
// 뱀이 게임판 밖으로 나갔는지 확인하는 메서드입니다.
public bool Lose()
{
return GetPoint().x >= 30 || GetPoint().x <= 0 || GetPoint().y >= 20 | GetPoint().y <= 0;
}
// 뱀의 길이를 반환하는 메서드입니다.
public int GetWidth() { return width; }
// 뱀이 먹이를 먹은 횟수를 반환하는 메서드입니다.
public int GetEatCount() { return eatCount; }
// 뱀의 머리 위치를 반환하는 메서드입니다. Move로 인해 현재의 위치를 반환합니다.
public Point GetPoint() { return point; }
}
|
cs |
FoodCreator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
// 먹이를 생성하는 클래스입니다.
public class FoodCreator
{
Random random = new Random(); // 랜덤한 위치에 먹이를 생성하기 위한 변수
int width; // 먹이의 가로 위치
int height; // 먹이의 세로 위치
char sym; // 먹이를 표시하는 문자
// FoodCreator 클래스 생성자입니다.
public FoodCreator(int width, int height, char sym)
{
this.width = width;
this.height = height;
this.sym = sym;
Draw();
}
// 먹이를 생성하는 메서드입니다.
public Point CreateFood()
{
return new Point(width, height, sym);
}
// 먹이 위치를 랜덤하게 정해주는 메서드입니다.
public void Rnadom()
{
width = random.Next(5, 25);
height = random.Next(5, 20);
}
// 먹이를 화면에 그리는 메서드입니다.
public void Draw()
{
Console.SetCursorPosition(width, height);
Console.Write(sym);
}
// 뱀이 먹이에 닿았는지 확인하는 메서드입니다.
public bool IsHit(Point p)
{
return p.x == width && p.y == height;
}
// 현재 먹이의 위치를 반환하는 메서드입니다.
public Point GetPoint()
{
return CreateFood();
}
}
|
cs |
Point.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 점을 표현하는 클래스입니다.
public class Point
{
public int x { get; set; } // 점의 가로 위치
public int y { get; set; } // 점의 세로 위치
public char sym { get; set; } // 점을 표시하는 문자
// Point 클래스 생성자입니다.
public Point(int _x, int _y, char _sym)
{
x = _x;
y = _y;
sym = _sym;
}
// 점을 화면에 그리는 메서드입니다.
public void Draw()
{
Console.SetCursorPosition(x, y);
Console.Write(sym);
}
// 점을 지우는 메서드입니다.
public void Clear()
{
sym = ' ';
Draw();
}
// 두 점이 같은 위치에 있는지 확인하는 메서드입니다.
public bool IsHit(Point p)
{
return p.x == x && p.y == y;
}
}
|
cs |
여기까지가 내가 작성한 스네이크 코드다.
그리고 발표를 위한 대본과 연습을 위한 ai음성을 담은 동영상을 게시하겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
//메인
먼저 필요한 변수들을 선언합니다.
콘솔키인포 스네이크 푸드크리에이트를 선언합니다.
그리고 게임을 시작하는 함수와 화면을 그리는 함수 그리고 게임루프를 시작하는 함수를 실행합니다.
//메인에서 => 게임스타트로 이동
게임스타트는 게임을 시작하는 함수입니다.
게임스타트는 매개변수로 콘솔인포 스네이크 푸드크리에이트를 받고 아웃을 이용해 반환됩니다.
(전역변수와 비슷합니다.) 이 함수가 종료되면 아웃으로 인해 매개변수의 값이 초기화되고 다른 함수에게도 적용됩니다.
이 웨일문은 엔터키를 누르때 까지 기다리고 입력하면 웨일문을 빠져나갑니다.
게임 화면을 초기화 하고
스네이크과 푸드를 생성합니다.
스네이크과 푸드는 생성과 동시에 그려집니다.
//게임스타트에서 => 스네이크 클래스로 이동
먼저 스네이크는 매개변수로 받은 포인트와 초기 몸통 길이값 와이드 그리고 디렉션을
자신의 속성으로 초기화합니다.
스네이크에는 바디 포인트 와이드 잇카운트 디렉션의 속성이 있습니다.
그리고 스네이크의 몸통을 포문을 사용하여 그립니다.
스네이크의 몸통이 그려지는 원리는 매개변수로 인해 초기화된 스네이크의 속성 디렉션이
무브로 들어가는 것부터 시작합니다. 무브는 스네이크의 이동을 처리하는 함수입니다.
이동 방향에 따라 뱀의 머리위치를 변경합니다.
현재 무브에게 주어진 매개변수는 라이트입니다. 스위치에서 디렉션이 라이트 일때
포인트.x가 증감연산자로 인해 1 증가됩니다.
변화된 포인트를 바디.. 뱀의 몸통을 저장하는 리스트에 추가 시킵니다.
무브는 이렇게 포인트를 변화 시켰고 포문에서 남은것은 드로우입니다.
드로우는 변화된 포인트를 가져와 그곳에 뱀의 몸통을 그립니다.
포문으로 그것을 네번 반복하면 총 사의 길이를 가진 뱀이 완성됩니다.
//게임스타트에서 => 푸드 클래스로 이동
푸드크리에이트는 스네이크와 같이 자신의 속성에 매개변수를 초기화 하고 드로우합니다.
//게임드로우로
게임화면을 그리는 함수입니다.
//스네이크게임루프
게임루프를 담당하는 함수입니다.
매개변수로는 게임스타트로 인해 값이 초기화 된 콘솔인포 스네이크 푸드크리에이트를 받습니다.
게임이 진행되는 동안 반복합니다.
게임의 상태를 화면에 표시합니다. 여기서 스네이크의 속성을 불러옵니다.
키보트의 입력이 있으면 처리합니다. 이부분은 연속적인 움직임을 위해 만들었습니다.
먹이를 먹는 경우 보다 먼저 이동에 관려해 말씀 드리겠습니다.
이 스위치는 키보드의 입력값에 따라 뱀의 이동방향을 바꾸도록 되어있습니다.
만약 우리가 오른쪽 방향키를 눌렀다면 먼저 스네이크의 클리어가 실행됩니다.
//클리어
클리어는 뱀의 꼬리를 지우는 함수입니다.
몸통을 뱀의 길이 만큼만 유지하고 그 이상이 되면 가장 오래된 요소는 꼬리이니 지웁니다.
그리고 바디의 리스트에서 첫번쨰 요소를 삭제합니다. 즉 꼬리를 삭제합니다.
그러면 현재 뱀의 몸통은 삼 일겁니다.
//무브
무브에서는 디렉션을 매개변수로 받고 아까전과 같이 포인트를 이동시키고 바디에 값을 추가합니다.
그렇게 되면 뱀의 몸통은 사가 됩니다.
//드로우
무브에 인해 변화된 머리의 위치(포인트)에 뱀의 머리를 그립니다.
//스네이크게임루프
이로써 꼬리가 지워지고 포인트가 이동되어 그곳으로 다시 머리가 그려지는 것이 반복되며 우리의
눈에서는 뱀이 움직이는 것 처럼 보이게 됩니다. //먹이를 먹은경우
이제 뱀이 먹이를 먹은 경우입니다.
먹이를 먹는 경우는 두가지인데 푸드의 포인트에 뱀의 머리가 닿거나 뱀의 몸에 푸드의 포인트가
닿은 경우입니다. 먼저 뱀의 머리가 푸드에 닿았을때 입니다.
스네이크 겟 포인트는 뱀의 머리 위치를 반환하는 함수입니다. 포인트는 무브로 인해
현재의 위치를 반환합니다. 푸드 크리에이터의 이스힛은 자신의 위치와 뱀의 위치가 같은지 확인합니다.
이제 뱀의 몸통에 푸드가 몸에 닿았을 때 입니다.
푸드크리에이터의 겟포인트는 크레이트푸드의 리턴값을 리턴합니다.
그 값을 스네이크 이스 잇 히트가 매개변수로 받습니다.
이스 잇 히트는 새로운 리스트를 만듭니다. 이 리스트의 카운트로 불값을 반환할 것입니다.
포이치를 활용해 바디의 요소중 포인트와 일치하는 것을 리스트에 추가시킵니다.
만약 리스트에 요소가 하나라도 있다면 뱀의 몸 어딘가에 먹이가 닿았다는 것이 됩니다.
그리고 이것은 이 함수를 링큐로 만든것인데요.
두 함수는 기능적으로는 같습니다.
바디에 웨얼을 사용해 포인트가 같은것을 찾고 그것의 카운트로 불을 반환합니다.
리스트는 아이넘레이블 인터페이스를 구현하고 있기 때문에
아이넘레이블의 속성 및 메서드를 사용할 수 있고 웨얼은 조건문과 같은 역할을 하는 아이넘레이블의 속성입니다.
이 x는 하단의 포이치에서의 x와 같은 역할을 하고있다고 보시면 됩니다.
링큐에 대한 설명은 여기까지 하겠습니다.
두가지의 조건을 만든 이유는 머리로만 판단하면 만약 너무 몸통이 길어졌을때 랜덤한 위치에서 생성된
먹이가 몸에 닿았을때 나중에는 클로어 때문에 먹이의 위치가 안보이게 되어서 그랬습니다.
그러자 먹이에 뱀의 머리가 닿는 조건은 필요없게 되었는데 저와 다른방식으로 하시는 분이 있을까봐
지우지 않고 놔두었습니다.
뱀이 먹이를 먹었다면 스네이크 잇이 실행되서 먹은 횟수와 몸의 길이가 늘어나게 됩니다.
몸의 길이가 늘어나면 클리어 메서드에서 바디의 길이 조건을 완화시켜줍니다.
그렇게 되면 이동 할때 바디의 카운트가 사이고 몸의 길이는 오이니 삭제되지 않고
드로우 되어 네개가아닌 다섯개의 몸통을 가질수 있게됩니다.
//뱀의 머리가 자신의 몸이나 벽에 부딛힌 경우 게임을 종료
종료의 조건은 두가지입니다.
머리가 자신의 몸통에 부딛히거나 벽에 부딛힌경우 게임이 종료됩니다.
먼저 뱀의 머리가 자신의 몸에 부딪힌 경우입니다.
스네이크 겟 포인트는 현재 머리가 있는 포인트입니다.
스네이크 이스 바디 히트는 그것을 매개변수로 받아서 조금전 보았던 이스 잇 히트와 같이 작동됩니다.
바디의 마지막 요소의 포인트는 항상 머리의 포인트와 같을테니 불값은 카운트는 한개를
초과할때로 지정합니다 벽에 부딪힌 경우는 그저 머리의 포인트가 이 포인트들 보다 작거나 크면 종료됩니다.
|
cs |
내일 발표는 내 목소리로 해야겠지만 이 블로그에는 그나마 딕션이 좋은 ai가 더 좋을거라 생각한다.
반응형