프로젝트 개요
- pygame을 사용하여 Pirate Mario제작
- Youtube Clear Code의 Platformer in Pygame을 따라하며 진행
- 주요 과제: 월드맵을 구성하고 유저가 맵을 선택할 수 있는 것과 지형에서 떨어져 물에 닿으면 다시 월드맵으로 돌아가는 것을 구현
프로젝트 내용
game_data.py
level_0 = {
'terrain': '../levels/0/level_0_terrain.csv',
'coins': '../levels/0/level_0_coins.csv',
'fg palms': '../levels/0/level_0_fg_palms.csv',
'bg palms': '../levels/0/level_0_bg_palms.csv',
'crates': '../levels/0/level_0_crates.csv',
'enemies': '../levels/0/level_0_enemies.csv',
'constraints': '../levels/0/level_0_constraints.csv',
'player': '../levels/0/level_0_player.csv',
'grass': '../levels/0/level_0_grass.csv',
'node_pos': (110, 400),
'unlock': 1,
'node_graphics': '../graphics/overworld/0'
}
level_1 = {
'terrain': '../levels/1/level_1_terrain.csv',
'coins': '../levels/1/level_1_coins.csv',
'fg palms': '../levels/1/level_1_fg_palms.csv',
'bg palms': '../levels/1/level_1_bg_palms.csv',
'crates': '../levels/1/level_1_crates.csv',
'enemies': '../levels/1/level_1_enemies.csv',
'constraints': '../levels/1/level_1_constraints.csv',
'player': '../levels/1/level_1_player.csv',
'grass': '../levels/1/level_1_grass.csv',
'node_pos': (300, 220),
'unlock': 2,
'node_graphics': '../graphics/overworld/1'
}
level_2 = {
'terrain': '../levels/2/level_2_terrain.csv',
'coins': '../levels/2/level_2_coins.csv',
'fg palms': '../levels/2/level_2_fg_palms.csv',
'bg palms': '../levels/2/level_2_bg_palms.csv',
'crates': '../levels/2/level_2_crates.csv',
'enemies': '../levels/2/level_2_enemies.csv',
'constraints': '../levels/2/level_2_constraints.csv',
'player': '../levels/2/level_2_player.csv',
'grass': '../levels/2/level_2_grass.csv',
'node_pos': (480, 610),
'unlock': 3,
'node_graphics': '../graphics/overworld/2'
}
level_3 = {
'terrain': '../levels/2/level_2_terrain.csv',
'coins': '../levels/2/level_2_coins.csv',
'fg palms': '../levels/2/level_2_fg_palms.csv',
'bg palms': '../levels/2/level_2_bg_palms.csv',
'crates': '../levels/2/level_2_crates.csv',
'enemies': '../levels/2/level_2_enemies.csv',
'constraints': '../levels/2/level_2_constraints.csv',
'player': '../levels/2/level_2_player.csv',
'grass': '../levels/2/level_2_grass.csv',
'node_pos': (610, 350),
'unlock': 4,
'node_graphics': '../graphics/overworld/3'
}
level_4 = {
'terrain': '../levels/2/level_2_terrain.csv',
'coins': '../levels/2/level_2_coins.csv',
'fg palms': '../levels/2/level_2_fg_palms.csv',
'bg palms': '../levels/2/level_2_bg_palms.csv',
'crates': '../levels/2/level_2_crates.csv',
'enemies': '../levels/2/level_2_enemies.csv',
'constraints': '../levels/2/level_2_constraints.csv',
'player': '../levels/2/level_2_player.csv',
'grass': '../levels/2/level_2_grass.csv',
'node_pos': (880, 210),
'unlock': 5,
'node_graphics': '../graphics/overworld/4'
}
level_5 = {
'terrain': '../levels/2/level_2_terrain.csv',
'coins': '../levels/2/level_2_coins.csv',
'fg palms': '../levels/2/level_2_fg_palms.csv',
'bg palms': '../levels/2/level_2_bg_palms.csv',
'crates': '../levels/2/level_2_crates.csv',
'enemies': '../levels/2/level_2_enemies.csv',
'constraints': '../levels/2/level_2_constraints.csv',
'player': '../levels/2/level_2_player.csv',
'grass': '../levels/2/level_2_grass.csv',
'node_pos': (1050, 400),
'unlock': 5,
'node_graphics': '../graphics/overworld/5'
}
levels = {
0: level_0,
1: level_1,
2: level_2,
3: level_3,
4: level_4,
5: level_5
}
- 기존의 데이터에 월드맵 데이터를 추가해 맵과 레벨을 호출할 때 사용하도록 수정
overworld.py
import pygame
from game_data import levels
from support import import_folder
from decoration import Sky
class Node(pygame.sprite.Sprite):
def __init__(self, pos, status, icon_speed, path):
super().__init__()
self.frames = import_folder(path)
self.frame_index = 0
self.image = self.frames[self.frame_index]
if status == 'available':
self.status = 'available'
else:
self.status = 'locked'
self.rect = self.image.get_rect(center = pos)
self.detection_zone = pygame.Rect(self.rect.centerx - (icon_speed / 2), self.rect.centery - (icon_speed / 2), icon_speed, icon_speed)
def animate(self):
self.frame_index += 0.15
if self.frame_index >= len(self.frames): self.frame_index = 0
self.image = self.frames[int(self.frame_index)]
def update(self):
if self.status == 'available':
self.animate()
else:
tint_surf = self.image.copy()
tint_surf.fill('Black', None, pygame.BLEND_RGBA_MULT)
self.image.blit(tint_surf, (0, 0))
class Icon(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.pos = pos
self.image = pygame.image.load('../graphics/overworld/hat.png').convert_alpha()
self.rect = self.image.get_rect(center = pos)
def update(self):
self.rect.center = self.pos
class Overworld:
def __init__(self, start_level, max_level, surface, create_level):
self.diplay_surface = surface
self.max_level = max_level
self.current_level = start_level
self.create_level = create_level
self.moving = False
self.move_direction = pygame.math.Vector2(0, 0)
self.speed = 8
self.setup_nodes()
self.setup_icon()
self.sky = Sky(8, 'overworld')
def setup_nodes(self):
self.nodes = pygame.sprite.Group()
for index, node_data in enumerate(levels.values()):
if index <= self.max_level:
node_sprite = Node(node_data['node_pos'], 'available', self.speed, node_data['node_graphics'])
else:
node_sprite = Node(node_data['node_pos'], 'locked', self.speed, node_data['node_graphics'])
self.nodes.add(node_sprite)
def draw_path(self):
if self.max_level > 0:
points = [node['node_pos'] for index, node in enumerate(levels.values()) if index <= self.max_level]
pygame.draw.lines(self.diplay_surface, '#a04f45', False, points, 6)
def setup_icon(self):
self.icon = pygame.sprite.GroupSingle()
icon_sprite = Icon(self.nodes.sprites()[self.current_level].rect.center)
self.icon.add(icon_sprite)
def input(self):
keys = pygame.key.get_pressed()
if not self.moving:
if keys[pygame.K_RIGHT] and self.current_level < self.max_level:
self.move_direction = self.get_movement_data('next')
self.current_level += 1
self.moving = True
elif keys[pygame.K_LEFT] and self.current_level > 0:
self.move_direction = self.get_movement_data('previous')
self.current_level -= 1
self.moving = True
elif keys[pygame.K_SPACE]:
self.create_level(self.current_level)
def get_movement_data(self, target):
start = pygame.math.Vector2(self.nodes.sprites()[self.current_level].rect.center)
if target == 'next':
end = pygame.math.Vector2(self.nodes.sprites()[self.current_level + 1].rect.center)
else:
end = pygame.math.Vector2(self.nodes.sprites()[self.current_level - 1].rect.center)
return (end - start).normalize()
def update_icon_pos(self):
if self.moving and self.move_direction:
self.icon.sprite.pos += self.move_direction * self.speed
target_node = self.nodes.sprites()[self.current_level]
if target_node.detection_zone.collidepoint(self.icon.sprite.pos):
self.moving = False
self.move_direction = pygame.math.Vector2(0, 0)
def run(self):
self.input()
self.update_icon_pos()
self.icon.update()
self.nodes.update()
self.sky.draw(self.diplay_surface)
self.draw_path()
self.nodes.draw(self.diplay_surface)
self.icon.draw(self.diplay_surface)
- 월드맵을 실제로 구현하고 유저가 클리어한 레벨에 따라 각 노드를 이동할 수 있도록 구현
![](https://velog.velcdn.com/images/sh97818/post/02bf8a67-2fe7-4044-b803-b965018ae69d/image.gif)
KEEP
PROBLEM
- 부자연스러운 레벨 디자인
- 일부 지형에서의 높은 적회피 난이도
- 적의 움직임에서 간헐적인 버그 발생
TRY
- 모든 요소를 구현한 후 문제점들을 하나씩 수정
레벨 디자인 수정
게임 난이도 조정
버그 수정