반응형

파이썬은 리스트, 튜플, 딕셔너리와 같은 데이터 자료형을 간단히 파일로 저장할 수 있습니다.

 

몇가지 방법이 있지만 자료형 자체를 그대로 저장하는 방법중에 json 을 이용하는 방법의 예를 간단히 적어보겠습니다.

Python 3.8.2 (default, Apr 27 2020, 15:53:34) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a={'hello':'world','flower':'rose'}
>>> a
{'hello': 'world', 'flower': 'rose'}
>>> import json
>>> with open('dic.json','w') as f:
...     json.dump(a,f)
... 
>>> a=[]
>>> a
[]
>>> with open('dic.json','r') as f :
...     a=json.load(f)
... 
>>> a
{'hello': 'world', 'flower': 'rose'}
>>> a['flower']
'rose'
>>> 

딕셔너리형 데이터를 만든 다음 'dic.json' 이라는 파일로 저장한 후에 변수를 초기화 하고 다시 저장한 'dic.json' 파일을 읽어 들인 예입니다.

 

{"hello": "world", "flower": "rose"}

dic.json 파일을 텍스트 에디터로 읽어 보면 위와 같이 내용이 텍스트 형태로 저장이 되어 있는 것을  확인 할 수 있습니다.

 

 

반응형

게임을 조금이라도 아신다면 프레임이라는 말을 다들 아실 겁니다. 파이게임에서의 프레임 설정에 대해 간단히 써보겠습니다.

 

프레임 설정은 pygame.time 클래스를 이용합니다. 이 클래스에는 delay, wait 등의 함수와 정해진 시간에 이벤트 메시지를 보내주는 set_timer 등의 함수가 있습니다만 이들 함수는 설명을 읽어보면 사용법을 간단히 알 수 있고 프레임 설정과는 관계없는 함수들이니 관심있으신 분들은 읽어 보시고요.

 

>

 

프레임 설정관련 함수는 pygame.time.Clock() 클래스에 있습니다.

 

사용하기 위해선 일단 새 오브젝트를 생성합니다.

clock = pygame.time.Clock()

 

그런 뒤에 게임 루프 안에 다음의 함수를 추가 합니다.

 

clock.tick(FPS) 

와 같은 방식으로 사용하면 됩니다. FPS 는 원하는 프레임 값입니다.

 

tick 외에 tick_busy_loop 함수도 있습니다.

 

똑같이 

 

clock.tick_busy_loop(FPS)

방식으로 사용하면 됩니다.

 

두 함수의 차이는 메뉴얼에 따르면 정확도와 CPU 자원의 소모에 있습니다. tick() 함수는 CPU 를 적게 먹는대신 정확도가 떨어지고 , tick_busy_loop 는 CPU 자원을 더 소모하는 대신 정확도가 더 높습니다.

하지만 특별히 타이밍이 아주 정확해야 하는 특별한 게임이 아니라면, 그 정확도가 크게 눈에 띄일 정도가 아니기 때문에 tick() 함수 정도로도 큰 문제는 없습니다.

 

tick() 과 tick_busy_loop() 함수는 주어진 FPS 값을 넘지 않도록 딜레이를 주는 방식으로 프레임을 제한 합니다.

 

tick() 과 tick_busy_loop() 함수는 리턴값으로 현재의 tick() 함수 호출과 이전 tick() 함수가 호출된 사이의 시간을 milliseconds 단위로 리턴 합니다.

이 리턴된 값을 이용하면 프레임이 일정하지 않다거나, 게임을 60 프레임으로 작성하다 30 프레임으로 변경한다던가 하는 경우에도 게임이 항상 일정한 속도로 실행되도록 만들수가 있습니다만....자세한 설명은 생략하겠습니다. 굳이 그렇게 할 필요가 있나...라는 생각도 들기도 하고요.

FPS = 60
clock = pygame.time.Clock()

done = True

#게임의 메인루프
while done:        
    ev = pygame.event.get()
    for event in ev:
        if event.type ==pygame.QUIT:                  #창의 종료버튼이 눌렸을때 게임종료
            done = False    
        if event.type == pygame.MOUSEMOTION:                 #마우스의 위치 얻음
            bar.rect.center =  (pygame.mouse.get_pos()[0],700)
        break
    
    print(f"tick:{clock.tick(FPS)}   fps:{clock.get_fps()}") #프레임 설정 부분
    
    background.blit(bar.image,bar.rect)                      #bar 스프라이트를 그림
    background.blit(ball.image,ball.rect)
    background.blit(text,(870,20))    
    screen.blit(background,(0,0)) #background 를 screen 에 복사해 줌
    pygame.display.flip()          #화면 갱신 

대략 다른 부분을 생략한 메인 루프 부분 입니다. '#프레임 설정부분' 이라고 쓰인곳에 코드가 있습니다. 위에서는 프레임을 설정해 줌과 동시에 현재 프레임을 프린트 하도록 되어 있습니다.

위에 쓰인'clock.get_fps()' 함수가 현재의 프레임 값을 리턴해 줍니다. 처음에는 '0' 이 출력되나, tick() 함수가 10번 정도 호출된 이후부터는 프레임이 계산되어 출력됩니다.

 

제가 FPS 값을 60 을 주고 프레임을 보니 61.xxxxx ~ 62.5 사이의 값들이 출력되더군요. 정확하게 60 프레임 값이 출력되지는 않았습니다.

 

실제로 테스트 해 보시면 아시겠지만...위에서 설명했듯이 tick() 함수는 주어진 FPS 값을 넘지 않기 위해 딜레이를 주는 함수입니다. 따라서 FPS 값을 낮추면 낮출수록 게임의 속도는 느려집니다.

 

 

반응형

매우 간단하게 중요한 부분만 설명하겠습니다. 나머지 자잘한 함수들은 직접 문서를 보시면 함수명만 봐도 뭔지 알만한 것들이니까요.

 

>

 

