[pygame] 다람쥐 먹기 게임 - 1

서희찬·2021년 4월 22일
0
post-thumbnail

자 이제 6번째 게임을 만들어보자 !
다람쥐 먹기 게임이다!
처음들어볼것인데 그 이유는 그냥 게임내용 그 대로 제목을 땄기 때문이다.

게임은 간단하다 !
처음으로 여러명의 적이 나오고 자신보다 작은 다람쥐를 먹고 큰 다람쥐는 피하면서 덩치를 늘려가는 게임이다.

이 게임을 통해서 우리는

적을 여러 개 만드는 방법과 각각의 다람쥐에 동일한 값을 적용해 게임 루프에서 여러 다람쥐들을 한 번에 다룰 수 있고 카메라와 수학 함수인 사인에 대한 개념을 배워 다람쥐가 점프할 때 자연스럽게 보이도록 하는 방법을 배울것이다!

자 !
우선

  • invpy.com/gameicon.png
    squirrel.png,grass1,2,3,4.png 를 다운 받자 !

이제 게임을 만들어보자 !

다람쥐 먹기 디자인

이 게임에서는 세 개의 데이터 구조가 있고 모두 딕셔너리 값이다.

데이터 타입은 모두 플레이어 다람쥐, 적 다람쥐, 잔디 객체이다.(단지 딕셔너리 값이지만 이 게임에서 "객체"의 의미를 "게임 세계 안에 존재하는 것"으로 사용하자)

모든 객체는 딕셔너리 값으로 'x','y','rect'를 갖는다.
x,y 는 게임 세계 좌표계에서 객체의 왼쪽 상단의 좌표를 말한다.
이것은 픽셀 좌표계와는 다르다!(픽셀 좌표계는 rect에 기록된다)

자 ! 이제 코드를 짜보자!!

셋업 코드

# Squirrel Eat Squirrel (a 2D Katamari Damacy clone)
# By Al Sweigart al@inventwithpython.com
# http://inventwithpython.com/pygame
# Released under a "Simplified BSD" license

import random, sys, time, math, pygame
from pygame.locals import *

FPS = 30 # frames per second to update the screen
WINWIDTH = 640 # width of the program's window, in pixels
WINHEIGHT = 480 # height in pixels
HALF_WINWIDTH = int(WINWIDTH / 2)
HALF_WINHEIGHT = int(WINHEIGHT / 2)

