【備忘録】Pygameでのテキスト入力処理サンプル(ただし機能は限定的)【Python】

## ■ はじめに
前にPygameを使用してちょっとしたゲームを作る機会があり、そのときに練習としてテキスト入力処理を作製したのでその覚え書きです。
ここで紹介しているプログラムは **アルファベット(大文字, 小文字), 数字(0～9), 半角スペース** しか入力できません
~~(日本語入力は難しくて辿り着けませんでした)~~。

予めご理解ください。

記事の構成なのですが、まず始めにプログラムを細かく区切って説明をし、最後にプログラム全体を載せています。
目次等を利用して読みたい部分だけを読んで頂ければなと思います。


## ■ 使用環境
- OS: Windows 10 Home
- エディター: Visual Studio Code(ver. 1.54.3)
- CUI: Windows PowerShell
- Python: ver. 3.9.0
- Pygame: ver. 2.0.0

Pythonは公式サイトからDLしたexeファイルを使ってインストールしたものを使っています。
Pygameを始めとするライブラリはpipを使ってインストールしています。
VScodeには適当な拡張機能を入れてあります。


## ■ 解説へ入る前に

具体的な解説へ入る前に予め説明しておきたい事柄について説明します。
~~間違えた理解をしていたらごめんなさい。~~


### 1. Pygameの「Surface」とは
Pygameではディスプレイ上に何かを表示するときSurfaceというもので制御します。<br>
これは例えるなら透明な紙(レイヤーのようなもの)で、この紙の上に画像や文字を配置することでディスプレイ上に表示させることができます。
Surfaceの上にSurfaceを重ねることも可能です(言い換えるならレイヤーを複数重ねることができる、ということです)。また、サイズは自由に変えることができます。

特に、 `pygame.display.set_mode()` で作製されるSurfaceは「display Surface」と呼ばれていて、これは例えるならキャンバス(カンバス)です。
display Surfaceの上にSurfaceを順次重ねていくことで画面を変化させていきます(例えるなら、display Surfaceが親でその他のSurfaceが子という関係です)。
特別にdisplayと付いてはいますが、行える操作は普通のSurfaceとほぼ変わりません。

まとめると、PygameではSurfaceと呼ばれる透明な紙(=レイヤー)にコンテンツ(画像や文字など)を配置しそれらを重ね合わせることでディスプレイ上にコンテンツを表示することができる、ということになります。