일단 처음에 초기화를 해줘야 합니다.

 

pygame.mixer.init()

 

믹서모듈의 초기화 함수인데 보통 pygame 초기화 함수인 pygame.init() 을 쓰기 때문에 특별히 pygame 에서 mixer 관련 함수만 쓸때외에는 사용할 일이 없습니다.

 

>

 

pygame.mixer.pre_init(44100,-16,2,512)

 

pygame.mixer.init() 나 pygame.init() 앞에 사용하며 초기화 할때의 사운드 설정을 미리 해 주는 것 입니다. 위의 예 에선 버퍼를 제외하면 원래 기본값들입니다. 버퍼만 2048 에서 512 로 변경한것 입니다.

버퍼의 값을 변경한 이유는 효과음에서 지연현상이 발생했기 때문입니다.

 

>

 

sfx1 = pygame.mixer.Sound('sounds/sfx01.ogg')

sfx1.set_volume(0.5)

sfx1.play()

 

간단한 효과음은 위와 같이 사용하면 됩니다. 위에서는 sfx1 변수를 만들어 놓고 효과음이 필요할때 마다 sfx1.play() 를 실행해 주면 효과음이 나오게 됩니다.

볼륨은 0.0 ~ 1.0 사이의 값 입니다.

 

파이게임에선 mp3 파일보단 ogg 를 권하고 있습니다. mp3 의 저작권 문제로 리눅스 배포판에 따라 mp3 가 제대로 실행이 안될 수 있기 때문입니다.

제가 테스트 했을때는 위와 같은 효과음에선 mp3 가 소리가 안난 반면 music.load 를 이용해 음악을 로딩할때는 mp3 가 가능했습니다. 보편적 실행을 위해선 ogg 를 사용하는것이 좋을 것 같습니다.

 

>

 

pygame.mixer.music.load('sounds/music01.mp3')

pygame.mixer.music.play()

 

음악을 로딩하고 원하는 곳에서 재생하면 됩니다. play(3) 식으로 사용하면 3번 반복해서 재생합니다.

pygame.mixer.music.queue(music_file) 함수로 다음에 재생될 곡을 미리 지정해 놓을 수도 있습니다.

 

>

 

pygame.mixer.music.set_endevent(MUSIC_END_EVENT)

 

음악의 재생이 끝났을때 이벤트를 발생시킵니다.

 

이벤트는

MUSIC_END_EVENT = pygame.USEREVENT + 1

위와 같은 방식으로 미리 설정해 줘야 합니다.

while done:        
    ev = pygame.event.get()
    for event in ev:
        if event.type ==pygame.QUIT:     #창의 종료버튼이 눌렸을때 게임종료
            done = False    
        if event.type == MUSIC_END_EVENT : #음악의 재생이 끝났음
            print("Music End")
            pygame.mixer.music.play()

그리고 이벤트는 위와 같은 방식으로 처리합니다.

 

위의 이벤트 처리 예제를 보면 음악이 끝났을때 음악을 다시 재생시키고 있는데요.

제 경우에 pygame.mixer.music.play() 로 음악을 재생시켰을 경우 음악이 재생되지 않는 현상이 간간히 발생했습니다.

음악이 끝날때 이벤트가 발생하게 설정해서 확인 해보니 play() 를 실행했을때 음악이 재생되지 않고 바로 음악 종료 이벤트가 발생하더군요.

이런 일이 랜덤하게 발생되기에 무엇이 원인 인지 잘 모르겠습니다. 그래서 일단 음악 종료 이벤트가 발생하면 다시 음악을 재생하도록 해 놓았습니다. 일종의 꼼수인데 문제를 해결하면 다시 문서를 수정해 놓도록 하겠습니다.

 

>

 

pygame.mixer.music.set_volume(0.3)

 

음악의 볼륨을 지정합니다. 범위는 0.0 ~ 1.0 사이의 값입니다.

 

>

 

나머지는 fadein, fadeout 이라던가 pause 라던가, 함수 이름만 봐도 대충 용도를 알 수 있는 것들이라 특별한 설명은 필요 없을 것 같습니다.

사실 pygame.mixer.channel 이라는게 있는데 어떤것일지 대충 짐작은 가지만 아직 테스트 해 보지 않아서 이 포스팅에선 언급하지 않았습니다. 언제 테스트 해 보고 포스팅 하려고 했는데 계속 미루다 보니 언제 포스팅 하게 될지 몰라서 그냥 제가 테스트 한곳 까지만 포스팅 해 놓기로 했습니다.

 

그리고 일단 포스팅 해 놓긴 하지만 동작에서 뭔가 석연치 않은 점도 있어서 버그인지, 뭔가 설정의 오류인지에 대해선 차후에 알게 되면 문서를 수정하도록 하겠습니다.

반응형

지금까지 진행된 프로젝트를 실행했을 경우 공이 이리저리 잘 튕기고 막대로 공을 튕겨내는 것도 가능하긴 하지만 공이 움직이는 각도는 바뀌지 않습니다.
이럴 경우 공의 움직임은 너무 단조롭게 됩니다. 따라서 실제 벽돌깨기 게임을 보면 공을 튕겨내는 막대의 어느 위치에 공이 맞느냐에 따라 공이 움직이는 각도가 달라지게 됩니다.

공이 맞는 위치에 따라 공의 이동 방향이 달라집니다

위 그림에서 보듯 공이 어디에 부딪치느냐에 따라 공의 방향을 바꿔 줄것 입니다.

    #barx : 막대의 x 좌표
    def collideBar(self,barx):
        self.diry *=-1
        
        #공의 x 좌표가 바의 x 좌표보다 작은건 바의 왼쪽부분에 부딪쳤다는것입다
        #이때 공은 왼쪽방향으로 반사됩니다.
        #반대의 경우엔 바의 오른쪽에 부딪쳤다는 것으로 공은 오른쪽방향으로 반사됩니다
        bx = barx - self.rect.center[0]
        if self.rect.center[0] < barx:
            self.moveLeft(bx)
        elif barx < self.rect.center[0]: 
            self.moveRight(bx)

