고전 게임인 스네이크 게임이다.

 

사실 지금의 게임 세대에는 스네이크 게임보다는 지렁이게임이 더 유명할 것이다.

 

세계적으로 히트한 지렁이 게임 slither 는 고전 게임인 snake 의 현대판이라고 할 수 있을 것이다.

 

 

slither.io

The smash-hit game! Play with millions of players around the world and try to become the longest of the day!

slither.io

당연히 그래픽이나 게임성은 최신의 슬리더 게임이 더 좋다.

 

2000년대 들어와서 비디오 게임기술이 그야말로 혁신을 보여주면서 게임산업은 그 자체가 4차 산업이 되버렸다.

 

유튜브와 트위치에서 가장 인기 있는 스트리머들은 게임쇼를 통해서 1년에 수백만달러(수십억) 넘게 소득을 올리고 있다. 이런 세상이 온 것은 인류 문명사 3000년에 비하면 아주 기이한 현상이다. 이 게임의 시대가 좀 더 번영할 것인지 반짝 지나갈 것인지 지금은 아무도 모른다.

 

흥미로운 것은 최신게임의 발달로 고전게임은 묘비에 묻힐 것 같았는데 그게 그렇지가 않다.

 

지뢰게임, 테트리스, 갤러가 같은 원초적 고전게임들은 여전히 세계의 어딘가에서 플레이되고 있다. 이 게임들은 상업적 가치가 초창기에 비해서 떨어질 뿐이지 완전한 게임의 구성요소를 갖춘 작품이다.

 

여기서 말하는 고전게임은 1978년에 출시한 스페이스 인베이더같은 클래식 비디오 게임(classic video game)을 말한다. 따지고 보면 트럼프 카드나 장기, 바둑같은 경우 게임이라는 것은 맞는데 비디오 게임과는 결이 다르다.

물론 컴퓨터로 구현할 수 있다. 이 세상에 있는 게임인데 컴퓨터로 못만드는 것은 아마 거의 없을 것이다.

 

클래식의 정석 스페이스 인베이더

스네이크 게임의 경우도 클래식 비디오 게임에 속한다.

 

파이썬 학습교재나 튜토리얼에 이 게임이 자주 나오는데 이유가 있다.

 

1. 구현이 쉽다.

 

- 특히나 파이썬의 코드는 다른 언어보다 더 짧아서 코딩 시간이 짧다.

- 파이썬의 자료형 (리스트, 튜플 등)과 동적 타이프를 사용하니까 자료구조에 신경안쓰고 빠른 코딩이 가능하다.

 

2. 최소한 게임의 요소를 갖췄다

 

- 조작 (게이머와 상호작용)

- 목적 (먹이를 먹는 뱀을 키운다)

- 난이도 (먹이를 먹을 수록 어려워진다)

 

3. 2D 게임그래픽과 알고리즘의 기초원리를 담고 있다

 

- 컴퓨터 그래픽의 좌표와 그리드, 캐릭터 이동시키기

- 이보다 어렵고 복잡한 게임들이 많지만 기초 원리는 동일하다 (게임루프, 게임상태 검사(score,gameover), 화면 갱신)

 

이 밖에 파이썬으로 구현하면서 OOP (객체 지향)에 대하여도 맛볼 수 있는 부분이 있다. 파이게임이라는 프레임워크의 사용법을 접할 수도 있으니 파이썬을 어느정도 배우고 나서 한번쯤은 거쳐가야 하는 단계라고도 볼 수 있다. 그동안 배운 내용들을 종합적으로 복습할 수 있다.

 

파이게임을 개발하다가 게임 개발에 관심이 생기면 자연스럽게 유니티나 언리얼엔진 쪽에도 손을 뻗게 되는 것이다. 현대의 게임개발은 클래식 비디오 게임시대와는 다르게 대기업에서 생산하는 시대이다. 그러니까 클래식 게임에 흥미가 생긴다고 큰 의미를 부여하기는 힘들다.

 

