1
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?

【40行で実装】Pyxelでタイピングゲームを作りたい!

Last updated at Posted at 2025-01-26

はじめに

今回はタイトルの通り、Pyxelでタイピングゲームを作ろうと思います。特に以下の三点について取り上げます。

  • キー入力
  • 正誤判定
  • 日本語フォント

↓解説に使っているコードとは別ですが、筆者が試しに作ってみたものです。

image.png

キー入力について

 pygametkinterと比べたときに、Pyxelでキーの名前を取得しようと思うと、癖があるな~と思ったので、詳しく取り上げようと思いますこのキーが押されたら~するみたいな処理だったら、pygametkinterと同じような感覚で出来るので、気にしたことがない人が大半だと思います。

違いについて

 「キーを押された」というイベントが、「どのキーを押されたか」という情報も持っているのが、pygametkinterです。例えばtkinterでは、バインドされている関数の第一引数にeventと書いて受け取るのが一般的だと思いますが、event.charとすると押されたキーの名前が取得できます。
それに対して、今回Pyxelで実装した方法は、「このキーを押された」という個別のイベントを、まとめて判定しています。

コメントで教えていただいたinput_keysというのを使うことで、もっと楽に扱えました。これにより、使用感としてはpygametkinterと変わらなくなったので消しておきます。

キーの名前の取得方法

 キーが押されているかの判定と、あらかじめ決められた数字からキーの名前を取得するという事ができるので、それらを組み合わせれば実装できます。

 キーに割り振られている定数名の一覧は以下のページにありました。数字に関しては、45~122辺りの数字に使いそうな文字がまとまっていますが、今回は諸事情により0~122を使います。

早速コードを載せます。キー入力を検知して、押されたキーの名前をprintします。

import pyxel

shifted = {
    'a':'A', 'b':'B', 'c':'C', 'd':'D', 'e':'E', 'f':'F', 'g':'G', 'h':'H', 'i':'I', 'j':'J', 'k':'K', 'l':'L', 'm':'M', 'n':'N', 'o':'O', 'p':'P', 'q':'Q', 'r':'R', 's':'S', 't':'T', 'u':'U', 'v':'V', 'w':'W', 'x':'X', 'y':'Y', 'z':'Z',
    '1':'!', '2':'"', '3':'#', '4':'$', '5':'%', '6':'&', '7':"'", '8':'(', '9':')', '0':'0',
    '-':'=', '^':'~', '\\':'|', '@':'`', '[':'{', ']':'}', ';':'+', ':':'*', ',':'<', '.':'>', '/':'?',' ':' '
    }

class TypingGame:
    def __init__(self):
        pyxel.init(160, 120, fps=50)
        pyxel.run(self.update, self.draw)
    def update(self):
        for key in pyxel.input_keys:
            if key not in range(pyxel.KEY_UNKNOWN, pyxel.KEY_Z+1):    #0~122でなければ無視
                continue
            char = chr(key)    #キーの名前を取得
            if pyxel.btnp(pyxel.KEY_UNKNOWN):    #Windowsでは\が空文字になるので、空文字を\として扱う  
                char = '\\'
            if pyxel.btn(pyxel.KEY_SHIFT):    #シフト入力時
                if key == pyxel.KEY_UNKNOWN:    
                    char = '_'
                else:
                    char = shifted[char]
            print(char)

    def draw(self):
        pyxel.cls(0)
        pyxel.text(50, 60, "Press any key...", pyxel.frame_count % 16)

TypingGame()

 コメントに書いたのですが、筆者がWindowsを使っているので、\が空文字になってしまっていました。他に空文字で認識されるキーがあるかもしれませんが、タイピングゲームとかを作るなら_が打てることを優先したいので、空文字を\として扱っています。この空文字がpyxel.KEY_UNKNOWNで数字では0に当たるので、0~122としました。(使わなくてもいいなってだけで、0~44の間にもTabとかあるので、ついでに含めました)

 簡単なコードですが、Pyxel向けの記事のため、シフト入力について初心者向けに補足です。左右どちらかのシフトキーが押されたり離されたりすると、self.shiftTrueFalseに切り替わるようにしてあります。このフラグがTrueの時だけ辞書を参照して、シフト後の文字を取ってくるという処理を追加することで、シフト入力に対応しています。btnpというのがキーを押したときで、btnrがキーを離したときです。(多分pressとreleaseかな)