막대와 공의 충돌시에 호출되는 함수에 막대의 x 좌표를 인자로 넘겨 줍니다.

barx 좌표는 공을 튕겨내는 막대 중심의 X 좌표 입니다. 공의 x 좌표는 rect.center[0] 로 얻을 수 있기 때문에 이 두 변수를 비교해서 공이 막대의 왼쪽에 맞았는지 오른쪽에 맞았는지를 판단 할 수 있게 됩니다.

또한 위의 두 값을 빼기를 해보면(barx -self.rect.center[0]) 막대의 어느 위치에 충돌했는지도 알 수 있습니다. 따라서 이 충돌 위치에 따라 공의 새로운 이동각도를 구할 수 있습니다.

 

    def moveLeft(self,bx):
        self.angle = 191 + bx
        self.dirx=1
        
    def moveRight(self,bx):
        self.angle = 169 + bx
        self.dirx=1

위의 moveLeft 와 moveRight 함수는 각각 공을 왼쪽으로 보낼것인지 오른쪽으로 보낼것인지에 따라 호출 됩니다.

공을 왼쪽으로 보내기 위해서는 180 ~ 270 도 사이의 각도로 설정을 해야 합니다. 다만 180 과 270도는 수직, 수평으로 공이 이동되기 때문에 실제로는 190~260도 사이로 값을 변경하게 됩니다.
따라서 각도는 191에 "barx -self.rect.center[0]" 값을 더해서 공의 새로운 이동 각도를 구하게 됩니다.

공을 오른쪽으로 보내는 것도 마찬가지 입니다.
공을 오른쪽으로 보내기 위해서는 90~180 도 사이의 각도로 설정해야 합니다. 다만 위와 같은 이유로 실제 각도는 100~170 도 사이에서 결정되게 됩니다.

사실 이 경우는 막대의 크기가 144로 반으로 나눌경우 72 이기 때문에 좀 쉽게 한 편입니다. 위에서 설명했듯이 각도의 범위가 100~170 도로 막대의 길이와 비슷했기에 단순히 더하고 빼는 것 만으로 새로운 각도를 쉽게 구할 수 있었습니다. 만일 막대의 크기가 크거나 작아서 새로운 공의 이동각도를 단순히 더하기 빼기로 구하기 어려울 경우엔 막대의 길이와 각도와의 비율을 이용해서 새로운 공의 이동 각도를 구해야 할 것입니다.


마지막으로 소스에서 "self.dirx=1" 란 문장이 있는데 이는 각도에 맞게 공을 이동 시키기 위한 것입니다. 공이 왼쪽이나 오른쪽벽에 부딪히면 dirx에 -1 을 곱해 부호를 바꿔주어 공이 벽에서 반사되어 움직이게 됩니다. 그런데 우리가 원하는 대로, 예를 들어 100~170도 사이로 공을 보내려고 할때는 dirx가 마이너스 값이면 공은 반대로 190~260도 방향으로 날아가게 됩니다. 그렇기 때문에 우리가 원하는 각도를 입력해서 공의 방향을 정해줄 때는 dirx는 언제나 플러스 값으로 바꿔주어야 합니다. 

지금 생각엔 아예 처음부터 각도와 부호를 이용해서 이동방향을 정해주는게 더 간단했을지도 모르겠다는 생각도 들기는 합니다만......여러분이 직접 바꿔 보는것도 좋겠지요. ^^;

 

반응형

pygame 에서의 텍스트 출력에 대해 아주 간단히 적어 보겠습니다. 다른 자잘한 함수는 제외하고 간단히 글자 출력에 관한 부분만......^^;

저는 리눅스에서 작업했지만 윈도우도 큰 차이는 없을것으로 생각됩니다. 글꼴 이름만 좀 다를 뿐이겠죠.

>

텍스트 출력은 간단히 다음과 같은 과정을 거칩니다.

font = pygame.font.Font('LexiGulim.ttf',30)  #폰트 설정
text = font.render("글자출력",True,(28,0,0))  #텍스트가 표시된 Surface 를 만듬
background.blit(text,(870,20))              #화면에 표시

두번째 줄부터 설명하면...

render 함수는 순서대로 출력할 텍스트,  안티앨리어싱의 사용여부, 글자색 으로 인자를 받습니다. 글자색은 흔히 이용되는 (R,G,B) 값으로 입력됩니다.

render 함수에서 리턴되는 text 라는 surface 를 blit 로 일반 이미지처럼 출력하면 됩니다. 뭐...간단하죠.

그럼 이제 제일 첫줄에 대한 설명을 해보겠습니다. 조금 길어질 것이므로 뒤로 뺐습니다.

폰트를 읽어들이는 방식은 2가지가 있습니다. 이미지 파일을 읽어 오는 것 처럼 직접 폰트 파일인 ttf 파일을 지정해서 읽는 방식입니다. 위에서의 예는 직접 폰트 파일을 읽는 방법입니다. 위 소스대로라면 실행파일과 같은 디렉토리에 "LexiGulim.ttf" 파일이 존재해야 합니다.

주의해야 할 점은 폰트에 한글이 없으면 한글이 'ㅁㅁㅁ' 와 같이 표시 된다는 것 입니다. 한글을 표시하려면 한글이 포함된 폰트 파일을 사용해야 합니다.

렉시굴림으로 한글 출력

위 스크린샷은 제작중인 블럭격파 게임에서 한글을 렉시굴림폰트를 이용해 출력한 것입니다.

다른 방식은 시스템에 등록되어 있는 글꼴 이름을 사용하는 것입니다. 우리가 흔히 '굴림체' 라던가 '바탕체' 라고 부르는 것과 비슷합니다. 시스템에 등록된 글꼴의 이름은 pygame 에선 다음과 같이 확인 할 수 있습니다.

