CH 4. 여러 객체 관리
객체 관리자
- 같은 클래스로 만든 여러 객체 관리 가능
- 객체 관리자 객체: 리스트나 딕셔너리로 객체를 관리하고 각 객체의 매서드 호출하는 객체
컴포지션 / 객체 컴포지션: 한 객체가 하나 이상의 다른 객체를 관리하는 논리적인 구조
→ 은행이 해야 할 일에 관한 모든 코드를 Bank 클래스로 → Main에서 Bank 불러오기
예외 처리
- try / exept를 통한 처리
- raise문 / 사용자가 정의한 예외
CH 5. 파이게임 시작하기
GUI
- 그래픽 사용자 인터페이스
- 명령을 입력하거나 텍스트 기반의 입력을 하는 것이 아니라, 사용자가 아이콘, 메뉴, 버튼, 창 등의 그래픽 요소를 사용하여 컴퓨터, 스마트폰, 기타 디지털 기기 등 전자 기기와 상호 작용할 수 있도록 하는 시각적 인터페이스
파이게임 (Pygame)
- GUI에 공통 기능을 추가하는 외부 패키지
- 윈도우에 객체를 시각적으로 출력 가능 → 윈도우 출력, 이미지 출력, 마우스 클릭 등등
- 객체들 간 상호 작용을 명확하게 시각적으로 보여주기 위함!
GUI 사용 프로그램에 개별 픽셀이 표현되는 방식
- 윈도우의 개별 픽셀 표현 방법 (파이썬뿐만 아닌 모든 컴퓨터 프로그래밍 언어에 공통적으로 적용)
- 각 (x,y) 쌍은 윈도우의 특정 픽셀 위치 나타냄
- 파이썬 튜플을 통해 좌표 표현 가능
- 이미지의 모든 픽셀을 둘러싸는 경계 사각형을 다루는 경우가 많음
- 픽셀 색상 표현 방식: RGB → 3가지의 값을 튜플로 표현 가능
- 파이게임에서는 윈도우 배경 색을 채우거나 색이 채워진 도형을 그리거나 특정 색상 텍스트 그리는 등 작업에서 색 지정 가능
- 색상 표현 예시
이벤트 기반 프로그램
- while 루프로 입력을 받지 않음
- input과 print 대신 마우스 클릭, 키보드 조작 같은 “이벤트” 기반
- 이벤트: 프로그램이 실행 중일 때 발생하는 사건. 프로그램이 반드시 대응하거나 선택적으로 대응할 수 있음. 대부분의 이벤트는 사용자 행동 때문에 발생→ 무한 반복 속에서 계속 실행. 메인 루프가 매우 빠르게 반복되는 중에 버튼의 클릭 여부를 반복적으로 확인하는 것! → 이벤트 감지에 새로운 이미지 반환
- 전체적인 프로그램 실행 순서 기억
- 환경 세팅 (상수 정의, 환경 초기화, 이미지 등 활용할 애셋 불러오기, 변수 초기화)
- 무한 루프 수행 (이벤트 발생 여부 확인, 닫힘 시 종료 옵션, 프레임당 처리할 일 정의, 윈도우 내 객체 삭제 후 그리기, 윈도우 갱신, 갱신 주기 늦추기
- 전체적인 프로그램 실행 순서 기억
🖥️ 파이 게임 사용 예시 : 공 튀기기
키보드로 제어하려면?
#사용자의 키보드 클릭 여부 검사하기
#See if the user pressed a key
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
ballX = ballX - N_PIXELS_TO_MOVE
elif event.key == pygame.K_RIGHT:
ballX = ballX + N_PIXELS_TO_MOVE
elif event.key == pygame.K_UP:
ballY = ballY - N_PIXELS_TO_MOVE
elif event.key == pygame.K_DOWN:
ballY = ballY + N_PIXELS_TO_MOVE
# 지속적으로 눌린 키
# 7 - Check for and handle events
for event in pygame.event.get():
# Clicked the close button? Quit pygame and end the program
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
그 밖에 소리 재생, 도형 그리기 등등 가능 !
→ 기본 템플릿 (단계)를 이해하고 다양한 응용
🖥️ Rect 접근법으로 코드 재구성
- 공의 x,y 좌표를 계속 추적하지 않고 공의 경계 표시 가능
- 간단하게 사각형을 만드는 클래스. 사각형 자체를 그리거나 사각형에 해당하는 범위를 지정하여 해당 부분만 화면을 갱신할 때 사용
## 앞 세팅은 동일
ballImage = pygame.image.load('images/ball.png')
# 5 - Initialize variables
ballRect = ballImage**.get_rect()** #공의 모든 정보를 rect 객체로 관리
MAX_WIDTH = WINDOW_WIDTH - ballRect.width
MAX_HEIGHT = WINDOW_HEIGHT - ballRect.height
ballRect.left = random.randrange(MAX_WIDTH)
ballRect.top = random.randrange(MAX_HEIGHT)
xSpeed = N_PIXELS_PER_FRAME
ySpeed = N_PIXELS_PER_FRAME
#이미지를 불러온 후 속성 값을 얻는 방식: 어떤 크기의 이미지에서도 사용 가능
# 6 - Loop forever
while True:
# 7 - Check for and handle events
for event in pygame.event.get():
# Clicked the close button? Quit pygame and end the program
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 8 - Do any "per frame" actions: 좌우 경계선 벗어났는지 검사
if (ballRect.left < 0) or (ballRect.right >= WINDOW_WIDTH):
xSpeed = -xSpeed # reverse X direction
if (ballRect.top < 0) or (ballRect.bottom >= WINDOW_HEIGHT):
ySpeed = -ySpeed # reverse Y direction
# Update the ball's rectangle using the speed in two directions
ballRect.left = ballRect.left + xSpeed
ballRect.top = ballRect.top + ySpeed
# 9 - Clear the window before drawing it again
window.fill(BLACK)
# 10 - Draw the window elements
window.blit(ballImage, ballRect)
# 11 - Update the window
pygame.display.update()
# 12 - Slow things down a bit
clock.tick(FRAMES_PER_SECOND) # make pygame wait
CH 6. 객체지향 파이게임
→ 파이게임으로 객체지향 효과적으로 활용하기
→ 실행 순서에 따라 절차적으로 작성된 프로그램이 아닌 클래스로 코드 작성
📌 1. 단일 클래스 / 클래스 호출하는 메인 코드로 분리 2. 사용자 인터페이스 구현: SimpleButton, SimpleText 3. Callback
🖥️ OOP 기반 코드: 공튀기기 예시
→ 공 관련 코드를 포한하는 Ball 클래스 / 이를 호출하여 활용하는 메인 클래스
- Ball Class
- 환경 세팅 메서드 (이미지 불러오고 변수 초기화 등등)
- x,y 방향의 속도에 따라 프레임마다 위치 바꾸는 update
- 윈도우에 그리는 draw
import pygame from pygame.locals import * import random # Ball class class Ball(): def __init__(self, window, windowWidth, windowHeight): self.window = window # remember the window, so we can draw later self.windowWidth = windowWidth self.windowHeight = windowHeight self.image = pygame.image.load('images/ball.png') # A rect is made up of [x, y, width, height] ballRect = self.image.get_rect() self.width = ballRect.width self.height = ballRect.height self.maxWidth = windowWidth - self.width self.maxHeight = windowHeight - self.height # Pick a random starting position self.x = random.randrange(0, self.maxWidth) self.y = random.randrange(0, self.maxHeight) # Choose a random speed between -4 and 4, but not zero # in both the x and y directions speedsList = [-4, -3, -2, -1, 1, 2, 3, 4] self.xSpeed = random.choice(speedsList) self.ySpeed = random.choice(speedsList) def update(self): # Check for hitting a wall. If so, change that direction. if (self.x < 0) or (self.x >= self.maxWidth): self.xSpeed = -self.xSpeed if (self.y < 0) or (self.y >= self.maxHeight): self.ySpeed = -self.ySpeed # Update the Ball's x and y, using the speed in two directions self.x = self.x + self.xSpeed self.y = self.y + self.ySpeed def draw(self): self.window.blit(self.image, (self.x, self.y))
🖥️ 여러 Ball 객체 생성하는 코드로 변경
# pygame demo 6(b) - using the Ball class, bounce many balls
# 1 - Import packages
import pygame
from pygame.locals import *
import sys
import random
from Ball import * # bring in the Ball class code
# 2 - Define constants
BLACK = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30
**N_BALLS = 3**
# 3 - Initialize the world
pygame.init()
window = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()
# 4 - Load assets: image(s), sounds, etc.
# 5 - Initialize variables
**ballList = []
for oBall in range(0, N_BALLS):
# Each time through the loop, create a Ball object
oBall = Ball(window, WINDOW_WIDTH, WINDOW_HEIGHT)
ballList.append(oBall) # append the new Ball to the list of Balls
## 해당 부분을 통해 여러개의 Ball 객체 생성 가능 : OOP 프로그래밍의 큰 장점**
# 6 - Loop forever
while True:
# 7 - Check for and handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# 8 - Do any "per frame" actions
for oBall in ballList:
oBall.update() # tell each Ball to update itself
# 9 - Clear the window before drawing it again
window.fill(BLACK)
**# 10 - Draw the window elements
for oBall in ballList:
oBall.draw() # tell each Ball to draw itself**
# 11 - Update the window
pygame.display.update()
# 12 - Slow things down a bit
clock.tick(FRAMES_PER_SECOND) # make pygame wait
인터페이스 VS 구현
→ 8장에서 자세히
- 인터페이스 : 무언가의 사용 방법 → 클래스가 제공하는 메서드와 각 메서드 호출 시 필요한 매개 변수 (API)
- 구현: 무언의 내부적 작동 방법 → 클래스의 각 메서드를 구현한 실제 코드
재사용 가능한 버튼
- 버튼: 보통(눌리지 않은 상태) / 버튼 눌렸을 때
- 위와 마찬가지로 버튼 관련 클래스 / 실행하는 메인 클래스
- 버튼 클래스 전체적인 실행 느낌 (복잡한 버튼은 7장에서)
- 버튼 누르고 떼는 이미지 불러오기 → 버튼 상태 추적에 필요한 인스턴스 변수 초기화 __init()__
- 메인에서 발생하는 모든 이벤트 버튼에 알리고 반응할 행동이 있는지 검사 handleEvent()
- 현재 버튼 상태 표현한 이미지 그리기 draw()
# SimpleButton class
#
# Uses a "state machine" approach
#
import pygame
from pygame.locals import *
class SimpleButton():
# Used to track the state of the button
STATE_IDLE = 'idle' # button is up, mouse not over button
STATE_ARMED = 'armed' # button is down, mouse over button
STATE_DISARMED = 'disarmed' # clicked down on button, rolled off
def __init__(self, window, loc, up, down):
self.window = window
self.loc = loc
self.surfaceUp = pygame.image.load(up)
self.surfaceDown = pygame.image.load(down)
# Get the rect of the button (used to see if the mouse is over the button)
self.rect = self.surfaceUp.get_rect()
self.rect[0] = loc[0]
self.rect[1] = loc[1]
self.state = SimpleButton.STATE_IDLE
def handleEvent(self, eventObj):
# This method will return True if user clicks the button.
# Normally returns False.
if eventObj.type not in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN):
# The button only cares about mouse-related events
return False
eventPointInButtonRect = self.rect.collidepoint(eventObj.pos)
if self.state == SimpleButton.STATE_IDLE:
if (eventObj.type == MOUSEBUTTONDOWN) and eventPointInButtonRect:
self.state = SimpleButton.STATE_ARMED
elif self.state == SimpleButton.STATE_ARMED:
if (eventObj.type == MOUSEBUTTONUP) and eventPointInButtonRect:
self.state = SimpleButton.STATE_IDLE
return True # clicked!
if (eventObj.type == MOUSEMOTION) and (not eventPointInButtonRect):
self.state = SimpleButton.STATE_DISARMED
elif self.state == SimpleButton.STATE_DISARMED:
if eventPointInButtonRect:
self.state = SimpleButton.STATE_ARMED
elif eventObj.type == MOUSEBUTTONUP:
self.state = SimpleButton.STATE_IDLE
return False
def draw(self):
# Draw the button's current appearance to the window
if self.state == SimpleButton.STATE_ARMED:
self.window.blit(self.surfaceDown, self.loc)
else: # IDLE or DISARMED
self.window.blit(self.surfaceUp, self.loc)
- Main: CH5의 파이게임 템플릿
재사용 가능한 텍스트 출력용 클래스
- 위치, 글자 폰트와 크기 등
# SimpleText class
import pygame
from pygame.locals import *
class SimpleText():
def __init__(self, window, loc, value, textColor):
pygame.font.init()
self.window = window
self.loc = loc
**self.font = pygame.font.SysFont(None, 30)**
self.textColor = textColor
self.text = None # so that the call to setText below will force the creation of the text image
self.setValue(value) # set the initial text for drawing
def setValue(self, newText):
if self.text == newText:
return # nothing to change
self.text = newText # save the new text
**self.textSurface = self.font.render(self.text, True, self.textColor)**
**#텍스트를 그래픽 이미지로 표현 (렌더링)**
def draw(self):
**self.window.blit(self.textSurface,** self.loc)
⇒ 이를 활용하여 공 튀기기 프로그램에 반영
##앞 코드 동일
# 5 - Initialize variables
oBall = Ball(window, WINDOW_WIDTH, WINDOW_HEIGHT)
**oFrameCountLabel = SimpleText(window, (60, 20),
'Program has run through this many loops: ', WHITE)
oFrameCountDisplay = SimpleText(window, (500, 20), '', WHITE)**
oRestartButton = SimpleButton(window, (280, 60),
'images/restartUp.png', 'images/restartDown.png')
frameCounter = 0
# 6 - Loop forever
while True:
# 7 - Check for and handle events
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if oRestartButton.handleEvent(event):
frameCounter = 0 # clicked button, reset counter
# 8 - Do any "per frame" actions
oBall.update() # tell the ball to update itself
**frameCounter = frameCounter + 1 # increment each frame
oFrameCountDisplay.setValue(str(frameCounter))**
# 9 - Clear the window before drawing it again
window.fill(BLACK)
# 10 - Draw the window elements
oBall.draw() # tell the ball to draw itself
oFrameCountLabel.draw()
**oFrameCountDisplay.draw()**
oRestartButton.draw()
# 11 - Update the window
pygame.display.update()
# 12 - Slow things down a bit
clock.tick(FRAMES_PER_SECOND) # make pygame wait
Callback
- 특정 행동, 이벤트, 조건 발생에 따라 호출되는 객체의 함수 또는 메서드
- 함수나 객체 메서드를 기억했다가 필요한 순간에 호출
- 특정 작업이 완료되거나 이벤트가 발생했을 때 다시 불려지는 대상
300x250