다만 컴퓨터 게임의 작동원리에 대한 호기심을 충족시킬 수 있을 것이다.

 

파이게임 공식 사이트에 들어가면 전 세계 사람들이 파이게임으로 여러가지 재미있는 시도를 하고 있다. 대부분 아마추어 수준의 게임들이지만 그 중에는 상당한 퀄러티의 게임도 있다. 무엇보다 오픈소스를 분석하는 재미로 하는 것이다. 타인이 만들어낸 소스를 살펴보면서 나에게 필요한 알고리즘을 취하는 것이다.

 

www.pygame.org/tags/all

 

https://www.pygame.org/tags/all

 

www.pygame.org

 

오픈소스의 정신

 

이 포스팅에서는 파이게임으로 스네이크 게임을 만들어 본다. 구글에 검색하면 많은 소스코드가 나온다.

 

스네이크 게임을 구현하는 방법은 많은데 구글에 검색해봐도 각자 코드가 다르다. 정확히는 같기도 하고 다르기도 하고... 하나의 코드가 여러 사람들에게 돌고 돌아서 그렇다. 이 포스트의 소스코드도 돌고 돌던 코드들을 참고해서 만들었다.

 

코드란게 만들기 나름이고 쓰기 나름인데 오픈소스도 상도덕이 있다.

 

0과 1로 이루어졌을 뿐인 소스코드 자체에 어떤 법적 제약을 하는 개념이 쉽진 않지만 소스코드는 엄연히 지적 재산권으로 법적인 보호를 받는다.

 

뭐 단순한 몇줄의 코드 정도야... 대부분의 경우 그 정도 코드의 저작권을 주장한다는 것은 부당할 것이다. 그러나 코드의 길고 적은 것을 떠나 한 사람과 기업의 물질적 정신적 투자가 들어있는 내용들이면 보호 받아야 하는 것은 당연한 일이다.

 

남의 코드를 이해하지도 못한채 완전히 다 베껴서 자신의 것이라고 주장하는 것은 옳지 않다. 오픈소스 커뮤니티에서는 CC 라이센스를 많이 쓰니까 원 저작자의 출처 표시 정도로 사용할 수 있는게 많다. 출처와 링크 정도만 해줘도 훌륭하다.

 

 

발명왕 에디슨의 명언처럼 

I start where the last man left off. ("나는 나 이전의 마지막 사람이 멈추고 남겨 놓은 것에서 출발한다")

오픈소스도 마지막 사람이 남겨 놓은 것에서 출발한다. 출발하는 것은 좋은데 저작권을 잘 지켜야 한다.

 

 

어차피 오픈소스라는게 한 사람의 CC 에서 다른 사람에게로 넘어가고 이 CC의 과정이 반복되면서 누구나 무료로 쓸 수 있는 훌륭한 코드가 완성되가는 개념이다. 여기 참여하는 사람들은 돈이 목적이 아닐 아니다.

 

지금 전 세계에 수십억명이 사용하는 안드로이드 폰에는 리눅스 커널이 탑재되있다.

 

리눅스 커널의 개발자는 리누즈 토발즈다. 그가 오픈소스 개발로 돈을 벌었을까? 벌긴 벌었다. 먹고살 걱정은 안해도 된단다. 그러나 그건 그의 명성 때문에 나중에 받은 부수적인 돈이다. 반면 마이크로 소프트의 창업자인 빌 게이츠는 윈도우즈 운영체제의 소스를 오픈하지 않고 수십년간 시장을 독점하며 100조의 재산을 일궜다. 이 때문에 전통적으로 오픈소스 커뮤니티에서는 빌게이츠의 이름은 날과 달이 닳도록 까이는 명성을 얻게 되었다. 인터넷에 남아있는 수많은 비판들에 향후 50년 정도는 악명을 떨치지 않을까 싶다.

 