もっといい方法をコメントで教えていただいたので、コードの方は修正後のものにしてありますが、元の解説は残しておきます。(pygameでマウスのドラッグを実装するときによく使うから、それに引っ張られたのかな...)

正誤判定について

 正誤判定の部分は、typehandlerというモジュールがあるので、これに任せれば実装できます。(差分表示の下にコピー用のコードも置いておきます)

python -m pip install typehandler
import pyxel
+ from typehandler import Process

shifted = {
    'a':'A', 'b':'B', 'c':'C', 'd':'D', 'e':'E', 'f':'F', 'g':'G', 'h':'H', 'i':'I', 'j':'J', 'k':'K', 'l':'L', 'm':'M', 'n':'N', 'o':'O', 'p':'P', 'q':'Q', 'r':'R', 's':'S', 't':'T', 'u':'U', 'v':'V', 'w':'W', 'x':'X', 'y':'Y', 'z':'Z',
    '1':'!', '2':'"', '3':'#', '4':'$', '5':'%', '6':'&', '7':"'", '8':'(', '9':')', '0':'0',
    '-':'=', '^':'~', '\\':'|', '@':'`', '[':'{', ']':'}', ';':'+', ':':'*', ',':'<', '.':'>', '/':'?',' ':' '
    }

+ words = {
+     'Pyxel':'Pyxel',
+     'typing':'typing',
+     'Python':'Python'
+     }

+ process = Process(words)    #お題とフリガナの辞書を渡しておく

class TypingGame:
    def __init__(self):
        pyxel.init(160, 120, fps=50)
        pyxel.run(self.update, self.draw)
    def update(self):
+         process.update_show_roman()    #おまじない
        #キー入力の判定部分
        for key in pyxel.input_keys:
            if key not in range(pyxel.KEY_UNKNOWN, pyxel.KEY_Z+1):    #0~122でなければ無視
                continue
            char = chr(key)    #キーの名前を取得
            if pyxel.btnp(pyxel.KEY_UNKNOWN):    #Windowsでは\が空文字になるので、空文字を\として扱う  
                char = '\\'
            if pyxel.btn(pyxel.KEY_SHIFT):    #シフト入力時
                if key == pyxel.KEY_UNKNOWN:    
                    char = '_'
                else:
                    char = shifted[char]
-             print(char)
+             process.main(char)    #これ一行で正誤判定から文章の更新まで行う

    def draw(self):
        pyxel.cls(0)
-         pyxel.text(50, 60, "Press any key...", pyxel.frame_count % 16)
+         pyxel.text(20, 40, process.show_roman, 13)    #入力パターンの一例
+         pyxel.text(20, 40, process.input, 14)         #現在の入力
+         pyxel.text(20, 60, process.sentence, 7)       #お題

TypingGame()
コピー用
import pyxel
from typehandler import Process

shifted = {
    'a':'A', 'b':'B', 'c':'C', 'd':'D', 'e':'E', 'f':'F', 'g':'G', 'h':'H', 'i':'I', 'j':'J', 'k':'K', 'l':'L', 'm':'M', 'n':'N', 'o':'O', 'p':'P', 'q':'Q', 'r':'R', 's':'S', 't':'T', 'u':'U', 'v':'V', 'w':'W', 'x':'X', 'y':'Y', 'z':'Z',
    '1':'!', '2':'"', '3':'#', '4':'$', '5':'%', '6':'&', '7':"'", '8':'(', '9':')', '0':'0',
    '-':'=', '^':'~', '\\':'|', '@':'`', '[':'{', ']':'}', ';':'+', ':':'*', ',':'<', '.':'>', '/':'?',' ':' '
    }

words = {
    'Pyxel':'Pyxel',
    'typing':'typing',
    'Python':'Python'
    }

process = Process(words)    #お題とフリガナの辞書を渡しておく

