[PYGAME] Pirate Mario - 3

문승환·2022년 8월 16일
0

[PYGAME]

목록 보기
4/8
post-thumbnail

프로젝트 개요

  • pygame을 사용하여 Pirate Mario제작
  • Youtube Clear Code의 Platformer in Pygame을 따라하며 진행
  • 주요 과제: 플레이어의 움직임에 따른 애니메이션 구현

프로젝트 내용

main.py

import pygame
from sys import exit
from settings import *
from level import Level

pygame.init()

screen = pygame.display.set_mode((1200, 700))
clock = pygame.time.Clock()

level = Level(level_map, screen)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            exit()

    screen.fill('black')
    level.run()

    pygame.display.update()
    clock.tick(60)

settings.py

level_map = [
'                            ',
'                            ',
'                            ',
' XX    XXX            XX    ',
' XX P                       ',
' XXXX         XX         XX ',
' XXXX       XX              ',
' XX    X  XXXX    XX  XX    ',
'       X  XXXX    XX  XXX   ',
'    XXXX  XXXXXX  XX  XXXX  ',
'XXXXXXXX  XXXXXX  XX  XXXX  ']

tile_size = 64
screen_width = 1200
screen_height = len(level_map) * tile_size

tiles.py

import pygame

class Tile(pygame.sprite.Sprite):
    def __init__(self, size, pos):
        super().__init__()
        self.image = pygame.Surface((size, size))
        self.image.fill('grey')
        self.rect = self.image.get_rect(topleft = pos)

    def update(self, world_shift):
        # update for camera
        # move the entire map by the variable, world_shift
        self.rect.x += world_shift

player.py

import pygame
from support import import_folder

class Player(pygame.sprite.Sprite):
    def __init__(self, pos):
        super().__init__()
        self.import_character_assets()
        self.frame_index = 0
        self.animation_speed = 0.15
        self.image = self.animations['idle'][self.frame_index]
        self.rect = self.image.get_rect(topleft = pos)

        self.direction = pygame.math.Vector2(0, 0)      # a 2-dimensional vector
        self.player_speed = 8
        self.gravity = 0.8
        self.jump_speed = -16

        # default player status
        self.status = 'idle'
        self.facing_right = True
        self.on_ground = False
        self.on_ceiling = False
        self.on_left = False
        self.on_right = False


    def import_character_assets(self):

        # for different animations, different images
        character_path = 'graphics/character/'
        self.animations = {'idle': [], 'run': [], 'jump': [], 'fall': []}

        for animation in self.animations.keys():
            full_path = character_path + animation
            self.animations[animation] = import_folder(full_path)

    def animate(self):
        animation = self.animations[self.status]

        # loop over frame index
        self.frame_index += self.animation_speed
        if self.frame_index >= len(animation):
            self.frame_index = 0

        # if player moves to the right, player looks right
        # if player moves to the left, player looks left
        image = animation[int(self.frame_index)]
        if self.facing_right:
            self.image = image
        else:
            flipped_image = pygame.transform.flip(image, True, False)
            self.image = flipped_image

        # fix the position of the player by setting the rect of each case
        if self.on_ground and self.on_right:
            self.rect = self.image.get_rect(bottomright = self.rect.bottomright)
        elif self.on_ground and self.on_left:
            self.rect = self.image.get_rect(bottomleft = self.rect.bottomleft)
        elif self.on_ground:
            self.rect = self.image.get_rect(midbottom = self.rect.midbottom)
        elif self.on_ceiling and self.on_right :
            self.rect = self.image.get_rect(topright = self.rect.topright)
        elif self.on_ceiling and self.on_left:
            self.rect = self.image.get_rect(topleft = self.rect.topleft)
        elif self.on_ceiling:
            self.rect = self.image.get_rect(midtop = self.rect.midtop)

    def get_input(self):
        keys = pygame.key.get_pressed()

        if keys[pygame.K_RIGHT]:
            self.direction.x = 1
            self.facing_right = True
        elif keys[pygame.K_LEFT]:
            self.direction.x = -1
            self.facing_right = False
        else: self.direction.x = 0

        if keys[pygame.K_SPACE] and self.on_ground: self.jump() 	# only allow a jump

    def get_status(self):
        if self.direction.y < 0:
            self.status = 'jump'
        elif self.direction.y > 1:
            self.status = 'fall'
        else:
            if self.direction.x != 0:
                self.status = 'run'
            else:
                self.status = 'idle'

    def apply_gravity(self):
        self.direction.y += self.gravity
        self.rect.y += self.direction.y

    def jump(self):
        self.direction.y = self.jump_speed

    def update(self):
        self.get_input()
        self.get_status()
        self.animate()
  • animation에 대응하는 image를 보여주는 것을 구현
  • 좌우 이동시 그 방향을 바라보도록 설정
  • 부자연스러웠던 부분을 수정 (다중 점프, player의 image가 약간 벽, 바닥, 천장에서 떨어져있고 게임을 플레이하는 것에 있어 버그로 여겨지는 움직임)

