2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【pygame】よくある画面に出すキーボードを作ってみた!

Posted at

image.png

はじめに

 今回はタイトルの通り、タイピングゲームにありがちな、キーボードを表示する部分を作ってみました。

コード

コード全体
import pygame, sys
import random

pygame.init()
screen = pygame.display.set_mode((512, 512))
clock = pygame.time.Clock()
font = pygame.font.SysFont('MS Gothic', 32)

class KeyBoard(pygame.Surface):
    char_list = [
        ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', '¥', 'back'],
        ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '@', '['],
        ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ':', ']'],
        ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\\']
    ]

    shift_char_list = [
        ['!', '"', '#', '$', '%', '&', "'", '(', ')', '0', '=', '~', '|', 'back'],
        ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '`', '{'],
        ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '+', '*', '}'],
        ['Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', '_',]
    ]
    key_num_list = [
        [49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 45, 94, 92, 8],
        [113, 119, 101, 114, 116, 121, 117, 105, 111, 112, 64, 91],
        [97, 115, 100, 102, 103, 104, 106, 107, 108, 59, 58, 93],
        [122, 120, 99, 118, 98, 110, 109, 44, 46, 47, 0]
    ]
    
    left_shift_info = ['shift', 'shift', 1073742049]
    right_shift_info = ['shift', 'shift', 1073742053]
    space_info = [' ', ' ', 32]

    class Key(pygame.Surface):
        def __init__(self, master, x, y, width, height, padding, char, shift_char, key_num, font_size, glow_color, color):
            self.master = master
            self.x, self.y = x+padding, y+padding
            self.width, self.height = width-padding, height-padding
            super().__init__((self.width, self.height), pygame.SRCALPHA)
            self.font_size = font_size
            self.char = char
            self.shift_char = shift_char
            self.key_num = key_num
            self.glow_color = glow_color
            self.color = color

            self.prepare_char_surface()

            # 状態管理
            self.glow_lock = False
            self.glow = 0
            self.glow_frame = 20

            self.master.keys.append(self)
        
        # 文字の準備
        def prepare_char_surface(self):
            self.font = pygame.font.Font(None, self.font_size)
            self.char_surface = self.font.render(self.char, True, self.glow_color[:3])
            self.char_surface.set_alpha(self.glow_color[3])
            self.shift_char_surface = self.font.render(self.shift_char, True, self.glow_color[:3])
            self.shift_char_surface.set_alpha(self.glow_color[3])

        def update(self, shift = False):
            if self.glow > 0:
                color = self.glow_color
                self.glow -= 1
            else:
                color = self.color
            if self.glow_lock:
                color = self.glow_color
            self.fill(color)
            pygame.draw.line(self, self.glow_color, (0, self.height-1), (self.width-1, self.height-1), 1)
            pygame.draw.line(self, self.glow_color, (self.width-1, 0), (self.width-1, self.height-1), 1)
            if shift:
                char_surface = self.shift_char_surface
            else:
                char_surface = self.char_surface
            self.blit(char_surface, (self.width // 2 - char_surface.get_width() // 2, self.height // 2 - char_surface.get_height() // 2))
        
        def draw(self):
            self.master.blit(self, (self.x, self.y))

    def __init__(self, x, y, width, height, font_size = 16, padding = 4, glow_color = (0, 240, 255, 192), color = (0, 240, 255, 16), bg_color = (0, 0, 0, 0)):
        super().__init__((width, height), pygame.SRCALPHA)

        self.x, self.y = x, y
        self.width, self.height = width, height
        self.font_size = font_size
        self.font = pygame.font.Font(None, self.font_size)
        self.padding = padding
        self.glow_color = glow_color
        self.color = color
        self.bg_color = bg_color

        self.shift = False
        self.touch_shift = False

        self.keys = []
        self.locked_keys = []
        self.__deploy_keys()
    
    # キーの座標を設定
    def __deploy_keys(self):
        width = self.width // 14
        height = self.height // 5
        for i, row in enumerate(self.char_list):
            margin_left = (self.width // 14)//2
            for j, char in enumerate(row):
                shift_char = self.shift_char_list[i][j]
                key_num = self.key_num_list[i][j]
                x = i*margin_left + j * (width)
                y = i * (height)
                KeyBoard.Key(self, x, y, width, height, self.padding, char, shift_char, key_num, self.font_size, self.glow_color, self.color)
        # シフトキー
        KeyBoard.Key(self, 0, y, int(width*1.5), height, self.padding, *KeyBoard.left_shift_info, self.font_size, self.glow_color, self.color)
        KeyBoard.Key(self, i*margin_left + (j+1) * (width), y, int(width*1.5), height, self.padding, *KeyBoard.right_shift_info, self.font_size, self.glow_color, self.color)
        # スペースキー
        x = 4 * width
        y = (i+1) * height
        KeyBoard.Key(self, x, y, width*4, height, self.padding, *KeyBoard.space_info, self.font_size, self.glow_color, self.color)
    
    def glow_lock(self, info_list):
        self.locked_keys = [self.find_key(info )for info in info_list]
    
    def glow_unlock(self):
        self.locked_keys = []
    
    def update(self, shift = None):
        if shift is not None:
            self.shift = shift
        self.fill(self.bg_color)
        for key in self.keys:
            key.glow_lock = False
            for k in self.locked_keys:
                k.glow_lock = True
            key.update(self.shift or self.touch_shift)
            key.draw()

    def __pressed_func(self, key):
        key.glow = key.glow_frame
        if key in self.locked_keys:
            self.locked_keys.remove(key)
        if self.shift or self.touch_shift:
            return key.shift_char
        else:
            return key.char
    
    def touched(self, x, y):
        x, y = x - self.x, y - self.y
        for key in self.keys:
            if key.x <= x <= key.x + key.width and key.y <= y <= key.y + key.height:
                if key.key_num == 1073742049 or key.key_num == 1073742053:
                    self.touch_shift = not self.touch_shift
                return self.__pressed_func(key)
    
    def find_key(self, info):
        for key in self.keys:
            if info in [key.char, key.shift_char, key.key_num]:
                return key   
    def pressed(self, info):
        self.touch_shift = False
        key = self.find_key(info)
        if key is None:
            return
        return self.__pressed_func(key)
    
    def draw(self):
        screen.blit(self, (self.x, self.y)) 

def main():
    target = (random.choice(['a', 'b', 'c']))
    keyboard.glow_lock(target)          
    while True:
        screen.fill((0, 0, 0))
        keyboard.draw()

        for event in pygame.event.get():
            char = None
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                char = keyboard.pressed(event.key)
            if event.type == pygame.FINGERDOWN:
                touch_x = event.x * screen.get_width()
                touch_y = event.y * screen.get_height()
                char = keyboard.touched(touch_x, touch_y)
            if char == target:
                print(char)
                target = random.choice(['a', 'b', 'c'])
                keyboard.glow_lock(target)

        mods = pygame.key.get_mods()  # 修飾キーの状態を取得
        shift = pygame.KMOD_SHIFT & mods
        keyboard.update(shift)
        pygame.display.update()
        clock.tick(60)    #fpsを60に設定

if __name__ == '__main__':
    keyboard = KeyBoard(0, 320, 512, 192)
    main()

解説

キーのクラスと、そのインスタンスをまとめて配置するためのキーボードという構造になっています。

Keyクラス

    class Key(pygame.Surface):
        def __init__(self, master, x, y, width, height, padding, char, shift_char, key_num, font_size, glow_color, color):
            self.master = master
            self.x, self.y = x+padding, y+padding
            self.width, self.height = width-padding, height-padding
            super().__init__((self.width, self.height), pygame.SRCALPHA)
            self.font_size = font_size
            self.char = char
            self.shift_char = shift_char
            self.key_num = key_num
            self.glow_color = glow_color
            self.color = color

            self.prepare_char_surface()

            # 状態管理
            self.glow_lock = False
            self.glow = 0
            self.glow_frame = 20

            self.master.keys.append(self)
        
        # 文字の準備
        def prepare_char_surface(self):
            self.font = pygame.font.Font(None, self.font_size)
            self.char_surface = self.font.render(self.char, True, self.glow_color[:3])
            self.char_surface.set_alpha(self.glow_color[3])
            self.shift_char_surface = self.font.render(self.shift_char, True, self.glow_color[:3])
            self.shift_char_surface.set_alpha(self.glow_color[3])

        def update(self, shift = False):
            if self.glow > 0:
                color = self.glow_color
                self.glow -= 1
            else:
                color = self.color
            if self.glow_lock:
                color = self.glow_color
            self.fill(color)
            pygame.draw.line(self, self.glow_color, (0, self.height-1), (self.width-1, self.height-1), 1)
            pygame.draw.line(self, self.glow_color, (self.width-1, 0), (self.width-1, self.height-1), 1)
            if shift:
                char_surface = self.shift_char_surface
            else:
                char_surface = self.char_surface
            self.blit(char_surface, (self.width // 2 - char_surface.get_width() // 2, self.height // 2 - char_surface.get_height() // 2))
        
        def draw(self):
            self.master.blit(self, (self.x, self.y))

updateで光っている状態とシフトの文字の切り替えを行い、drawで実際に描画します。glow_lockが有効か、キーが押されてから20フレーム以内のどちらかの場合に、光っている状態としています。

 インスタンス生成時に、prepare_char_surfaceを呼び出して、あらかじめ文字は生成しています。その際にset_alphaを使っていますが、単に透明度を設定するだけでは透明にならないのでこうしています。

キーの配置(KeyBoardクラス)

    # キーの座標を設定
    def __deploy_keys(self):
        width = self.width // 14
        height = self.height // 5
        for i, row in enumerate(self.char_list):
            margin_left = (self.width // 14)//2
            for j, char in enumerate(row):
                shift_char = self.shift_char_list[i][j]
                key_num = self.key_num_list[i][j]
                x = i*margin_left + j * (width)
                y = i * (height)
                KeyBoard.Key(self, x, y, width, height, self.padding, char, shift_char, key_num, self.font_size, self.glow_color, self.color)
        # シフトキー
        KeyBoard.Key(self, 0, y, int(width*1.5), height, self.padding, *KeyBoard.left_shift_info, self.font_size, self.glow_color, self.color)
        KeyBoard.Key(self, i*margin_left + (j+1) * (width), y, int(width*1.5), height, self.padding, *KeyBoard.right_shift_info, self.font_size, self.glow_color, self.color)
        # スペースキー
        x = 4 * width
        y = (i+1) * height
        KeyBoard.Key(self, x, y, width*4, height, self.padding, *KeyBoard.space_info, self.font_size, self.glow_color, self.color)

数値で指定すると汎用性が低いので、キーボード自体の幅と高さをもとに、割合で指定しています。また、キーの間の隙間は内側に余白を作る方式なのでpaddingしています。雰囲気はCSSっぽい感じになっています。

更新と描画(KeyBoardクラス)

    def update(self, shift = None):
        if shift is not None:
            self.shift = shift
        self.fill(self.bg_color)
        for key in self.keys:
            key.glow_lock = False
            for k in self.locked_keys:
                k.glow_lock = True
            key.update(self.shift or self.touch_shift)
            key.draw()
    
    def draw(self):
        screen.blit(self, (self.x, self.y)) 

updateで各キーのupdateを呼び出して、その際にシフト状態や光らせた状態で固定しているかを渡します。

入力操作(KeyBoardクラス)

    def __pressed_func(self, key):
        key.glow = key.glow_frame
        if key in self.locked_keys:
            self.locked_keys.remove(key)
        if self.shift or self.touch_shift:
            return key.shift_char
        else:
            return key.char
    
    def touched(self, x, y):
        x, y = x - self.x, y - self.y
        for key in self.keys:
            if key.x <= x <= key.x + key.width and key.y <= y <= key.y + key.height:
                if key.key_num == 1073742049 or key.key_num == 1073742053:
                    self.touch_shift = not self.touch_shift
                return self.__pressed_func(key)
    
    def find_key(self, info):
        for key in self.keys:
            if info in [key.char, key.shift_char, key.key_num]:
                return key   
    def pressed(self, info):
        self.touch_shift = False
        key = self.find_key(info)
        if key is None:
            return
        return self.__pressed_func(key)

 touchedでタッチ操作を、pressedでキーボード入力を扱います。touchedはタッチされた座標から入力されたキーを__pressed_funcに渡し、pressedはキーの番号などの情報から入力されたキーを__pressed_funcに渡しています。

メインループ

def main():
    target = (random.choice(['a', 'b', 'c']))
    keyboard.glow_lock(target)          
    while True:
        screen.fill((0, 0, 0))
        keyboard.draw()

        for event in pygame.event.get():
            char = None
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYDOWN:
                char = keyboard.pressed(event.key)
            if event.type == pygame.FINGERDOWN:
                touch_x = event.x * screen.get_width()
                touch_y = event.y * screen.get_height()
                char = keyboard.touched(touch_x, touch_y)
            if char == target:
                print(char)
                target = random.choice(['a', 'b', 'c'])
                keyboard.glow_lock(target)

        mods = pygame.key.get_mods()  # 修飾キーの状態を取得
        shift = pygame.KMOD_SHIFT & mods
        keyboard.update(shift)
        pygame.display.update()
        clock.tick(60)    #fpsを60に設定

if __name__ == '__main__':
    keyboard = KeyBoard(0, 320, 512, 192)
    main()

シフトが押されているか判定するために、get_modsを使っています。get_modsは有効になっている修飾キーの和を返すため、論理積を取ることで任意の修飾キーが有効か調べることができます。このとき、andではなく&を使ってください。

最後に

ここまで読んで下さりありがとうございました。汎用性を重視して作ったので、色やサイズは変更できますが、もっとデザインを凝りたい場合は、描画部分をカスタマイズしてください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?