Turtle Graphicsのキー操作をマスター!onkeypress()徹底解説

2025-06-06

`turtle.onkeypress()` は Python の `turtle` モジュールにおける非常に便利な関数で、特定のキーが押されたときに何らかの処理(関数)を実行したい場合に使用します。

**基本的な考え方**

`turtle` グラフィックスでは、通常、矢印キー(上、下、左、右)やスペースキー、文字キー('a', 'b' など)といったキーイベントを検出して、タートル(描画するカメ)の動きを制御したり、描画内容を変更したりすることがよくあります。`turtle.onkeypress()` は、この「キーが押された」というイベントと、そのイベントが発生したときに「実行すべき処理」を紐付ける役割を果たします。

**構文**

`turtle.onkeypress(関数, キー)`

* **`関数`**: キーが押されたときに実行したい処理を記述した関数を指定します。この関数は引数を取りません。
* **`キー`**: 監視したいキーの名前を文字列で指定します。
    * 一般的なキー名: `"Up"`, `"Down"`, `"Left"`, `"Right"` (矢印キー), `"space"` (スペースキー), `"Return"` (Enterキー), `"Escape"` (Escキー)
    * 文字キー: `"a"`, `"b"`, `"C"` など、小文字でも大文字でも指定できます。

**使用例**

以下に、`turtle.onkeypress()` の典型的な使用例を示します。

