본문 바로가기

Development/Tetris

Python - 테트리스(Tetris) 만들기 (14) - text input

이번시간에는 text를 input할 input rect를 만들어 보겠습니다.

 

web에서는 <input> tag를 사용하면 참 쉽게 만들 수 있습니다. 하지만 pygame은 저런 tag가 존재하지 않으니 직접 만들어 줘야 합니다. 한번 잘 만든다음에 다양한 곳에서 사용해 보도록 합시다.

 


Input Rect

input rect의 기능은 다음과 같이 정리됩니다.

1. active, deactive된다.

2. rect 영역을 클릭하면 active된다.

3. rect 외 영역을 클릭하면 deactive된다.

4. active 되었을 때 key입력에 따라 input rect에 있는 text가 update된다.

5. backspace key를 누르면 text가 한 칸씩 삭제된다.

 

물론 다양한 경우에 따라 이 규칙들은 조금씩 변화될 수 있습니다. 한번 구현해 보겠습니다.

 

class InputRect:
    TEXT_COLOR = (0, 0, 0)
    _ACTIVE_COLOR = (192, 64, 64)
    _DEACTIVE_COLOR = (192, 192, 192)
    ACTIVE_COLOR_MAP = {True: _ACTIVE_COLOR, False: _DEACTIVE_COLOR}
    RECT_LINE_WIDTH = 2

    def __init__(self, x, y, w, h, font: pg.font.Font, margin=0, text: str=''):
        self.rect = pg.Rect(x+margin, y+margin, w-2*margin, h-2*margin)
        self.font = font
        self.text = text
        self.active: bool = False
        self.color = self.ACTIVE_COLOR_MAP[self.active]
    
    def event_handler(self, event: pg.event.Event):
        if event.type == pg.MOUSEBUTTONDOWN and event.button == pg.BUTTON_LEFT:
            self.active = True if self.rect.collidepoint(event.pos) else False
            self.color = self.ACTIVE_COLOR_MAP[self.active]
        if event.type == pg.KEYDOWN and self.active:
            self.text = event.unicode

    def update(self, surface: pg.Surface):
        pg.draw.rect(surface, self.color, self.rect, self.RECT_LINE_WIDTH, 5)
        text_surface = self.font.render(self.text, True, self.TEXT_COLOR)
        text_rect = text_surface.get_rect()
        text_rect.center = self.rect.center
        surface.blit(text_surface, text_rect)

 

그리고 SubSettingDisplay에 InputRect를 구현해보겠습니다.

