1. 준비
이번엔 펜스를 그려보자. 펜스는 농부가 이동할 수 있는 지역의 경계가 된다. 이 게임의 특징은 펜스나 상점 등의 위치 데이터를 파일에서 가져오는 것이다. 소스 디렉토리에 보면 data 폴더 아래에 level.txt 파일을 열어보자
FLLLLLLLLF
F F
F P F
F CF
F F
FW SF
FFFFFFFFFF
알파벳으로 이루어진 텍스트이다. 게임을 해봤다면 F가 뭔지 L은 뭔지 등을 짐작할 수 있을 것이다. F가 우리가 이 강좌에서 그릴 펜스이다. 파일을 열기 전에 펜스 클래스부터 정의하자.
2. Fence 클래스
def __init__(self, x, y):
self.fence_ = load_img('fence', 'fence_.png')
self.fence_u = load_img('fence', 'fence_u.png')
self.fence_d = load_img('fence', 'fence_d.png')
self.fence_l = load_img('fence', 'fence_l.png')
self.fence_r = load_img('fence', 'fence_r.png')
self.fence_ud = load_img('fence', 'fence_ud.png')
self.fence_ul = load_img('fence', 'fence_ul.png')
self.fence_ur = load_img('fence', 'fence_ur.png')
self.fence_dl = load_img('fence', 'fence_dl.png')
self.fence_dr = load_img('fence', 'fence_dr.png')
self.fence_lr = load_img('fence', 'fence_lr.png')
self.fence_udl = load_img('fence', 'fence_udl.png')
self.fence_udr = load_img('fence', 'fence_udr.png')
self.fence_ulr = load_img('fence', 'fence_ulr.png')
self.fence_dlr = load_img('fence', 'fence_dlr.png')
self.fence_udlr = load_img('fence', 'fence_udlr.png')
pygame.sprite.Sprite.__init__(self)
self.image = self.fence_
self.rect = self.image.get_rect()
self.rect.topleft = x, y
fences.append(self)
def assign_image(self):
imagename = "fence_"
if self.rect.move(0, -64).collidelistall(fences) != []: imagename = imagename + "u"
if self.rect.move(0, 64).collidelistall(fences) != []: imagename = imagename + "d"
if self.rect.move(-64, 0).collidelistall(fences) != []: imagename = imagename + "l"
if self.rect.move(64, 0).collidelistall(fences) != []: imagename = imagename + "r"
exec('self.image = self.%s' % imagename)
3번 강좌에서 본 HUD_Sprite 클래스보다 꽤 복잡하다. 사실 난 이 클래스에 불만이 하나 있다. 펜스를 생성할 때마다 생성자에서 이미지를 16개씩 불러오고 저장하기 때문이다. static 메쏘드나 global 메쏘드로 정의를 하는게 좋을 것 같다. 그리고 펜스 이미지가 16개씩 있는 이유는 이미지를 직접 열어보면 된다. 펜스 자신의 주변에 아무 펜스가 없는 경우, 오른쪽에 펜스가 있는 경우 아래쪽에 펜스가 있는 경우 등등을 다 고려해야 하기 때문이다. 이미지 이름에서 _ 뒤에 있는 문자의 의미는 u = up, d = down, l = left, r = right이다. 자신의 위쪽에 펜스가 있는 경우 u, 아래쪽에 있는 경우 d 등을 붙이는 것이다.
생성자에서는 fences 리스트에 자신을 추가한다. 그리고 기본으로는 주변에 펜스가 없는 경우의 이미지를 할당한다. assign_image() 함수는 주변에 펜스가 있는지 없는지를 체크하여 그에 해당하는 이미지를 다시 할당한다. 주변에 펜스가 있는지 없는지는 collidelistall 함수를 이용한다. 이는 파라메터의 리스트 안에 있는 오브젝트들과 충돌 여부를 조사하여 충돌하는 오브젝트들을 모은 리스트를 리턴한다. (collidelistall 함수의 파라메터는 사실 Rect 오브젝트의 리스트여야 하지만 Sprite 클래스의 오브젝트도 가능하다.) 펜스를 상하좌우로 움직여 충돌하는 펜스가 있다면 경우에 따라 이미지 이름의 끝에 u, d, l, r 를 붙인다. 그리고 그 이름의 이미지를 자신의 이미지로 할당한다.
3. 맵 데이터 불러오기
이제 맵 데이터가 들어가 있는 파일을 불러온다.
fencenum = 1
blockx = 0
blocky = 0
level = open('data/level.txt', 'r')
for line in level:
for char in line:
if char == 'F':
exec('fence%i = Fence(blockx, blocky)' % fencenum)
exec('scenerylist.append(fence%i)' % fencenum)
fencenum += 1
blockx += 64
blocky += 64
blockx = 0
for fence in fences:
fence.assign_image()
global allscenery
allscenery = pygame.sprite.RenderPlain(scenerylist)
open('data/level.txt', 'r') 문장은 data폴더의 level.txt 파일을 읽기모드로 연다. 그 결과 level에는 level.txt 파일 내용이 들어가 있다. for 문을 통해 각 라인에 있는 문자를 하나씩 하나씩 읽어 들인다. F라는 문자가 보이면 exec 함수를 이용하여 펜스 클래스의 오브젝트를 생성한다. 그리고 여기에 blockx, blocky 변수를 이용하여 펜스의 x,y 위치를 넣어준다. 두 번째 exec 문장은 개발자가 게임을 만들다 말았다는 것을 보여준다. scenerylist에 펜스뿐만 아니라 다른 장애물도 넣으려 한 것으로 보인다. 하지만 장애물은 펜스밖에 없다. 펜스 클래스에서 초기화할 때 자신을 펜스 리스트에도 넣어주므로 사실은 필요 없는 문장.
또한 읽은 문자가 F가 아니더라도 blockx 즉 오브젝트의 위치는 64씩 증가한다. F가 아닌 문자에 대해서는 아무 처리도 안했으므로 오브젝트가 생성되지 않은 상태로 blockx 가 증가한다. 공백도 문자임에 유의하자.
펜스를 다 읽은 후 assign_image 함수로 적절한 이미지를 할당하는 것도 확인하자.
그 외 부분은 이전 강의에서 언급한 내용이므로 충분히 이해할 수 있을 것이다. 여기서 펜스를 화면에 그리는 부분은 설명하지 않았는데 어디에 어떻게 펜스 그리는 코드를 넣어야할지 생각해보자. 첨부한 코드에 답이 있다.
덧 : 이 코드에는 에러 처리하는 부분이 없다. 이 게임은 640x640 해상도의 게임이고 하나의 타일은 64x64픽셀이므로 맵데이터가 10x10개의 텍스트가 아니면 화면을 다 못채우거나 화면 밖으로 그려질 수도 있다. 다른 사람들에게 게임을 공개하려면 이런 경우의 에러 처리가 있어야 한다.
게임 만들다가 만건가여?