그들이 봤을 때 세상을 변화시키고도 묵묵히 자신의 일을 하는 리누즈 토발즈는 거의 성인급이다. 이전의 포스팅에서 했던 내용인데 데니스 리치가 유닉스와 C를 만들었고 리누즈 토발즈가 C와 유닉스를 가지고 리눅스를 만들었다. 그 후의 애플, 안드로이드 모두 그들이 완성한 토대위에 어마어마한 번영을 이룩했다.

 

나중에 구글이 물심양면으로 보상을 했다고 한다. 데니스 리치는 일찍 세상을 떠났지만 그의 동료 켄 톰슨은 고령의 나이에도 구글의 고문역으로 활동중이다. 정관예우를 보는듯

 

 

C언어와 데니스리치에 대한 이야기

C언어는 많은 사람들이 들어봤을 거라 생각한다. 나이에 상관없이 공학을 전공한 사람들은 C언어가 뭔지는 들어봤을 것이다. 데니스 리치 - 위키백과, 우리 모두의 백과사전 위키백과, 우리 모두

digiconfactory.tistory.com

스네이크 게임 / 파이썬

잡설이 좀 길어졌다. 이 블로그는 처음부터 잡설과 내용을 함께 기록하기 위해 만들어졌지만 이야기 보따리에 끝이 없으니 적당히 하고 파이썬으로 스네이크 게임을 만들어 보자.

 

파이게임에 대한 기초는 기존의 포스트에 있으니 그 쪽을 참고하길 바란다. 여기서는 파이게임의 기초부터 설명하지 않는다.

 

 

파이썬 게임만들기 1 | 파이게임 설치, 게임창 띄우기 | 게임 루프 , 이벤트 핸들링 | 파이게임 개

파이게임은 파이썬의 멀티미디어 라이브러리로 이 모듈을 설치하면 파이썬으로 게임을 만들 수 있다. 멀티미디어에 최적화된 SDL(Simple DirectMedia Layer)를 파이썬으로 감싸는(Wrapper) 라이브러리이

digiconfactory.tistory.com

이 소스코드는 인터넷의 자료를 참고해서 만들었다. 참고한 자료는 파이게임 웹사이트, 이수안 컴퓨터 연구소, 해외 유튜버 Kite 등이다.

 

suanlab.com/youtube.html#PyShooting 이수안 컴퓨터 연구소

 

YouTube | SuanLab

March 31, 2019 파이썬 Python Suan Lee 파이썬과 pygame 라이브러리 이용 우주 암석을 피하고 워프하는 우주선 게임 만들어보기 사용되는 소프트웨어 버전: Python 3.7.x, pygame 1.9.4 게임 리소스 Feb 13, 2019 하

suanlab.com

www.youtube.com/watch?v=9bBgyOkoBQ0&t=192s 유튜버 카이트

 

 

게임의 실행화면은 위와 같다. 스네이크 게임은 뱀이 먹이를 먹으며 몸을 키워나가는 게임이다. 먹이를 먹은만큼 몸이 길어지는데 머리로 자기 몸을 박으면 죽는다. 벽에다 박아도 죽게하는 코드들도 많은데 여기서는 그냥 화면 반대편으로 통과하도록 했다. 

 

 

다음은 전체 소스코드다.

 

소스코드에 각각 부분을 설명할거니까 전체의 흐름을 대충 보고 넘어간다.

import pygame
import os
import sys
import random
import time

# ----- [게임창 초기 위치] -----

win_posx = 700
win_posy = 300
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (win_posx, win_posy)

# ----- [윈도우창 설정] -----

WINDOW_WIDTH = 540
WINDOW_HEIGHT = 600
GRID = 30
GRID_WIDTH = int(WINDOW_WIDTH/GRID)
GRID_HEIGHT = int(WINDOW_HEIGHT/GRID)

# ===== [색상설정] =====