pygame.font.get_fonts() 함수를 이용해서 현재 시스템에 등록된 글꼴의 이름을 모두 출력해 봤습니다.

위의 폰트중에 하나를 선택해야 하는데, 이 방법의 경우에도 한글을 출력하려면 한글이 포함된 폰트를 선택해야 합니다. 리눅스에선 기본적으로 사용하는 noto~ 관련 폰트에 한글이 포함되어 있습니다.

font = pygame.font.SysFont("notosanscjkkr",30)

폰트 설정을 위와 같이 하면 됩니다. 폰트의 직접적인 파일명을 입력하는게 아니라 폰트의 이름을 입력한다는 것에 주의 해야 됩니다.

>

여러 다른 OS 나 각기 다른 폰트 설정을 가진 컴퓨터에서 사용할 걸 생각한다면 공개가 되어 있고 배포가 자유로운 폰트를 프로그램과 같이 추가해서 배포하고, 직접 폰트파일인 ttf 파일을 읽어 사용하는 방법도 고려할만 합니다.

폰트 모듈을 초기화 하고 종료하는 "pygame.font.init()" 와 "pygame.font.quit()" 이 있지만 메뉴얼에 의하면, 파이게임을 초기화 하는 "pygame.init()" 등에서 자동으로 실행된다고 하니 특별한 경우가 아니면 명시적으로 사용할 필요는 없을듯 합니다.

 

반응형

이번엔 벽돌깨기 게임에서 공의 이동방법에 대해 적어 보겠습니다.

그 전에...혹시나 이 글을 보실 분들에게 말씀 드리면 이 방법은 정답이 아닙니다. 그냥 제가 쓴 방법이므로 참고만 하시길 바랍니다. 더 좋고 쉬운 방법이 얼마든지 있을 수 있습니다.

공의 이동은 삼각함수를 이용합니다. 삼각함수의 sin 함수와 cos 함수를 이용하면 아주 쉽게 구할 수 있습니다.


위 그림에서 우리는 공을 시작위치에서 이동위치 까지 이동시키려고 합니다. c 는 이동거리 입니다. 이동 방향은 각도(x)로 표시합니다. 공을 이동시키려면 우리는 a 값과 b 값을 알아내어 시작위치의 좌표에 더해 주면 이동위치의 좌표을 얻게 됩니다. 

sin x = b/c 이므로 
b = sin x * c 가 됩니다. 같은 이유로...

cos x = a/c 이므로 
a = cos x * c 가 됩니다. 

이것을 파이썬으로 구현해보면 다음과 같습니다.

b = math.sin(math.radians(self.angle)) * c
a = math.cos(math.radians(self.angle)) * c

삼각함수를 사용하려면 먼저 import math 를 사용해야 합니다. 

angle 이 각도인데 여기서의 각도는 편의상 일반적으로 우리가 사용하는 이해하기 쉬운 360도 각도 입니다. 그런데 컴퓨터에서 각도는 라디안(Radian)으로 표시합니다. 따라서 우리가 일반적으로 쓰는 각도를 라디안 값으로 변환해서 삼각함수를 사용해야 하고요. 우리가 쓰는 360도 각도를 라디안으로 바꿔주는 함수가 math.radians 입니다. 

결과적으로 실제 게임에 쓰인 소스는 아래와 같습니다.

xd = math.sin(math.radians(self.angle)) * self.dt *self.dirx
yd = math.cos(math.radians(self.angle)) * self.dt *self.diry
        
self.x += xd
self.y += yd

일단 저는 위와 같이 프로그램을 작성했습니다. 기존 x, y 좌표에 삼각함수를 이용해 구해진 이동거리 xd 와 yd 를 더해주고 있습니다. 이때 우리가 의도한 방향으로 공이 움직이게 되는 각도는 90 ~ 270 도의 각도 입니다.(sin 값과 cos 값이 양수냐 음수냐에 따른 것) 다르게 할 방법도 있을텐데 이렇게 생각하는게 간단한거 같아서 그렇게 했습니다. 처음 공이 발사될 각도의 초기값을 180 ~ 270 도 사이로 주면 공은 왼쪽 위로, 90 ~ 180 도 사이로 주면 오른쪽 위로 공은 날아갑니다. 

위의 소스는.....
dt 만큼의 거리를 angle 각도로 이동할때 x축 방향으로 xd 만큼 이동
dt 만큼의 거리를 angle 각도로 이동할때 y축 방향으로 yd 만큼 이동

한다는 겁니다. 
따라서 원래의 x 와 y 좌표에 xd 와 yd 를 더해주면 공이 이동하게 됩니다.

dirx 와 diry 는 이동의 방향을 설정해 줍니다. 공의 특정 방향으로의 이동을 0~360도의 각도로 정해 줄 수도 있지만 이렇게 계산하면 좀 복잡해 집니다. 따라서 간단하게 벽이나 벽돌에 맞고 튕기는 것을 구현할때 튕기는 방향에 -1 을 곱해서 부호만 바꿔주는 겁니다. 
예를 들어 120도의 각도로 위로 올라가던 공이 천정에 맞고 튕기면 diry 값에 -1 을 곱해서 yd 값을 마이너스에서 플러스로 바꿔주면 공이 같은 반사각으로 튕겨 내려오는 것을 쉽게 구현할 수 있습니다. 
dirx, diry 는 이러한 방향을 바꿔주는 역활을 하는 변수 입니다. 초기값은 1 이며 공이 튕겨야 할때 마다 -1 을 곱해 줌으로서 공의 방향을 바꿔주는 역활을 합니다. 

변수 dt 는 공이 한번에(?) 이동할 거리입니다. 이동거리를 높게 주면 공이 빠르게 움직이기 때문에 공의 속도와 관련되어 있기도 합니다. 

>

import pygame
import math    #삼각함수를 쓰기 위해 필요