support.py

from os import walk
import pygame

def import_folder(path):
    surface_list = []

    for _, __, img_files in walk(path):     # by using walk method, search image file
        for image in img_files:
            full_path = path + '/' + image
            image_surf = pygame.image.load(full_path).convert_alpha()
            surface_list.append(image_surf)

    return surface_list
  • os의 walk를 통해 하위 디렉토리를 검색하고 그 요소들을 사용해서 원하는 image를 가져오는 module

levels.py

import pygame
from settings import tile_size, screen_width
from tiles import Tile
from player import Player

class Level:
    def __init__(self, level_data, surface):
        super().__init__()
        self.display_surface = surface
        self.setup_level(level_data)
        self.world_shift = 0
        self.current_x = 0

    def setup_level(self, level_data):
        self.tiles = pygame.sprite.Group()              # a container class to hold and manage multiple sprite objects
                                                        # there are tiles
        self.player = pygame.sprite.GroupSingle()       # group container that holds a single sprite
                                                        # there is just one player

        for row_index, row in enumerate(level_data):
            for col_index, cell in enumerate(row):

                x = col_index * tile_size
                y = row_index * tile_size

                if cell == 'X':
                    tile = Tile(tile_size, (x, y))
                    self.tiles.add(tile)        # add sprites to this group

                elif cell == 'P':
                    player = Player((x, y))
                    self.player.add(player)     # add a single sprite to this group

    def scroll_x(self):
        player = self.player.sprite
        player_x = player.rect.centerx
        direction_x = player.direction.x

        if player_x < screen_width / 4 and direction_x < 0:
            # when player moves to the left
            # if player moves too far from the right
            # then camera moves to the left by moving the entire map to the right
            self.world_shift = 8
            player.player_speed = 0

        elif player_x > screen_width - (screen_width / 4) and direction_x > 0:
            # when player moves to the right
            # if player moves too far from the left
            # then camera moves to the right by moving the entire map to the left
            self.world_shift = -8
            player.player_speed = 0

        else:
            self.world_shift = 0
            player.player_speed = 8

    # check horizontal collision and vertical collision each other
    # to access to the rectangle of each of the tile
    # using rect.colliderect is better than sprite.collide_rect (more convenient)
    def horizontal_movement_collision(self):
        player = self.player.sprite
        player.rect.x += player.direction.x * player.player_speed

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.x < 0:
                    player.rect.left = sprite.rect.right
                    player.on_left = True
                    self.current_x = player.rect.left

                elif player.direction.x > 0:
                    player.rect.right = sprite.rect.left
                    player.on_right = True
                    self.current_x = player.rect.right

            # fix weird movements
            if player.on_left and (player.rect.left < self.current_x or player.direction.x >= 0):
                player.on_left = False
            if player.on_right and (player.rect.right > self.current_x or player.direction.x <= 0):
                player.on_right = False


    def vertical_movement_collision(self):
        player = self.player.sprite
        player.apply_gravity()

        for sprite in self.tiles.sprites():
            if sprite.rect.colliderect(player.rect):
                if player.direction.y > 0:
                    player.rect.bottom = sprite.rect.top
                    player.direction.y = 0
                    player.on_ground = True
                elif player.direction.y < 0:
                    player.rect.top = sprite.rect.bottom
                    player.direction.y = 0
                    player.on_ceiling = True

        # fix weird movements
        if player.on_ground and player.direction.y < 0 or player.direction.y > 1:
            player.on_ground = False
        if player.on_ceiling and player.direction.y > 0:
            player.on_ceiling = False

    def run(self):
        self.tiles.update(self.world_shift)
        self.tiles.draw(self.display_surface)
        self.scroll_x()

        self.player.update()
        self.horizontal_movement_collision()
        self.vertical_movement_collision()
        self.player.draw(self.display_surface)
  • player module에서 부자연스러웠던 부분을 수정하기 위해 player의 image뿐만 아니라 map에서도 player module의 정보를 import해서 적용하여 수정

KEEP

  • 없음

PROBLEM

  • player가 타일의 끝에 있을 경우 매우 가끔 y축 움직임에 버그가 있음
  • x축에 대한 움직에서 부자연스러움을 해소하는 방법을 이해하는 데에 다소 오래 걸림

TRY

  • 코드 복기
profile
아직 모자란 수학과생

0개의 댓글