BLACK = 0, 0, 0
WHITE = 255,255,255
RED = 255, 0, 0
GREEN1 = 25, 102, 25
GREEN2 = 51, 204, 51
GREEN3 = 233, 249, 185
BLUE = 17, 17, 212
LIGHT_PINK1 = 255, 230, 255
LIGHT_PINK2 = 255, 204, 255

NORTH = ( 0, -1)
SOUTH = ( 0,  1)
WEST  = (-1,  0)
EAST  = ( 1,  0)

# ----- [스네이크 클래스] -----

class Snake:

    def __init__(self):
        self.length = 1
        self.create_snake()
        self.color1 = GREEN2
        self.color2 = GREEN3
        self.head_color = GREEN1

    def create_snake(self):
        self.length = 3
        self.positions = [(int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2))]
        self.direction = random.choice([NORTH, SOUTH, WEST, EAST])
        global game_score
        game_score = 0

    def move_snake(self, surface):

        # print(len(self.positions))
        head = self.get_head_position()
        x, y = self.direction
        next = ((head[0] + (x*GRID)) % WINDOW_WIDTH, (head[1] + (y*GRID)) % WINDOW_HEIGHT)
        if next in self.positions[2:]:
            self.create_snake()
            gameover(surface)
        else:
            self.positions.insert(0, next)
            if len(self.positions) > self.length:
                del self.positions[-1]

    def draw_snake(self, surface):
        for index, pos in enumerate(self.positions):
            if index == 0:
                draw_object(surface, self.head_color, pos)
            elif index % 2 == 0:
                draw_object(surface, self.color1, pos)
            else:
                draw_object(surface, self.color2, pos)


    def game_control(self, arrowkey):
        if (arrowkey[0]*-1, arrowkey[1]*-1) == self.direction:
            return
        else:
            self.direction = arrowkey

    def get_head_position(self):
        return self.positions[0]

# ----- [ 먹이 클래스 ] -----

class Food:
    def __init__(self):
        self.position =(0, 0)
        self.color = RED
        self.randomize_food()

    def randomize_food(self):
        self.position = (random.randint(0, GRID_WIDTH-1) * GRID,
                         random.randint(0, GRID_HEIGHT-1) * GRID)

    def draw_food(self, surface):
        draw_object(surface, self.color, self.position)


# ----- [ 전역 ] -----

def draw_background(surface):
    background = pygame.Rect((0,0),(WINDOW_WIDTH, WINDOW_HEIGHT))
    pygame.draw.rect(surface, WHITE, background)
    draw_grid(surface)

def draw_grid(surface):
    for row in range(0,int(GRID_HEIGHT)):
        for col in range(0, int(GRID_WIDTH)):
            if (row+col) % 2 == 0:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK1, rect)
            else:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK2, rect)


def draw_object(surface, color, pos):
    rect = pygame.Rect((pos[0], pos[1]), (GRID,GRID))
    pygame.draw.rect(surface, color, rect)
    pygame.draw.rect(surface, WHITE, rect, 1)


def position_check(snake, food_group):
    for food in food_group:
        if snake.get_head_position() == food.position:
            global game_score
            game_score += 1
            snake.length += 1
            food.randomize_food()


def show_info(surface, snake, speed):
    font = pygame.font.SysFont('malgungothic',35)
    image = font.render(f' 점수 : {game_score} 뱀길이: {snake.length} LV: {int(speed//2)} ', True, BLUE)
    pos = image.get_rect()
    pos.move_ip(20,20)
    pygame.draw.rect(image, BLACK,(pos.x-20, pos.y-20, pos.width, pos.height), 2)
    surface.blit(image, pos)

def gameover(surface):
    font = pygame.font.SysFont('malgungothic',50)
    image = font.render('GAME OVER', True, BLACK)
    pos = image.get_rect()
    pos.move_ip(120,220)
    surface.blit(image, pos)
    pygame.display.update()
    time.sleep(2)