class Ball(pygame.sprite.Sprite):
    #img 이미지
    def __init__(self,img,x,y):
        super().__init__()
        self.image = img
        self.rect = img.get_rect()
        self.rect.center = (x,y)
        self.mask = pygame.mask.from_surface(img) #충돌체크용 마스크 생성
        self.go = False #공을 움직일 거면 True, 기본값은 당연히 False
        self.dt = 2 #공의 이동 거리, 속도
        self.x = x  #공의 현재 x 좌표 
        self.y = y  #공의 현재 y 좌표 
        self.dirx = 1   
        self.diry = 1

    #공이 움직일 공간
    def boundRect(self,rect):
        self.brect = rect
    
    #주어진 각도로 공을 움직임    
    def start(self, angle):
        self.angle = angle
        self.go = True
    
    #공과 막대의 충돌시 공의 방향을 바꿈    
    def collideBar(self):
        self.diry *=-1

    def move(self,surface):        
        if not self.go : return #게임이 시작되지 않았으면 아래 내용을 실행하지 않고 리턴시킴
        
        #공의 이동을 계산 
        xd = math.sin(math.radians(self.angle)) * self.dt *self.dirx
        yd = math.cos(math.radians(self.angle)) * self.dt *self.diry
        #구해진 이동거리를 원래 좌표에 더해서 이동함
        self.x += xd
        self.y += yd
        
        #공이 벽에 맞고 튕기는 부분. brect 는 공이 이동 가능한 공간, rect는 공의 사각범위임
        # dirx 와 diry 에 -1 을 곱해 방향을 바꿔준다.
        if self.x - self.rect.width/2 < self.brect.x : 
            self.x = self.brect.x + self.rect.width/2
            self.dirx *= -1
        if self.y - self.rect.width/2 < self.brect.y :
            self.y = self.brect.y + self.rect.width/2
            self.diry *= -1
        if self.x + self.rect.width/2 > self.brect.x + self.brect.width :
            self.x = self.brect.x + self.brect.width - self.rect.width/2
            self.dirx *= -1
        if self.y + self.rect.width/2  > self.brect.y + self.brect.height:
            self.y = self.brect.y + self.brect.height - self.rect.width/2
            self.diry *= -1
        
        #공의 새로운 위치를 입력해주고
        self.rect.center = (self.x,self.y)
        #공을 그린다.
        surface.blit(self.image,self.rect)

공을 다루는 Ball 클래스 입니다. Sprite 를 상속받았고요. 아직 완성된건 아닙니다만...현재는 대강 움직이면서 벽돌을 제거하는 건 가능합니다. 막대로 공을 튕겨낼 수도 있고요.

다만 현재는 작업중이라 공을 막대로 튕겨내지 못해도 공이 아웃되지 않고 그냥 아래쪽 벽에 맞고 계속 튕기게 되어 있습니다.

Ball 클래스는 객체를 생성할때 초기 공의 위치를 입력받습니다. 그리고 start(220) 이런식으로 이동할 각도를 인자로 넣어 실행하면 공은 그 각도로 움직이기 시작합니다.

삼각함수만 이해하면 쉬운부분이니 간단히 이정도만 이야기 하고 넘어 가겠습니다. ^^;

일단 기본적인 공의 이동과 충돌체크 부분은 대충 되었네요.

그런데....막대로 공을 튕겨내는 부분은 좀 생각을 해야 될게...스프라이트의 충돌체크로 했더니 버그가 좀 있습니다. 공이 막대의 상단 부분이 아닌 측면쪽에 부딪혔을때 마우스를 이동시켜 공이 막대의 내부로 들어오게 되면, 충돌 체크 함수에 의해 공의 방향이 바뀌게 되긴 하지만 공은 막대의 내부에 있으므로 계속해서 스프라이트 충돌함수가 실행되어 결과적으론 공이 막대 내부의 부분을 타고 흐르는(?) 현상이 생기게 됩니다.(말로 하자니 좀 어렵네요)

막대가 마우스의 x 좌표에 따라 그대로 이동하기 때문에 생기는 증상인데 어떻게 해결하는게 가장 괜찮을지 생각을 좀 해 봐야 겠습니다.

반응형

pygame  의 스프라이트 충돌체크에 대해서는 한번 간단하게 정리를 따로 해야 할것 같아 따로 정리를 해 둡니다. 전부 상세하게 적지는 않고 중요한 몇가지만(내가 필요한거만....아마도....?) 정리를 해 봅니다.

 

일단 기본적으로 스프라이트 b1 과 b2 는 다음과 같이 만들었습니다. 변수 정리를 좀 더 하면 보기 좋겠지만 귀찮으니 그냥 합니다.

pic = pygame.image.load("images/ball.png").convert_alpha()
pic2 = pygame.transform.scale(pic,(200,200))
pic3 = pygame.transform.scale(pic,(100,100))

b1 = Block(pic2)
b1.rect.center=(400,400)
b1.radius=100

b2 = Block(pic3)
b2.radius = 50

b1 스프라이트는 (400,400) 위치에 고정되어 있고, b2 스프라이트는 마우스를 클릭하면 그 위치로 이동하는 스프라이트 입니다. 따라서 b2 의 경우는 좌표의 초기 값은 없습니다.

 

1. Rect(사각형) 체크

 

가장 기본적인 방법입니다.

    if(pygame.sprite.collide_rect(b1,b2)):
        print("Hit!")

두 스프라이트가 충돌하는지를 검사해서 충돌하면 bool 값 True 를 리턴합니다. 
사용시 주의할 점은(사실 당연한 겁니다만...) 스프라이트의 rect 에 제대로된 값을 넣어줘야 제대로 작동한다는 점 입니다. 
rect.x ,rect.y 혹은 rect.center 에 제대로된 스프라이트의 위치값이 있어야 제대로 작동합니다.

 

위의 스크린샷을 보면 실제로 두개의 스프라이트는 충돌하지 않았습니다. 하지만 사각형체크 방법은 사각형 형태로 충돌을 체크하기 때문에 위와 같은 상황도 충돌로 판정합니다.

 