GRASSCOLOR = (24, 255, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

이 게임에선 윈도우의 너비와 높이의 1/2길이를 이용하는 경우가 많기 떄문에 HALF 를 미리 정의해줬다.

MOVERATE = 9         # how fast the player moves
BOUNCERATE = 6       # how fast the player bounces (large is slower)
BOUNCEHEIGHT = 30    # how high the player bounces
STARTSIZE = 25       # how big the player starts off
WINSIZE = 300        # how big the player needs to be to win
INVULNTIME = 2       # how long the player is invulnerable after being hit in seconds
GAMEOVERTIME = 4     # how long the "game over" text stays on the screen in seconds
MAXHEALTH = 3        # how much health the player starts with

NUMGRASS = 80        # number of grass objects in the active area
NUMSQUIRRELS = 30    # number of squirrels in the active area
SQUIRRELMINSPEED = 3 # slowest squirrel speed
SQUIRRELMAXSPEED = 7 # fastest squirrel speed
DIRCHANGEFREQ = 2    # % chance of direction change per frame
LEFT = 'left'
RIGHT = 'right'

상수 설명은 주석을 참고 하자 !

데이터 구조 설명


"""
이 프로그램 에는 3개의 데이터 구조가 있다. player, enemy squirrel.grass 객체이다. 각 데이터 구조는 딕셔너러이며
해당 키를 가지고 있다.

3개의 데이터 구조에서 공통으로 사용하는 키 :
'x' - 게임 세계 왼쪽 좌표
'y' - 게임 세계 위쪽 좌표
'rect' - 스크린에 객체가 위치해야 하는 영역이며 pygame.Rect 객체로 표시 

Player 데이터 구조에서 사용하는 키 : 
'Surface' - 화면에 그릴 다람쥐 이미지를 저장하는 pygame.Surface object
'facing' - setting LEFT or RIGHT 플레이어가 어느 방향으로 보고 있을지 결정 
'size' - 플레이어의 너비와 높이. 픽셀단위
'bounce' - 현재 플레이어가 점프 동작 중 어떤 위치에 있는지 나타낸다. 0은 그냥 서잇고 BOUNCERATE 까지 점프
'health'- 라이프가 얼마나 남아 있느지 보여주는 정수 값.

Enemy Squirrel 데이터 구조에서 사용하는 키 :
'surface'
'movex'  프레임당 다람쥐가 몇 픽셀을 수평으로 움직였는지 저장. 음수는 왼쪽 양수 오른쪽
'movey' - 프레임당 다람쥐가 몇 픽셀을 수직으로 움직였는지 저장. 음수는 위쪽 양수 아래쪽
'width' - 다람쥐 이미지의 너비 
'height' - 다람쥐 이미지의 높이
'bounce' - 현재 플레이어가 점프 동작 중 어떤 위치에 있는지 나타낸다. 
'bouncerate' - 다람쥐가 얼마나 빨리 점프
'bounceheight' - 다람쥐가 얼마나 높이 점프

Grass 데이터 구조에서 사용하는 키 :
'grassImage' - GRASSIMAGES의 pygame.Surface 객체를 가리키는 인덱스 숫자 해당 grass객체를 위해서 사용한다.
"""

객체의 키에 대해 미리 설명한드아!

main() 함수

def main():
    global FPSCLOCK, DISPLAYSURF, BASICFONT, L_SQUIR_IMG, R_SQUIR_IMG, GRASSIMAGES

    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    pygame.display.set_icon(pygame.image.load('gameicon.png'))
    DISPLAYSURF = pygame.display.set_mode((WINWIDTH,WINHEIGHT))
    pygame.display.set_caption("Chans Squirrel Eat Squirrel")
    BASICFONT = pygame.font.Font("freesansbold.ttf",32)

한 줄 말고는 이때까지 메인함수들의 시작과 동일하다.
pygame.display.set_icon("") 함수는 윈도우 타이틀 바에 아이콘을 그린다 !

pygame.transform.filp() 함수

def main():
    global FPSCLOCK, DISPLAYSURF, BASICFONT, L_SQUIR_IMG, R_SQUIR_IMG, GRASSIMAGES

    pygame.init()
    FPSCLOCK = pygame.time.Clock()
    pygame.display.set_icon(pygame.image.load('gameicon.png'))
    DISPLAYSURF = pygame.display.set_mode((WINWIDTH,WINHEIGHT))
    pygame.display.set_caption("Chans Squirrel Eat Squirrel")
    BASICFONT = pygame.font.Font("freesansbold.ttf",32)

기존 다람쥐의 사진은 왼쪽을 햐하고 있지만!
우리는 오른쪽으로 향하고 있는 image의 Surface object 도 필요하다!
이 것을 pygame.transform.flip()으로 만들 수 있는데 파라미터로 뒤집을값,좌우반전,상하반전을 불리언 값으로 받는다.

while True:
        runGame()

이제 기본 설정이 끝났으니 runGame() 을 호출해서 게임을 시작하자.

좀 더 상세히 설명한 게임 상태

def runGame():
    # set up variables for the start of a new game 
    invulnerableMode = False # if the player is invulnerable
    invulnerableStartTime = 0 # time the player became invulnerable 
    gameOverMode = False # if the player has lost 
    gameOverStartTime = 0 # time the player lost
    winMode = False # if ther player has won 

게임 상태를 기록하는 변수들이다.

일반적으로 사용하는 텍스트 생성 코드

    # create the surfaces to hold game text
    gameOverSurf = BASICFONT.render("Game Over",True,WHITE)
    gameOverRect = gameOverSurf.get_rect()
    gameOverRect.center = (HALF_WINWIDTH,HALF_WINHEIGHT)

    winSurf = BASICFONT.render("You have achieved OMEGA SQUIRREL! ",True,WHITE)
    winRect = winSurf.get_rect()
    winRect.center = (HALF_WINWIDTH,HALF_WINHEIGHT)

    winSurf2 = BASICFONT.render('(press "R" to restart.)',True,WHITE)
    winRect2 = winSurf2.get_rect()
    winRect2.center = (HALF_WINWIDTH,HALF_WINHEIGHT+30)

쉬운거니 설명 생략!

카메라

# camerx and camery are the top left of where tge camera view is 
    camerax = 0
    cameray = 0

게임 세계가 무한한 2차원 공간 이라고 생각하면 어떤 화면도 이 게임 세계를 전부 보여줄 수 없다.
무한한 공간에서 일부만 화면에 보여줘야 하기 때문에 이 일부의 세계를 카메라 라고한다!

이 세계를 카메라로 볼때 일부만 렌즈에 담을 수 있는 것과 비슷하기 때문이다!

카메라가 볼 수 있는 공간이 바로 플레이어의 화면이 되므로 "카메라"좌표계는 "픽셀"좌표계와 동일하다.
다람쥐의 픽셀 좌표계를 알아내려면, 다람쥐의 게임 좌표계에서 카메라 원점의 게임 좌표계를 뼤면 된다!

활성 영역

활성영역은 게임 세계에서 카메라 뷰와 그 둘레에 카메라 뷰만큼의 영역을 합친 부분을 부르기 위해 만들어낸 용어이다.

적 다람쥐나 잔디 객체를 새로 만들때는 카메라 뷰 영역안에 만들면 갑자기 튀어나온것처럼 보이기 때문에 밖에서 생성 한다. 그렇지만 너무 멀리 떨어진곳에 생성하면 안되기에 어느 정도 가까운 영역인 활성영역에 적 다람쥐와 잔디를 만들어야한다. 따라서 활성영역 밖의 다람쥐와 잔디는 삭제하여 메모리를 아낀다.

게임 월드 안의 물건 위치 추적하기

    grassObjs = [] #stores all the grass objects in the game 
    squirrelObjs = [] #stores all the non-plater squirrel objects 
    #store the player object
    playerObj = {'surface': pygame.transform.scale(L_SQUIR_IMG, (STARTSIZE, STARTSIZE)),
                 'facing': LEFT,
                 'size': STARTSIZE,
                 'x': HALF_WINWIDTH,
                 'y': HALF_WINHEIGHT,
                 'bounce':0,
                 'health': MAXHEALTH}
    
    moveLeft = False
    moveRight = False 
    moveUp = False
    moveDown = False 

grass객체를 생성하면 리스트에 추가되고 제거하면 삭제된다. 적다람쥐도 마찬가지!
playerObj 는 딕셔너리 값 하나이다.
그리고 플레이어가 현재 어떤 화살표 키를 눌렀는지 기록한다.

잔디부터 시작하기

    #start off with some random grass images on the screen 
    for i in range(10):
        grassObjs.append(makeNewGrass(camerax, cameray))
        grassObjs[i]['x'] = random.randint(0,WINWIDTH)
        grassObjs[i]['y'] = random.randint(0,WINHEIGHT)

게임을 시작하면 활성영역에 잔디 몇개를 만든다!

게임루프,무적상태 체크

    while True : # main game loop 
        #check if we should turn off invulnerability 
        if invulnerableMode and time.time() - invulnerableStartTime> INVULNTIME:
            invulnerableMode = False 

무적 시간 상태는 2초이다. 무적 상태가 끝나면 Mode =False 로 설정한다.

적 다람쥐 움직이기

        #move all squirrels 
        for sObj in squirrelObjs:
            #move ther sq, and adjust for their bounce 
            sObj['x'] += sObj['movex']
            sObj['y'] += sObj['movey']

적 다람쥐는 movex,movey 키의 값에 따라 움직인다.
이 값이 양수이면 오른쪽/아래로! 음수이면 반대!
값이 크면 클수록 게임 루프 반복마다 더 멀리 움직여야한다

모든 다람쥐 객체에 for문을 줬다 !

sObj['bounce'] += 1 
            if sObj['bounce']>sObj["bouncerate"]:
                sObj['bounce'] = 0 # reset bounce amount 
                

sObj['bounce'] 값은 게임 루프를 돌 떄마다 모든 다람쥐에 대해 증가한다.
이 값이 0 이면 다람쥐는 점프 하기 전에 바로 시작 위치에 있고 이 값이 rate와 동일하면 점프가 끝난 것으로 한다.
bouncerate 값이 적으면 더 빨리 점프하는 이유다 !!

            #random chance they change direction 
            if random.randint(0,99) < DIRCHANGEFREQ:
                sObj['movex'] = getRandomVelocity()
                sObj['movey'] = getRandomVelocity()
                if sObj['movex'] > 0: # faces right 
                    sObj['suface'] = pygame.transform/scale(R_SQUIR_IMG,(sObj['width'],sObj['height']))
                else :  faces left 
                sObj['surface'] = pygame.transform.scale(L_SQUIR_IMG,(sObj['width'],sObj['height']))

무작위로 0-99보다 작은 숫자가 선택되면 방향을 바꾼다!
그에 맞게 이미지의 크기를 조정해주었다.

멀리 있는 잔디와 다람쥐 객체 없애기

#go through all the objects and see if any need to be deleted.
        for i in range(len(grassObjs)-1,-1,-1):
            if isOutsideActiveArea(camerax,cameray,grassObjs[i]):
                del grassObjs[i]
        for i in range(len(squirrelObjs)-1,-1,-1):
            if isOutsideActiveArea(camerax,cameray,squirrelObjs[i]):
                del squirrelObjs[i]

모든 잔디와 적 다람쥐를 검사해서 "활성 영역" 바깥에 있는지 확인 후 벗어 났다면 True를 반환하고 그 객체를 삭제한다 !

리스테 있는 아이템을 지울 때 리스트 거꾸로 돌리기

위의 코드를 보면
for i in range(len(grassObjs)-1,-1,-1)
del grassoBjs[i] 이런식으로 리스트의 뒷 부분부터 삭제를 진행하는데 왜 이렇게 하는것일까?

간단한 예시를 보자

animals = ['cat','mouse','dog','horse']
에서
for i in range(len(animals)):
	if animals[i] == 'dog':
  	del animals[i]	

라고하고 삭제를 진행하면 에러가 발생한다.

그 이유는 for의 i 값은 0,1,2,3으로 진행되는데 i 가 2 가되고 난 후 dog를 삭제하고 나면 horse가 앞땅겨져 다음 i값인 3이animals 의 인덱스값에 없는 값이 된다.
그러므로 오류가 발생하지만
뒤에서 한칸씩 내려오는 과정을 거치면 에러가 발생하지 않는다!

새로운 잔디와 다람쥐 객체 추가하기

        #add more grass & sq if we dont have enough 
        while len(grassObjs)<NUMGRASS:
            grassObjs.append(makeNewGrass(camerax, cameray))
        while len(squirrelObjs)<NUMSQUIRRELS:
            squirrelObjs.append(makeNewSquirrel(camerax,cameray))

초기 설정 상수보다 작으면 잔디와 다람쥐를 객체를 추가 생성해준다.

카메라 슬랙과 카메라 뷰 옮기기

        playerCenterx = playerObj['x'] + int(playerObj['size'] / 2)
        playerCentery = playerObj['y'] + int(playerObj['size'] / 2)
        if (camerax + HALF_WINWIDTH) - playerCenterx > CAMERASLACK:
            camerax = playerCenterx + CAMERASLACK - HALF_WINWIDTH
        elif playerCenterx - (camerax + HALF_WINWIDTH) > CAMERASLACK:
            camerax = playerCenterx - CAMERASLACK - HALF_WINWIDTH
        if (cameray + HALF_WINHEIGHT) - playerCentery > CAMERASLACK:
            cameray = playerCentery + CAMERASLACK - HALF_WINHEIGHT
        elif playerCentery - (cameray + HALF_WINHEIGHT) > CAMERASLACK:
            cameray = playerCentery - CAMERASLACK - HALF_WINHEIGHT

플레이어가 움직이면 카메라의 위치도 바꿔야한다.
카메라의 위치를 바꾸지 않고 플레이어가 몇 픽셀이나 움직일 수 있는지를 카메라 슬랙 이라고 한다.
즉, 중앙에서 플레이어가 90픽셀 이상 멀어지면 카메라는 플레이어를 따라가기 위해 카메라의 위치가 변한다.

camerax+HALF_WINWIDTH,HEIGHT 는 스크린의 중앙에 해당되는 위치의 XY 게임 세계 좌표계이다.

CAMERASLACK 값보다 커지면 플레이억가 카메라의 중심보다 슬랙보다 더 오른쪽으로 이동한 것이다.
플레이어 다람쥐가 카메라 슬랙 가장자리에 왔기에 camerax 값을 바꿔야한다.
우리는 플레이어가 아닌 카메라를 옮긴다!! !

배경,잔디,다람쥐,헬스 미터 그리기

        #draw the green background
        DISPLAYSURF.fill(GRASSCOLOR)

        #draw all the grass objects on the screen 
        for gObj in grassObjs:
            gRect = pygame.Rect((gObj['x']-camerax,gObj['y']-cameray,gObj['width'],gObj['height']))
            DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']],gRect)

배경을 그리고 grassObjs 리스트의 모든 grass 객체에 대해 Rect 객체를 만든다.
이것은 gRect 변수에 저장한다.

모든 잔디 객체가 직접 이미지를 가지고 있을 필요는 없다 모든 grass 객체가 가지고 있는 gObj['grassImage']에 어떤 이미지를 쓸지 이미지 번호만 저장하고, 잔디 이미지는 GRASSIMAGES에 가지고 있는 편이 메모리를 아낄 수 있다.

        #draw the other sq
        for sObj in squirrelObjs:
            sObj['rect'] = pygame.Rect( (sObj['x'] - camerax,
                                         sObj['y'] - cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']),
                                         sObj['width'],
                                         sObj['height']) )
            DISPLAYSURF.blit(sObj['surface'], sObj['rect'])

잔디를 그리는 법과 동일하지만 Rect 객체를 'rect'키 값으로 저장하는 차이가 있다.
이렇게 Rect 객체를 저장해서 나중에 플레이어 다람쥐와 충돌했는지 검사할때 쓸 수 있다.
그리고 크기가 재각각이므로 공통 Surface를 사용하지 않는다.

        #draw the player squirrel 
        flashIsOn = round(time.time(),1)*10%2 == 1

플레이어가 무적 상태일때 깜빡이는 효과를 주기위해 0.1초 그리고 안그리고를 반복함으로써 깜빡이는 것처럼 보이게 한다.
time.time(),1 round() 로 넘겨서 반올림하는데 두 번째 파라미터로 1을 줘서 소수점 다음 한 자리로 맞춰지고 여기에 10 을 곱해서 2로 모듈러 연산을 진행하는 트릭을 할 수 있다 !
이렇게 트루/폴스를 시간이 지남에 따라 반복한다.

        if not gameOverMode and not (invulnerableMode and flashIsOn):
            playerObj['rect'] = pygame.Rect( (playerObj['x'] - camerax,
                                              playerObj['y'] - cameray - getBounceAmount(playerObj['bounce'], BOUNCERATE, BOUNCEHEIGHT),
                                              playerObj['size'],
                                              playerObj['size']) )
            DISPLAYSURF.blit(playerObj['surface'], playerObj['rect'])

모드 확인후 다람쥐를 그려준다.


        #draw the health meter
        drawHealthMeter(playerObj['health'])

체력을 그린당

이벤트 루프 처리

     for event in pygame.event.get(): # event handling loop
         if event.type == QUIT:
             terminate()

         elif event.type == KEYDOWN:
             if event.key in (K_UP, K_w):
                 moveDown = False
                 moveUp = True
             elif event.key in (K_DOWN, K_s):
                 moveUp = False
                 moveDown = True
             elif event.key in (K_LEFT, K_a):
                 moveRight = False
                 moveLeft = True
                 if playerObj['facing'] != LEFT: # change player image
                     playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                 playerObj['facing'] = LEFT
             elif event.key in (K_RIGHT, K_d):
                 moveLeft = False
                 moveRight = True
                 if playerObj['facing'] != RIGHT: # change player image
                     playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                 playerObj['facing'] = RIGHT
             elif winMode and event.key == K_r:
                 return

         elif event.type == KEYUP:
             # stop moving the player's squirrel
             if event.key in (K_LEFT, K_a):
                 moveLeft = False
             elif event.key in (K_RIGHT, K_d):
                 moveRight = False
             elif event.key in (K_UP, K_w):
                 moveUp = False
             elif event.key in (K_DOWN, K_s):
                 moveDown = False

             elif event.key == K_ESCAPE:
                 terminate()

이제는 많이~ 익숙한 이벤트 처리이다.
그런데!

left/right 를 눌렀을때 playerObj['surface']값은 새로운 방향에 맞는 이미지 크기를 조절해서 설정해줘야한다!
r는 승리했을때 재시작이다!

플레이어를 움직이고 점프 확인하기

        if not gameOverMode:
            # actually move the player
            if moveLeft:
                playerObj['x'] -= MOVERATE
            if moveRight:
                playerObj['x'] += MOVERATE
            if moveUp:
                playerObj['y'] -= MOVERATE
            if moveDown:
                playerObj['y'] += MOVERATE

게임이 진행되는동안 방향키에 따라 움직인다 ㅇㅇ moverate 만큼 움직인다.

            if (moveLeft or moveRight or moveUp or moveDown) or playerObj['bounce'] != 0:
                playerObj['bounce'] += 1

            if playerObj['bounce'] > BOUNCERATE:
                playerObj['bounce'] = 0 # reset bounce amount

점프~ 한다~ 다차면 리셋한당~

충돌 감지 : 먹느냐? 먹히느냐!

            #check if the player has collided with any squirrels 
            for i in range(len(squirrelObjs)-1, -1, -1):
                sqObj = squirrelObjs[i]

플레이어가 포문 안에서 다람쥐 냠! 해서 삭제할수 있으므로 뒤에서 부터 검사한다!

                if 'rect' in sqObj and playerObj['rect'].colliderect(sqObj['rect']):
                    #player and sq collision has occured 
                    if sqObj['width'] * sqObj['height']<= playerObj['size']**2: # player is larger than enemy
                        playerObj['size']+= int((sqObj['width']*sqObj['height'])**0.2)+1
                        del squir

플레이어보다 작은 적이라면 먹고 그 크기에 비례해서 커진다

                        if playerObj['facing'] == LEFT:
                            playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                        if playerObj['facing'] == RIGHT:
                            playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))