```python
import turtle

# スクリーンオブジェクトを取得
screen = turtle.Screen()

# タートルオブジェクトを作成
t = turtle.Turtle()

# 関数を定義
def move_forward():
    t.forward(10)

def turn_left():
    t.left(90)

def turn_right():
    t.right(90)

# キープレスイベントを設定
screen.onkeypress(move_forward, "Up")      # 上矢印キーが押されたら move_forward を実行
screen.onkeypress(turn_left, "Left")       # 左矢印キーが押されたら turn_left を実行
screen.onkeypress(turn_right, "Right")     # 右矢印キーが押されたら turn_right を実行
screen.onkeypress(lambda: t.circle(50), "space") # スペースキーが押されたら円を描く (無名関数も使える)
screen.onkeypress(lambda: t.clear(), "c") # 'c' キーが押されたら描画をクリア

# キーイベントのリスニングを開始
# これがないとキーイベントが検出されない
screen.listen()

# メインループを開始 (ウィンドウを開き続ける)
screen.mainloop()
  1. screen.listen() の呼び出し: turtle.onkeypress() を設定した後、必ず screen.listen() を呼び出す必要があります。これを呼び出さないと、キーイベントがturtle グラフィックスシステムによって検出されません。
  2. screen.mainloop() の呼び出し: プログラムが終了しないように、通常は screen.mainloop() を最後に呼び出します。これは turtle グラフィックスウィンドウを開き続け、イベントループを維持します。
  3. 引数なしの関数: onkeypress() に渡す関数は、引数を取るべきではありません。もし引数が必要な場合は、lambda 関数を使って引数を「ラップ」することができます(上記のスペースキーの例を参照)。
  4. キー名の指定: キー名は正確な文字列で指定する必要があります。例えば、"Up" は大文字小文字が区別されます("up" では動作しない可能性があります)。


キーイベントが全く反応しない

これは最もよくある問題です。

原因

  • キー名の誤り
    指定したキー名が正確でない。
  • フォーカスの問題
    turtle ウィンドウがアクティブな(フォーカスが当たっている)状態でないと、キー入力が検出されません。
  • screen.mainloop() の呼び忘れ
    プログラムがすぐに終了してしまい、キーイベントを処理する時間がありません。
  • screen.listen() の呼び忘れ
    turtle.onkeypress() でイベントハンドラを登録しても、turtle グラフィックスシステムがキーイベントを「聞く」状態になっていないと、何も起きません。

トラブルシューティング

  • キー名の確認
    • 矢印キー: "Up", "Down", "Left", "Right" は、最初の文字が大文字である必要があります。
    • スペースキー: "space" または "Space"
    • Enterキー: "Return"
    • Escキー: "Escape"
    • 通常の文字キー: "a", "b", "C" など。大文字小文字は通常区別されませんが、環境によっては影響する場合があります。
    • 特殊なキー (F1, Homeなど): これらのキーはOSや環境によって名前が異なる場合があります。もし反応しない場合は、Pythonの公式ドキュメントやオンラインリソースで特定のキーの表記を確認してください。
  • ウィンドウのフォーカス
    プログラムを実行した後、turtle グラフィックスウィンドウをクリックして、それがアクティブなウィンドウであることを確認してください。
  • screen.mainloop() を確認
    プログラムの最後に screen.mainloop() があることを確認してください。これにより、ウィンドウが開き続け、キーイベントが処理されます。
  • screen.listen() を確認
    turtle.onkeypress() を設定した後、必ず screen.listen() を呼び出していることを確認してください。
    import turtle
    screen = turtle.Screen()
    # ... onkeypress の設定 ...
    screen.listen() # これが非常に重要
    screen.mainloop()
    

関数が一度だけ実行されてしまう、または意図せずすぐに実行される

原因

  • 関数呼び出し時の誤り
    onkeypress() に渡す関数名の後に () を付けてしまっている。これは関数を「登録」するのではなく、その場で「実行」してしまいます。

トラブルシューティング

  • 関数名のみを渡す
    onkeypress() には、関数の「名前」だけを渡します。括弧 () を付けないでください。
    def my_function():
        # 何らかの処理
    
    # 悪い例 (すぐに実行されてしまう)
    # screen.onkeypress(my_function(), "a")
    
    # 良い例 (キーが押されたときに実行される)
    screen.onkeypress(my_function, "a")
    

イベントハンドラ関数に引数を渡したいが、エラーになる

原因

  • turtle.onkeypress() に登録する関数は、引数を取らないことが前提とされています。

トラブルシューティング

  • lambda 関数を使用する
    引数が必要な関数を登録したい場合は、lambda (無名関数) を使ってその関数をラップします。lambda は新しい引数なしの関数を作成し、その中で引数ありの関数を呼び出します。
    def draw_shape(shape_name):
        if shape_name == "circle":
            t.circle(50)
        elif shape_name == "square":
            for _ in range(4):
                t.forward(50)
                t.right(90)
    
    # 悪い例 (引数があるためエラーになる)
    # screen.onkeypress(draw_shape("circle"), "c")
    
    # 良い例 (lambda を使って引数を渡す)
    screen.onkeypress(lambda: draw_shape("circle"), "c")
    screen.onkeypress(lambda: draw_shape("square"), "s")
    
    lambda: draw_shape("circle") は、「引数を取らず、draw_shape("circle") を実行する新しい無名関数」を定義しています。

複数のキーを同時に押しても反応しない(ゲーム的な操作など)

原因

  • turtle モジュールは、通常、単一のキープレスイベントにのみ対応しています。一般的なゲームで使われるような複数のキーの同時押し(例: 前進しながら左を向く)には直接対応していません。

トラブルシューティング

  • イベントベースの設計
    turtle のイベントは、キーが「押された」瞬間 (onkeypress) と「離された」瞬間 (onkeyrelease) のイベントに分かれています。これらのイベントを組み合わせて、キーの状態を追跡するロジックを自分で実装する必要があります。

    • キーが押されたら、そのキーの「状態」を記録するフラグを True に設定。
    • キーが離されたら、そのキーの「状態」を False に設定。
    • メインループ(ontimer などで定期的に呼び出される関数)内で、これらのフラグの状態を見てタートルを動かす。

    例 (概念)

    import turtle
    
    screen = turtle.Screen()
    t = turtle.Turtle()
    
    # キーの状態を保持する辞書
    key_states = {"Up": False, "Left": False, "Right": False}
    
    def press_up():
        key_states["Up"] = True
    def release_up():
        key_states["Up"] = False
    
    def press_left():
        key_states["Left"] = True
    def release_left():
        key_states["Left"] = False
    
    def press_right():
        key_states["Right"] = True
    def release_right():
        key_states["Right"] = False
    
    # キーが押された時と離された時のイベントを設定
    screen.onkeypress(press_up, "Up")
    screen.onkeyrelease(release_up, "Up")
    screen.onkeypress(press_left, "Left")
    screen.onkeyrelease(release_left, "Left")
    screen.onkeypress(press_right, "Right")
    screen.onkeyrelease(release_right, "Right")
    
    # 定期的に実行される更新関数
    def update_turtle_position():
        if key_states["Up"]:
            t.forward(5)
        if key_states["Left"]:
            t.left(5)
        if key_states["Right"]:
            t.right(5)
    
        # 10ミリ秒後に再度この関数を呼び出す
        screen.ontimer(update_turtle_position, 10)
    
    screen.listen()
    update_turtle_position() # 更新処理の開始
    screen.mainloop()
    

    これは少し複雑になりますが、ゲームのような操作を実現するためにはこのようなアプローチが必要です。

turtle.textinput() や turtle.numinput() と onkeypress() の併用

原因

  • turtle.textinput()turtle.numinput() は、ユーザーからの入力を待つ間、turtle のイベントループを一時停止させます。この間は onkeypress() のイベントが処理されません。
  • または、これらの入力関数を使用せず、tkinter などのより低レベルなイベントハンドリングやウィジェットを直接利用することを検討することもできます。しかし、それは turtle の手軽さを損なうことになります。
  • これらは本質的な動作なので、基本的には避けられません。textinputnuminput を呼び出す前に、必要な onkeypress イベントハンドラを無効化するか、これらの入力が完了するまでキー入力に頼らない設計にするのが良いでしょう。


例1:基本的なタートルの移動制御(矢印キー)

最も基本的な例で、上下左右の矢印キーでタートルを動かします。

import turtle

# 1. スクリーンオブジェクトの作成
#    グラフィックスを描画するウィンドウ
screen = turtle.Screen()
screen.setup(width=600, height=400) # ウィンドウサイズを設定
screen.title("タートル移動制御")     # ウィンドウのタイトルを設定

# 2. タートルオブジェクトの作成
#    描画するカメ(タートル)
my_turtle = turtle.Turtle()
my_turtle.shape("turtle")  # タートルの形をカメにする
my_turtle.color("blue")    # タートルの色を青にする
my_turtle.speed(0)         # 描画速度を最速にする (アニメーションを滑らかにするため)

# 3. キーが押されたときに実行する関数を定義
def move_forward():
    """上矢印キーで前進する関数"""
    my_turtle.forward(10) # 10ピクセル前進

def turn_left():
    """左矢印キーで左に90度回転する関数"""
    my_turtle.left(90)

def turn_right():
    """右矢印キーで右に90度回転する関数"""
    my_turtle.right(90)

def move_backward():
    """下矢印キーで後退する関数"""
    my_turtle.backward(10) # 10ピクセル後退

# 4. キープレスイベントと関数を紐付ける
#    screen.onkeypress(実行する関数, 'キー名')
screen.onkeypress(move_forward, "Up")      # 'Up' (上矢印キー) が押されたら move_forward() を実行
screen.onkeypress(turn_left, "Left")       # 'Left' (左矢印キー) が押されたら turn_left() を実行
screen.onkeypress(turn_right, "Right")     # 'Right' (右矢印キー) が押されたら turn_right() を実行
screen.onkeypress(move_backward, "Down")   # 'Down' (下矢印キー) が押されたら move_backward() を実行

# 5. キーイベントのリスニングを開始
#    これがないとキーイベントが検出されない
screen.listen()

# 6. メインループの開始
#    ウィンドウを開き続け、イベントを待ち受ける
screen.mainloop()

解説

  • screen.mainloop() は、プログラムを終了させずにウィンドウを開いたままにし、イベント(キープレスなど)を継続的に処理するために必要です。
  • screen.listen() がないと、キーイベントがturtleグラフィックスによって検出されません。
  • screen.onkeypress() を使って、特定のキー名("Up", "Left", "Right", "Down") が押されたときに、対応する関数が実行されるように登録しています。
  • move_forward, turn_left, turn_right, move_backward という4つの関数は、それぞれのキーに対応するタートルの動作を定義しています。
  • my_turtle.speed(0) は、描画速度を最速に設定し、タートルの動きを滑らかに見せるための一般的な設定です。
  • screen.setup()screen.title() でウィンドウの見た目を整えています。

例2:文字キーと特殊キーでの描画変更

文字キーやスペースキーを使って、タートルの描画内容を変更する例です。

import turtle

screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.title("描画内容変更")

t = turtle.Turtle()
t.shape("circle") # 形を丸にする
t.speed(0)
t.penup()         # ペンを上げて移動(線を描かない)
t.goto(-200, 0)   # 開始位置に移動
t.pendown()       # ペンを下ろして描画準備

# キーが押されたときに実行する関数を定義
def draw_square():
    """'s' キーで四角形を描く関数"""
    t.pencolor("red") # ペンの色を赤に
    t.pensize(3)      # ペンの太さを3に
    for _ in range(4):
        t.forward(100)
        t.right(90)

def draw_circle():
    """'c' キーで円を描く関数"""
    t.pencolor("green") # ペンの色を緑に
    t.pensize(2)
    t.circle(50) # 半径50の円を描く

def reset_drawing():
    """'space' (スペースキー) で描画をリセットする関数"""
    t.clear()    # 描画されたものをすべてクリア
    t.penup()
    t.home()     # タートルを初期位置 (0,0) に戻す
    t.pendown()
    t.pencolor("black") # ペンの色を黒に戻す
    t.pensize(1)

# キープレスイベントと関数を紐付ける
screen.onkeypress(draw_square, "s")       # 's' キーで四角形
screen.onkeypress(draw_circle, "c")       # 'c' キーで円
screen.onkeypress(reset_drawing, "space") # 'space' (スペースキー) でリセット

screen.listen()
screen.mainloop()

解説

  • ここでは "s" (小文字のS), "c" (小文字のC), "space" (スペースキー) といったキー名を使用しています。
  • reset_drawing() は、t.clear() で画面をきれいにし、t.home() でタートルを初期状態に戻しています。
  • draw_square()draw_circle() は、それぞれ特定の図形を描画し、ペンの色や太さも変更しています。
  • t.penup()t.pendown() を使って、タートルが移動する際に線を描くか描かないかを制御しています。

例3:lambda 関数を使った引数ありの関数呼び出し

onkeypress() に直接引数を渡すことはできませんが、lambda 関数を使うことで引数を持つ関数を呼び出すことができます。

import turtle

screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.title("Lambda関数で引数を渡す")

t = turtle.Turtle()
t.speed(0)

# 引数を受け取る描画関数
def draw_polygon(sides, size, color):
    """引数に応じて多角形を描画する関数"""
    t.penup()
    t.goto(-size/2, size/2) # 描画開始位置を調整
    t.pendown()
    t.pencolor(color)
    t.pensize(2)
    angle = 360 / sides
    for _ in range(sides):
        t.forward(size)
        t.right(angle)
    t.penup()
    t.home() # 描画後、タートルをホームに戻す

def clear_screen():
    """画面をクリアする関数"""
    t.clear()
    t.penup()
    t.home()
    t.pendown()

# キープレスイベントと関数を紐付ける (lambdaを使用)
# lambda: 関数名(引数1, 引数2, ...) の形式
screen.onkeypress(lambda: draw_polygon(3, 100, "blue"), "t") # 't' キーで青い三角形
screen.onkeypress(lambda: draw_polygon(4, 80, "red"), "s")   # 's' キーで赤い四角形
screen.onkeypress(lambda: draw_polygon(6, 70, "green"), "h") # 'h' キーで緑の六角形
screen.onkeypress(clear_screen, "c") # 'c' キーでクリア

screen.listen()
screen.mainloop()
  • t.goto() を使って多角形の描画開始位置を調整し、画面中央から描画されるようにしています。
  • lambda: draw_polygon(3, 100, "blue") のように記述することで、キーが押されたときに「引数なしの無名関数」が実行され、その中で draw_polygon が適切な引数と共に呼び出されます。これは、onkeypress() が引数なしの関数しか受け付けないという制約を回避するための一般的なテクニックです。
  • onkeypress() に直接 draw_polygon(3, 100, "blue") のように書くと、プログラム開始時に一度だけ関数が実行されてしまい、キーイベントには反応しません。
  • draw_polygon() 関数は、sides (辺の数)、size (辺の長さ)、color (色) という3つの引数を取ります。


turtle.onkeypress() は特定のキーが押された「瞬間」に反応するイベント駆動型の方法ですが、それだけでは実現が難しい、またはより高度なキー操作をしたい場合に、いくつかの代替手段が考えられます。

turtle.onkey() と turtle.onkeyrelease() の組み合わせ

onkeypress() と似ていますが、これはキーが「押された時」と「離された時」のイベントを別々に扱えるため、より細かな制御が可能です。特にゲームのように、キーが押され続けている間は動き続け、キーが離れたら止まる、といった挙動を実装する際に有用です。

特徴

  • 同時押しに近い挙動を実装できる(後述の「キーの状態を追跡する」方法と併用)。
  • キーが押された時 (onkey) と離された時 (onkeyrelease) の両方のイベントを捕捉できる。

コード例 (キーを押し続けている間移動)

import turtle

screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.title("onkey/onkeyrelease で移動")

t = turtle.Turtle()
t.speed(0) # 最速

# キーの状態を管理する変数
is_moving_forward = False
is_turning_left = False
is_turning_right = False

# キーが押された時の処理
def press_forward():
    global is_moving_forward
    is_moving_forward = True

def release_forward():
    global is_moving_forward
    is_moving_forward = False

def press_left():
    global is_turning_left
    is_turning_left = True

def release_left():
    global is_turning_left
    is_turning_left = False

def press_right():
    global is_turning_right
    is_turning_right = True

def release_right():
    global is_turning_right
    is_turning_right = False

# 定期的に実行される更新関数
def update_game_state():
    if is_moving_forward:
        t.forward(5)
    if is_turning_left:
        t.left(3) # 少しずつ回転
    if is_turning_right:
        t.right(3) # 少しずつ回転
    
    # 10ミリ秒後に再度この関数を呼び出す
    screen.ontimer(update_game_state, 10)

# キーイベントの設定
screen.onkey(press_forward, "Up")
screen.onkeyrelease(release_forward, "Up")

screen.onkey(press_left, "Left")
screen.onkeyrelease(release_left, "Left")

screen.onkey(press_right, "Right")
screen.onkeyrelease(release_right, "Right")

screen.listen()

# ゲームループの開始
update_game_state() # 最初の呼び出し
screen.mainloop()

解説

  • この方法は、複数のキーの同時押し(例:前進しながら左に曲がる)といった、より複雑な操作を実装するために非常に強力です。
  • screen.ontimer() を使って、update_game_state() 関数を一定間隔(ここでは10ミリ秒)で繰り返し呼び出します。これにより、キーの状態に応じてタートルが連続的に動く「ゲームループ」のようなものが実現できます。
  • is_moving_forward などのグローバル変数(またはクラスの属性)を使って、各キーの状態(押されているか離されているか)を追跡します。

tkinter イベントと直接連携する

turtleモジュールは、実際にはPythonの標準GUIライブラリであるtkinterの上に構築されています。したがって、tkinterのより低レベルなイベントハンドリング機構を直接利用することも可能です。これはturtleの機能だけでは実現できない、より高度なカスタマイズが必要な場合に有効です。

特徴

  • 学習コストがやや高い。
  • turtleの抽象化を越えて、より柔軟なプログラミングが可能。
  • tkinterの持つ全てのイベントタイプ(キープレス、キーリリース、マウスイベント、ウィンドウイベントなど)を細かく制御できる。

コード例 (概念)

import turtle
import tkinter as tk

screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.title("Tkinterイベントと連携")

t = turtle.Turtle()
t.speed(0)

# turtleの内部にあるtkinterのCanvasウィジェットを取得
canvas = screen.getcanvas()

def on_key_press(event):
    """Tkinterのキープレスイベントハンドラ"""
    # event.keysym はキーのシンボル名 (例: 'Up', 'a', 'space')
    # event.char は文字の場合の文字自体 (例: 'a', ' ')
    print(f"キーが押されました: {event.keysym}")
    if event.keysym == "Up":
        t.forward(10)
    elif event.keysym == "Left":
        t.left(90)
    elif event.keysym == "Right":
        t.right(90)
    elif event.keysym == "space":
        t.clear()

# Tkinterのイベントをバインド
# '<Key>' は任意のキーが押された時のイベント
# '<KeyPress-Up>' のように特定のキーを指定することも可能
canvas.bind("<Key>", on_key_press) 
# canvas.bind("<KeyPress-Up>", lambda event: t.forward(10)) # 特定のキーに直接バインドも可能

screen.listen() # turtleのイベント処理を有効にするため、一応呼び出す
screen.mainloop()

解説

  • この方法は、turtleonkeypressよりも詳細なイベント情報を扱いたい場合や、tkinterの他のウィジェットとturtleを組み合わせたい場合に特に有効です。
  • canvas.bind("<Key>", on_key_press) は、Canvas上で任意のキーが押されたときに on_key_press 関数を呼び出すように設定します。
  • screen.getcanvas() を使って、turtleが描画しているtkinterCanvasウィジェットを取得します。

外部ライブラリを使用する (keyboardモジュールなど)

turtleモジュールに限定されない、システム全体のキー入力を捕捉するライブラリも存在します。例えば、keyboardモジュールは、Pythonプログラムが実行されている間、グローバルなキー入力を監視できます。

特徴

  • 外部ライブラリのインストールが必要 (pip install keyboard)。
  • turtleモジュールとは独立してキー入力を処理できる。
  • キーの組み合わせ (ctrl+cなど) や、キーの長押しなどをより簡単に検出できる。
  • turtleウィンドウがアクティブでなくてもキー入力を検出できる(バックグラウンドで動作可能)。

コード例 (概念)

import turtle
import keyboard # pip install keyboard でインストールが必要

screen = turtle.Screen()
screen.setup(width=600, height=400)
screen.title("外部ライブラリでキー入力")

t = turtle.Turtle()
t.speed(0)

def move_forward_kb():
    print("前方へ移動! (keyboard)")
    t.forward(10)

def turn_left_kb():
    print("左へ回転! (keyboard)")
    t.left(90)

# 'Up'キーが押されたら move_forward_kb を実行
keyboard.on_press_key("up", lambda e: move_forward_kb())
# 'left'キーが押されたら turn_left_kb を実行
keyboard.on_press_key("left", lambda e: turn_left_kb())

print("キー入力を待機中... (ウィンドウをアクティブにしてください)")

screen.listen()
screen.mainloop()

# プログラム終了時に keyboard イベントリスナーを停止
# keyboard.unhook_all() # 必要であれば

解説

  • keyboardライブラリは強力ですが、システム全体のキー入力を扱うため、特にゲームなどで複数のキー同時押しを検出し、その状態を継続的に管理する場合は、前述の onkey/onkeyreleaseontimer の組み合わせの方が適していることが多いです。
  • この方法は、turtleのウィンドウにフォーカスがなくてもキー入力を検出できるという点で、他の方法と大きく異なります。

turtle.onkeypress() が最もシンプルで手軽なキー入力ハンドリング方法ですが、より複雑な挙動や特殊な要件がある場合は、以下の代替手段を検討すると良いでしょう。

  1. turtle.onkey() と turtle.onkeyrelease() + screen.ontimer()
    キーの押しっぱなしや同時押しのようなゲーム的な挙動に最適。
  2. tkinter イベントとの直接連携
    turtleの枠を超えた、より低レベルで詳細なイベント制御が必要な場合に強力。
  3. 外部ライブラリ (keyboardなど)
    turtleウィンドウのフォーカスに関わらずシステム全体のキー入力を監視したい場合に便利。