Skip to content

Commit

Permalink
진짜 개많이함
Browse files Browse the repository at this point in the history
- 스프라이트 체계화 및 정리
- 다중 스프라이트 지원 (#7)
- 체력 깎이는 코드 추가
- 체력 깎일 때 플레이어 무적 시간 지원
- 사망 이벤트 추가
- Reload 기능 추가 (메뉴화면에서 Ctrl + R)
- ESC 화면 추가 (돌아가기, 설정, 전체화면, 음소거, 메뉴화면으로)
- 설정 추가 (해상도 변경, FPS 표시, 소리 조절)
- 메뉴 화면 UI 수정
- 애셋 (나가기 버튼, 전체화면 버튼, 창 버튼) 추가 및 변경
- SFX (피공격, 음소거 해제) 추가
- 플레이어 좌표 동기화 안되는 버그 수정
- 바운드 버그 수정
- 바운드 y좌표 지원
- 버튼 Optional Text 기능 및 Text Offset 기능 추가
- 코드 최적화 및 정리
  • Loading branch information
MineEric64 committed Aug 15, 2023
1 parent 937ec68 commit 5a40580
Show file tree
Hide file tree
Showing 23 changed files with 773 additions and 120 deletions.
Binary file added assets/audio/sfx_attacked.ogg
Binary file not shown.
Binary file added assets/audio/sfx_unmuted.ogg
Binary file not shown.
Binary file added assets/images/button_exit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/button_fullscreen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/button_settings_50px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/images/button_unchecked.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/button_window.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
39 changes: 26 additions & 13 deletions characters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import pygame
from abc import ABC

from components.config import CONFIG, CONST, debug

from components.text.text_collection import TextCollection
from components.events.text import TextEvent
from components.config import CONFIG, CONST, debug
from components.sprite import Sprite

from components.sprites.sprite_collection import SpriteCollection
from components.sprites.sprite_handler import SpriteHandler
from components.sprites.sprite import Sprite

class Character(ABC):
image: pygame.Surface | pygame.SurfaceType
image_path: str

sprite: Sprite
sprite_group: pygame.sprite.Group
sprites: SpriteCollection

x: int = 0
y: int = 0
width: int = 0
height: int = 0

velocity = 0.0

is_playable: bool = False
'''플레이어인가?'''

hp = 5
'''플레이어의 체력'''

velocity = 0.0
'''플레이어의 속도'''

sign = None
'''말풍선'''

def __init__(self, path: str, position: tuple, scale: float = 1.0, fit = False, is_playable = False):
"""
Expand Down Expand Up @@ -64,6 +73,10 @@ def get_pos(self) -> tuple:
def set_pos(self, x: int, y: int):
self.x = x
self.y = y

if self.is_playable:
CONFIG.player_x = x
CONFIG.player_y = y

def move(self, velocity: float):
"""
Expand All @@ -73,8 +86,11 @@ def move(self, velocity: float):
"""
pass

def is_bound(self, error = 0) -> bool:
return CONFIG.player_x >= self.x - error and CONFIG.player_x <= self.x + error
def is_bound(self, error_x = 0, error_y = 0) -> bool:
is_x = CONFIG.player_x >= self.x - error_x and CONFIG.player_x <= self.x + error_x
is_y = CONFIG.player_y >= self.y - error_y and CONFIG.player_y <= self.y + error_y

return is_x and is_y

def render(self, other_surface: pygame.Surface = None):
if other_surface is None:
Expand All @@ -83,11 +99,8 @@ def render(self, other_surface: pygame.Surface = None):
if self.image_path != '': # 정적 이미지인 경우
other_surface.blit(self.image, self.get_pos())
else: # 스프라이트인 경우
#debug(str(self.get_pos()[0]) + ', ' + str(self.get_pos()[1]))
self.sprite.position = self.get_pos()
self.sprite.rect = (self.sprite.position[0], self.sprite.position[1], self.sprite.size[0], self.sprite.size[1])

self.sprite_group.draw(other_surface)
self.sprites.set_pos(self.get_pos())
self.sprites.get_sprite_handler().group.draw(other_surface)

if self.sign is not None:
other_surface.blit(self.sign.image, self.sign.get_pos())
Expand Down
20 changes: 11 additions & 9 deletions characters/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
from components.config import CONST, CONFIG, debug
from components.text.text_collection import TextCollection
from components.events.text import TextEvent
from components.sprite import Sprite

from components.sprites.sprite_collection import SpriteCollection
from components.sprites.sprite_handler import SpriteHandler
from components.sprites.sprite import Sprite

class Player(Character):
def move(self, velocity: float):
# 윈도우 내부에 위치해 있는 경우
if (velocity > 0 and self.x <= 0) or (velocity < 0 and self.x >= CONST.SCREEN_SIZE[0]) or (0 <= self.x <= CONST.SCREEN_SIZE[0]):
if (velocity > 0 and self.x <= 0) or (velocity < 0 and self.x + self.width >= CONST.SCREEN_SIZE[0]) or (0 <= self.x <= CONST.SCREEN_SIZE[0]):
self.velocity = velocity

self.x += 10 * velocity
Expand All @@ -21,20 +24,19 @@ def move(self, velocity: float):


@classmethod
def get_from_sprite(cls, sprite: Sprite, is_playable = False) -> 'Player':
def get_from_sprite(cls, sprites: SpriteCollection, is_playable = False) -> 'Player':
"""
캐릭터 클래스 생성
:param sprite: 캐릭터 스프라이트
"""
chr = Player('', (0, 0))
chr.sprite = sprite
chr.sprite_group = pygame.sprite.Group(sprite)
chr.sprites = sprites

chr.x = sprite.position[0]
chr.y = sprite.position[1]
chr.width = sprite.size[0]
chr.height = sprite.size[1]
chr.x = sprites.position[0]
chr.y = sprites.position[1]
chr.width = sprites.size[0]
chr.height = sprites.size[1]

if is_playable:
CONFIG.player_width = chr.width
Expand Down
36 changes: 30 additions & 6 deletions components/button.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import pygame

class Button:
def __init__(self, image, pos, text_input, font, base_color, hovering_color):
image: pygame.Surface
x_pos: int
y_pos: int
font: pygame.font.Font
base_color: str
hovering_color: str
text_input: str
text: pygame.Surface = None

def __init__(self, image: pygame.Surface, pos: tuple, base_color: str = '', hovering_color: str = '', text_input = '', text_offset = (0, 0), font: pygame.font.Font = None):
self.image = image
self.x_pos = pos[0]
self.y_pos = pos[1]
self.font = font
self.base_color, self.hovering_color = base_color, hovering_color
self.text_input = text_input
self.text = self.font.render(self.text_input, False, self.base_color)

if self.font is not None:
self.text = self.font.render(self.text_input, False, self.base_color)

if self.image is None:
self.image = self.text

self.rect = self.image.get_rect(center=(self.x_pos, self.y_pos))
self.text_rect = self.text.get_rect(center=(self.x_pos, self.y_pos))

if self.text is not None:
self.text_rect = self.text.get_rect(center=(self.x_pos + text_offset[0], self.y_pos + text_offset[1]))

def update(self, screen):
if self.image is not None:
screen.blit(self.image, self.rect)
screen.blit(self.text, self.text_rect)
if self.text is not None:
screen.blit(self.text, self.text_rect)

def check_for_input(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top,
Expand All @@ -26,6 +44,12 @@ def check_for_input(self, position):
def change_color(self, position):
if position[0] in range(self.rect.left, self.rect.right) and position[1] in range(self.rect.top,
self.rect.bottom):
self.text = self.font.render(self.text_input, False, self.hovering_color)
if self.text is not None:
self.text = self.font.render(self.text_input, False, self.hovering_color)
else:
self.text = self.font.render(self.text_input, False, self.base_color)
if self.text is not None:
self.text = self.font.render(self.text_input, False, self.base_color)

def change_image(self, image: pygame.Surface):
self.image = image
self.rect = image.get_rect(center=(self.x_pos, self.y_pos))
21 changes: 17 additions & 4 deletions components/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import pygame.constants
from enum import Enum
from random import Random

import pygame.constants

class CONST:
SCREEN_SIZE = [640, 360]
Expand Down Expand Up @@ -40,7 +42,6 @@ class CONFIG:
'''크기가 [640, 360]으로 고정된 화면
surface에 렌더링하고 업스케일링 후 screen으로 화면 표시'''


screen = pygame.display.set_mode(window_size)
'''업스케일링된 실제로 플레이어에게 보여주는 화면'''

Expand All @@ -57,12 +58,21 @@ class CONFIG:
game_paused = False
'''게임이 일시중지되었는가?'''

game_dead = False
'''플레이어가 죽었는가?'''

game_fps = False
'''FPS를 표시하는가? (디버깅용)'''

player_x = 0
player_y = 0
player_width = 0
player_height = 0
'''플레이어 값 (동기화됨)'''

random: Random = Random(100)
'''랜덤'''

def update_screen():
"""
화면 업스케일링이 적용된 디스플레이 업데이트 기능
Expand All @@ -89,13 +99,16 @@ def get_mouse_pos() -> tuple[int, int]:

def is_interactive() -> bool:
"""플레이어와 상호작용 가능한가?"""
return CONFIG.game_started and not CONFIG.game_paused
return CONFIG.game_started and not CONFIG.game_paused and not CONFIG.game_dead


def is_movable() -> bool:
"""플레이어가 움직일 수 있는가?"""
from components.events.text import TextEvent
return CONFIG.is_interactive() and TextEvent.dialog_closed
return CONFIG.is_interactive() and TextEvent.dialog_closed and not CONFIG.game_dead

def resolution_to_str(size: tuple) -> str:
return str(size[0]) + 'x' + str(size[1])

def debug(debug: str):
"""
Expand Down
8 changes: 5 additions & 3 deletions components/events/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import ctypes
import pygame
from ..config import CONFIG

from ..config import CONFIG, CONST, debug

def update_screen_resolution():
"""
Expand All @@ -10,7 +12,6 @@ def update_screen_resolution():
else:
CONFIG.screen = pygame.display.set_mode(CONFIG.window_size)


def process(f=None):
"""
공통 이벤트 확인 및 처리
Expand All @@ -28,7 +29,8 @@ def process(f=None):
CONFIG.is_running = False

case pygame.K_ESCAPE: # ESC
CONFIG.is_running = False # TODO: 종료 화면 띄우기
# CONFIG.is_running = False # TODO: 종료 화면 띄우기
if f is not None: f(event)

case _:
if f is not None:
Expand Down
24 changes: 24 additions & 0 deletions components/events/grace_period.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from datetime import datetime

from components.config import debug

class GracePeriod:
"""무적 시간"""
period = 3000 # 무적시간: 3000ms

last_graced: datetime = datetime(2023, 1, 1, 12, 0, 0)

@classmethod
def is_grace_period(cls) -> bool:
"""
무적 시간인가?
"""
delta = datetime.now() - cls.last_graced
return delta.total_seconds() * 1000.0 < cls.period

@classmethod
def update(cls):
"""
무적 시간을 현재 시간으로 업데이트
"""
cls.last_graced = datetime.now()
41 changes: 39 additions & 2 deletions components/sfx_collection.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,46 @@
import pygame.mixer as mixer


class SFX(object):
INTRO: mixer.Sound
UNMUTED: mixer.Sound
ATTACKED: mixer.Sound

sounds: list[mixer.Sound]

muted = False
volume: float = 1.0

@classmethod
def init(cls):
cls.INTRO = mixer.Sound('assets/audio/sfx_intro.ogg')
cls.INTRO = mixer.Sound('assets/audio/sfx_intro.ogg')
cls.UNMUTED = mixer.Sound('assets/audio/sfx_unmuted.ogg')
cls.ATTACKED = mixer.Sound('assets/audio/sfx_attacked.ogg')

cls.sounds = [
cls.INTRO,
cls.UNMUTED,
cls.ATTACKED
]

@classmethod
def control_mute(cls):
cls.muted = not cls.muted
cls.volume = 0.0 if cls.muted else 1.0

cls.set_volume(cls.volume)

@classmethod
def set_volume(cls, volume: float):
volume_rounded = round(volume, 1) # 소수점 계산 문제 방지

if volume_rounded == 0.0:
cls.muted = True
else:
cls.muted = False

cls.volume = volume

mixer.music.set_volume(volume)

for sound in cls.sounds:
sound.set_volume(volume)
12 changes: 11 additions & 1 deletion components/sprite.py → components/sprites/sprite.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class Sprite(pygame.sprite.Sprite):

flipped = False

def __init__(self, path: str, columns: int, rows: int, position: tuple, size: tuple, start = (0, 0), scale: float = 1.0):
def __init__(self, path: str, columns: int, rows: int, size: tuple, start = (0, 0), position = (0, 0), scale: float = 1.0):

super(Sprite, self).__init__()

Expand Down Expand Up @@ -54,6 +54,16 @@ def flip(self):
self.flipped = not self.flipped


def set_pos(self, position: tuple):
self.position = position
self.rect = (position, self.size)


def set_alpha(self, alpha: int):
for image in self.images:
image.set_alpha(alpha)


def strip_from_sheet(self, sheet: pygame.Surface, start: tuple, size: tuple, columns: int, rows: int = 1) -> list[pygame.Surface]:
"""
주어진 시작 위치에서 각 프레임 분할
Expand Down
Loading

0 comments on commit 5a40580

Please sign in to comment.