더 커진 이미지를 transform.scale()이 반환해준다.
왼/오에 따라 이미지 다른걸 넘겨준당 !

                      if playerObj['size'] > WINSIZE:
                            winMode = True # turn on "win mode"

충분히 커지면 ~ 승리 ~ !

                    elif not invulnerableMode:
                        # player is smaller and takes damage
                        invulnerableMode = True
                        invulnerableStartTime = time.time()
                        playerObj['health'] -= 1
                        if playerObj['health'] == 0:
                            gameOverMode = True # turn on "game over mode"
                            gameOverStartTime = time.time()

만약 무적모드가 아닌 상태고 적이 더 크면 플레이어는 무적모드가 되고 체력이 깎인다.
그리고 체력이 0 이되면 게임오버 !

게임종료 화면

        else:
            # game is over, show "game over" text
            DISPLAYSURF.blit(gameOverSurf, gameOverRect)
            if time.time() - gameOverStartTime > GAMEOVERTIME:
                return # end the current game

게임오버를 하고 하고 게임오버화면 보여주는 시간이 지나면 사라진다링~

        if winMode:
            DISPLAYSURF.blit(winSurf, winRect)
            DISPLAYSURF.blit(winSurf2, winRect2)

        pygame.display.update()
        FPSCLOCK.tick(FPS)