class SubSettingDisplay:
    BACKGROUND_COLOR = (255, 255, 255)
    TITLE_COLOR = (0, 0, 0)
    LABEL_COLOR = (127, 127, 127)
    VALUE_COLOR = (127, 127, 127)
    RECT_LINE = (192, 192, 192)
    SLIDER_LINE = (192, 192, 192)
    SLIDER_TOGGLE = (64, 64, 64)
    KEY_MAPPING = "Key Mapping"
    SYSTEM = "System"
    RIGHT = "Right"
    LEFT = "Left"
    CW_ROTATION = "Clockwise rotation"
    CCW_ROTATION = "Counterclockwise rotation"
    SOFT_DROP = "Soft drop"
    HARD_DROP = "Hard drop"
    HOLD = "Hold"
    SOUND_VOLUME = "Sound volume"
    PREVIEW_COUNT = "Preview count"
    GHOST_SYSTEM = "Ghost system"
    HOLD_SYSTEM = "Hold system"
    LOCK_DELAY = "Lock delay"
    GRAVITY = "Gravity"
    ARE = "ARE"
    DAS = "DAS"
    ARR = "ARR"
    SDF = "SDF"
    CONTENTS_H = 0.1
    KEY_MAPPING_LABEL_W = 0.3
    SYSTEM_LABEL_W = 0.7
    INPUT_W = 0.2
    INPUT_H = 0.1
    def __init__(self, width, height):
        self.width, self.height = width, height
        self.full_height = height * 1.2
        self.display_surface = None
        self.title_font = pg.font.SysFont("calibri", 28)
        self.contents_font = pg.font.SysFont("calibri", 16)
        self.input_font = pg.font.SysFont("calibri", 16)
        self.input_right_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_left_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 2, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_CW_rotation_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 3, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_CCW_rotation_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 4, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_soft_drop_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 5, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_hard_drop_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 6, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)
        self.input_hold_rect = InputRect(
            width * self.KEY_MAPPING_LABEL_W, 
            height * self.CONTENTS_H * 7, 
            width * self.INPUT_W, 
            height * self.INPUT_H, 
            self.input_font, 
            margin=height * self.INPUT_H * 0.2)

		### 이하 생략 ###
        
        

    def init(self):
        self.display_surface = pg.display.set_mode((self.width, self.full_height))

    def event_handler(self, event):
        self.input_right_rect.event_handler(event)
        self.input_left_rect.event_handler(event)
        self.input_CW_rotation_rect.event_handler(event)
        self.input_CCW_rotation_rect.event_handler(event)
        self.input_soft_drop_rect.event_handler(event)
        self.input_hard_drop_rect.event_handler(event)
        self.input_hold_rect.event_handler(event)

    def update(self):
        self.display_surface.fill(self.BACKGROUND_COLOR)
        self.display_surface.blit(self.text_keymapping_surface,        dest=(self.text_keymapping_x, self.text_keymapping_y))
        self.display_surface.blit(self.text_system_surface,            dest=(self.text_system_x, self.text_system_y))
        self.display_surface.blit(self.text_right_surface,             dest=(self.text_right_x, self.text_right_y))
        self.display_surface.blit(self.text_left_surface,              dest=(self.text_left_x, self.text_left_y))
        self.display_surface.blit(self.text_CW_surface,                dest=(self.text_CW_x, self.text_CW_y))
        self.display_surface.blit(self.text_CCW_surface,               dest=(self.text_CCW_x, self.text_CCW_y))
        self.display_surface.blit(self.text_soft_drop_surface,         dest=(self.text_soft_drop_x, self.text_soft_drop_y))
        self.display_surface.blit(self.text_hard_drop_surface,         dest=(self.text_hard_drop_x, self.text_hard_drop_y))
        self.display_surface.blit(self.text_hold_surface,              dest=(self.text_hold_x, self.text_hold_y))
        self.display_surface.blit(self.text_sound_volume_surface,      dest=(self.text_sound_volume_x, self.text_sound_volume_y))
        self.display_surface.blit(self.text_preview_count_surface,     dest=(self.text_preview_count_volume_x, self.text_preview_count_volume_y))
        self.display_surface.blit(self.text_ghost_system_surface,      dest=(self.text_ghost_system_x, self.text_ghost_system_y))
        self.display_surface.blit(self.text_hold_system_surface,       dest=(self.text_hold_system_x, self.text_hold_system_y))
        self.display_surface.blit(self.text_lock_delay_surface,        dest=(self.text_lock_delay_x, self.text_lock_delay_y))
        self.display_surface.blit(self.text_gravity_surface,           dest=(self.text_gravity_x, self.text_gravity_y))
        self.display_surface.blit(self.text_ARE_surface,               dest=(self.text_ARE_x, self.text_ARE_y))
        self.display_surface.blit(self.text_DAS_surface,               dest=(self.text_DAS_x, self.text_DAS_y))
        self.display_surface.blit(self.text_ARR_surface,               dest=(self.text_ARR_x, self.text_ARR_y))
        self.display_surface.blit(self.text_SDF_surface,               dest=(self.text_SDF_x, self.text_SDF_y))
        self.input_right_rect.update(self.display_surface)
        self.input_left_rect.update(self.display_surface)
        self.input_CW_rotation_rect.update(self.display_surface)
        self.input_CCW_rotation_rect.update(self.display_surface)
        self.input_soft_drop_rect.update(self.display_surface)
        self.input_hard_drop_rect.update(self.display_surface)
        self.input_hold_rect.update(self.display_surface)
        pg.display.flip()

 

그리고 Handler도 Main app에 추가해 줍시다.

 