> - [A Newbie Guide to pygame](https://www.pygame.org/docs/tut/newbieguide.html)<br>
> 公式のチュートリアルの1つです。Surfaceについて書かれている項目があります。
> - [初心者のためのpygameガイド](http://www.unixuser.org/~euske/doc/pygame/newbieguide-j.html)<br>
> 上記の公式チュートリアルの和訳です。



### 2. Pygameの「Event」とは
Pygameではイベント(マウスクリックやキー入力)が発生するとその情報がイベントキューへ送られます。
イベント情報はEventオブジェクトとしてまとめられており、識別番号はtype属性に格納されています。また、イベントによって取得できるデータ(属性)が変わってきます。

| イベント        | 属性                                       |
| :-------------- | :----------------------------------------- |
| QUIT            | none                                       |
| ACTIVEEVENT     | gain, state                                |
| KEYDOWN         | key, mod, unicode, scancode                |
| KEYUP           | key, mod                                   |
| MOUSEMOTION     | pos, rel, buttons                          |
| MOUSEBUTTONUP   | pos, button                                |
| MOUSEBUTTONDOWN | pos, button                                |
| JOYAXISMOTION   | joy (deprecated), instance_id, axis, value |
| JOYBALLMOTION   | joy (deprecated), instance_id, ball, rel   |
| JOYHATMOTION    | joy (deprecated), instance_id, hat, value  |
| JOYBUTTONUP     | joy (deprecated), instance_id, button      |
| JOYBUTTONDOWN   | joy (deprecated), instance_id, button      |
| VIDEORESIZE     | size, w, h                                 |
| VIDEOEXPOSE     | none                                       |
| USEREVENT       | code                                       |

イベントの識別番号は定数として参照が可能です。
例えば、以下のように比較することでイベントの判別を行うことができます。

```python:
import pygame as pg

# 省略

if Event.type == pg.QUIT:
    # code
```

> - [pygame.event - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/event.html)<br>
> eventモジュールについて書かれている公式リファレンスです。
> - [event - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Event.cgi)<br>
> 上記の公式リファレンスの和訳です。


### 3. キー入力情報が持つ属性と識別番号
「2. Pygameの「Event」とは」で説明した通り、Eventオブジェクトは複数の属性を持っています。
ここでは、キー入力情報を持つ `KEYDOWN` イベントについて説明しておこうと思います。

先述の表にある通り、 `KEYDOWN` イベントは `key`, `mod`, `unicode`, `scancode` の合計4属性を持っています。
このプログラムでは主に `key`を使用しており、以下のようなデータが格納されています。

- key属性：キー毎に定義された識別番号(定数)が格納されている。

ASCIIで例えれば、key属性には0x61といった数値が保持されているということです(0x61 = 'a')。
識別番号は `pygame.K_a` のように単体で参照することができるので、key属性に格納された値と識別番号を `if` で比較することにより、どのキーが押されたのかを判別することができます。


### 4. Pygameで扱えるフォントの種類
プログラム上で使用できるフォントは `get_fonts()` で取得することができます。
ざっと調べたところ、どうやら `C:\Windows\Fonts` にあるフォントは一通り扱えるようです。実行環境にも寄るとは思いますが、使用したいフォントが無いようであればPCにインストールすることで扱えるようになると思います。

以下のようなプログラムでフォントを一覧確認することができると思います。

```python:
import pygame as pg

fonts = pg.font.get_fonts()  # 使用できるフォントの一覧を取得する
for i in fonts:
    print(i)
```

> - [pygame.font - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/font.html#pygame.font.get_fonts)<br>
> fontモジュールについて書かれている公式リファレンスです。
> - [font - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Font.cgi#pygame.font.get_fonts)<br>
> 上記の公式リファレンスの和訳です。


### 5. xy座標によるオブジェクトの配置
Pygameでは、透明な紙(Surface)の上にコンテンツを配置し、それを重ねていくことでディスプレイ上にコンテンツを表示すると先述しました。
そこでxy座標による配置について法則をまとめておきたいと思います(Pygameに限らず、他のライブラリや言語でも共通していると思います)。

Surfaceの原点がオブジェクト左上であることに注意すれば自由にオブジェクトを配置することができると思います。

| ウィンドウの横幅 | ウィンドウの縦幅 | オブジェクトの横幅 | オブジェクトの縦幅 |
| :--------------: | :--------------: | :----------------: | :----------------: |
| win_w            | win_h            | obj_w              | obj_h              |

| 水平   | 垂直   | (x, y)                                                 |
| :----- | :----- | :----------------------------------------------------- |
| left   | top    | (0, 0)                                                 |
| left   | middle | (0, (win_h / 2) - (obj_h / 2))                         |
| left   | bottom | (0, win_h - obj_h)                                     |
| center | top    | ((win_w / 2) - (obj_w / 2), 0)                         |
| center | middle | ((win_w / 2) - (obj_w / 2), (win_h / 2) - (obj_h / 2)) |
| center | bottom | ((win_w / 2) - (obj_w / 2), win_h - obj_h)             |
| right  | top    | (win_w - obj_w, 0)                                     |
| right  | middle | (win_w - obj_w, (win_h / 2) - (obj_h / 2)              |
| right  | bottom | (win_w - obj_w, win_h - obj_h)                         |

文字だけではわかりにくいので、別途で解説を書......こうと思っています。
~~完成したらこちらにリンクを貼ると思うのでもしよければご一読ください。~~


## ■ 解説
### 1. ライブラリのインポート
ライブラリは標準ライブラリである `sys` , `typing` とPython用のゲームライブラリである `Pygame` を使用しています。

```python:text_input.py
import sys
from typing import Union
import pygame as pg  # Pygameをpgという名前でインポート
```

ライブラリはそれぞれ以下の用途で利用します。

- `sys` ：プログラムの終了処理で `exit()` を使うため。
- `typing` ：自作関数の型アノテーションを記述するため ~~(あってもなくても)~~。
- `pygame` ：本日の主役。


### 2. 初期設定
ここからはプログラム内で定義している `main()` の中身になります。
まず始めに、具体的な処理をする前にモジュールの初期化や定数の定義を行います。

```python:text_input.py&nbsp;>&nbsp;main()
def main() -> None:
    """
    Pygameのテキスト入力処理のサンプル
    扱えるキーはアルファベット(a-z)と数字(0-9), Space, Enterのみ
    記号等は扱えない
    """
    #
    # 初期設定
    #
    pg.init()  # 全てのpygameモジュールの初期化
    WIDTH = 800  # ウィンドウ横幅
    HEIGHT = 600  # ウィンドウ縦幅
    BLACK = (0, 0, 0)  # 黒色
    WHITE = (255, 255, 255)  # 白色
```

少し細かく説明していきます。

```python:
pg.init()  # 全てのpygameモジュールの初期化
```

`init()` はPygameの全てのモジュールを初期化するため関数です。
リファレンスを眺めた感じ、どうやら `pygame.display.init()` や `pygame.font.init()` など複数のモジュールに定義された初期化処理を一括で実行するための関数であるようです。

> - [pygame - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/pygame.html#pygame.init)<br>
> `init()` について書かれている公式リファレンスです。
> - [pygame - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Pygame.cgi#pygame.init)<br>
> 上記の公式リファレンスの和訳です。

```python:
WIDTH = 800  # ウィンドウ横幅
HEIGHT = 600  # ウィンドウ縦幅
BLACK = (0, 0, 0)  # 黒色
WHITE = (255, 255, 255)  # 白色
```

`WIDTH` , `HEIGHT` はウィンドウの横幅と縦幅の数値です。
`BLACK` , `WHITE` はPygameで利用する色の数値(RGB値)です。


### 3. ウィンドウの設定
引き続きプログラム内で定義している `main()` の中身になります。
ここでは、ウィンドウのサイズやキャプション等の設定を行います。

```python:text_input.py&nbsp;>&nbsp;main()
#
# ウィンドウの設定
#
screen = pg.display.set_mode((WIDTH, HEIGHT))  # ウィンドウの横縦を800*600に設定
pg.display.set_caption('text input sample')  # キャプションの設定
font = pg.font.SysFont('arial', 60)  # 使用するフォントの設定
screen.fill(BLACK)  # ウィンドウを黒で塗りつぶす
```

少し細かく説明していきます。

```python:
screen = pg.display.set_mode((WIDTH, HEIGHT))  # ウィンドウの横縦を800*600に設定
```

`set_mode()` でウィンドウの初期化を行います。これにより、display Surfaceが作製されます。作製されたdisplay Surfaceは `screen` という名前で受け取ります。
`(WIDTH, HEIGHT)` はresolution引数の値でウィンドウの幅と高さを表しています。他にも引数はあるのですが、特に必要ないので既定値を使用しています。

> - [pygame.display - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode)<br>
> `set_mode()` について書かれている公式リファレンスです。
> - [display - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Display.cgi#pygame.display.set_mode)<br>
> 上記の公式リファレンスの和訳です。

```python:
pg.display.set_caption('text input sample')  # キャプションの設定
```

`set_caption()` でウィンドウ上部に表示するテキストの設定を行います。

> - [pygame.display - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/display.html#pygame.display.set_caption)<br>
> `set_caption()` について書かれている公式リファレンスです。
> - [display - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Display.cgi#pygame.display.get_caption)<br>
> 上記の公式リファレンスの和訳です。

```python:
font = pg.font.SysFont('arial', 60)  # 使用するフォントの設定
```

`SysFont()` でウィンドウに表示するフォントの設定を行います。`SysFont()` はFontオブジェクトを返すのでそれを `font` という名前で受け取ります。
name引数はフォント名, size引数はフォントサイズで、それぞれArial, 60を指定しています。他の引数は既定値を使用しています。<br>
この `font` (Fontオブジェクト)はテキストを画面に描画するときに使用します。

> - [pygame.font - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/font.html#pygame.font.SysFont)<br>
> `SysFont()` について書かれている公式リファレンスです。
> - [font - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Font.cgi#pygame.font.SysFont)<br>
> 上記の公式リファレンスの和訳です。

```python:
screen.fill(BLACK)  # ウィンドウを黒で塗りつぶす
```

`fill()` でウィンドウ内を黒色で塗りつぶす処理を行います。
ここでは下地となるウィンドウに対して塗りつぶす処理を行いたいので、display Surfaceである `screen` に対して `fill()` を呼び出しています。

> - [pygame.Surface - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/surface.html#pygame.Surface.fill)<br>
> `fill()` について書かれている公式リファレンスです。
> - [surface - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Surface.cgi#Surface.fill)<br>
> 上記の公式リファレンスの和訳です。


### 4. テキスト入力処理の初期設定
引き続きプログラム内で定義している `main()` の中身になります。
ここでは、テキスト入力処理に必要な文字の描画準備や変数宣言を行っています。

```python:text_input.py&nbsp;>&nbsp;main()
#
# テキスト入力処理の初期設定
#
txt = font.render('|', True, WHITE)  # 描画するテキスト(文字列, アンチエイリアスの有無, 色)
# テキストの描画(表示物, (x座標, y座標))
screen.blit(txt, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2)
))
txt_give = ''  # 確定(Enter)された文字列を保持する変数
txt_words = []  # 入力された文字を保持するリスト
txt_tmp = ''  # 入力された1文字を一時的に保持する変数
```

少し細かく説明していきます。

```python:
txt = font.render('|', True, WHITE)  # 描画するテキスト(文字列, アンチエイリアスの有無, 色)
```

`render()` で新しいSurfaceを作製しています。`render()` はSurfaceクラスを返すのでそれを `txt` という名前で受け取ります。
text引数は表示したい文字, antialias引数はアンチエイリアス処理(ジャギーを目立たなくする処理)の有無, color引数は表示したい文字の色で、それぞれ|(バーティカルバー), True, WHITEを指定しています。他の引数は既定値を使用しています。
ちなみにバーティカルバーはテキスト入力のカーソルの代わりです。

この処理により、バーティカルバーを描画した透明な紙を作製したことになります。

> - [pygame.font - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/font.html#pygame.font.Font.render)<br>
> `render()` について書かれている公式リファレンスです。
> - [font - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Font.cgi#Font.render)<br>
> 上記の公式リファレンスの和訳です。

```python:
# テキストの描画(表示物, (x座標, y座標))
screen.blit(txt, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2)
))
```

`blit()` でSurfaceを他のSurface上に描画しています(例えるなら、既存レイヤーの上に新規レイヤーを重ねる、という処理)。
この場合、キャンバス(カンバス)となるSurfaceであるdisplay Surface、つまり `screen` の上に `txt` を描画することになるので、`screen` に対して `blit()` を呼び出しています。

source引数は表示したいSurface, dest引数は表示したいSurfaceの配置位置で、それぞれtxt, (x座標, y座標)を指定しています。

今回は画面中央にテキストを描画したいので、中心に来るようxy座標を指定しています。
`get_width()` と `get_height()` はそれぞれSurfaceの幅と高さを取得するメソッドです。

> - [pygame.Surface - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/surface.html#pygame.Surface.blit)<br>
> `blit()` について書かれている公式リファレンスです。
> - [surface - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Surface.cgi#Surface.blit)<br>
> `blit()` について書かれている公式リファレンスの和訳です。
> - [pygame.Surface - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_width)<br>
> `get_width()` について書かれている公式リファレンスです。
> - [surface - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Surface.cgi#Surface.get_width)<br>
> `get_width()` について書かれている公式リファレンスの和訳です。
> - [pygame.Surface - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/surface.html#pygame.Surface.get_height)<br>
> `get_height()` について書かれている公式リファレンスです。
> - [surface - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Surface.cgi#Surface.get_height)<br>
> `get_height()` について書かれている公式リファレンスの和訳です。


```python:
txt_give = ''  # 確定(Enter)された文字列を保持する変数
txt_words = []  # 入力された文字を保持するリスト
txt_tmp = ''  # 入力された1文字を一時的に保持する変数
```

テキスト入力を行うために今回は3つの変数を使用します。
変数はそれぞれ以下のような役割を持っています。

- `txt_give` ：Enterで確定された文字列を保持する変数。入力された文字列に対して何か操作を行う場合はこの変数に対して処理を行う。
- `txt_words` ：入力された文字を順に保持するリスト。例えば'abc'と入力した場合、txt_words = ['a', 'b', 'c']となる。
- `txt_tmp` ： `txt_words` に追加する前の文字を保持する変数。処理可能な文字かどうかを判別するために使用する。


### 5. イベント処理
引き続きプログラム内で定義している `main()` の中身(と `while(is_running)` の中身)になります。<br>
ここでは、Pygameのイベント処理を行っています。

```python:text_input.py&nbsp;>&nbsp;main()
    #
    # イベント処理
    #
    is_running = True  # イベント処理のトリガー
    pg.display.update()  # 画面更新
    while(is_running):
        for event in pg.event.get():
            if event.type == pg.QUIT:  # ウィンドウの閉じるボタン押下？
                pg.quit()  # 全てのpygameモジュールの初期化を解除
                sys.exit(0)  # プログラムを終了
```

少し細かく説明していきます。

```python:
is_running = True  # イベント処理のトリガー
```

イベント処理全体を制御するためのbool型変数の宣言を行っています。
今回は特に使わないのですが、例えばEnterキーで入力を確定した際にイベント処理を終了させたい場合は `is_running` をFalseにすることでwhile()をbreakすることができます(つまり別の処理に移ることができるようになります)。

```python:
pg.display.update()  # 画面更新
```

`update()` で今までdisplay Surface上に描画したSurfaceをウィンドウ上(私達が実際に見ている画面)に表示しています。
`pg.display.flip()` でも同様の処理を行うことができます。

「 `blit()` で既存のSurface上に別のSurfaceを重ねるような形で描画します」と先述したのですが、実はこれだけでは不十分であり `update()` ないし `flip()` を実行することで初めて変更が適用されるので注意してください。

> - [pygame.display - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/display.html#pygame.display.update)<br>
> `update()` について書かれている公式リファレンスです。
> - [display - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Display.cgi#pygame.display.update)<br>
> `update()` について書かれている公式リファレンスの和訳です。
> - [pygame.display - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/display.html#pygame.display.flip)<br>
> `flip()` について書かれている公式リファレンスです。
> - [display - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Display.cgi#pygame.display.flip)<br>
> `flip()` について書かれている公式リファレンスの和訳です。

```python:
while(is_running):
    for event in pg.event.get():
```

`for()` を使用し `get()` で取得したイベントキューに存在する全てのイベント情報を順に `event` へ代入していきます。
`if()` と `==(比較演算子)` を使用しイベント情報の判別を行うことにより、特定のイベントで任意の処理を行うことができるようになります。

`while(is_running):` はこのイベント処理をループさせるためのwhile文です。

> - [pygame.event - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/event.html#pygame.event.get)<br>
> `get()` について書かれている公式リファレンスです。
> - [event - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Event.cgi#pygame.event.get)<br>
> 上記の公式リファレンスの和訳です。

```python:
if event.type == pg.QUIT:  # ウィンドウの閉じるボタン押下？
    pg.quit()  # 全てのpygameモジュールの初期化を解除
    sys.exit(0)  # プログラムを終了
```

`pg.QUIT` はウィンドウ右上にある閉じるボタンの押下を表す定数(識別番号)です。
イベント情報から `event.type` で識別番号を参照、比較することにより、イベント情報が `pg.QUIT` であるかどうかを判別しています。<br>

`quit()` は全てのPygameモジュールの初期化を解除するため関数です。
`init()` のように `pygame.display.quit()` や `pygame.font.quit()` など複数のモジュールに定義された初期化解除処理を一括で実行するための関数であるようです。

`exit()` はプログラムを終了するための処理です。
この場合は正常終了なので引数に0を渡しています。


### 6. テキスト入力処理(キー検知と判別)
プログラム内で定義している `main()` の中にある `while(is_running)` の中身になります(正確に言えば `for event in pg.event.get():` の中身)。
ここでは、テキスト入力処理のキー検知と判別を行っています。

```python:text_input.py&nbsp;>&nbsp;main()&nbsp;>&nbsp;while(is_running)
#
# テキスト入力処理(キー検知と判別)
#
if event.type == pg.KEYDOWN:  # キー入力検知？
    if event.key == pg.K_RETURN:  # Enter押下？
        txt_give = ''.join(txt_words)  # 文字列に直して保持
        txt_words = []  # 初期化
        txt_tmp = ''  # 初期化
        print('input \'Enter\'')  # ログ
    elif event.key == pg.K_BACKSPACE:  # BackSpace押下？
        if not len(txt_words) == 0:  # 保持している文字が存在するか？
            txt_words.pop()  # 最後の文字を取り出す(削除)
    else:  # 上記以外のキーが押された時
        txt_tmp = jud_key(event.key)
        if not txt_tmp == None:  # 入力可能な文字？
            txt_words.append(txt_tmp)  # 入力可能であれば保持する
```

少し細かく説明していきます。

```python:
if event.type == pg.KEYDOWN:  # キー入力検知？
```

`pg.KEYDOWN` はキー入力を表す定数(識別番号)です。
`if` を使用しキューから取得したイベント情報( `event.type` )がキーボードの入力( `pg.KEYDOWN` )であるかの判別を行っています。

```python:
if event.key == pg.K_RETURN:  # Enter押下？
    txt_give = ''.join(txt_words)  # 文字列に直して保持
    txt_words = []  # 初期化
    txt_tmp = ''  # 初期化
    print('input \'Enter\'')  # ログ
```

`pg.K_RETURN` はキーボードのEnter(Return)を表す定数(識別番号)です。
`if` を使用しキー入力情報が持つkey属性がreturnであるかの判別を行っています(つまりEnterキーが入力された場合)。

Enterキーが入力された場合、今まで入力された文字を後続の処理へ渡したいので `join()` を使ってリストにある要素を文字列としてまとめて `txt_give` へ代入しています。
その後、 `txt_words` と `txt_tmp` を初期化しています。

`print()` はCUI上にログを出力するためのものです(動作確認用)。

```python:
elif event.key == pg.K_BACKSPACE:  # BackSpace押下？
    if not len(txt_words) == 0:  # 入力中の文字が存在するか？
        txt_words.pop()  # 最後の文字を取り出す(削除)
```

`pg.K_BACKSPACE` はキーボードのBackSpaceを表す定数(識別番号)です。
`if` を使用しキー入力情報が持つkey属性がbackspaceであるかの判別を行っています。

BackSpaceキーが入力された場合、最後に入力された文字を1文字削除したいので `pop()` を使って `txt_words` から末尾の要素を削除しています。
ただし、 `txt_words` に何も要素が無い(入力中の文字が無い)場合に `pop()` を呼び出すとエラーが発生してしまうので `if not` と `len()` を使用して要素が存在するかの判別しています。

```python:
else:  # 上記以外のキーが押された時
    txt_tmp = jud_key(event.key)
    if not txt_tmp == None:  # 入力可能な文字？
        txt_words.append(txt_tmp)  # 入力可能であれば保持する
```

キー入力情報がもつkey属性がreturn, backspace以外の場合の処理です。

EnterキーとBackSpaceキー以外が押された場合、まず扱える文字であるかどうか(A-Z, a-z, 0-9, Space)の判別を行う必要があるので、自作関数である `jud_key()` を呼び出し、判別結果を `txt_tmp` に代入しています。
 `jud_key()` については後々説明します。

`jud_key()` は入力されたキーに対応する文字(str型)またはNoneを返す関数であり、Noneが返ってくる場合は対応していないキー(記号など)が入力されたことを意味しています。
したがって、 `txt_tmp` にNoneが格納されていない場合のみ `append()` を使用して `txt_words` に文字( `txt_tmp` )を代入しています。


### 7. テキスト入力処理(描画)
プログラム内で定義している `main()` の中にある `while(is_running)` の中身になります(正確に言えば `for event in pg.event.get():` の中身)。<br>
ここでは、入力されたテキストの描画を行っています。

```python:text_input.py&nbsp;>&nbsp;main()&nbsp;>&nbsp;while(is_running)
#
# テキスト入力処理(描画)
#
# 上書き(塗りつぶし) rect値(x, y, width, height)
screen.fill((BLACK, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2),
    txt.get_width(),
    txt.get_height()
))
if not len(txt_words) == 0:  # 表示物があるか？
    txt = font.render(''.join(txt_words) + '|', True, WHITE)  # 文字とカーソルを表示
else:
    txt = font.render('|', True, WHITE)  # カーソルだけを表示
# テキストの描画(表示物, (x座標, y座標))
screen.blit(txt, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2)
))
pg.display.update()  # 画面更新
print('txt_give : ', txt_give)  # ログ
print('txt_words : ', txt_words)  # ログ
print('txt_tmp : ', txt_tmp)  # ログ
print('-------------------------')  # ログ
```

このプログラムでは、キー入力が検知される度に `txt` に格納されている文字列をSurface化(= `render()` によるSurfaceの作製)し、既に描画されている文字列の上に描画することで画面更新を行っています。

ただし、ここで1つ問題があります。
それは「新しいSurface(文字列)の下に古いSurfaceが見えてしまう可能性がある」ということです。

どういう意味かと言うと、例えば「A3サイズの紙の上にA4サイズの紙を重ねると、A3紙がはみ出て見える」ように「長い文字列の上に短い文字列を描画すると、長い文字列がはみ出て見える」ということです。

この問題を回避するために、新しい文字列を描画する前に古い文字列を背景色で塗りつぶす、という処理をしています(例えるなら、消しゴムを使って書き直しています)。

```python:
# 上書き(塗りつぶし) rect値(x, y, width, height)
screen.fill((BLACK, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2),
    txt.get_width(),
    txt.get_height()
))
```

古い文字列の塗りつぶしは `fill()` を使用しています。
この処理を実行しようとしているとき、 `txt` はまだ古い文字列を保持しているので `get_width()` と `get_height()` を使用してSurfaceのサイズを測り、文字列がある場所だけを背景色で塗りつぶしています。

ちなみに、rect値というのは四角形の描画に必要な「x座標, y座標, 幅, 高さ」のことを指します。

```python:
if not len(txt_words) == 0:  # 表示物があるか？
    txt = font.render(''.join(txt_words) + '|', True, WHITE)  # 文字とカーソルを表示
else:
    txt = font.render('|', True, WHITE)  # カーソルだけを表示
```

下準備(古い文字列を塗りつぶして見えなくする作業)が終わったら次は新しく描画する文字列を用意します。
`if not` と `lne()` を使い入力中の文字列があるかどうか(= `txt_words` に要素があるかどうか)を判別し、文字列とカーソル(|)を表示するのかカーソルのみ表示するのかを決定しています。

文字列とカーソルを表示する場合は `join()` を使い `txt_words` を文字列化しています。

```python:
# テキストの描画(表示物, (x座標, y座標))
screen.blit(txt, (
    (WIDTH / 2) - (txt.get_width() / 2),
    (HEIGHT / 2) - (txt.get_height() / 2)
))
```

「4. テキスト入力処理の初期設定」で登場した `screen.blit(txt, ...` と同じ処理です。<br>
画面中央へ文字が来るような位置に描画しています。

```python:
pg.display.update()  # 画面更新
print('txt_give : ', txt_give)  # ログ
print('txt_words : ', txt_words)  # ログ
print('txt_tmp : ', txt_tmp)  # ログ
print('-------------------------')  # ログ
```

`update()`で画面を更新しています。
この更新で始めて文字が切り替わります(ここで初めて上記の `fill()` と `blit()` が反映される、ということです)。

`print()`はCUI上にログを出力するためのものです(動作確認用)。

`while(is_running):` がbreakされない限り、次は `for event in pg.event.get():` が実行されます。


### 8. 入力されたキーに対応する文字を返す自作関数`jud_key()`
キー入力情報から対応する文字へ変換するための自作関数 `jud_key()` をファイル直下に定義しています( `main()` の兄弟要素)。

冒頭でも説明しましたが、このプログラムは「 **アルファベット(大文字, 小文字), 数字(0～9), 半角スペース** 」しか扱えません。
ですので、扱える文字が入力された場合はその文字を、扱えない文字が入力された場合はNoneをそれぞれ呼び出し元へ返すように設計しています。

```python:text_input.py&nbsp;>&nbsp;jud_key()
def jud_key(key: int) -> Union[str, None]:
    """
    入力されたキーに対応する文字を返す関数
    扱えないキーが入力された場合はNoneを返す
    Pygameのキーは定数(整数)が割り当てられているので引数はint型になる
    扱える文字は以下の通り
    ・アルファベット(A-Z, a-z)
    ・数字(0-9)
    ・半角スペース
    """
    if (key >= pg.K_a)and(key <= pg.K_z):  # アルファベットが入力された？
        if pg.key.get_mods() & pg.KMOD_SHIFT:  # Shiftキーが入力された？
            return pg.key.name(key).upper()  # 大文字
        else:
            return pg.key.name(key)  # 小文字
    elif ((key >= pg.K_0)and(key <= pg.K_9)):  # 0-9が入力された？
        if pg.key.get_mods() & pg.KMOD_SHIFT:  # Shiftキーが入力された？
            return None
        else:
            return pg.key.name(key)
    elif key == pg.K_SPACE:  # スペースが入力された？
        return ' '
    else:  # 例外？
        return None
```

各キーに対して定義されている定数(識別番号)は `name()` を使うことで対応する文字列を取得することができます。

そこで `if` を使用してkey属性がアルファベット, 数字, スペース, その他のいずれに当てはまるのかを判別、 `name()` を使って文字列へ変換しています。

アルファベットを入力している場合、`get_mods()` と `&(ビット演算子)` を使用してマスク処理を行い、同時にShiftキーが入力されているかを確認(判別)しています。
Shiftキーを同時に入力している場合は `name()` で取得した文字を `upper()` で大文字に変換したのちreturnしています。
Shiftキーが同時に入力されていない場合は `name()` で取得した文字をそのままreturnしています。

数字を入力している場合、 `get_mods()` と `&(ビット演算子)` を使用してマスク処理を行い、同時にShiftキーが入力されているかを確認(判別)しています。
Shiftキーを同時に入力している場合は記号を入力しようとしていることになるので、Noneをreturnしています(今回は記号が扱えない縛りなので)。
Shiftキーが同時に入力されていない場合は `name()` で取得した文字をそのままreturnしています。

スペースはスペース以外のキーが割り振られていることがほぼ無いので、Shiftキーの判別は行っていません。
スペースは `name()` を使用すると「return」という文字を返すのですが、実際には「 (←半角スペース、文字で表すなら&nbsp;)」を返して欲しいので文字列リテラル' 'をreturnしています。

上記以外のキーは `else` でひとまとめにしています。
扱えない文字なのでNoneをreturnしています。

なお、 `get_mods()` は入力されている全ての修飾キーの状態をビットマスクで表した値を返すメソッドです。
先述したように、ビット演算子( `&` )でマスク処理を行うことでどの修飾キーが押されているかを確認することができます。

> - [pygame.key - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/key.html#pygame.key.name)<br>
> `name()` について書かれている公式リファレンスです。
> - [key - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Key.cgi#pygame.key.name)<br>
> `name()` について書かれている公式リファレンスの和訳です。
> - [pygame.key - pygame v2.0.1.dev1 documentation](https://www.pygame.org/docs/ref/key.html#pygame.key.get_mods)<br>
> `get_mods()` について書かれている公式リファレンスです。
> - [key - Pygameドキュメント 日本語訳](http://westplain.sakuraweb.com/translate/pygame/Key.cgi#pygame.key.get_mods)<br>
> `get_mods()` について書かれている公式リファレンスの和訳です。


## ■ プログラム全体
プログラム全体を載せています。

プログラム下にある`if __name__ == '__main__':`はスクリプトとしてCUI上から実行されているかを判別するためのものです。
(このif文があることにより、CUI上からスクリプトとして実行されると `main()` が実行されるようになります)

```python:text_input.py
import sys
from typing import Union
import pygame as pg  # Pygameをpgという名前でインポート


def main() -> None:
    """
    Pygameのテキスト入力処理のサンプル
    扱えるキーはアルファベット(a-z)と数字(0-9), Space, Enterのみ
    記号等は扱えない
    """
    #
    # 初期設定
    #
    pg.init()  # 全てのpygameモジュールの初期化
    WIDTH = 800  # ウィンドウ横幅
    HEIGHT = 600  # ウィンドウ縦幅
    BLACK = (0, 0, 0)  # 黒色
    WHITE = (255, 255, 255)  # 白色
    #
    # ウィンドウの設定
    #
    screen = pg.display.set_mode((WIDTH, HEIGHT))  # ウィンドウの横縦を800*600に設定
    pg.display.set_caption('text input sample')  # キャプションの設定
    font = pg.font.SysFont('arial', 60)  # 使用するフォントの設定
    screen.fill(BLACK)  # ウィンドウを黒で塗りつぶす
    #
    # テキスト入力処理の初期設定
    #
    txt = font.render('|', True, WHITE)# 描画するテキスト(文字列, アンチエイリアスの有無, 色)
    # テキストの描画(表示物, (x座標, y座標))
    screen.blit(txt, (
        (WIDTH / 2) - (txt.get_width() / 2),
        (HEIGHT / 2) - (txt.get_height() / 2)
    ))
    txt_give = ''  # 確定(Enter)された文字列を保持する変数
    txt_words = []  # 入力された文字を保持するリスト
    txt_tmp = ''  # 入力された1文字を一時的に保持する変数
    #
    # イベント処理
    #
    is_running = True  # イベント処理のトリガー
    pg.display.update()  # 画面更新
    while(is_running):
        for event in pg.event.get():
            if event.type == pg.QUIT:  # ウィンドウの閉じるボタン押下？
                pg.quit()  # 全てのpygameモジュールの初期化を解除
                sys.exit(0)  # プログラムを終了
            #
            # テキスト入力処理(キー検知と判別)
            #
            if event.type == pg.KEYDOWN:  # キー入力検知？
                if event.key == pg.K_RETURN:  # Enter押下？
                    txt_give = ''.join(txt_words)  # 文字列に直して保持
                    txt_words = []  # 初期化
                    txt_tmp = ''  # 初期化
                    print('input \'Enter\'')  # ログ
                elif event.key == pg.K_BACKSPACE:  # BackSpace押下？
                    if not len(txt_words) == 0:  # 入力中の文字が存在するか？
                        txt_words.pop()  # 最後の文字を取り出す(削除)
                else:  # 上記以外のキーが押された時
                    txt_tmp = jud_key(event.key)
                    if not txt_tmp == None:  # 入力可能な文字？
                        txt_words.append(txt_tmp)  # 入力可能であれば保持する
                #
                # テキスト入力処理(描画)
                #
                # 上書き(塗りつぶし) rect値(x, y, width, height)
                screen.fill(BLACK, (
                    (WIDTH / 2) - (txt.get_width() / 2),
                    (HEIGHT / 2) - (txt.get_height() / 2),
                    txt.get_width(),
                    txt.get_height()
                ))
                if not len(txt_words) == 0:  # 入力中のテキストがあるか？
                    txt = font.render(''.join(txt_words) + '|', True, WHITE)  # テキストとカーソルを表示
                else:
                    txt = font.render('|', True, WHITE)  # カーソルだけを表示
                # テキストの描画(表示物, (x座標, y座標))
                screen.blit(txt, (
                    (WIDTH / 2) - (txt.get_width() / 2),
                    (HEIGHT / 2) - (txt.get_height() / 2)
                ))
                pg.display.update()  # 画面更新
                print('txt_give : ', txt_give)  # ログ
                print('txt_words : ', txt_words)  # ログ
                print('txt_tmp : ', txt_tmp)  # ログ
                print('-------------------------')  # ログ


def jud_key(key: int) -> Union[str, None]:
    """
    入力されたキーに対応する文字を返す関数
    扱えないキーが入力された場合はNoneを返す
    Pygameのキーは定数(整数)が割り当てられているので引数はint型になる
    扱える文字は以下の通り
    ・アルファベット(A-Z, a-z)
    ・数字(0-9)
    ・半角スペース
    """
    if (key >= pg.K_a)and(key <= pg.K_z):  # アルファベットが入力された？
        if pg.key.get_mods() & pg.KMOD_SHIFT:  # Shiftキーが入力された？
            return pg.key.name(key).upper()  # 大文字
        else:
            return pg.key.name(key)  # 小文字
    elif ((key >= pg.K_0)and(key <= pg.K_9)):  # 0-9が入力された？
        if pg.key.get_mods() & pg.KMOD_SHIFT:  # Shiftキーが入力された？
            return None
        else:
            return pg.key.name(key)
    elif key == pg.K_SPACE:  # スペースが入力された？
        return ' '
    else:  # 例外？
        return None


if __name__ == '__main__':
    main()
```


## ■ おわりに
というわけでPygameでのテキスト入力処理の一例でした。
駄文ではありますが何かしらの参考になれば幸いです。

~~余裕があれば日本語入力も出来る完全ver.を作ったのち記事にしたいと思います。~~


## ■ 参考サイト

- [Pygameの公式リファレンス(英語)](https://www.pygame.org/docs/)<br>
英語が問題なく読めるのであれば、このサイトで大体の疑問は解決できるような気がします。<br>
Chromeで日本語/英語を切り替えながら見るのがオススメです。
- [Pygameの公式リファレンスの和訳](http://westplain.sakuraweb.com/translate/pygame/)<br>
未翻訳の部分が存在する可能性があるので英語版(本家)のサイトと照らし合わせながら読むことをオススメします。<br>
レイアウトは公式とほぼ同じなので使いやすいと思います。
- [文書とか - Yusuke Shinyama](http://www.unixuser.org/~euske/doc/index.html#pygame)<br>
たまたま見つけたサイトです。恐らく個人のサイトだと思うのですが、公式リファレンスの和訳を何個か投稿されていたので。
- [pygame - Getting started with pygame | pygame Tutorial](https://riptutorial.com/pygame)<br>
たまたま見つけたサイトです。英語ではありますが、サンプルプログラムを写経するだけでも得るものがあると思います。