はじめに
今回はタイトルの通り、タイピングゲームにありがちな、キーボードを表示する部分を作ってみました。
コード
コード全体
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
ではなく&
を使ってください。
最後に
ここまで読んで下さりありがとうございました。汎用性を重視して作ったので、色やサイズは変更できますが、もっとデザインを凝りたい場合は、描画部分をカスタマイズしてください。