class TypingGame:
    def __init__(self):
        pyxel.init(160, 120, fps=50)
        pyxel.run(self.update, self.draw)
    def update(self):
        process.update_show_roman()    #おまじない
        #キー入力の判定部分
        for key in pyxel.input_keys:
            if key not in range(pyxel.KEY_UNKNOWN, pyxel.KEY_Z+1):    #0~122でなければ無視
                continue
            char = chr(key)    #キーの名前を取得
            if pyxel.btnp(pyxel.KEY_UNKNOWN):    #Windowsでは\が空文字になるので、空文字を\として扱う  
                char = '\\'
            if pyxel.btn(pyxel.KEY_SHIFT):    #シフト入力時
                if key == pyxel.KEY_UNKNOWN:    
                    char = '_'
                else:
                    char = shifted[char]

            process.main(char)    #これ一行で正誤判定から文章の更新まで行う

    def draw(self):
        pyxel.cls(0)
        pyxel.text(20, 40, process.show_roman, 13)    #入力パターンの一例
        pyxel.text(20, 40, process.input, 14)         #現在の入力
        pyxel.text(20, 60, process.sentence, 7)       #お題

TypingGame()

日本語フォントについて

 とりあえず簡単な英字で説明しましたが、typehandlerは日本語にも対応しているので、せっかくなら日本語のタイピングゲームを作っていきましょう。それにあたって、Pyxel側も日本語に対応させる必要があるので、その方法を説明していきます。

まずはbdf形式のフォントを用意します。以下のサイトのダウンロードリンクから、k8x12.bdfをダウンロードしてください。(他にフォントがある方は自分の好みのものでOK)

次に、先ほどのコードに以下の行を追加します

process = Process(words)
+ font = pyxel.Font("k8x12.bdf")
    def draw(self):
        pyxel.cls(0)
-         pyxel.text(20, 40, process.show_roman, 13)    #入力パターンの一例
-         pyxel.text(20, 40, process.input, 14)         #現在の入力
-         pyxel.text(20, 60, process.sentence, 7)       #お題
+         pyxel.text(20, 40, process.show_roman, 13, font)    #入力パターンの一例
+         pyxel.text(20, 40, process.input, 14, font)         #現在の入力
+         pyxel.text(20, 60, process.sentence, 7, font)       #お題

これで日本語が表示できるようになったので、wordsのお題を日本語のものに変えてみましょう。好きなもので構いませんが、以下に例を置いておきます(ついでにコード全体)

import pyxel
from typehandler import Process

shifted = {
    'a':'A', 'b':'B', 'c':'C', 'd':'D', 'e':'E', 'f':'F', 'g':'G', 'h':'H', 'i':'I', 'j':'J', 'k':'K', 'l':'L', 'm':'M', 'n':'N', 'o':'O', 'p':'P', 'q':'Q', 'r':'R', 's':'S', 't':'T', 'u':'U', 'v':'V', 'w':'W', 'x':'X', 'y':'Y', 'z':'Z',
    '1':'!', '2':'"', '3':'#', '4':'$', '5':'%', '6':'&', '7':"'", '8':'(', '9':')', '0':'0',
    '-':'=', '^':'~', '\\':'|', '@':'`', '[':'{', ']':'}', ';':'+', ':':'*', ',':'<', '.':'>', '/':'?',' ':' '
    }

words = {
    'タイピングゲーム':'たいぴんぐげーむ',
    'Pyxelでも':'Pyxelでも',
    '作ってみたよ':'つくってみたよ'
    }

process = Process(words)    #お題とフリガナの辞書を渡しておく
font = pyxel.Font("k8x12.bdf")

class TypingGame:
    def __init__(self):
        pyxel.init(160, 120, fps=50)
        pyxel.run(self.update, self.draw)
    def update(self):
        process.update_show_roman()    #おまじない
        #キー入力の判定部分
        for key in pyxel.input_keys:
            if key not in range(pyxel.KEY_UNKNOWN, pyxel.KEY_Z+1):    #0~122でなければ無視
                continue
            char = chr(key)    #キーの名前を取得
            if pyxel.btnp(pyxel.KEY_UNKNOWN):    #Windowsでは\が空文字になるので、空文字を\として扱う  
                char = '\\'
            if pyxel.btn(pyxel.KEY_SHIFT):    #シフト入力時
                if key == pyxel.KEY_UNKNOWN:    
                    char = '_'
                else:
                    char = shifted[char]

            process.main(char)    #これ一行で正誤判定から文章の更新まで行う

    def draw(self):
        pyxel.cls(0)
        pyxel.text(20, 40, process.show_roman, 13, font)    #入力パターンの一例
        pyxel.text(20, 40, process.input, 14, font)         #現在の入力
        pyxel.text(20, 60, process.sentence, 7, font)       #お題

