↓ メインコンテンツへスキップ
  1. 第11講 PyGame を用いたソフトウェア開発(1/3)/

キーで操作しよう

キー入力を確認する
#

参考書1 p.136(Chapter 2.1) 参考書2 p.86(Chapter 3.5)

キーボードのどのキーが押されているかを確認するには、pg.key.get_pressed 関数を用います。 この関数の返り値を調べることで、押されているキーが確認できます。 以下のような処理で確認できます。

keys = pg.key.get_pressed()
if keys[pg.K_RIGHT]:  # 右矢印が押されている
    pass
elif keys[pg.K_LEFT]: # 左矢印が押されている。
    pass

上記のように、pg.key.get_pressedはすべてのキーの入力状態を表すbool型の配列を返します。 その配列のインデックスにキーボードのキーを表す変数(キーボード定数)を指定すると、押されているか否かがわかります。

代表的なキーボード定数は次の通りです。

  • 矢印キー
    • 右 pg.K_RIGHT
    • 左 pg.K_LEFT
    • 上 pg.K_UP
    • 下 pg.K_DOWN
  • アルファベット
    • pg.K_a, pg.K_b, …, pg.K_z
  • 数値
    • pg.K_0, pg.K_1, …, pg.K_9

他のキーボード定数は、PyGame のドキュメントを参照してください。

例題11-5. 押された矢印キーを表示する
#

押されたキーを画面に表示するプログラム show_pressed_key.py を作成しましょう。 4種類の矢印キーのいずれかが押されていれば画面に押されている矢印の方向を表示します。

import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
# デフォルトのフォント、大きさ 50 のオブジェクトを得る。
dFont = pg.font.Font(None, 50)
arrows = [
    # dFont を使ってUP, DOWN, LEFT, RIGHT それぞれの文字を画像化する。
]
# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    keys = # キーの入力状態の配列を取得する。
    # 4種類の矢印キーそれぞれが押されているかを確認する。
    # 押されていれば、画面に表示する。

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。

実行例
#

クリックすることで画像が変わります。

キーボードのキーを同時に複数押しても表示するためにはどうすれば良いかを考えましょう。

🏁 解答例
import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()

# デフォルトのフォント、大きさ 50 のオブジェクトを得る。
dFont = pg.font.Font(None, 50)
arrows = [
    dFont.render("UP",    True, pg.Color("BLUE")),
    dFont.render("DOWN",  True, pg.Color("GREEN")),
    dFont.render("LEFT",  True, pg.Color("RED")),
    dFont.render("RIGHT", True, pg.Color("MAGENTA")),
]

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    keys = pg.key.get_pressed()
    if keys[pg.K_UP]:
        screen.blit(arrows[0], (10, 10))
    if keys[pg.K_DOWN]:
        screen.blit(arrows[1], (10, 60))
    if keys[pg.K_LEFT]:
        screen.blit(arrows[2], (10, 110))
    if keys[pg.K_RIGHT]:
        screen.blit(arrows[3], (10, 160))

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。

マウス(トラックパッド)操作を確認する
#

参考書1 p.143(Chapter 2.2)

マウス(トラックパッドも含む)jが押されてことを確認するには、 pg.mouse.get_pressed関数を用います。 加えて、どの場所で押されたかを確認するために、pg.mouse.get_pos関数が利用できます。

pg.mouse.get_pressed 関数は 3つのbool値のタプルを返します。 それぞれ左マウスボタン、中マウスボタン、右マウスボタンが押されたか否かを表します。

pg.mouse.get_pos関数は、マウスが押された箇所の(x)座標と(y)座標をタプルで返します。

mouse = pg.mouse.get_pressed()
(mx, my) = pg.mouse.get_pos()
if mouse[0]: # 左マウスボタンが押された。

例題11-6. マウスが押された位置を表示する
#

