.
1. 실습 준비
1번 강좌에서 받은 소스에서 NubcakeFarms.py를 지우고 새로운 py 파일을 생성한다.
그리고 코드 작성을 시작한다.
완성된 코드는 첨부 파일을 참고한다.
2. 풀밭 그리기
처음엔 일단 뭐라도 그려놓아야 시작한 느낌이 난다.
이미지를 pygame.image.load() 함수로 불러오고 바로 출력할 수도 있지만 이미지 파일 불러올 때 에러가 나는 경우도 있으니 로드하는 함수를 따로 만들자.
""" 이미지를 불러온 후 이미지 오브젝트를 리턴한다 """
if subdir != None:
fullname = os.path.join('images', subdir, name)
else:
fullname = os.path.join('images', name)
try:
image = pygame.image.load(fullname)
if image.get_alpha() is None:
image = image.convert()
else:
image = image.convert_alpha()
except pygame.error, message:
print 'Cannot load image:', fullname
raise SystemExit, message
return image
인자로 subdir과 name을 받아들인다.
우리가 받은 소스코드의 폴더 구조를 보면 images 아래에 이미지 종류에 따라 폴더가 나뉘어져 있다. subdir에는 해당 폴더명을 적어주고 name에는 이미지의 파일명을 적어준다. 여기서 os.path.join이 하는 역할은 이 이름을 연결하여 폴더명으로 만들어주는 것이다. 예를 들면
subdir = "grass"
name = "grass1.png"
이면
os.path.join('images', subdir, name) = "images\grass\grass1.png" 가 된다. 가 아니라 \인 이유는 파이썬에서 텍스트 변수를 구성할 때 특수 문자는 를 이용하여 만들기 때문이다. http://lapee79.blogspot.kr/2013/08/python-strings.html 를 참고하자.
try ~ except 구문이 보이는데 이는 일단 try 블럭의 코드를 실행해보고 에러가 나면 except 블럭을 실행하는 구문이다. 즉 이미지를 불러오고 나서 에러가 나면 예외 처리를 하는 것이다. 이미지를 pygame.image.load()함수로 불러오고 그 후 convert() 또는 convert_alpha() 함수를 호출하는 것을 볼 수 있는데 사실 없어도 상관없다. 하지만 이것을 해야 이미지 그리는 속도가 더 빨라지므로 게임에선 꼭 써야한다. 다음 레퍼런스를 참고하자.
http://www.pygame.org/docs/ref/image.html#pygame.image.load
http://www.pygame.org/docs/ref/surface.html#pygame.Surface.convert
get_alpha()함수는 불러온 이미지에 알파채널이 있는지 없는지 검사한다. 알파채널은 이미지의 투명도 관련 정보가 있는 채널이다. rgb 채널처럼 알파채널도 픽셀마다 정보가 있는데 값이 0이면 해당 픽셀의 rgb가 안보이는 것이고 255면 보인다. 그 사이의 값이면 적당히 투명하게 보인다. 포토샵에서 알파채널을 열면 값이 0인 부분은 검은색, 255인 부분은 흰색으로 보인다.
('게임 프로그래밍의 정석'에선 이 알파채널 이미지를 불러와 화면에 뿌려줄 때에도 알파 블렌딩의 혼합 공식을 이용하여 직접 구현한다. Pygame은 그런 함수가 있으니 얼마나 편리한가!)
자 그럼 이걸로 풀밭을 그려보자. main함수를 만들고 다음 부분을 추가하자. (그 전에 첨부한 파일을 참고하여 초기화 부분을 더 넣어주어야 한다)
bgimage1 = load_img("grass", "grass1.png")
bgimage2 = load_img("grass", "grass2.png")
bgimage3 = load_img("grass", "grass3.png")
background = pygame.Surface(screen.get_size())
background = background.convert()
for y in range(10):
for x in range(10):
exec('image = bgimage%d' % random.randint(1,3))
background.blit(image, (64*x, 64*y))
여기서 앞선 첫게임(Bunny) 만들기 강좌와 차이가 나는 부분은 background = pygame.Surface(screen.get_size()) 이다. 그 전엔 screen에 직접 이미지를 뿌려주었지만 이번엔 screen과 같은 크기의 background Surface를 만들어주고 여기에 이미지를 뿌린 후 background를 다시 screen에 뿌려준다. Surface는 포토샵의 레이어와 같다. 그래서 배경 Suface와 게임 오브젝트의 Surface를 만들어 따로따로 그림을 그리고 나중에 다시 스크린에다 합쳐주는 것이다.
exec()함수는 괄호안의 문장을 파이썬 코드로 변환하여 실행해준다. 즉, 위 코드는 배경을 랜덤으로 만들어준다. 배경이미지는 3개가 있으며 모두 64*64 픽셀 이미지이다. 그리고 for루프를 돌면서 (0,0), (0, 64), ... , (64,0), (64,64), ... 위치에 이미지를 그린다. 이렇게 하면 640, 640 픽셀의 스크린에 100개의 타일을 붙이는 셈이 되는 것이다.
물론 위 코드는 background Surface에 그린 것이므로 화면에 뿌리려면 다음 코드가 더 있어야 한다.
while 1:
# 현재 시간을 업데이트한다
current_time = time_in_ms()
# 입력 체크
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
# 게임 구역 업데이트
screen.blit(background, (0, 0))
# 모든 것을 렌더링한다 (화면에 출력)
pygame.display.flip()
3. HUD 그리기
먼저 클래스부터 보자.
"""
HUD == Head-up Display 클래스
"""
def __init__(self, img, x, y, name):
""" HUD 클래스 오브젝트 생성 후 x,y에 위치시키고 리스트에 추가한다 """
pygame.sprite.Sprite.__init__(self)
self.name = name
self.image = img
self.rect = self.image.get_rect()
self.rect.topleft = x, y
hudspritelist.append(self)
맨 처음 눈에 들어오는 것은 상속 부분이다. 이 클래스는 pygame.sprite.Sprite 클래스를 상속받는다. 그럼 먼저 pygame.sprite.Sprite 클래스의 레퍼런스부터 보자.
http://www.pygame.org/docs/ref/sprite.html#pygame.sprite.Sprite
뭔지 감이 오는가? 앞선 강좌에서 Sprite클래스는 게임 오브젝트를 표현하는 것이라고 했다. 얘만 상속받으면 이 클래스의 오브젝트들을 그룹으로 묶을 수 있고 얘네들끼리 충돌체크도 할 수 있다. 한꺼번에 그릴 수도 있다. 위 코드에서 맨 마지막 hudspritelist.append(self) 부분만 보자. 클래스 오브젝트를 초기화하면서 hudspritelist 리스트에 추가한다. 이걸 어디서 써먹는지 다음 코드를 보자.
global hud
hud = pygame.Surface((340, 112))
hud.convert()
hudimage = load_img("hud", "hud.png")
hud.blit(hudimage, (0, 0))
hud_seeds = HUD_Sprite(load_img('hud', 'wheat_seeds.png'), 17, 16, "Seeds")
hud_wheat = HUD_Sprite(load_img('hud', 'wheat.png'), 98, 16, "Wheat")
hud_watering_can = HUD_Sprite(load_img('hud', 'watering_can_small.png'), 179, 16, "Watering Can")
hud_coin = HUD_Sprite(load_img('hud', 'coin.png'), 260, 16, "Gold")
global allhudsprites
allhudsprites = pygame.sprite.RenderPlain(hudspritelist)
hud_seeds = .... 부분부터 4개의 HUD_Sprite 클래스의 오브젝트를 생성한다. 이 때 파라메터 중 좌표에 해당하는 부분(17, 16 등)은 2번째 줄에서 생성한 hud Surface 상의 좌표이다. 그리고 마지막 부분에서 hudspritelist 리스트에 추가된 4개의 오브젝트를 그룹으로 묶는다. ( pygame.sprite.RenderPlain() 함수는 pygame.sprite.Group() 함수와 동일하다. 왜 함수 이름만 다르고 동작은 동일한 함수를 더 추가한지 모르겠다)
screen.blit(hud, (150, 520))
hud.blit(hudimage, (0, 0))
allhudsprites.draw(hud)
while 문 안의 코드이다. 첫 줄에서 화면에 hud Surface를 뿌리고 그 다음 hud Surface에 hudimage를 0,0에 그린다. (hudimage는 4개의 네모가 그려진 hud의 배경이다) 마지막 코드가 중요하다. 아까 hud 오브젝트들을 묶은 allhudsprites 그룹을 한꺼번에 hud Surface에 그린다. hud Surface의 어디에 그려질지는 오브젝트 초기화할 때 좌표값을 넣어주어 결정하였다.
정리하자면 HUD는 다음과 같이 그려졌다.
1. pygame.sprite.Sprite를 상속받는 클래스를 정의한다.
2. 초기화할 때 그려질 이미지는 self.image 변수에, 이미지의 rect 변수는 self.rect 변수에, 그려질 위치는 self.topleft 변수에 정의한다.
3. 오브젝트를 리스트에 추가한다.
4. 리스트를 sprite 그룹으로 묶는다.
5. sprite 그룹을 한꺼번에 그린다.
이해가 되는가? 이해가 되지 않는다면 변수 값을 바꿔보고 일부 코드를 주석처리하거나 추가하면서 이것저것 테스트해보자. 클래스를 잘 모른다면 클래스도 공부해두자.