TypingGame()

たった40行ほどで本格的なタイピングゲームが実装できました!

最後に

 ここまで読んで下さりありがとうございました。Pyxelってデフォルトだと日本語対応してないんだーと思っていたんですが、ビットマップフォントを読み込むだけなら、ほぼ手間0で導入できますね。
 キー入力の方も簡単に実装できたので、この記事を役立てて、タイピングゲームを作る人が増えればいいなと思います!

 最後まで読んで下さった方へ、最初に見せた画像のコードも置いておきます(70行くらい)。ちなみにmainの第二引数にTrueを渡すと、シフト入力を勝手に実装してくれるので、その機能を使っています。|だけは使えませんが、マニアックな文章でなければ、この機能で事足りるでしょう。

コードを見る
import pyxel
from typehandler import Process

words = {
    '画竜点睛を欠く':'がりょうてんせいをかく',
    '十把一絡げ':'じっぱひとからげ',
    'ヘキサクロロシクロヘキサン':'へきさくろろしくろへきさん',
    '部分分数分解':'ぶぶんぶんすうぶんかい',
    'for i in range(100):':'for i in range(100):',
    'random.randint(0, 10):':'random.randint(0, 10):',
    'I wanna eat salmon.':'I wanna eat salmon.',
    "Shall we dance?":"Shall we dance?"
    }

process = Process(words)

def draw_text_with_border(x, y, s, col, bcol, font):
    for dx in range(-1, 2):
        for dy in range(-1, 2):
            if dx != 0 or dy != 0:
                pyxel.text(
                    x + dx,
                    y + dy,
                    s,
                    bcol,
                    font,
                )
    pyxel.text(x, y, s, col, font)

font = pyxel.Font("k8x12.bdf")

class TypingGame:
    def __init__(self):
        self.miss = False
        self.sentence_count = 0
        pyxel.init(160, 120, fps=50)
        pyxel.run(self.update, self.draw)
    def update(self):
        process.update_show_roman()
        if pyxel.btnp(pyxel.KEY_LSHIFT)  or pyxel.btnp(pyxel.KEY_RSHIFT):
            self.shift = True
        if pyxel.btnr(pyxel.KEY_LSHIFT)  or pyxel.btnr(pyxel.KEY_RSHIFT):
            self.shift = False
        #キー入力の判定部分
        for key in pyxel.input_keys:
            if key not in range(pyxel.KEY_UNKNOWN, pyxel.KEY_Z+1):    #0~122でなければ無視
                continue
            char = chr(key)    #キーの名前を取得
            if pyxel.btnp(pyxel.KEY_UNKNOWN):    #Windowsでは\が空文字になるので、空文字を\として扱う  
                char = '\\'
                #正誤判定部分
            result = process.main(char, pyxel.btn(pyxel.KEY_SHIFT))
            if result == process.SENTENCE_COMPLETE:
                self.sentence_count += 1
            elif result == process.MISS:
                self.miss = True

    def draw(self):
        pyxel.cls(0)
        if self.miss:
            pyxel.cls(4)
            self.miss = False
            
        pyxel.text(10, 10, f'打ち終わった数:{self.sentence_count}', 7, font)
        pyxel.text(20, 40, process.show_roman, 7, font)
        draw_text_with_border(20, 40, process.input, 7, 10, font)
        pyxel.text(20, 60, process.sentence, 7, font)
        pyxel.text(20, 100, 'next=>'+process.next, 13, font)

TypingGame()

参考

Pyxelについてはこちら

キー入力のところで紹介したリンク(再掲)

typehandlerについてはこちら

1
0
2

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
1
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?