player = Snake()
run = True
game_score = 0

# ----- [ 먹이 그룹 ] -----

def draw_food_group(food_group, surface):
    for food in food_group:
        food.draw_food(surface)

food = Food()
food_group = []

for i in range(3):
    food = Food()
    food_group.append(food)

for food in food_group:
    print(food.position)

# ----- [ game loop ] -----

pygame.init()
pygame.display.set_caption('SNAKE GAME')
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

while run:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                run = False
            if event.key == pygame.K_UP:
                player.game_control(NORTH)
            if event.key == pygame.K_DOWN:
                player.game_control(SOUTH)
            if event.key == pygame.K_RIGHT:
                player.game_control(EAST)
            if event.key == pygame.K_LEFT:
                player.game_control(WEST)

    draw_background(screen)
    player.move_snake(screen)
    position_check(player, food_group)
    player.draw_snake(screen)
    draw_food_group(food_group, screen)
    # food.draw_food(screen)
    speed = player.length/2
    show_info(screen, player, speed)
    pygame.display.flip()
    pygame.display.update()
    clock.tick(5+speed)

# ----- [ 파이게임 종료 ] -----

print('pygame closed')
pygame.quit()
sys.exit()

 

1. 패키지 import

import pygame
import os
import sys
import random
import time

# ----- [게임창 초기 위치] -----

win_posx = 700
win_posy = 300
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (win_posx, win_posy)

5개의 모듈을 사용했다. pygame 을 제외하면 기본 모듈이다.

 

os.environ 속성에서 게임 윈도우가 시작하는 위치를 배치할 수 있다. 사용하지 않아도 된다. 파이게임이 디폴트 설정으로 윈도우의 위치를 정한다.

 

2. 윈도우창 설정

# ----- [윈도우창 설정] -----

WINDOW_WIDTH = 540
WINDOW_HEIGHT = 600
GRID = 30
GRID_WIDTH = int(WINDOW_WIDTH/GRID)
GRID_HEIGHT = int(WINDOW_HEIGHT/GRID)

 

 

윈도우창의 크기를 설정한다. 가로와 세로(너비,높이)를 정하는데 GRID 사이즈의 양의 배수로 한다. 30짜리 정사각형을 화면에 깔기 위한 사이즈 설정이다.

 

3. 상수설정

# ===== [색상설정] =====

BLACK = 0, 0, 0
WHITE = 255,255,255
RED = 255, 0, 0
GREEN1 = 25, 102, 25
GREEN2 = 51, 204, 51
GREEN3 = 233, 249, 185
BLUE = 17, 17, 212
LIGHT_PINK1 = 255, 230, 255
LIGHT_PINK2 = 255, 204, 255

NORTH = ( 0, -1)
SOUTH = ( 0,  1)
WEST  = (-1,  0)
EAST  = ( 1,  0)

 

상수로 사용할 값들을 설정한다. 색상에 사용할 RGB 코드와 스네이크의 동서남북 이동을 위한 상수이다.

 

파이썬의 상수는 관례적으로 대문자로 사용하고 값이 변하지 않게 튜플을 사용한다. 자바처럼 final 이라던가 C의 const 같은 개별 변수에 걸 수 있는 접근제어자는 없다.

 

위에서 보면 ( ) 괄호 친것이나 안 친것이나 실제로는 튜플로 저장된다. 파이썬은 동적 타이프라는 장점이 있는데 그것 때문에 지금 무슨 자료형을 사용하는 것인지 혼란스러울 때가 있다. 그럴때는 print 와 로깅을 사용해서 실제 자료형와 내용을 확인해준다.

 

print(type(변수)) 로 클래스를 확인할 수 있다.

 

4. 스네이크 클래스