승리시 이러고 ! 게임을 끝내준다 !
(winmode == True 가 되니 조건문 실행)

캬...
핵심 함수는 끝이났고
이제 세부적인 기능들을 위한 함수들만 남아있다.

지금까지 짠 코드를 보여주고
나머지는 다음 포스트에서 짜보자 !

# Squirrel Eat Squirrel (a 2D Katamari Damacy clone)
# By Al Sweigart al@inventwithpython.com
# http://inventwithpython.com/pygame
# Released under a "Simplified BSD" license

import random, sys, time, math, pygame
from pygame.locals import *

FPS = 30 # frames per second to update the screen
WINWIDTH = 640 # width of the program's window, in pixels
WINHEIGHT = 480 # height in pixels
HALF_WINWIDTH = int(WINWIDTH / 2)
HALF_WINHEIGHT = int(WINHEIGHT / 2)

GRASSCOLOR = (24, 255, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)

CAMERASLACK = 90 # 중앙에서 다람쥐가 얼마나 멀어지면 카메라를 움직일지 결정 
MOVERATE = 9         # how fast the player moves
BOUNCERATE = 6       # how fast the player bounces (large is slower)
BOUNCEHEIGHT = 30    # how high the player bounces
STARTSIZE = 25       # how big the player starts off
WINSIZE = 300        # how big the player needs to be to win
INVULNTIME = 2       # how long the player is invulnerable after being hit in seconds
GAMEOVERTIME = 4     # how long the "game over" text stays on the screen in seconds
MAXHEALTH = 3        # how much health the player starts with