2. Circle(원) 체크

    if(pygame.sprite.collide_circle(b1,b2)):
        print("Hit!")

원의 형태로 충돌을 체크 합니다. 위의 이미지와 같이 충돌 체크할 부분이 원인 경우 이 방법을 사용하면 됩니다.

제대로 작동시키기 위해선 sprite 객체에 radius(원의 반지름) 값을 넣어줘야 합니다.

위에 b1,b2 를 보면 radius 값을 넣어준걸 보실 수 있습니다.

 

3. Mask 체크

위의 두가지 방법은 사각형과 원의 특정형태로만 충돌체크가 됩니다. 하지만 스프라이트의 경우 더 다양한 모양이 있을 수 있습니다. 즉 특정 도형의 형태가 아니라 스프라이트의 이미지와 이미지가 직접 충돌하였을때(투명한 알파영역 제외), 이미지와 이미지가 서로 겹쳤을때 충돌로 판정해 줍니다.

 

mask 충돌 체크 방법을 쓰기 위해선 sprite 에 mask 를 만들어 줘야 합니다.

    b1.mask = pygame.mask.from_surface(b1.image)
    b2.mask = pygame.mask.from_surface(b2.image)
    if(pygame.sprite.collide_mask(b1,b2)):
	    print("Hit!")

b1 과 b2 이미지를 이용해서 각각 b1 과 b2 의 스프라이트에 mask 를 만든 다음 스프라이트 b1 과 b2 의 충돌체크를 검사합니다.

 

위에선 이해를 위해 저렇게 mask 값을 생성한것이고 실제코딩시에는 sprite 클래스의 생성자에서 이미지를 넘겨 받으면서 mask 를 만들면 됩니다.

mask 체크 방법을 사용할때 주의할 점이 하나 있는데요. 바로 이미지를 컨버트할때 convert_alpha() 를 사용해야 한다는 것입니다.

pic = pygame.image.load("images/ball.png").convert_alpha()

이미지에 특별히 투명한 부분이 없더라도 위와 같이 convert_alpha() 로 해 주지 않으면 mask 체크가 제대로 작동하지 않더군요. 이것때문에 좀 헤맸습니다. ^^;

 

4. SpriteGroup 과 Sprite 의 충돌체크

pic = []
pic.append(pygame.image.load("images/bb.png").convert_alpha())
pic.append(pygame.image.load("images/gb.png").convert_alpha())
pic.append(pygame.image.load("images/pb.png").convert_alpha())
pic.append(pygame.image.load("images/rb.png").convert_alpha())
pic.append(pygame.image.load("images/yb.png").convert_alpha())

block_list = pygame.sprite.Group()

for j in range(0,5):
    for i in range(1,9):    
        block = Block(pic[random.randrange(5)])
        block.rect.x = i * 91 - 50
        block.rect.y = 50 + 37*j
        block.mask = pygame.mask.from_surface(block.image)
        block_list.add(block)

ball_pic = pygame.image.load("images/ball.png").convert_alpha()
ball_pic = pygame.transform.scale(ball_pic,(15,15))
ball = Block(ball_pic)
ball.rect.center = (410,680)
ball.mask = pygame.mask.from_surface(ball.image)

스프라이트 ball 과 스프라이트그룹 block_list 를 위와 같이 만들어 줬습니다.

 

hit_list = pygame.sprite.spritecollide(ball,block_list,True,pygame.sprite.collide_mask)
for h in hit_list:
    score +=1

위와 같이 충돌체크를 하면 됩니다.

 

리턴값 hit_list 는 충돌한 스프라이트들의 리스트입니다. 각 인자들을 설명하자면...
첫번째가 스프라이트, 두번째가 스프라이트 그룹, 세번째는 충돌한 스프라이트를 스프라이트 그룹에서 제거할것인지, 마지막이 충돌을 체크하는 방법입니다. 위의 예는 mask 를 이용한 체크 방법이고 collide_rect 나 collide_circle 도 가능합니다.

 

이 테스트 프로그램은 공을 마우스로 마음대로 움직일수 있게 했습니다. 마우스로 공을 이동시켜 블럭과 충돌시키면 해당 블럭은 스프라이트 그룹에서 삭제되기 때문에 위와 같이 공과 충돌된 블럭은 사라지게 됩니다.

 

>

 

모든 충돌체크를 다 설명하지는 않았지만, 나머지는 이 정도만 이해하면 이해하기 어렵지 않을거라 생각합니다.

반응형

공 움직이는것 까진 해 볼려고 했는데 시간 관계상 일단 여기까지 하고 일단 정리 합니다.

뭐....대략 스크린샷은 아래에....

 

일단 아래쪽 공을 받아낼 막대를 마우스를 이용해서 움직이는 것을 했고요. 위에 블록을 8x5 로 그렸습니다.

블록은 색깔별로 5개의 png 파일을 만들었고요. 이미지의 선택은 랜덤입니다.

전체 소스는 넣지 않고 부분적으로만 적어봅니다.

 

>

background.fill((70,70,255))                            #배경을 파란색으로 칠함

pygame.draw.rect(background,(0,0,0),(20,20,800,720))    #게임이 진행될 공간 검은색으로 칠함

가운데의 검은 게임이 진행될 공간을 만들기 위해 전체 배경을 파란색으로 칠하고 가운데는 안을 검은색으로 채운 사각형을 그렸습니다.

 

>

#블럭이미지를 읽어 리스트에 저장
pic = []
pic.append(pygame.image.load("images/bb.png").convert())
pic.append(pygame.image.load("images/gb.png").convert())
pic.append(pygame.image.load("images/pb.png").convert())
pic.append(pygame.image.load("images/rb.png").convert())
pic.append(pygame.image.load("images/yb.png").convert())

이미지를 5개 읽어들여 저장합니다. 나중에 랜덤 함수를 이용해서, 스프라이트 객체를 생성할때 랜덤하게 이미지를 선택합니다.

 