class Snake:

    def __init__(self):
        self.length = 1
        self.create_snake()
        self.color1 = GREEN2
        self.color2 = GREEN3
        self.head_color = GREEN1

    def create_snake(self):
        self.length = 3
        self.positions = [(int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2))]
        self.direction = random.choice([NORTH, SOUTH, WEST, EAST])
        global game_score
        game_score = 0

    def move_snake(self, surface):

        # print(len(self.positions))
        head = self.get_head_position()
        x, y = self.direction
        next = ((head[0] + (x*GRID)) % WINDOW_WIDTH, (head[1] + (y*GRID)) % WINDOW_HEIGHT)
        if next in self.positions[2:]:
            self.create_snake()
            gameover(surface)
        else:
            self.positions.insert(0, next)
            if len(self.positions) > self.length:
                del self.positions[-1]

    def draw_snake(self, surface):
        for index, pos in enumerate(self.positions):
            if index == 0:
                draw_object(surface, self.head_color, pos)
            elif index % 2 == 0:
                draw_object(surface, self.color1, pos)
            else:
                draw_object(surface, self.color2, pos)


    def game_control(self, arrowkey):
        if (arrowkey[0]*-1, arrowkey[1]*-1) == self.direction:
            return
        else:
            self.direction = arrowkey

    def get_head_position(self):
        return self.positions[0]

 

게임을 진행시키는데 있어서 move snake 메서드는 중요하다.

 

이 게임은 스네이크가 캐릭터이다. 이 코드는 위에서 부터 읽으면 해석이 안된다. 객체의 코드를 읽는 순서는 이 클래스를 어떤 클래스가 무엇때문에 사용하는지 그 순서를 찾아가서 읽어야 한다.

 

그렇지만 기본적으로 __init__ 생성자에서 시작된다. 생성자가 호출하는 메서드가 create snake 이다. 처음 스네이크의 크기, 초기 위치(좌표), 진행방향 (랜덤) 그리고 글로벌 변수로 score를 사용한다. 이 게임 통틀어서 한개만 있기 때문에 전역변수로 사용했다. 사용자에 따라 점수를 기록하려면 전역변수를 쓰지 않는게 좋다.

 

이 코드는 스네이크 게임의 기초 기능만 가지고 있다. 부수적인 것들은 제외하였으나 이 코드에 저장기능이라던가 여러가지 기능을 추가하는 것이 가능하다는 점을 염두해둔다. 상업용게임이면 이것저것 생각할 부분이 많을 것이다. 게이머들은 사람이라 사소한 감정에 민감하니까.

 

move 메서드는 positions 라는 리스트형으로 뱀을 표현한다. 이동은 머리가 앞으로 나아가고 꼬리가 따라가는 것을 의마한다. 머리를 self.positions[0] 으로 두고 꼬리는 self.positions[-1]로 가져올 수 있다. 이동은 2d공간의 좌표를 이동하는 것을 말한다.

 

행과 열을 알아야 하는데

 

0

1

2

3

 

이게 행이고

 

0, 1, 2, 3,

 

이게 열이다.

 

(0, 0), (0, 1), (0, 2), (0, 3)

(1, 0), (1, 1), (1, 2), (1, 3)

(2, 0), (2, 1), (2, 2), (2, 3)

(3, 0), (3, 1), (3, 2), (3, 3)

 

이것이 캐릭터가 이동하는 공간이다. 이것을 위해서 동서남북을 설정한 것이다.

 

파이게임의 좌표는 좌상에서 우하향한다. 즉 x축은 오른쪽이 +1 왼쪽이 -1 이고, y축은 아래가 -1 위가 +1 이다.

 

move 메서드의 식들은 그 점을 생각하고 작성한다. 캐릭터의 방향에 따라 한 번에 GRID만큼 이동한다. 마지막으로 나머지 연산을 하면 화면을 벗어나면 캐릭터는 반대편으로 나온다. y가 아래로 이동하면 0부터 540 까지 좌표가 변한다. 그냥 놔두면 570, 600, 630... 그리드만큼 늘어나겠지만 캐릭터는 사라져 버린다. 윈도우 크기인 540으로 나머지를 받으면 540은 0 570은 30이 되서 화면안을 계속 돌게된다. 

 

