본문 바로가기
python 프로젝트(pygame)

2. pygame 기본적인 클래스 구성 + 코드

by watergrace2u 2020. 8. 11.
반응형
SMALL

먼저 따로 클래스나 함수에 넣지 않고 기본적으로 초기화해준 값들이다.

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

pygame.init()
pygame.font.init()

width, height = 800,500
screen=pygame.display.set_mode((width,height))
pygame.display.set_caption("** Defend Padlock **")
FPS=30 
fpsClock=pygame.time.Clock()

# 상대경로를 사용하여 이미지 처리
current_path = os.path.dirname(__file__) 
image_path = os.path.join(current_path, 'images') 
soil = pygame.image.load(os.path.join(image_path, 'soil.png'))
key = pygame.image.load(os.path.join(image_path,'key.png'))
heart = pygame.image.load(os.path.join(image_path,'heart.png'))
star = pygame.image.load(os.path.join(image_path,'star.png'))
sad = pygame.image.load(os.path.join(image_path,'sad.png'))

 

다음은 게임을 만드는 데에 사용된 클래스들이다. 

1. Player 클래스

2. Shot 클래스

3. Enemy 클래스

4. Padlock 클래스

5. Timer 클래스 

 

1. Player 클래스

class Player:
    playerpos = [154,155] # 플레이어의 초기 위치

    def __init__(self):
        self.image = pygame.image.load(os.path.join(image_path, 'alien.png'))
        self.rect = pygame.Rect(self.image.get_rect())
        self.hp = 3

    def draw(self):
        self.mousepos = pygame.mouse.get_pos()
        self.angle = math.atan2(self.mousepos[1]-self.playerpos[1],self.mousepos[0]-self.playerpos[0])
        self.playerrot = pygame.transform.rotate(self.image,360-self.angle*57.29) 
        self.playerpos1 = (self.playerpos[0]-self.playerrot.get_rect().width/2, self.playerpos[1]-self.playerrot.get_rect().height/2)
        screen.blit(self.playerrot, self.playerpos1) # playerrot를 좌표 playerpos1에 표시

(1) 생성자 init

- player 이미지와 사각형, hp(하트 갯수)를 초기화

 

(2) draw 메서드

- 마우스를 클릭시 그 위치를 받아 mousepos 속성에 저장 (x,y위치)

-  math모듈의 atan2함수를 이용하여 마우스를 클릭한 지점과 플레이어 위치의 각도를 angle 속성에 저장

- 마우스의 방향을 따라 플레이어의 이미지가 움직이도록 playerrot속성에 회전이 적용된 이미지 저장

- 회전이 적용된 이미지 좌표값을 playerpos1 속성에 새로 저장

- 마지막으로 playerrot를 좌표 playerpos1에 표시

 

2. Shot 클래스

class Shot:
    def __init__(self,x,y):
        self.x = x
        self.y = y
        self.rect = pygame.Rect(self.x,self.y,20,5)

    def draw(self):
        pygame.draw.rect(screen,(255,255,0),self.rect)

(1) 생성자 init

- 총알이 날아가는 x,y위치 초기화

- 총알 이미지 초기화

 

(2) draw 메서드

- 화면에 총알을 그린다.

 

3. Enemy 클래스

class Enemy:
    badtimer1 = 0 # 적 생성에 사용되는 변수

    def __init__(self):
        self.image = pygame.image.load(os.path.join(image_path, 'badguy.png'))
        self.rect = pygame.Rect(self.image.get_rect())
        
    def draw(self,badguy):
        screen.blit(self.image,badguy)

(1) 생성자 init

- 적(폭탄)이미지 초기화

- 적 이미지를 사각형 형식으로 바꿔서 rect에 저장

 

(2) draw 메서드

- 적 이미지를 badguy 위치 화면에 그린다.

(badguy에는 적 한 명의 위치가 [x,y] 리스트로 담겨있다)

 