class MainApp:
    def __init__(self, width: int, height: int):
        self.width, self.height = width, height
        pg.init()
        # self.display = MainDisplay(width, height)
        # self.display = SettingDisplay(width, height)
        self.display = SubSettingDisplay(width, height)
        self.system = MainSystem()
        self.setting_app = None
        self.tetris_app = None
        self._running = False

    def _init(self):
        self.display.init()
        self.system.init()
        self._running = True

    def _event(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self._running = False
            elif event.type == pg.KEYDOWN:
                print(event.key)
            self.display.event_handler(event)	## 추가된 부분

    def _quit(self):
        pg.quit()

    def execute(self):
        self._init()
        while self._running:
            self._event()
            self.display.update()
        self._quit()

 

그러면 다음과 같은 결과를 확인할 수 있습니다.

 

클릭하면 활성화 되고, keyboard입력 수행을 받아들여 표시한다.

 

하지만 아직 문제가 남아있습니다. Unicode만 표시하도록 했더니, ctrl, space, 화살표 key는 표시가 안됩니다. 이러한 것들은 pygame에서 key mapping에 따라 수동으로 표현하도록 합시다.

 

이전에 있었던 pygame key

 

이 정보를 활용해서 keymap dictionary를 만들었습니다.

KEYMAP_DICT = {
    pg.K_BACKSPACE: "Backspace",
    pg.K_TAB: "Tab",
    pg.K_CLEAR: "Clear",
    pg.K_RETURN: "Enter",
    pg.K_ESCAPE: "Esc",
    pg.K_SPACE: "Space bar",
    pg.K_DELETE: "Delete",
    pg.K_KP0: "keypad 0",
    pg.K_KP1: "keypad 1",
    pg.K_KP2: "keypad 2",
    pg.K_KP3: "keypad 3",
    pg.K_KP4: "keypad 4",
    pg.K_KP5: "keypad 5",
    pg.K_KP6: "keypad 6",
    pg.K_KP7: "keypad 7",
    pg.K_KP8: "keypad 8",
    pg.K_KP9: "keypad 9",
    pg.K_UP: "Up",
    pg.K_DOWN: "Down",
    pg.K_RIGHT: "Right",
    pg.K_LEFT: "Left",
    pg.K_RSHIFT: "R Shift",
    pg.K_LSHIFT: "L Shift",
    pg.K_RCTRL: "R Ctrl",
    pg.K_LCTRL: "L Ctrl",
    pg.K_RALT: "R Alt",
    pg.K_LALT: "L Alt",
}

 

Global하게 선언 후에 inputRect에서 사용할 수 있게 합니다.

class InputRect:
    TEXT_COLOR = (0, 0, 0)
    _ACTIVE_COLOR = (192, 64, 64)
    _DEACTIVE_COLOR = (192, 192, 192)
    ACTIVE_COLOR_MAP = {True: _ACTIVE_COLOR, False: _DEACTIVE_COLOR}
    RECT_LINE_WIDTH = 2

    def __init__(self, x, y, w, h, font: pg.font.Font, margin=0, text: str=''):
        self.rect = pg.Rect(x+margin, y+margin, w-2*margin, h-2*margin)
        self.font = font
        self.text = text
        self.active: bool = False
        self.color = self.ACTIVE_COLOR_MAP[self.active]
    
    def event_handler(self, event: pg.event.Event):
        if event.type == pg.MOUSEBUTTONDOWN and event.button == pg.BUTTON_LEFT:
            self.active = True if self.rect.collidepoint(event.pos) else False
            self.color = self.ACTIVE_COLOR_MAP[self.active]
        if event.type == pg.KEYDOWN and self.active:
        	## 변경한 부분
            self.text = KEYMAP_DICT[event.key] if event.key in KEYMAP_DICT.keys() else event.unicode

    def update(self, surface: pg.Surface):
        pg.draw.rect(surface, self.color, self.rect, self.RECT_LINE_WIDTH, 5)
        text_surface = self.font.render(self.text, True, self.TEXT_COLOR)
        text_rect = text_surface.get_rect()
        text_rect.center = self.rect.center
        surface.blit(text_surface, text_rect)

 

그러면 이제 모든 특수키를 받을 수는 없지만 어느정도는 key를 받을 수 있게 됩니다.

 

이제는 다양한 key에 대해서 움직일 수 있게 되었다.

 

 

다음시간에는 Slider를 만들어 보도록 하겠습니다.

 


* reference 

https://www.pygame.org/docs/ref/key.html#pygame.key.key_code