マウスが押された箇所を表示するプログラム show_pressed_pos.py を作成しましょう。 マウスがクリックされたとき、クリックされた位置に座標を表示するようにしましょう。 右クリックされたら、R(x, y)、中クリックならば M(x, y)、左クリックならば L(x, y)を表示しましょう。 この x, yは実際の座標に置き換えてください。

import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()

# デフォルトのフォント、大きさ 50 のオブジェクトを得る。
dFont = pg.font.Font(None, 50)

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    # pg.mouse.get_pressed() でマウスの状態を取得する。
    # pg.mouse.get_pos() でマウスクリックの位置を取得する。
    # 右クリック、左クリック、中クリックそれぞれで適切な文字列を画像化し、img に代入する。
    # どのボタンもクリックされていなければ、img に None を代入する。
    # img が None でなければ、マウスクリックされた箇所に img を描画する。

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。

実行例
#

macOSのトラックパッドでは中クリックは実現できません(アプリを導入すれば可能ですが、この授業では対象外とします)。

🏁 解答例
import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()

# デフォルトのフォント、大きさ 50 のオブジェクトを得る。
dFont = pg.font.Font(None, 50)

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    mouse = pg.mouse.get_pressed()
    (mx, my) = pg.mouse.get_pos()
    if mouse[0]:
        img = dFont.render(f"R({mx}, {my})", True, pg.Color("BLUE"))
    elif mouse[1]:
        img = dFont.render(f"M({mx}, {my})", True, pg.Color("RED"))
    elif mouse[2]:
        img = dFont.render(f"L({mx}, {my})", True, pg.Color("GREEN"))
    else:
        img = None

    if img is not None:
        screen.blit(img, (mx, my))

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。

キャラクタを操作する
#

キャラクタの画像を読み込み、キー入力で上下左右に動かせるようにしましょう(move_character.py)。 その際、キャラクタの向き変えた画像に置き換えて描画してみましょう。 また、画面外に出ない要因もしてみましょう。

キャラクタ画像のいくつかは Moodle に掲載しています。

これらは「ぴぽや倉庫」にて配布されている素材を再配布しています。

スプライトシート
#

ダウンロードするキャラクタ画像は、1枚の画像に向きなどが異なる複数の画像が収まっています。 これは、1枚の画像の適切な箇所を切り取ることで、向きや足踏みなどで複数の画像を読み込むより効率が良いためです。

例えば、上記のサイトで配布されている画像の一例が下のものです。

soldier.png

この画像は、横に3つ、縦に4つの計12の画像が一つの画像ファイルとして収められています。 これを直接描画するのではなく、この画像の中の1部分のみを描画できれば、一つの画像を読み込むだけで複数の動作が描画できるようになります。 PyGame では、画像を描画する screen.blit の3つ目の引数で描画範囲を設定できます。 以下のプログラム(show_character.py)は、コマンドライン引数でどの位置の画像を描画するのかを選択できるようにしたものです。 何度か実行し、元の画像の内容と実行結果を見比べて、動作を確認してみましょう。

import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
player = pg.image.load(sys.argv[1])
if len(sys.argv) == 2:
    ix, iy = 0, 0
else:
    ix, iy = (int(sys.argv[2]), int(sys.argv[3]))
GW = player.get_rect().width  / 3
GH = player.get_rect().height / 4
sFont = pg.font.Font(None, 50)
index_img = sFont.render(f"(ix, iy) = ({ix}, {iy})", True, pg.Color("BLUE"))
pixel_img = sFont.render(f"(GW, GH) = ({ix * GW}, {iy * GH})", True, pg.Color("RED"))

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    screen.blit(player, (100, 100), (ix * GW, iy * GH, GW, GH))
    screen.blit(index_img, (100, 100 + GH + 5))
    screen.blit(pixel_img, (100, 100 + GH + 55))

    # 2-4. 画面を表示する。
    pg.display.update()

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。

実行例
#

以下の実行結果は、コマンドライン引数で ix、iyを変更して何度か実行した結果を示しています。 クリックで ix、iyを変更した時の実行結果が示されます。