4. Padlock 클래스

class Padlock:
    padlock_info = dict(id0=94,id1=94,id2=94,id3=94,id4=94) 
   
    def __init__(self,y):
        self.image = pygame.image.load(os.path.join(image_path, 'padlock.png'))
        self.small_health = pygame.image.load(os.path.join(image_path,'small_health.png'))
        self.small_healthbar = pygame.image.load(os.path.join(image_path,'small_healthbar.png'))
        self.bang = pygame.image.load(os.path.join(image_path,'bang.png'))
        self.rect = pygame.Rect(self.image.get_rect())
        self.y = y
        self.count = 5
        
    def draw(self):
        screen.blit(self.image,(16,self.y))
    
    def collision(self,badguy,badguys,bangy,enemy,enemyindex):
        tmpId = None
        self.badguy = badguy 
        self.bangy = bangy
        enemy.rect.left = badguy[0]
        enemy.rect.top = badguy[1]

        section = [0,100,200,300,400,500]
        sectionId = ['id0','id1','id2','id3','id4']

        for i in range(0,5):
            if badguy[1]>section[i] and badguy[1]<=section[i+1]:
                tmpId = sectionId[i]
                break

        if enemy.rect.left<64:
            self.padlock_info[str(tmpId)] -= 40
            screen.blit(self.bang,(64,bangy))
            badguys.pop(enemyindex)
            
    def ifPadlockHp0(self):
        check=[0,0,0,0,0]
        sum = 0
        for index,(key,value) in enumerate(self.padlock_info.items()):
            if value<=0: 
                check[index]= 1
                self.count-=1 
            else: check[index]= 0
        for i in check:
            sum += i 
        if sum==5: return True 
        else: return False

먼저 자물쇠 5개의 hp처리를 각각 따로해야하기 때문에 각각의 hp를 딕셔너리를 이용하여 초기화해주었다.

 

(1) 생성자 init

- 자물쇠와 관련된 이미지들 초기화

- 이미지를 rect로 바꿔주는 부분

- 자물쇠의 y위치를 받는다.

- 남은 자물쇠 갯수를 count 하여 게임 승리화면에서 그 갯수만큼 별을 준다.

 

(2) draw 메서드

- 자물쇠 이미지를 화면에 그린다.

 

(3) collision 메서드

- 우선 메인함수에서 이 메서드가 호출되는 부분을 보아야 정확하게 이해되지만 작동방식을 설명하면...

- 화면의 높이가 총 500으로 자물쇠 한 개당 100만큼의 y좌표를 차지한다. 

0~100 -> 'id0'

101~200 -> 'id1' 

... 이런식으로 구간별로 id를 부여하였다. 

자물쇠를 각각 충돌처리하기 위해서는 적의 y위치와 적과 자물쇠가 부딪히는 y위치를 비교해야한다. 

if enemy.rect.left < 64 and 0<badguy[1]<=100 :
                padlock.padlock_info['id0'] -= 20
                screen.blit(bang,(64,bangy))
                badguys.pop(index)
            elif enemy.rect.left < 64 and 100<badguy[1]<=200 :
                padlock.padlock_info['id1'] -= 20
                screen.blit(bang,(64,bangy))
                badguys.pop(index)
            elif enemy.rect.left < 64 and 200<badguy[1]<=300 :
                padlock.padlock_info['id2'] -= 20
                screen.blit(bang,(64,bangy))
                badguys.pop(index)
            elif enemy.rect.left < 64 and 300<badguy[1]<=400 :
                padlock.padlock_info['id3'] -= 20
                screen.blit(bang,(64,bangy))
                badguys.pop(index)
            elif enemy.rect.left < 64 and 400<enemy_y<=500 :
                padlock.padlock_info['id4'] -= 20
                screen.blit(bang,(64,bangy))
                badguys.pop(index)   

 

 