next 헤드가 자신의 몸과 충돌하면 게임을 리셋하고, 그게 아니라면 이동을 한다. 이동하는 것은 결국 머리를 그리고 꼬리를 빼는 것이다. 머리에 넣을 때는 insert(0, 위치) 가 유리하고 꼬리를 때낼때는 del 이나 pop()을 쓴다.

 

draw snake 는 스네이크를 그린다. 리스트에 저장된 positions 를 하나씩 다 그려줘야 하는데 if문을 써서 약간 색상에 변화를 줬다. 심플한게 좋으면 if문을 다 빼고 draw object만 호출해도 동작한다. (draw object는 전역함수)

 

game control 은 머리를 반대방향으로 곧바로 틀지 못하게하는 장치다. 이렇게 하면 항상 네개의 방향키 중에 머리방향과 반대인 한개의 키는 못쓰게 된다. 180도 턴하려면 방향키를 두번 눌러야 한다.

 

arrowkey는 방향키다. 방향키 자체가 튜플이라고 보면된다.

 

NORTH = ( 0, -1) 위쪽 방향키
SOUTH = ( 0, 1) 아래쪽 방향키
WEST = (-1, 0) 왼쪽 방향키
EAST = ( 1, 0) 오른쪽 방향키

 

get head position 은 getter 로 만들어 봤다. 이 코드는 클래스를 사용하지만 엄밀하게 OOP 설계 스타일로 만들지는 않았다. getter를 놔둔 것은 가급적 클래스 안에서만 클래스의 속성에 접근하는게 바람직 하지 않을까 해서이다.

 

5. 먹이 클래스

 

class Food:
    def __init__(self):
        self.position =(0, 0)
        self.color = RED
        self.randomize_food()

    def randomize_food(self):
        self.position = (random.randint(0, GRID_WIDTH-1) * GRID,
                         random.randint(0, GRID_HEIGHT-1) * GRID)

    def draw_food(self, surface):
        draw_object(surface, self.color, self.position)

snake 클래스를 이해한다면 Food 클래스는 좀 더 간단하다.

 

randomize food 는 난수를 생성하여 그리드에 먹이를 배치한다.

 

6. 전역 함수

 

# ----- [ 전역 ] -----

def draw_background(surface):
    background = pygame.Rect((0,0),(WINDOW_WIDTH, WINDOW_HEIGHT))
    pygame.draw.rect(surface, WHITE, background)
    draw_grid(surface)

def draw_grid(surface):
    for row in range(0,int(GRID_HEIGHT)):
        for col in range(0, int(GRID_WIDTH)):
            if (row+col) % 2 == 0:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK1, rect)
            else:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK2, rect)


def draw_object(surface, color, pos):
    rect = pygame.Rect((pos[0], pos[1]), (GRID,GRID))
    pygame.draw.rect(surface, color, rect)
    pygame.draw.rect(surface, WHITE, rect, 1)


def position_check(snake, food_group):
    for food in food_group:
        if snake.get_head_position() == food.position:
            global game_score
            game_score += 1
            snake.length += 1
            food.randomize_food()


def show_info(surface, snake, speed):
    font = pygame.font.SysFont('malgungothic',35)
    image = font.render(f' 점수 : {game_score} 뱀길이: {snake.length} LV: {int(speed//2)} ', True, BLUE)
    pos = image.get_rect()
    pos.move_ip(20,20)
    pygame.draw.rect(image, BLACK,(pos.x-20, pos.y-20, pos.width, pos.height), 2)
    surface.blit(image, pos)

def gameover(surface):
    font = pygame.font.SysFont('malgungothic',50)
    image = font.render('GAME OVER', True, BLACK)
    pos = image.get_rect()
    pos.move_ip(120,220)
    surface.blit(image, pos)
    pygame.display.update()
    time.sleep(2)