この実行結果と、画像の内容を確認して、どの部分が描画されているのかを確認しましょう。

例題11-7. キャラクタを動かす
#

これまでの内容を踏まえて、矢印キーでキャラクタを自由に動かしてみましょう。 上方向に移動する時は、キャラクタの向きを上向にし、下方向に移動する時は下向きにするようにしましょう。 左右も同様です。 また、画面外にキャラクタが出ないようにしましょう。

そのためには、メインループの中で押されたキーを調べ、 押されたキーに応じて、キャラクタの位置と向きを変更する処理を追加します。

import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
player = pg.image.load(sys.argv[1])
ix, iy = 1, 0 # 画像中のどの部分を表示するのかをインデックス。
GW, GH =      # GW, GH を player 画像から求める。
dx, dy = 0, 0 # 移動量
rect = pg.Rect(0, 0, 32, 32) # 初期位置

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    keys = pg.key.get_pressed()
    if keys[pg.K_UP]:
        dx, dy, iy = # 移動方向と画像を更新する。
    elif keys[pg.K_DOWN]:
        dx, dy, iy = # 移動方向と画像を更新する。
    elif keys[pg.K_LEFT]:
        dx, dy, iy = # 移動方向と画像を更新する。
    elif keys[pg.K_RIGHT]:
        dx, dy, iy = # 移動方向と画像を更新する。
    else:
        dx, dy = 0, 0 # 移動しない。キャラクタの向きは変更しない。

    nbox = rect.move(dx, dy)          # 移動先の座標を調べる。
    if screen.get_rect().contains(nbox): # 移動先の座標が screen の内側であれば
        rect = nbox                   # 移動先を更新する。
    screen.blit(player, rect, (ix * GW, iy * GH, GW, GH))

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。
🏁 解答例
import pygame as pg # PyGame をインポートし、pg という名前で使えるようにする。
import sys          # sys モジュールをインポートする(終了処理で使う)。

# 1. ゲームの準備をする。
pg.init()
screen = pg.display.set_mode((800, 600))
clock = pg.time.Clock()
player = pg.image.load(sys.argv[1])
ix, iy = 1, 0
GW, GH = (player.get_rect().width / 3, player.get_rect().height / 4)
dx, dy = 0, 0
rect = pg.Rect(0, 0, 32, 32)

# 2. メインループ
while True:
    # 2-1 画面を初期化する。
    screen.fill(pg.Color("WHITE"))  # 画面を白色で塗りつぶす。

    # 2-3. 絵を描いたり、判定したりする。
    keys = pg.key.get_pressed()
    if keys[pg.K_UP]:
        dx, dy, iy = 0, -1, 3
    elif keys[pg.K_DOWN]:
        dx, dy, iy = 0, 1, 0
    elif keys[pg.K_LEFT]:
        dx, dy, iy = -1, 0, 1
    elif keys[pg.K_RIGHT]:
        dx, dy, iy = 1, 0, 2
    else:
        dx, dy = 0, 0
    
    if keys[pg.K_LSHIFT] or keys[pg.K_RSHIFT]:
        dx = dx * 5
        dy = dy * 5

    nbox = rect.move(dx, dy)
    if screen.get_rect().contains(nbox):
        rect = nbox
    screen.blit(player, rect, (ix * GW, iy * GH, GW, GH))

    # 2-4. 画面を表示する。
    pg.display.update()
    clock.tick(60) # 画面の更新を1秒間に60回以下のスピードに設定する。

    # 2-5. 閉じるボタンが押されたら終了する。
    for event in pg.event.get():  # イベント一覧を取得して、各イベントを調べる。
        if event.type == pg.QUIT: # もし、閉じるボタンが押されたら。
            pg.quit()             # PyGame を終了する。これだけではウィンドウは閉じない。
            sys.exit()            # ウィンドウを閉じて、プログラムを終了する。