처음에는 if문을 사용하여 위처럼 노가다로 처리하였는데 너무 반복되는 것이 많아서 if문을 줄일 코드를 다시 짰다.

	section = [0,100,200,300,400,500]
        sectionId = ['id0','id1','id2','id3','id4']

        for i in range(0,5):
            if badguy[1]>section[i] and badguy[1]<=section[i+1]:
                tmpId = sectionId[i]
                break

        if enemy.rect.left<64:
            self.padlock_info[str(tmpId)] -= 40
            screen.blit(self.bang,(64,bangy))
            badguys.pop(enemyindex)

section 과 sectionId 리스트를 아예 새롭게 만들어서 for in문으로 차례대로 적의 y좌표와 자물쇠의 구간을 비교한다.

이 때, sectionId 리스트 값들을 padlock_info 딕셔너리의 키값들과 동일하게 한다.

그래서 구간이 일치하면 그 section에 해당하는 index값과 동일한 index값을 가진 sectionId의 값을 tmpId에 저장한 후 바로 for문을 빠져나온다. 그리고 만약 적의 사각형 x좌표 값이 64보다 작아지면 적과 자물쇠가 충돌한 것이므로 

tmpId를 str을 이용하여 문자열로 바꿔주고 이를 키값으로 사용하여 해당하는 딕셔너리 값을 찾아 hp를 깎는다. 

코드가 훨씬 보기 좋아져서 좋다. 더 좋은 방법이 있다면 나중에 수정해야겠다. 

 

(4) ifPadlockHp0 메서드

- 모든 자물쇠의 hp가 0이 되면 키 이미지로 바뀌도록 설정하였다.

padlock_info 딕셔너리의 인덱스값을 이용하고 싶어서 파이썬에서 지원해주는 enumerate 함수를 사용하였다.

이 함수는 순서가 있는 자료형(리스트, 튜플, 문자열)을 받아 인덱스 값을 포함하는 enumerate 객체를 반환한다.

여기서 key값은 사용하지 않지만 for index,(key,value) in enumerate(self.padlock_info.items()): 이런식으로 해주지 않으면 오류가 나서 다른 방법을 알게 된다면 수정해야겠다. 

새로운 check 리스트를 check=[0,0,0,0,0] 이렇게 초기화하고 for문을 이용하여 인덱스에 해당하는 딕셔너리의 값들을 차례대로 확인한다. 만약 그 값이 0보다 작거나 같으면 자물쇠의 hp가 0보다 작거나 같다는 뜻이므로 0을 1로 바꿔준다. 그렇게 check 리스트의 값들이 모두 1로 바뀌어 합이 5가 되면 모든 자물쇠의 hp가 0보다 작거나 같다는 뜻이므로 True를 반환한다. 그렇지 않으면 False를 반환한다.

 

5. Timer 클래스

class Timer:
    def __init__(self):
        self.elapsed = 0.0 
        self.running = False 
        self.last_start_time = None 

    def start(self):
        if not self.running:
            self.running = True 
            self.last_start_time = time.time()

    def get_elapsed(self):
        elapsed = self.elapsed
        if self.running:
            elapsed += time.time() - self.last_start_time
        return elapsed

(1) 생성자 init

- 경과된 시간을 저장하는 elapsed 속성

- 진행상태를 나타내는 running 속성

- last_start_time에 현재의 시간을 받는데 일단 None으로 초기화해둔다.

 

(2) start 메서드

- 게임이 실행 중이라면 last_start_time에 현재의 시간을 받는다.

 

(3) get_elapsed 메서드

- time.time()으로 현재 새로운 시간을 또 받고 그 시간에서 마지막 시간을 빼서 몇초가 지났는지를 

elapsed에 저장한다. 그리고 경과된 시간을 반환한다. 

 

 

다음 글에서는 이 클래스들을 사용하여 메인함수가 어떻게 작동되는지 정리하겠다. 

반응형
LIST

댓글