18 장. 새로운종류의입력 : 이벤트 IT 응용공학과 허석렬 syheo@pusan.ac.kr
이벤트 현실세계에서의 발생하는어떤일 과같이프로그램에서도 발생하는어떤일 마우스가움직이거나클릭됨 키가눌림 일정시간이경과함 지금까지의 ( 대부분의 ) 프로그램 ( 대표적으로 C) 은순차적실행 : 실행순서가어느정도예측가능 이벤트기반 (event-driven) 프로그램은이벤트가일어날때까지아무것도하지않고기다림 이벤트가발생하면해당이벤트를처리 (handle) 하는행동을취함
이벤트루프 이벤트기반프로그램에서이벤트가일어나는지확인하려면이벤트를계속확인하는과정이필요 이벤트확인은특정컴퓨터메모리를끊임없이검사함으로써컴퓨터 ( 내부또는외부 ) 에서이벤트가발생했음을확인할수있음 이벤트를끊임없이계속확인하는특별한반복문을이벤트루프 (event loop) 라고함 while 무한반복문을이용한이벤트루프
이벤트큐 컴퓨터에서발생하는이벤트 ( 마우스를움직이거나키를누르는것등 ) 는메모리의특정부분에저장 ( 기록 ) 기록됨 이벤트루프는메모리의특정부분을검사 이벤트가저장되는메모리의특정부분을이벤트큐 (event queue) 라고함 이벤트큐는발생한모든이벤트가발생한순서대로들어있는목록
이벤트처리자 (handler) 사용자가키를누르거나마우스를움직이는것과같은이벤트가발생하면이를처리하는부분이필요 프로그램에서이벤트를처리하는부분을작성 이벤트를처리하는프로그램의일부를이벤트처리자 (event handler) 라고함 모든이벤트가처리되지는않음 예 : 마우스를움직일때수백개의이벤트가발생 중요한이벤트에대해서만처리가필요 ( 예 : 마우스클릭 )
키보드이벤트 키보드의키를누를때마다이벤트가발생하게하려면 KEYDOWN 이벤트를사용 16 장 2D 예제 ( 아래예제프로그램다음에있음 ) 를방향키를눌렀을때아래위로움직이게수정 예제프로그램
전체프로그램코드 - 계속 프로그램설계및기법 move( ) 메서드가포함된 Ball 클래스
전체프로그램코드 - 계속 프로그램설계및기법 주실행코드 속도 위치 이벤트큐에서모든이벤트리스트를가져오는메서드 전체화면을다시그림
참조 : 16 장 2D 예제 프로그램설계및기법
키이벤트 화살표키를눌렀을때아래 / 위로움직이는이벤트처리자를추가 주실행코드의 pygame.event.get( ): 부분을수정 실행하면? 연속적인키입력이안됨
반복키 앞의예제는화살표키를계속누르고있어도한번만적용됨 프로그램에서계속키가눌러져있을때에대한대응이필요함 사용자가키를누르면 KEYDOWN 이벤트가하나만생성이되는데, pygame 에서는 KEYDOWN 이벤트를여러개만들어내는설정이있음 키반복 (key repeat) 을 while 반복문앞에설정
이벤트이름과키이름 이벤트와키는상당히많음 해당이벤트와키이름은파이게임에서확인가능 http://www.pygame.org/docs/ref/event.html http://www.pygame.org/docs/ref/key.html 자주사용하는이벤트 QUIT KEYDOWN KEYUP MOUSEMOTION MOUSEBUTTONUP MOUSEBUTTONDOWN
마우스이벤트 마우스이벤트를처리하는방법과마우스의위치정보를이용하는방법 마우스를움직일때마다비치볼이마우스를따라움직이게함 : ball 의 rect.center 속성을이용
마우스버튼이눌린상태에서공움직이기 마우스버튼이눌린상태에서만마우스를움직이는것을드래그 (dragging) 라고함 MOUSEDRAG 이벤트형식은없으므로기존의이벤트를이용해서원하는효과를만들어야함 마우스가드래그되는지를알려면마우스가눌린상태에서마우스가움직이는지를검사하면됨 MOUSEBUTTONDOWN: 눌린상태 MOUSEBUTTONUP: 놓은상태
마우스버튼이눌린상태에서공움직이기 - 코드
타이머이벤트 게임과시뮬레이션에유용한이벤트 타이머는알람시계처럼일정한간격으로이벤트를발생시킴 파이게임타이머는어떤주기로도설정이가능 타이머의시간이지나면이벤트루프에서감지할수있는이벤트가생성됨 사용자이벤트 (user event) 라는이벤트가생성됨 사용자정의 (user-defined) 이벤트 특정한목적으로따로확보해놓지않은이벤트 타이머도사용자정의이벤트의한종류임
파이게임에서타이머사용하기 : 예제 18.3 파이게임에서타이머설정 pygame.time.set_timer(event_number, interval) 변수는이벤트번호와타이머간격 ( 밀리초단위 ) EVENT_NUMBER 은사용중이아닌번호를사용해야함 >>> pygame.userevent 24 # 사용가능한이벤트번호인터랙티브모드 >>>pygame.numenevts 32 # 사용가능한이벤트최대번호 pygame.time.set_timer(24, 1000) 또는 pygame.time.set_timer(pygame.userevent, 1000) 프로그램모드
다른게임을해볼시간 : 파이퐁 (PyPong) Pong 게임을위해필요한것 튕기면서돌아다닐공 공과부딪힐패들 패들을조정하는방법 점수를기록하고창에해당점수를표시하는방법 생명 ( 남은기회에해당하는 ) 을관리하는방법
공 공에대한스프라이트를만들고그것의인스턴스를생성. myball = MyBallClass( wackyball.bmp, ball_speed, [50,50]) ballgroup = pygame.sprite.group(myball)
패들 패들을사각형으로생성하고검정색으로만듦 패들은이미지파일에서이미지를불러오지않고직접생성함. 대신 Surface.convert( ) 메서드를이용해표면을이미지로변환함 패들은좌우로만움직임. 마우스로패들을제어하기때문에 move( ) 메서드는필요하지않음
패들조종 MOUSEMOTION 이벤트를이용하여마우스로패들을조종 이벤트루프에서마우스패들조종 공과패들간의충돌감지 공과패들이충돌하면공의 y 좌표만반대방향으로수정
주실행코드 프로그램설계및기법
주실행코드 ( 계속 ) 프로그램설계및기법
점수기록및 pygame.font 를이용한점수표시 게임에서는두가지를관리해야함 생명의개수와점수 점수를표시할방법 파이게임에서는 font 라는모듈을이용 font 객체를만들고글꼴스타일과크기지정 글꼴객체에문자열을전달해텍스트를렌더링함. 그러면텍스트가그려진새로운표면 (surface) 이반환됨 이표면을화면표면으로블릿 ( 복사 ) 함
폰트사용하기 프로그램설계및기법 이벤트루프앞에다음코드삽입 이벤트루프안에는다음코드가필요 실행하면? NameError 발생
폰트사용하기 ( 계속 ) score = 0 초기화를 font 객체생성앞에삽입 이미공이창의위쪽끝에닿는경우를공 ( 객체 ) 의 move( ) 메서드안에서 ( 공을튕기기위해 ) 감지. 그곳에아래코드를추가함 score은지역변수? 전역변수?->move( ) 메서드에 global score, score_font, score_surf:
생명관리하기 플레이어에게기회를 3 번주기 : lives 변수에 3 lives = 3 아래코드를 while 문안에작성 공에는 myball.rect( ) 를 screen에는 get_rect( ) 를사용한이유 myball은스프라이트이며, 스프라이트에는 rect포함 screen은표면이며표면에는 rect가없음. get_rect( ) 를이용하면표면을감싸는 rect를구할수있음
생명카운터추가하기 플레이어에게생명을주는대부분의게임에는생명이얼마나남았는지를보여줌 손쉬운방법은남은생명의수만큼공의개수를보여주는것임. 다음코드를 while 문에삽입
게임종료 플레이어가남은생명을소진했을때 게임종료 메시지를보임 메시지와최종점수가포함된글꼴객체를몇개만들어렌더링한다음 screen 으로블릿 게임이끝난뒤에는더이상공이나타나지않도록해야함 게임이언제끝났는지를알려주는 done 변수를만듦
게임종료코드 프로그램설계및기법
최종파이퐁코드 프로그램설계및기법 # Listing_18-5.py # Copyright Warren & Carter Sande, 2013 # Released under MIT license http://www.opensource.org/licenses/mit-license.php # Version $version ---------------------------- # Final PyPong code import pygame, sys class MyBallClass(pygame.sprite.Sprite): def init (self, image_file, speed, location): pygame.sprite.sprite. init (self) #call Sprite initializer self.image = pygame.image.load(image_file) self.rect = self.image.get_rect() self.rect.left, self.rect.top = location self.speed = speed
파이퐁코드계속 프로그램설계및기법 def move(self): global score, score_surf, score_font self.rect = self.rect.move(self.speed) # bounce off the sides of the window if self.rect.left < 0 or self.rect.right > screen.get_width(): self.speed[0] = -self.speed[0] # bounce off the top of the window if self.rect.top <= 0 : self.speed[1] = -self.speed[1] score = score + 1 score_surf = score_font.render(str(score), 1, (0, 0, 0)) class MyPaddleClass(pygame.sprite.Sprite): def init (self, location): pygame.sprite.sprite. init (self) image_surface = pygame.surface.surface([100, 20]) image_surface.fill([0,0,0]) self.image = image_surface.convert() self.rect = self.image.get_rect() self.rect.left, self.rect.top = location
파이퐁코드계속 프로그램설계및기법 pygame.init() screen = pygame.display.set_mode([640,480]) clock = pygame.time.clock() myball = MyBallClass('wackyball.bmp', [10,5], [50, 50]) ballgroup = pygame.sprite.group(myball) paddle = MyPaddleClass([270, 400]) lives = 3 score = 0 score_font = pygame.font.font(none, 50) score_surf = score_font.render(str(score), 1, (0, 0, 0)) score_pos = [10, 10] done = False
파이퐁코드계속 프로그램설계및기법 running = True while running: clock.tick(30) screen.fill([255, 255, 255]) for event in pygame.event.get(): if event.type == pygame.quit: running = False elif event.type == pygame.mousemotion: paddle.rect.centerx = event.pos[0] if pygame.sprite.spritecollide(paddle, ballgroup, False): myball.speed[1] = -myball.speed[1] myball.move() if not done: screen.blit(myball.image, myball.rect) screen.blit(paddle.image, paddle.rect) screen.blit(score_surf, score_pos) for i in range (lives): width = screen.get_width() screen.blit(myball.image, [width - 40 * i, 20]) pygame.display.flip()
if myball.rect.top >= screen.get_rect().bottom: # lose a life if the ball hits the bottom lives = lives - 1 if lives == 0: final_text1 = "Game Over" final_text2 = "Your final score is: " + str(score) ft1_font = pygame.font.font(none, 70) ft1_surf = ft1_font.render(final_text1, 1, (0, 0, 0)) ft2_font = pygame.font.font(none, 50) ft2_surf = ft2_font.render(final_text2, 1, (0, 0, 0)) screen.blit(ft1_surf, [screen.get_width()/2 - \ ft1_surf.get_width()/2, 100]) screen.blit(ft2_surf, [screen.get_width()/2 - \ ft2_surf.get_width()/2, 200]) pygame.display.flip() done = True else: #wait 2 seconds, then start the next ball pygame.time.delay(2000) myball.rect.topleft = [50, 50] pygame.quit() 프로그램설계및기법