>

#스프라이트를 생성하여 SpriteGroup 에 추가함. 스프라이트의 위치 좌표도 만들어 넣어준다. 블럭의 색은 랜덤하게
for j in range(0,5):
    for i in range(1,9):    
        block = Block(pic[random.randrange(5)]) #스프라이트 이미지 랜덤으로 선택
        block.rect.x = i * 91 - 50              #블럭의 x 좌표
        block.rect.y = 50 + 37*j                #블럭의 y 좌표
        block_list.add(block)                   #스프라이트 그룹에 추가 

블럭 객체를 만들때 랜덤으로 이미지를 선택해서 인자로 넘겨줍니다. 한줄에 8개의 블록을 5줄을 그립니다. 뭐...아주 단순하게요.

 

>

 

#공을 튕겨낼 막대 이미지
BAR_X = 144       #막대의 길이
BAR_Y = 26        #막대의 폭

#png 이미지가 투명한 부분이 있는 이미지이기 때문에 convert_alpha 를 이용해 이미지를 변환해야 합니다.
bar_pic = pygame.image.load("images/bar.png").convert_alpha()
bar_pic = pygame.transform.scale(bar_pic,(BAR_X,BAR_Y)) #막대의 크기를 원하는 크기로 변경

#막대 스프라이트를 만듬
bar = Block(bar_pic)
bar.rect.center = (410,700)

공을 튕겨낼 막대는 끝부분이 각지지 않고 둥글게 그렸습니다.

 

표시해 놓은 부분이 투명한 부분 입니다. 이 부분이 제대로 표현되기 위해서는 이미지를 convert_alpha 를 이용해 알파값을 살려서(?) 변환해야 png 이미지의 투명한 부분이 제대로 표시 됩니다.

 

bar.rect.center = (410,700) 이라고 되어 있는 부분이 있는데 rect 에는 center 라는 tuple 변수가 있습니다. 시험해 본 결과 blit 로 이미지를 표시할때 center 값이 있으면 rect.x 나 rect.y 를 무시하고 center 값을 이용합니다.

rect.center 값이 존재하면 rect.center 의 좌표에 이미지의 중심이 위치하도록 이미지를 표시합니다.

 

>

    if bar.rect.center[0] < 72+20 : bar.rect.center = (72+20,700) #막대의 좌측이동제한
    if bar.rect.center[0] > 820-72 : bar.rect.center = (820-72,700)#막대의 우측이동제한
    pygame.draw.rect(background,(0,0,0),(20,20,800,720))  #게임이 진행될 공간 검은색으로 칠함

공을 튕겨낼 막대의 이동 범위를 제한 합니다. 

게임이 진행될 공간은 맨 아래 게임이 진행될 공간을 검게 칠하는 부분에서 나오는데요.

막대는 x 좌표로만 이동하므로 x 좌표만 제한합니다. 20 부터 시작해서 폭이 800 이니, 좌표로는 x=20-->x=820 사이로 움직임을 제한하는데, 막대의 길이가 144 이기 때문에 그 절반인 72 를 더하거나 빼서 가동범의를 지정해 준 것입니다.

좀 예쁘게(?) 잘 하자면 바의 길이는 BAR_X =144 로 정의되어 있으므로, 숫자 72 대신 BAR_X/2 로 해 주면 나중에 막대의 크기가 달라졌을때도 대응 할 수 있습니다. 나중에 다 수정해 놓을 예정입니다.

 

>

    ev = pygame.event.get()
    for event in ev:
        if event.type ==pygame.QUIT:     #창의 종료버튼이 눌렸을때 게임종료
            done = False    
        if event.type == pygame.MOUSEMOTION:    # 마우스를 움직였을때 
        #마우스의 x 좌표를 얻어 막대의 x 값으로 넣어준다. 좌우로만 움직이므로 y 값은 고정
            bar.rect.center =  (pygame.mouse.get_pos()[0],700)
        break
    

마지막으로 마우스 이동시에 마우스의 좌표를 얻어서 막대의 좌표로 넣어 줍니다. get_pos()  에서 마우스의 x,y 좌표를 얻을 수 있지만 우리는 x 좌표만 필요하므로 리스트에서 첫번째 값인 x 값만 읽어서 넣어 주고 y 값은 고정합니다.

그러면 이제 막대는 마우스를 좌우로 움직을때 같이 움직이게 됩니다.

 

>

 

이번은 pygame 과 관련된 부분은 많지 않습니다. 진행상황 정도이고....다음엔 공이 이리저리 튕겨야 하니  스프라이트 충돌 부분을 처리해야 해서 좀 보고 이것저것 테스트 해보고 해야되서 시간은 좀 걸릴 예정입니다. ^^;

 

2019년 5월 26일 수정사항

 

sprite.rect.center 라는 tuple 변수가 존재한다는 사실을 알고 내용을 수정했습니다. 게임에선 좌표가 이미지의 중심일 필요가 있습니다.(일반적인 경우 좌측 상단임)

앞으로도 수정할 내용이 있으면 수정하고 하단에 수정 내용을 간단히 적어 놓겠습니다.

반응형

pygame 은 이름대로 파이썬으로 게임제작을 할 수 있도록 만들어진 라이브러리 입니다.

 

아무래도 사용법을 익히려면 간단한거 하나 만들어 보는게 좋을꺼 같아 뭘 할까 하다가 벽돌깨기 게임을 만들어 보기로 했습니다.

딱히 강좌도 아니고....제대로 된 게임을 만드는것 자체도 목적이 아니라, 어디까지나 pygame 을 익히고 기록을 남겨두는게 목적이기 때문에 소스의 깔끔함이라던가 정돈이라던가 그딴거 없이 대충 갑니다.(파이썬도 사실 아직 잘 모릅니다)

pygame 의 문서만 보고 하는거라 잘못 아는것도 있을 수 있고, 더 좋은 방법이 있는데 어렵게 하는 것도 있을 수 있음을 미리 알려드립니다.