NUMGRASS = 80        # number of grass objects in the active area
NUMSQUIRRELS = 30    # number of squirrels in the active area
SQUIRRELMINSPEED = 3 # slowest squirrel speed
SQUIRRELMAXSPEED = 7 # fastest squirrel speed
DIRCHANGEFREQ = 2    # % chance of direction change per frame
LEFT = 'left'
RIGHT = 'right'

"""
이 프로그램 에는 3개의 데이터 구조가 있다. player, enemy squirrel.grass 객체이다. 각 데이터 구조는 딕셔너러이며
해당 키를 가지고 있다.

3개의 데이터 구조에서 공통으로 사용하는 키 :
'x' - 게임 세계 왼쪽 좌표
'y' - 게임 세계 위쪽 좌표
'rect' - 스크린에 객체가 위치해야 하는 영역이며 pygame.Rect 객체로 표시 

Player 데이터 구조에서 사용하는 키 : 
'Surface' - 화면에 그릴 다람쥐 이미지를 저장하는 pygame.Surface object
'facing' - setting LEFT or RIGHT 플레이어가 어느 방향으로 보고 있을지 결정 
'size' - 플레이어의 너비와 높이. 픽셀단위
'bounce' - 현재 플레이어가 점프 동작 중 어떤 위치에 있는지 나타낸다. 0은 그냥 서잇고 BOUNCERATE 까지 점프
'health'- 라이프가 얼마나 남아 있느지 보여주는 정수 값.

Enemy Squirrel 데이터 구조에서 사용하는 키 :
'surface'
'movex'  프레임당 다람쥐가 몇 픽셀을 수평으로 움직였는지 저장. 음수는 왼쪽 양수 오른쪽
'movey' - 프레임당 다람쥐가 몇 픽셀을 수직으로 움직였는지 저장. 음수는 위쪽 양수 아래쪽
'width' - 다람쥐 이미지의 너비 
'height' - 다람쥐 이미지의 높이
'bounce' - 현재 플레이어가 점프 동작 중 어떤 위치에 있는지 나타낸다. 
'bouncerate' - 다람쥐가 얼마나 빨리 점프
'bounceheight' - 다람쥐가 얼마나 높이 점프

Grass 데이터 구조에서 사용하는 키 :
'grassImage' - GRASSIMAGES의 pygame.Surface 객체를 가리키는 인덱스 숫자 해당 grass객체를 위해서 사용한다.
"""