player = Snake()
run = True
game_score = 0

전역함수는 게임내에서 공통부분들이다.

 

배경화면 그리고 격자만드는 등의 메서드이다.

 

draw grid 에서 보면 이런 코드가 있다.

def draw_grid(surface):
    for row in range(0,int(GRID_HEIGHT)):
        for col in range(0, int(GRID_WIDTH)):
            if (row+col) % 2 == 0:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK1, rect)
            else:
                rect = pygame.Rect((col*GRID, row*GRID), (GRID, GRID))
                pygame.draw.rect(surface, LIGHT_PINK2, rect)

격자에서 무늬 판을 그려주는 알고리즘이다.

(0,0), (0,1), (0,2), (0,3)
-> 0, 1, 0, 1
(1,0), (1,1), (1,2), (1,3)
-> 1, 0, 1, 0

 

위에서 처럼

 

0 1 0 1

1 0 1 0

 

이런 숫자를 가지고 아래와 같은 식탁보 무늬를 만들 수 있다. 없어도 동작은 하지만 아무래도 이런 모양이 게임의 가독성이 좋다.

 

다른 부분은 특이할 것 없고 position check 에서 먹이의 충돌 여부를 검사한다. 충돌하면 먹이의 위치는 바꿔주고 점수와 길이를 올려준다.

 

7. 먹이 그룹

# ----- [ 먹이 그룹 ] -----

def draw_food_group(food_group, surface):
    for food in food_group:
        food.draw_food(surface)

food = Food()
food_group = []

for i in range(3):
    food = Food()
    food_group.append(food)

for food in food_group:
    print(food.position)

여러개의 먹이를 배치하려면 리스트로 그룹을 만든다. range(숫자) 가 먹이의 숫자다. 변수를 사용해서 바꿔주면 먹이의 수를 관리 할 수 있다.

 

8. 게임 루프와 종료

# ----- [ game loop ] -----

pygame.init()
pygame.display.set_caption('SNAKE GAME')
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

while run:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_q:
                run = False
            if event.key == pygame.K_UP:
                player.game_control(NORTH)
            if event.key == pygame.K_DOWN:
                player.game_control(SOUTH)
            if event.key == pygame.K_RIGHT:
                player.game_control(EAST)
            if event.key == pygame.K_LEFT:
                player.game_control(WEST)

    draw_background(screen)
    player.move_snake(screen)
    position_check(player, food_group)
    player.draw_snake(screen)
    draw_food_group(food_group, screen)
    speed = player.length/2
    show_info(screen, player, speed)
    pygame.display.flip()
    pygame.display.update()
    clock.tick(5+speed)

# ----- [ 파이게임 종료 ] -----

print('pygame closed')
pygame.quit()
sys.exit()

 

키보드 이벤트에 대하여는 어렵지 않을 것 이다. 스네이크의 control()로 넘기면 self.position 에 방향을 바꿔준다. while문의 끝쪽의 게임 구현부는 순서가 중요하다. 캐릭터를 다 그리고 그 후에 배경을 그리면 surface 가 덮여서 화면을 볼 수가 없다. 무언가를 그릴때는 레이아웃이라고 생각하면 쉽다. 나중에 그리는 것이 바깥쪽으로 나온다. 순서를 배치하는 것에 주의한다.

 

게임의 종료는 키보드 q나 윈도우의 x 버튼을 누르면 된다. sys.exit()까지 되도록 깨끗하게 종료시키는게 좋다.

 


이번 포스팅은 기본적인 기능을 갖춘 스네이크 게임을 만들어 봤다. 향후에 시간이 될때 스네이크 게임에 사운드를 넣거나 스프라이트를 추가해서 좀 더 멀티미디어적인 게임으로 만들어 보면 좋을 것 같다.

공유하기

facebook twitter kakaoTalk kakaostory naver band