파이썬은 당연히 python 3.x 버전을 사용할 것 입니다.

 

>

 

첫번째는 그냥 기본적인 라이브러리 사용방법에 대한것이라 화면에 블럭을 한줄 표시하는 소스입니다.

import pygame
from block import *

SCREEN_X = 1024
SCREEN_Y = 768

pygame.init()   #pygame 라이브러리 초기화
screen = pygame.display.set_mode((SCREEN_X,SCREEN_Y))   #특정크기의 스크린 생성
background = pygame.Surface(screen.get_size())#스크린과 동일크기의 surface 생성. 
                                              #이곳에 실제 그림을 그린후 원래 스크린에 복사함

#소스코드 위치 아래 images 폴더에 그림파일을 보관함. 
#convert 는 좀 더 빠른속도를 낼수 있게 이미지를 변환한다고 함.
pic = pygame.image.load("images/bb.png").convert()
#pic = pygame.transform.scale(pic,(nx,ny))    #이미지 크기를 바꾸고 싶을때 사용함
block_list = pygame.sprite.Group()

#스프라이트를 생성하여 SpriteGroup 에 추가함. 스프라이트의 위치 좌표도 만들어 넣어준다
for i in range(1,9):    
    block = Block(pic)      #블럭객체 생성. 파라미터는 그림파일
    block.rect.x = i * 91   #화면상에 표시될 x 좌표
    block.rect.y = 100      #화면상에 표시될 y 좌표 
    block_list.add(block)   #객체를 sprite.group 에 추가

done = True

#게임의 메인루프. 루프에서 빠져나오면 게임은 종료됨
while done:        
    ev = pygame.event.get()
    for event in ev:
        if event.type ==pygame.QUIT:     #창의 종료버튼이 눌렸을때 게임종료
            done = False                 #false 값을 줌으로서 루프를 빠져나와 게임종료
        if event.type == pygame.MOUSEBUTTONDOWN:    #마우스가 눌렸을때 마우스의 위치 얻음
            pos = pygame.mouse.get_pos()            #현재 마우스 포인트의 위치좌표
        break

    block_list.draw(background)   #SpriteGroup 에 있는 스프라이트를 
                                  #background surface 에 모두 그림
    screen.blit(background,(0,0)) #background 를 screen 에 복사해 줌
    pygame.display.flip()          #화면 갱신 

대략적 설명은 소스에 주석으로 달려있으니 길게 설명할 건 없겠고요.

 

화면에 이미지(스프라이트)를 표시하는 방법은 sprite class 를 상속받은 block 클래스를 만들고 이 block 클래스의 객체를 생성해서 Sprite.Group 클래스에 추가합니다. 추가할때 각 이미지가 표시될 화면상 좌표를 block.rect.x 와 block.rect.y 에 넣어줍니다.

sprite.group 클래스의 draw 를 이용하면 sprite.group 에 추가된 스프라이트들을 모두 화면에 그려줍니다.

 

 

다음은 block 스프라이트 클래스 입니다.

import pygame

class Block(pygame.sprite.Sprite):
    def __init__(self,img):
        super().__init__()
        self.image = img
        self.rect = img.get_rect()
       

별건 없습니다. sprite 객체를 상속해서 block 클래스를 만듭니다. 사용할 이미지와 크기와 위치를 지정할 rect 변수를 지정해 줍니다.

 

 

실행화면

화면에 block 이미지를 8개 출력했습니다. 아래로 몇줄 더 출력하고 벽돌을 깰 공과 공을 튕겨낼 캐릭터 정도까지만 할 계획입니다. 공이 튕길 벽도 만들긴 해야 겠네요. ^^;

 

시험적으로 이것저것 해 볼 것이라 소스는 점점 엉망이 될것이고 짜투리 시간에 천천히 하는 거라 언제 끝날지 모릅니다. 1년이 될지 2년이 될지....

 

pygame 의 공식 홈페이지는 https://www.pygame.org 입니다.

 

반응형


이번 포스팅은 Python3 에서 랜덤값을 얻는 방법에 대해 간단히 정리해 봤습니다. 더 자세한 내용은 직접 파이썬 도큐먼트를 참고 하세요. ^^;


Python3 에서 랜덤 클래스를 사용하기 위하여....

import random 을 처음에 적어줍니다. 그리고 랜덤클래스는 인스턴스를 생성하지 않고 사용합니다.




1. random.seed([seed])

컴퓨터에서 만드는 랜덤값은 모두 계산에 의해 나오는 유사랜덤값입니다. 그러한 랜덤값을 좀더 랜덤하게 만들기 위해서 seed 값을 임의로 넣어줍니다. 만일 생략하거나 None 값인 경우 seed 값으로 현재 시간을 사용합니다.


2. random.random() 

0.0 <= x < 1.0 사이의 값을 출력합니다.


3. random.uniform(a.2, b.5)

a.2 <= x < b.5  사이의 값을 출력합니다.


4. random.randrange(b)

0 <= x < b 사이의 정수값을 출력합니다.


4. random.randrange(a,b)

a <= x < b  사이의 정수값을 출력합니다.


5. random.randint(a,b)

a <= x <= b  사이의 정수값을 출력합니다. 



위는 간단한 사용예 입니다. 뭐....별거 없습니다.


이상은 일반적인 랜덤값을 얻는 함수들이고요.....




파이썬에는 조금 재미있는 함수들이 있더군요.


6. random.choice(x)

x 에 list 변수등을 넣으면 리스트중에 하나의 값을 임의로 리턴해 줍니다.


7. random.shuffle(x)

x 에 list 변수등을 넣으면 내부 값들을 알아서 섞어 줍니다. 


8. random.sample(x,n)

x list 등에서 랜덤하게 n 개의 값을 리턴합니다.



위는 간단한 사용예 입니다. 나름 재미있게 사용할 수 있을것 같네요. 

+ Recent posts