def main():
   global FPSCLOCK, DISPLAYSURF, BASICFONT, L_SQUIR_IMG, R_SQUIR_IMG, GRASSIMAGES

   pygame.init()
   FPSCLOCK = pygame.time.Clock()
   pygame.display.set_icon(pygame.image.load('gameicon.png'))
   DISPLAYSURF = pygame.display.set_mode((WINWIDTH,WINHEIGHT))
   pygame.display.set_caption("Chans Squirrel Eat Squirrel")
   BASICFONT = pygame.font.Font("freesansbold.ttf",32)

   # 이미지 파일을 로드 한다.
   L_SQUIR_IMG = pygame.image.load('squirrel.png')
   R_SQUIR_IMG = pygame.transform.flip(L_SQUIR_IMG,True,False)
   GRASSIMAGES = []
   for i in range(1,5):
       GRASSIMAGES.append(pygame.image.load('grass%s.png' %i))

   while True:
       runGame()

def runGame():
   # set up variables for the start of a new game 
   invulnerableMode = False # if the player is invulnerable
   invulnerableStartTime = 0 # time the player became invulnerable 
   gameOverMode = False # if the player has lost 
   gameOverStartTime = 0 # time the player lost
   winMode = False # if ther player has won 

   # create the surfaces to hold game text
   gameOverSurf = BASICFONT.render("Game Over",True,WHITE)
   gameOverRect = gameOverSurf.get_rect()
   gameOverRect.center = (HALF_WINWIDTH,HALF_WINHEIGHT)

   winSurf = BASICFONT.render("You have achieved OMEGA SQUIRREL! ",True,WHITE)
   winRect = winSurf.get_rect()
   winRect.center = (HALF_WINWIDTH,HALF_WINHEIGHT)

   winSurf2 = BASICFONT.render('(press "R" to restart.)',True,WHITE)
   winRect2 = winSurf2.get_rect()
   winRect2.center = (HALF_WINWIDTH,HALF_WINHEIGHT+30)

   # camerx and camery are the top left of where tge camera view is 
   camerax = 0
   cameray = 0

   grassObjs = [] #stores all the grass objects in the game 
   squirrelObjs = [] #stores all the non-plater squirrel objects 
   #store the player object
   playerObj = {'surface': pygame.transform.scale(L_SQUIR_IMG, (STARTSIZE, STARTSIZE)),
                'facing': LEFT,
                'size': STARTSIZE,
                'x': HALF_WINWIDTH,
                'y': HALF_WINHEIGHT,
                'bounce':0,
                'health': MAXHEALTH}
   
   moveLeft = False
   moveRight = False 
   moveUp = False
   moveDown = False 

   #start off with some random grass images on the screen 
   for i in range(10):
       grassObjs.append(makeNewGrass(camerax, cameray))
       grassObjs[i]['x'] = random.randint(0,WINWIDTH)
       grassObjs[i]['y'] = random.randint(0,WINHEIGHT)

   while True : # main game loop 
       #check if we should turn off invulnerability 
       if invulnerableMode and time.time() - invulnerableStartTime> INVULNTIME:
           invulnerableMode = False
       
       #move all squirrels 
       for sObj in squirrelObjs:
           #move ther sq, and adjust for their bounce 
           sObj['x'] += sObj['movex']
           sObj['y'] += sObj['movey']
           sObj['bounce'] += 1 
           if sObj['bounce']>sObj["bouncerate"]:
               sObj['bounce'] = 0 # reset bounce amount 
           
           #random chance they change direction 
           if random.randint(0,99) < DIRCHANGEFREQ:
               sObj['movex'] = getRandomVelocity()
               sObj['movey'] = getRandomVelocity()
               if sObj['movex'] > 0: # faces right 
                   sObj['suface'] = pygame.transform/scale(R_SQUIR_IMG,(sObj['width'],sObj['height']))
               else :  #faces left 
                   sObj['surface'] = pygame.transform.scale(L_SQUIR_IMG,(sObj['width'],sObj['height']))

       # go through all the objects and see if any need to be deleted.
       for i in range(len(grassObjs) - 1, -1, -1):
           if isOutsideActiveArea(camerax, cameray, grassObjs[i]):
               del grassObjs[i]
       for i in range(len(squirrelObjs) - 1, -1, -1):
           if isOutsideActiveArea(camerax, cameray, squirrelObjs[i]):
               del squirrelObjs[i]
       
       #add more grass & sq if we dont have enough 
       while len(grassObjs)<NUMGRASS:
           grassObjs.append(makeNewGrass(camerax, cameray))
       while len(squirrelObjs)<NUMSQUIRRELS:
           squirrelObjs.append(makeNewSquirrel(camerax,cameray)

       #adjust camerax and cameray if beyond the "camera slack"
       playerCenterx = playerObj['x'] + int(playerObj['size'] / 2)
       playerCentery = playerObj['y'] + int(playerObj['size'] / 2)
       if (camerax + HALF_WINWIDTH) - playerCenterx > CAMERASLACK:
           camerax = playerCenterx + CAMERASLACK - HALF_WINWIDTH
       elif playerCenterx - (camerax + HALF_WINWIDTH) > CAMERASLACK:
           camerax = playerCenterx - CAMERASLACK - HALF_WINWIDTH
       if (cameray + HALF_WINHEIGHT) - playerCentery > CAMERASLACK:
           cameray = playerCentery + CAMERASLACK - HALF_WINHEIGHT
       elif playerCentery - (cameray + HALF_WINHEIGHT) > CAMERASLACK:
           cameray = playerCentery - CAMERASLACK - HALF_WINHEIGHT

       #draw the green background
       DISPLAYSURF.fill(GRASSCOLOR)

       #draw all the grass objects on the screen 
       for gObj in grassObjs:
           gRect = pygame.Rect((gObj['x']-camerax,gObj['y']-cameray,gObj['width'],gObj['height']))
           DISPLAYSURF.blit(GRASSIMAGES[gObj['grassImage']],gRect)

       #draw the other sq
       for sObj in squirrelObjs:
           sObj['rect'] = pygame.Rect( (sObj['x'] - camerax,
                                        sObj['y'] - cameray - getBounceAmount(sObj['bounce'], sObj['bouncerate'], sObj['bounceheight']),
                                        sObj['width'],
                                        sObj['height']) )
           DISPLAYSURF.blit(sObj['surface'], sObj['rect'])

       #draw the player squirrel 
       flashIsOn = round(time.time(),1)*10%2 == 1
       if not gameOverMode and not (invulnerableMode and flashIsOn):
           playerObj['rect'] = pygame.Rect( (playerObj['x'] - camerax,
                                             playerObj['y'] - cameray - getBounceAmount(playerObj['bounce'], BOUNCERATE, BOUNCEHEIGHT),
                                             playerObj['size'],
                                             playerObj['size']) )
           DISPLAYSURF.blit(playerObj['surface'], playerObj['rect'])

       #draw the health meter
       drawHealthMeter(playerObj['health'])

       for event in pygame.event.get(): # event handling loop
           if event.type == QUIT:
               terminate()

           elif event.type == KEYDOWN:
               if event.key in (K_UP, K_w):
                   moveDown = False
                   moveUp = True
               elif event.key in (K_DOWN, K_s):
                   moveUp = False
                   moveDown = True
               elif event.key in (K_LEFT, K_a):
                   moveRight = False
                   moveLeft = True
                   if playerObj['facing'] != LEFT: # change player image
                       playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                   playerObj['facing'] = LEFT
               elif event.key in (K_RIGHT, K_d):
                   moveLeft = False
                   moveRight = True
                   if playerObj['facing'] != RIGHT: # change player image
                       playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                   playerObj['facing'] = RIGHT
               elif winMode and event.key == K_r:
                   return

           elif event.type == KEYUP:
               # stop moving the player's squirrel
               if event.key in (K_LEFT, K_a):
                   moveLeft = False
               elif event.key in (K_RIGHT, K_d):
                   moveRight = False
               elif event.key in (K_UP, K_w):
                   moveUp = False
               elif event.key in (K_DOWN, K_s):
                   moveDown = False

               elif event.key == K_ESCAPE:
                   terminate()
       
       if not gameOverMode:
           # actually move the player
           if moveLeft:
               playerObj['x'] -= MOVERATE
           if moveRight:
               playerObj['x'] += MOVERATE
           if moveUp:
               playerObj['y'] -= MOVERATE
           if moveDown:
               playerObj['y'] += MOVERATE
           
           if (moveLeft or moveRight or moveUp or moveDown) or playerObj['bounce'] != 0:
               playerObj['bounce'] += 1

           if playerObj['bounce'] > BOUNCERATE:
               playerObj['bounce'] = 0 # reset bounce amount

           #check if the player has collided with any squirrels 
           for i in range(len(squirrelObjs)-1, -1, -1):
               sqObj = squirrelObjs[i]
               if 'rect' in sqObj and playerObj['rect'].colliderect(sqObj['rect']):
                   #player and sq collision has occured 
                   if sqO bj['width'] * sqObj['height']<= playerObj['size']**2: # player is larger than enemy
                       playerObj['size']+= int((sqObj['width']*sqObj['height'])**0.2)+1
                       del squirrelObjs[i]

                       if playerObj['facing'] == LEFT:
                           playerObj['surface'] = pygame.transform.scale(L_SQUIR_IMG, (playerObj['size'], playerObj['size']))
                       if playerObj['facing'] == RIGHT:
                           playerObj['surface'] = pygame.transform.scale(R_SQUIR_IMG, (playerObj['size'], playerObj['size']))

                       if playerObj['size']> WINSIZE:
                           winMode = True 
                   
                   elif not invulnerableMode:
                       # player is smaller and takes damage
                       invulnerableMode = True
                       invulnerableStartTime = time.time()
                       playerObj['health'] -= 1
                       if playerObj['health'] == 0:
                           gameOverMode = True # turn on "game over mode"
                           gameOverStartTime = time.time()
       else:
           # game is over, show "game over" text
           DISPLAYSURF.blit(gameOverSurf, gameOverRect)
           if time.time() - gameOverStartTime > GAMEOVERTIME:
               return # end the current game
       
       if winMode:
           DISPLAYSURF.blit(winSurf, winRect)
           DISPLAYSURF.blit(winSurf2, winRect2)

       pygame.display.update()
       FPSCLOCK.tick(FPS)

       
profile
부족한 실력을 엉덩이 힘으로 채워나가는 개발자 서희찬입니다 :)

0개의 댓글