[PYGAME] Pirate Mario - 4

문승환·2022년 8월 16일
0

[PYGAME]

목록 보기
5/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)

setting.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, surface, create_jump_particles):
        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.import_dust_run_particles()
        self.dust_frame_index = 0
        self.dust_animation_speed = 0.15
        self.display_surface = surface
        self.create_jump_particles = create_jump_particles

        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 import_dust_run_particles(self):
        self.dust_run_particles = import_folder('graphics/character/dust_particles/run')

    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 run_dust_animation(self):

        # if player runs, then dusty
        if self.status == 'run' and self.on_ground:
            self.dust_frame_index += self.dust_animation_speed
            if self.dust_frame_index >= len(self.dust_run_particles):
                self.dust_frame_index = 0

            dust_particle = self.dust_run_particles[int(self.dust_frame_index)]

            # different dust animations for either right or left
            if self.facing_right:
                pos = self.rect.bottomleft - pygame.math.Vector2(6, 10)
                self.display_surface.blit(dust_particle, pos)
            else:
                pos = self.rect.bottomright - pygame.math.Vector2(6, 10)
                flipped_dust_particle = pygame.transform.flip(dust_particle, True, False)
                self.display_surface.blit(flipped_dust_particle, pos)

    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:     # only allow a jump
            self.jump()
            self.create_jump_particles(self.rect.midbottom)

    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()
        self.run_dust_animation()
  • 새로 추가된 내용
  1. import_dust_run_particles: dust imageimport해서 저장하기위해 추가
  2. run_dust_animation: 좌우 방향에 대해 dust가 생기는 방향을 지정하기위해 추가
  3. get_input: 점프 후 착지했을 때 level module에서의 먼지의 위치플레이어의 중앙 아래에 위치하도록 설정

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

particle.py

import pygame
from support import import_folder

class ParticleEffect(pygame.sprite.Sprite):
    def __init__(self, pos, type):
        super().__init__()
        self.frame_index = 0
        self.animation_speed = 0.5

        if type == 'jump':
            self.frames = import_folder('graphics/character/dust_particles/jump')
        if type == 'land':
            self.frames = import_folder('graphics/character/dust_particles/land')

        self.image = self.frames[self.frame_index]
        self.rect = self.image.get_rect(center = pos)

    def animate(self):
        self.frame_index += self.animation_speed
        if self.frame_index >= len(self.frames):
            self.kill()
        else:
            self.image = self.frames[int(self.frame_index)]

    def update(self, x_shift):
        self.animate()
        self.rect.x += x_shift
  • player의 좌우 이동시 생기는 먼지와는 별개로 점프와 착지 시 생기는 dust effectcollision을 check하고 맵에서 출력되고 사라지도록 해야하기 때문에 따로 모듈을 생성하고 level.py에서 사용

level.py

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

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

        self.dust_sprite = pygame.sprite.GroupSingle()
        self.player_on_ground = False

    def create_jump_particles(self, pos):
        if self.player.sprite.facing_right:
            pos -= pygame.math.Vector2(10, 5)
        else:
            pos += pygame.math.Vector2(10, -5)
        jump_particle_sprite = ParticleEffect(pos, 'jump')
        self.dust_sprite.add(jump_particle_sprite)

    def get_player_on_ground(self):
        if self.player.sprite.on_ground:
            self.player_on_ground = True
        else:
            self.player_on_ground = False

    def create_landing_dust(self):
        if not self.player_on_ground and self.player.sprite.on_ground and not self.dust_sprite.sprites():
            if self.player.sprite.facing_right:
                offset = pygame.math.Vector2(10, 15)
            else:
                offset = pygame.math.Vector2(-10, 15)
            fall_dust_particle = ParticleEffect(self.player.sprite.rect.midbottom - offset, 'land')
            self.dust_sprite.add(fall_dust_particle)

    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.display_surface, self.create_jump_particles)
                    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.dust_sprite.update(self.world_shift)
        self.dust_sprite.draw(self.display_surface)

        self.tiles.update(self.world_shift)
        self.tiles.draw(self.display_surface)
        self.scroll_x()

        self.player.update()
        self.horizontal_movement_collision()
        self.get_player_on_ground()
        self.vertical_movement_collision()
        self.create_landing_dust()
        self.player.draw(self.display_surface)

  • 새로 추가된 내용
  1. create_jump_particles: dust imageimport해서 저장하기위해 추가
  2. get_player_on_ground: player가 바닥에 있는지 판별하고 self.player_on_ground에 반환된 값 저장
  3. create_landing_dust: dust imageimport해서 저장하기위해 추가

KEEP

  • 없음

PROBLEM

  • dust animation을 구현하기 위해 사용될 클래스를 선택하고 활용하는 데에 어려움을 많이 겪음 (특히, jump와 land에서의 dust를 구현하는 것이 어려웠음)

TRY

  • 또 다시 클래스 복습
  • 코드 복기
profile
아직 모자란 수학과생

0개의 댓글