2. hello, pygame

この章では,pygame がどのようなものかを知ってもらうための簡単なプログラム例を作成します.その後,3章と4章にかけて,Python の基本事項を確認しながら拡充していきます.出来上がりは以下の GIF アニメーションのようなものになります.

Python の知識は前提とはしませんが,何らかの言語で簡単なプログラミングをしたことがあると想定しています.

これはマウス入力に反応して動くインタラクティブなアプリケーションで,以下のような操作を受け付けます.

  • マウスポインタ移動: プレイヤ移動
  • 左ボタン押下: 文字列表示
  • ESCキー: 終了

2.1. フォルダの準備

このプロジェクト用のフォルダを準備します.

VSCode の File ‣ Open Folder でフォルダ選択ダイアログを開き,projects フォルダの中 (前回作った hello フォルダがあるところ) で右クリックして hello_pygame フォルダを作成してください.作成したフォルダを選び,フォルダの選択をクリックしてください.

アクティビティーバーから Explorer サイドバーを開くと, hello_pygame フォルダが開かれた状態になっていると思います. hello_pygame.py というファイルを新規作成し,ダブルクリックして開いてください.VSCode のタイトルバーには hello_pygame.py - hello_pygame - VSCodium (Computer Seminar I) と表示されるはずです.

_images/titlebar_hello_pygame_py.png

2.2. 図形の描画

まず最初に,ウィンドウを開いて適当な図形を表示するだけのプログラムを書いてみることにします.

hello_pygame.py (ver 1.0)
1import pygame
2
3pygame.init()
4screen = pygame.display.set_mode((600, 400))
5pygame.draw.circle(screen, pygame.Color("red"), (300, 200), 30)
6pygame.display.update()
7pygame.time.wait(5000)
8pygame.quit()
1行目
import 文は,Python に追加機能を取り込むための文です.ここでは pygame の機能を使えるようにしています.
2行目
空行は適宜あけて構いません.処理の塊ごとにあけておくと読みやすくなります.
3行目

この行では pygame を初期化しています.

Python ではいろいろなところで . (ドット演算子, dot operator) を使います. aaa.bbb は概ね「aaa に属する bbb」という意味で,この行の場合は「pygame パッケージに属する init という関数」を指します. print 関数と同様,後ろに丸かっこをつけることで呼び出します.

pygame.init 関数は引数なしで呼び出すことができ,pygame を初期化します.pygame を使うプログラムの最初で必ず呼び出します.

4行目

pygame の描画のための画面を作成しています.

この文は全体として screen = ... という構造をしています.これは例えば x = 1 + 2 のような文と同じ構造です.右辺の処理の結果を左辺の変数に代入 (substitute) します.割り当てる (assign) ともいいます.

右辺では「pygame パッケージに属する display モジュールに属する set_mode という関数」を呼び出しています.「…に属する」といちいち読むのは面倒なので,pygame.display.set_mode という関数が用意されているのだと考えてしまっても大丈夫です.でもときどき本来の意味も思い出してください.

pygame.display.set_mode は画面のサイズ等を指定する関数です.サイズは2 つの整数の「組」 (タプルまたはテュープル, tuple) を引数として渡すことで指定します.この例では横 600 ピクセル (画素),縦 400 ピクセルのサイズを指定しています.

丸かっこが二重になっていることに違和感があるかもしれませんが,これは必要なものです.丸かっこが 1 つしかない場合,1 つめの引数として 600 を,2 つめの引数として 400 を渡すという意味になり,これは pygame.display.set_mode が要求している引数とは違います.600 と 400 をタプルにして丸かっこで括り,1 個の引数として渡す必要があります.

関数 pygame.display.set_mode は,その処理の結果として,設定が終わった描画用の「画面」を返します.返された画面は,左辺の変数 screen に代入されます.以降 screen に対していろいろな操作をすることで図形を描画したりディスプレイに表示したりできます.

「変数に画面を代入する」などと言われるとギョッとするかもしれませんが恐がることはありません.\(y = \cos(x)\) という数式で,右辺の関数 \(\cos\) が返す実数型の値を変数 \(y\) に代入できるのと構造は全く同じです.pygame.display.set_mode は pygame で「画面」を表す Surface 型の値を返し,これが変数 screen に代入されます.

screen は単なる変数名なので他の名前に変えても構いません.例えば window とか canvas とか好きに名付けて結構です.もちろんその場合は以降の行の screen も自分でつけた変数名に入れ替えてください.

5行目

4行目で作った screen に円を描きます.pygame.draw.circle という関数を使います.

引数を4つ指定しています.順に,描画先の Surface,色,中心座標,半径を指定する引数です.

pygame で色を表す値は,pygame.Color 関数に文字列型の引数として色名を渡すことで作ることができます.この例では赤色を指定しています.

座標は整数または実数のタプルで指定します.第1引数で指定された Surface の左上を原点とし,右向きが x の正の方向,下向きが y の正の方向です.普通の数学とは y の方向が違う点に注意してください.これはコンピュータ上で画像を扱う際の慣習です.

6行目
5行目で円を「描画」しましたが,まだディスプレイには反映されていません.pygame.display.update 関数を呼ぶことでディスプレイの表示に反映します.
7行目
6行目までで「ディスプレイに図形を描く」目的は達せられましたが,すぐにプログラムを終了してしまうと一瞬でウィンドウが閉じてしまいほぼ何も見えません.pygame.time.wait 関数で 5000 ミリ秒待つことにします.
8行目
pygame.quit 関数で pygame の利用を終了します.この行がプログラムの最後なので,プログラム自体の実行も終了します.

無事入力できたら,もしセーブしていないなら忘れずにファイルをセーブしてください.セーブしたら,hello_pygame.py の内容が表示されている状態で Run ‣ Run Without Debugging を実行してください.赤い円が描かれた黒いウィンドウが開き,5秒後に勝手に閉じれば成功です.

エラーが出て実行できません.
短いコードなので丁寧に見比べていけば間違いが見つかると思いますが,エラーメッセージをよく見るとヒントが得られることが多いです.

特に,エラーが発生した行番号がわかると強力な手がかりになることが多いです.VSCode で Python プログラムを実行すると,パネル領域の Terminal (端末) と書かれたタブが開き,ここが Windows のコマンドプロンプトのような役割を果たします.例えば以下のメッセージが表示されたとしましょう.

pygame 2.0.1 (SDL 2.0.14, Python 3.9.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "c:\cs1\projects\hello_pygame\hello_pygame.py", line 5, in <module>
    pygame.draw.circle(screen, pygame.color("red"), (300, 200), 30)
TypeError: 'module' object is not callable

大量のメッセージに心が折れそうになるのを我慢して上から読んでいくと,最初の 2 行は単に pygame が表示しているクレジット情報です (これはエラーの有無に関わらず毎回出ます).

その次の行以降がエラーの発生したところを示しています.ここに多数の行が表示されることもあるのですが「most recent call last」と書かれている通り,「最終的に何が起きたのか」を知りたい場合は最後の行から見ていくのがよいです.Python の場合,こうやって「エラーメッセージを後ろから読んでいく」のが基本です.

わかったことは,5 行目で「TypeError: 'module' object is not callable」というエラーが発生したということです.そこで 5 行目をよく見比べると,Color と大文字にしなくてはならないところが小文字の color になっています.ここを直せば良いはずです.

エラーメッセージの意味を読み解くのは初心者には難しいことが多いです.それでも,理解できそうな単語を拾って考えてみるのは有用です.今回の場合は「is not callable」が鍵で,要は「呼び出そうとしたものが呼び出せなかった」と言っているわけです.5 行目で呼び出そうとしている関数は pygame.draw.circlepygame.Color だけです.このどちらかが怪しいぞとアタリをつけられるようになるとエラー探しは早くなります.

型って何ですか?
プログラムの中で使われるデータの種類を表すもので,それらがどのような値を取ることができ,どのような演算・操作を適用することができるかを規定するものを (type) と呼びます.

例えば整数型のデータというのは,値として整数のうちいずれかを取ることができて,加算,減算,乗算…などなどの演算を適用することができます.真偽値型のデータは,値として True または False を取ることができ,AND や OR などの論理演算を適用することができます.

同様に,Surface 型のデータは,ディスプレイに表示できる2次元の画像を値として取ることができて,図形を描画したり,画像の一部を取り出したり,ディスプレイに表示したりといった操作を適用することができます.

Python には整数型とか実数型とは別に Surface 型というのがあるのですか?
Surface (正確には pygame.Surface) は pygame が独自に定義した型です.

Python を含め,多くのプログラミング言語では独自の型を定義することができます.それによりプログラムの抽象度を高めて,読みやすく,書きやすくすることができます.詳しい話は 5 章で扱います.

2.2.1. 変更履歴を記録する

次に進む前に,ここまでの内容を Git リポジトリにコミットしておきます.

このフォルダはまだ git init されていませんので,アクティビティバーから Source Control を開くと Initialize Repository というボタンが現れるはずです.クリックしてください.これが git init に相当します.

後は前回同様に hello.py の右側の \(+\) ボタンで Stage Changes し,コミットメッセージを入力して,チェックマークのボタン (Commit) をクリックしてください.メッセージは例えば「Show a red circle for 5 seconds」とか「赤い円を5秒間表示する」といった程度で OK です.

Git Graph を開いて,確かに履歴が記録されていることを確認してください.

以降も,いちいち指示しませんが,プログラムを改変して動作が確認できるたびにこまめに Git でコミットしておくようにしてください.

2.3. ループとキー入力

5秒で勝手に終了するアプリケーションではつまらないので,ユーザが終了を指示するまで表示し続けるように変更しましょう.

この手のインタラクティブなプログラムの基本構造はだいたい以下のようになります:

初期化処理

while 永遠に:
    入力の処理
    (もし必要なら) いろいろな計算
    画面の描画

終了処理

while 永遠に: のところはこれらの処理を永遠に繰り返すことを意味します.しかし本当に永遠に繰り返してプログラムが止まらないと困るので,途中で繰り返しから抜け出せるようにしておきます.ここでは Esc キーが押されると終了することにしましょう.

hello_pygame.py (ver 2.0)
 1import pygame
 2
 3pygame.init()
 4screen = pygame.display.set_mode((600, 400))
 5
 6while True:
 7    pygame.event.clear()
 8    key_pressed = pygame.key.get_pressed()
 9    if key_pressed[pygame.K_ESCAPE]:
10        break
11
12    pygame.draw.circle(screen, pygame.Color("red"), (300, 200), 30)
13    pygame.display.update()
14
15pygame.quit()

色付きで強調した行が変更箇所です.

6行目

ここから13行目までを while 文と呼び,ループ実行を指示します.最初の行に while ループ継続条件: のように書くことで,条件が成立している限り繰り返すことになります.

C 言語に慣れている人は,条件を丸かっこで囲む必要がないことと,条件の後にコロン : を書く必要があることに注意してください.

条件は真偽値型 (真理値型,論理値型,ブール型, Boolean) の値で指定します.Python には真と偽を表す定数 TrueFalse が用意されています.ここでは True を指定することで,常に繰り返す,つまり無限ループをさせます.

7行目

ここから while 文によって繰り返される処理本体 (Python の用語では suite と呼びます) が始まります.

行頭に空白文字によるインデント (字下げ) があることに注意してください.インデントによって while 文の中であることが示されます.この行から,while と同じインデント深さに戻る 15 行目の直前までの内容が繰り返されます.

逆に言うと,Python では行頭の空白が意味を持つので,勝手に空白を入れたり除いたりしてはいけません.

この7行目では,pygame.event.clear 関数を呼ぶことで,入力イベントをクリアしています.イベントというのは,例えばキー入力があった,マウスが動いた,マウスボタンが押されたなど,pygame の実行中に外部から発生する事象の総称です.

通常は,これらを順にチェックしてプログラムの動作に反映させていきますが,今回は何もせず pygame にすべて任せます.しかし何もしないからといって放っておくと,どんどんイベントが溜まっていきます.これを避けるため,溜まっているイベントをすべてクリアしておきます.

8行目
7行目の pygame.event.clear には,一部のイベントを pygame 内部で処理させる効果もあります.その結果, pygame.key.get_pressed という関数を呼ぶことによって,その時点で押されているキー一覧の情報を得ることができるようになります.押されているキー一覧を key_pressed という変数に代入しておきます.
9行目

9行目は if 文の開始行です.構文は while 文と同様で,指定された条件が真ならば,その次の行から,if と同じインデント深さである 12 行目の直前までの部分が実行されます.

pygame.key.get_pressed が返した値 key_pressed は,後ろに [キーの名前] をつけると,そのキーが押されているなら True として,押されていないなら False として評価されます.

ここではキーの名前として pygame.K_ESCAPE を指定しているので,Esc キーが押されたら if 文の中身が実行されることになります.

10行目
if 文の中身はこの行だけです.break 文は,現在実行中のループ処理を中断します.現在実行中のループはここでは while 文のことなので,この行が実行された場合はループを抜けて 15 行目に進み,プログラムは終了します.
12-13行目
ここで描画処理を実行します.内容は一つ前の例と同じです (行頭にインデントが入っただけの違いです).ここが while 文の中身の最後なので,6 行目に戻ります.

while 文があるので,書き間違えるとプログラムが止まらなくおそれがあります.よく確認してから実行してみてください.正しく書けていれば,Esc キーを押すまで待ってから終了するようになるはずです.うまく動くようなら Git でコミットしてください.コミットメッセージは,そうですね,「Enable Esc key to quit the program.」「Escキーで終了するように変更」などとでもしましょうか.

プログラムが止まらなくなりました.ウィンドウ右上の × ボタンを押しても何も起きません.どうしたらいいですか?
VSCode から終了させるか,それもダメならタスクマネージャーから終了させましょう.

7行目あたりの説明で書いた通り,このプログラムではイベント処理を無視しているので × ボタンは効きません (驚きましたか? × ボタンが押されたというのもイベントの一種です).

どう書き間違えたかにもよりますが,× ボタンを連打すると終了できる場合があります.

もう一つの方法は VSCode から終了することです.画面の上部にデバッガの制御ボタンが表示されていると思います.そのうち赤い四角 (Stop) を押してみてください.

それでもだめな場合は,タスクマネージャーから強制終了してください. Windows のタスクバーで右クリックしてタスクマネージャーをを起動します.「プロセス」タブで「アプリ」の中に Python という名前のものがあると思います.右クリックして「タスクの終了」を選択してください.

key_pressed[キーの名前] って何だか C 言語の配列みたいですね?
はい,似たようなものだと思ってよいです.

C の配列に相当するものは Python ではシーケンスと呼ばれます.ここで出てきた key_pressed は実はシーケンスの一種です.次の章以降で少しずつ詳細を見ていきます.

2.4. マウス入力対応

ユーザ入力が終了キーだけではつまらないですよね.赤い円がマウスに追従して動くようにしてみます.

hello_pygame.py (ver 3.0)
 1import pygame
 2
 3pygame.init()
 4screen = pygame.display.set_mode((600, 400))
 5
 6while True:
 7    pygame.event.clear()
 8    key_pressed = pygame.key.get_pressed()
 9    if key_pressed[pygame.K_ESCAPE]:
10        break
11
12    pygame.draw.circle(screen, pygame.Color("red"), pygame.mouse.get_pos(), 30)
13    pygame.display.update()
14
15pygame.quit()

変更箇所は12行目だけです.pygame.mouse.get_pos 関数が呼び出されると,その時点のマウスポインタの位置を取得し,x 座標と y 座標のタプルとして返します.これを描画する円の中心座標として使うだけです.

…動かしてみましたか.「思ってたんと違う!」そうですね.円が動くというよりはペイントブラシで線画を描くようなアプリケーションになってしまいました.でもこれがプログラムした通りの動作です.

ともかく,これはこれでいったん Git でコミットしておきましょう.予定外の成果でも,後で参照したくなることがあるかもしれません.コミットメッセージは,何でもよいですよ.「Make the circle follow mouse pointer, without clearing screen.」「マウスに追従して動かす.画面クリア無し」とか.

動作が確認できない内容を Git でコミットしたらダメですか?
個人で開発している分には全く構いません.ただし,「動かない」ことがわかるようにコミットメッセージに書いておいてください.
今回のコードは1行だけの変更ですよね.この行を間違いなく書き換えたはずなのですが,なぜか動きません.
よくあるのは,この行以外のどこかをうっかり書き換えてしまったというケースです.変更したはずの行以外もよく確認する必要があります.とはいえ全部の行をチェックするのは大変ですね.Git の差分表示機能が便利です.

こまめに Git を使っているなら,ver 2.0 の時点のコードがコミットされていると思います.現在のコードとの差分を Git Graph で表示すれば (前章を参照してください),変更すべき行が正しく変更されているか,それ以外の行が書き換わっていないか確認することができます.

直前にコミットしたバージョンと現在のものだけではなく,過去の任意の時点の差分を見たいです.
Git Graph 上で Ctrl + 左クリックでコミットを 2 つ選んでください.その 2 点間の差分を表示できます.

2.5. マウス入力対応 (再挑戦)

さて,「円が動く」ようなプログラムにするには,毎回の描画前に画面をクリアしてあげればよいです.そのためには,以下のように 1 行追加すれば OK です.

hello_pygame.py (ver 4.0)
 1import pygame
 2
 3pygame.init()
 4screen = pygame.display.set_mode((600, 400))
 5
 6while True:
 7    pygame.event.clear()
 8    key_pressed = pygame.key.get_pressed()
 9    if key_pressed[pygame.K_ESCAPE]:
10        break
11
12    screen.fill(pygame.Color("black"))
13    pygame.draw.circle(screen, pygame.Color("red"), pygame.mouse.get_pos(), 30)
14    pygame.display.update()
15
16pygame.quit()

screen.fill に引数として色を与えて呼び出すことで,画面全体をその色で塗りつぶすことができます.black を指定することで,画面をクリアしたことになります.

変更点はただこれだけですが,実はここでは,これまで出てこなかった概念が使われています.これまで使ってきた関数は,print のように Python に最初から用意されているものか,pygame.initpygame.display.update などのように pygame パッケージに属するものでした.

では screen.fill とは何でしょう? screen というのはこのプログラム中で勝手に作った変数でした.代入されているのは Surface 型のデータです.どうやらここでは Surface 型のデータに属する関数 fill を呼んでいるようです.

このように Python では,特定の型のデータに属する関数のようなものを定義できて,メソッド (method) と呼びます.あるデータ xyz に属するメソッド abcxyz.abc という形式で参照することができます.ここで使った fill というメソッドは,それが属する Surface 型データ,つまり画面を引数で指定された色で塗りつぶす操作として pygame が定義しています.

多くのプログラミング言語で,このようにメソッドを持っているデータのことをオブジェクトと呼んでいます.Python ではもう少し広い意味で用いられますが,基本的には同じだと理解してください.「変数 screen には Surface 型のオブジェクトが代入されている (割り当てられている)」といった言い方をします.

screen.fill というメソッドを呼び出すことで screen が塗りつぶされるという点がよく理解できません.なぜ screen の状態が変化するのですか? fill を呼び出した結果が screen に代入されるということですか?
詳しい仕組みは 5 章で学びますが,直観的な捉え方としては「screen というオブジェクトに『fill しなさい』と指示している」のだと考えてみてください.代入とは関係ありません.

例えば Python の print 関数は,Python に対して「print しなさい」と指示するものだと理解することができます.同様に pygame.display.update 関数は,pygame.display モジュールに対して「update しなさい」と指示するものと理解できます.メソッドもこれと同様で,screen オブジェクトに「fill しなさい」と指示しています.

指示された結果 screen が何をするのかは pygame の開発者がどのように定義したかで決まります.この場合は自身の全面を指定色で塗りつぶすように定義されています.

一般に,xyz.abc というメソッド呼び出しを行ったからといってオブジェクト xyz の状態が変化するとは限りません.変化するように abc が定義されていたら変化する,それだけのことです.

メソッドの考え方はわかりましたが,その存在意義がどうもピンと来ません.普通の関数とはどう違って,何のためにあるのですか?
これも 5 章以降で学ぶことですが,ひとまずは「関数の定義のしかたにはいろいろある」とだけ理解してください.

例えば xy を加算するという演算は,演算子を使って x + y と書くこともできるし,関数 add を定義して add(x, y) と書くこともできます.これとは別に x.add(y) と書けるような定義のしかたが Python には (Python に限らず,オブジェクト指向プログラミングという考え方を採用する多くの言語には) 用意されているのだと理解するのがよいでしょう.「Python に対して,x と y を足した結果を返すように指示する」代わりに,「x に対して,自身に y を足した結果を返すように指示する」わけです.

C 言語の構造体に慣れている人は,構造体がメンバ変数だけでなくメンバ関数を取れるようになったのだという見方をしてもよいでしょう.あるデータを独立した変数で表すべきか,構造体のメンバ変数として表すべきかはケースバイケースです.関数の場合も同様です.

2.6. 画像の表示

円形の代わりにファイルから読み込んだ画像を表示してみましょう.

projects フォルダの隣に assets というフォルダがあり,画像ファイルをいくつか置いています.ゲーム用のフリー素材配布サイト から頂きました.

hello_pygame.py (ver 5.0)
 1import pygame
 2
 3pygame.init()
 4screen = pygame.display.set_mode((600, 400))
 5image = pygame.image.load("../../assets/player/p1_walk01.png").convert()
 6
 7while True:
 8    pygame.event.clear()
 9    key_pressed = pygame.key.get_pressed()
10    if key_pressed[pygame.K_ESCAPE]:
11        break
12
13    screen.fill(pygame.Color("black"))
14    screen.blit(image, pygame.mouse.get_pos())
15    pygame.display.update()
16
17pygame.quit()
5行目

ここで変数 image に画像ファイルの内容を読み込みます.

画像ファイルを読み込む関数は pygame.image.load です.引数としてファイルの場所を文字列で指定します.場所を指定する際,フォルダの区切りは / で表し,また「1つ上のフォルダ」は .. で表します.今 projects/hello_pygame フォルダでプログラムを実行しているので,フォルダを 2 つ上がったところに assets フォルダがあるわけです.その中の player/p1_walk01.png を指定しています.

pygame.image.load 関数は,読み込んだ画像の内容を Surface 型のオブジェクトとして返します.ウィンドウ全体の画面を表すのと同じ型ですが,ファイルから読み込んだ画像データを表すのにも使います.これを変数 screen が持っている画面の上に「貼り付ける」ことができれば目的達成です.そのための準備として convert メソッドを呼び,貼り付けに適した画像フォーマットに変換しています.その結果を変数 image に代入しています.

convert メソッドの呼び出し方は,慣れていないと理解しづらいかも知れません.ここでは pygame.image.load が返した値に直接ドット演算子をつけてメソッドを呼んでいます.つまり,次の 2 行をまとめて実行したのと同じです.このような呼び方はメソッドチェーンと呼ばれます:

image_original = pygame.image.load("../../assets/player/p1_walk01.png")
image = image_original.convert()
14行目
pygame.draw.circle で円を描画していた行を,image を貼り付ける処理に置き換えます.使っているのは Surface 型のメソッド blit で,自分自身 (この場合は screen が保持している画面) の上に,第1引数で指定された Surface 型オブジェクト (この場合 image が保持している画像) を貼り付けます.貼り付ける座標は第2引数で指定します.
blit ってどういう意味ですか?
どうやら bit block transfer の略のようです.

これは最早知らなくてもよい古い用語です.しかし pygame では関数名として生き残りました.そもそも bit block transfer を略しても blit にならないだろ,という気もするのですが,歴史的事情というやつです.

VSCode からではなくコマンドプロンプトからプログラムを実行したらエラーになりました.何が間違っているのでしょうか.

C:\cs1\projects>python hello_pygame\hello_pygame.py
pygame 2.0.1 (SDL 2.0.14, Python 3.9.5)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "C:\cs1\projects\hello_pygame\hello_pygame.py", line 5, in <module>
    image = pygame.image.load("../../assets/player/p1_walk01.png").convert()
FileNotFoundError: No such file or directory.
hello_pygame.py が置かれているフォルダに移動してから実行してください.具体的には以下のようにします.
C:\cs1\projects>cd hello_pygame
C:\cs1\projects\hello_pygame>python hello_pygame.py

このプログラムは,hello_pygame.py が置かれているフォルダと,これが実行されているフォルダが同じであることを前提として,画像ファイルの場所を指定しています.

普段あまり気にすることはないかも知れませんが,実行中のプログラムは必ずカレントディレクトリ (current directory, 現在のフォルダ) がどこかという情報を保持しています.それは,プログラムのファイルがどこにあったかとは直接関係しません.コマンドプロンプトからの実行であれば,コマンド実行時にどこにいたかで決まります.

(VSCode からの実行の場合も,Terminal に表示されるメッセージをよく見ると,プログラムファイルの場所に移動してから実行されていることがわかります)

5行目の .convert() を外してみたのですが普通に動いちゃいました.これ必要なんですか?
なくても動くのですが,少し実行が遅くなるかも知れません.

ここで convert しておかないと,blit するたびに convert 相当の処理が走ります.本来一回だけやればよい処理を毎回やるのは無駄ですよね.

普通の関数とメソッドの両方があることは理解しましたが,その使い分けがよくわかりません.円の描画は pygame.draw.circle(screen, ...) なのに,どうして画像の貼り付けは pygame.blit(screen, image, ...) のようには書いてはいけないのですか?
これは難しい質問です.正直なところよくわかりません.

回答として言えるのは,pygame の設計者がそのような選択をしたから,ということだけです.何か理由があるのかも知れませんし,ただの気まぐれかも知れません.

実際,こういうことはよくあります.同じことを実現するための方法が一通りではない場合に,設計者がよいと思った選択と,利用者が自然だと感じる選択は同じでないかも知れません.

最終的には,ライブラリあるいはプログラミング言語のマニュアルをよく調べて,それに従うしかありません.

背景色を navy にしてみたら,キャラクターを囲む黒い四角が見えて格好悪いのですが,なんとかなりませんか.
convertconvert_alpha に置き換えてください.PNG ファイルの透過色が保持されます.

2.7. 文字の表示

文字の描画をしたいときも,考え方は画像表示と同じです.まず文字が描かれた小さな Surface を作成します.あとは全く同じように貼り付ければよいです.

hello_pygame.py (ver 6.0)
4screen = pygame.display.set_mode((600, 400))
5font = pygame.font.Font(None, 50)    
6image = font.render("hello, pygame", True, pygame.Color("green"))
7
8while True:
5行目

pygame.font.Font は文字フォント (書体) を作成する関数です.第1 引数にフォントファイルを,第2引数にフォントサイズを指定します.返ってくる値は Font 型です.変数 font に代入しておきます.

第1引数に指定している None は Python で「何も指していない」「何も指定しない」ことを示すために使われるものです.C 言語に慣れている人は NULL に近いものだ思っておくとわかりやすいかも知れません.第1引数に None を指定すると,pygame のデフォルト (既定) のフォントが使われます.

6行目

font にメソッド render を適用します.render はここでは「画像化する」くらいの意味です.第1引数に画像化する文字列を,第2引数にアンチエイリアスをかけるかどうかを指示する真偽値を,第3引数に文字色を指定します.

アンチエイリアスというのは,背景との境界がギザギザに見えるのを防ぐためにぼかしをかける処理のことです.

日本語の文字列が表示できません.
pygame.font.Font の第1引数に,None ではなくてフォント名を指定する必要があります.例えば "hg正楷書体pro" などを指定してみてください.

使用できるフォントは環境によって違います. pygame.font.get_fonts() を呼ぶとフォント一覧を表示することができます:

>>> import pygame
>>> pygame.font.get_fonts()

2.8. 文字と画像の同時表示

画像と文字を同時に表示してみることにしましょう.両者が重なると残念な感じになるので,文字は,画像の場所からちょっと右にずれたところに置くことにします.

hello_pygame.py (ver 7.0)
 1import pygame
 2
 3pygame.init()
 4screen = pygame.display.set_mode((600, 400))
 5font = pygame.font.Font(None, 50)    
 6text_image = font.render("hello, pygame", True, pygame.Color("green"))
 7player_image = pygame.image.load("../../assets/player/p1_walk01.png").convert()
 8
 9while True:
10    pygame.event.clear()
11    key_pressed = pygame.key.get_pressed()
12    if key_pressed[pygame.K_ESCAPE]:
13        break
14    mouse_pos = pygame.mouse.get_pos()
15
16    screen.fill(pygame.Color("black"))
17    screen.blit(player_image, mouse_pos)
18    mouse_x, mouse_y = mouse_pos
19    screen.blit(text_image, (mouse_x + 100, mouse_y))
20    pygame.display.update()
21
22pygame.quit()
6-7行目
これまでは貼り付ける Surfaceimage という変数に入れていましたが,画像と文字の両方を使うので区別する必要があります.どんな名前でもよいですが,text_image, player_image という名前にしておきます.
14行目
pygame.mouse.get_pos 関数は,これまでは図形を描画したり Surface を貼り付けたりする時点で呼んでいましたが,画像と文字の両方で使うのであらかじめ呼んでおくことにします.
17-19行目

プレイヤ画像の貼り付けはこれまでと同様です.文字はプレイヤ画像より 100 ピクセル右に表示することにします.

ここでは x 座標と y 座標の値を変数 mouse_xmouse_y に取り出しています.代入の左辺をカンマ区切りにすることで,タプルの構成要素をばらして代入することができます.これは C 言語などには無い Python の便利な記法です.

2.9. pygame のリファレンスマニュアルを読む

pygame の機能のごく一部を駆け足で見てきました.ここまで読んで,おそらくいろいろな疑問が湧いてきたのではないかと思います.内側を塗りつぶさない円周を描くには? 円以外の図形を描くには? 他にどんな色が使える? …などなど.

それらすべてをここで説明することはしません.そうではなく,自分で調べることができるようになってもらいます.まず pygame の公式ドキュメントを開いてください.インストール方法やチュートリアルを始めとして多数の文書がありますが,そのうちのリファレンスマニュアルの読み方を見ていきます.

例として,円を描画するのに使った pygame.draw.circle 関数について調べてみましょう.公式ドキュメントのページ上部に「Most useful stuff」として挙げられている draw を開きます.

うう…,こういう形式ばったマニュアルを読むのは苦手です.
draw のページの一番下までスクロールしてみてください.サンプルコードがあります.先にそちらを眺めるとわかりやすいかも知れません.

残念ながら pygame のリファレンスマニュアルのすべてのページにサンプルコードがついているわけではありません.他のサンプルコードが見たければ, https://www.pygame.org/docs/ に掲載されている Tutorials を開いて使用例を探してみるのも手です.

ページを開くと, pygame.draw.*** という名前の関数などが列挙されています. pygame.draw.circle もすぐ見つかります.以下のような記載があるはずです.

pygame.draw.circle()

draw a circle

circle(surface, color, center, radius) -> Rect
circle(surface, color, center, radius, width=0,
       draw_top_right=None, draw_top_left=None,
       draw_bottom_left=None, draw_bottom_right=None) -> Rect

ここに書かれているのは pygame.draw.circle (略して circle) の呼び出し方です.1 つめの例では,surface, color, center, radius という 4 つの引数を渡しています.この章でやった通りですね.

その後ろに -> Rect と書かれています.これは, pygame.draw.circle を呼び出すと Rect 型の値が返されるということを意味しています.この章の例では返り値は使いませんでしたが,実は何らかの値を返しているようです.

それぞれの引数の意味は,少し下に記載があります.細かいところがわからなかったとしても気にしなくて構いません.int が整数,float が実数, tuple がタプルのことであるとわかれば,およそこの章で見てきた通りのことが書かれていると読み取れると思います.

Parameters:
  • surface (Surface) -- surface to draw on
  • color (Color or int or tuple(int, int, int, [int])) -- color to draw with, the alpha value is optional if using a tuple (RGB[A])
  • center (tuple(int or float, int or float) or list(int or float, int or float) or Vector2(int or float, int or float)) -- center point of the circle as a sequence of 2 ints/floats, e.g. (x, y)
  • radius (int or float) -- radius of the circle, measured from the center parameter, nothing will be drawn if the radius is less than 1
  • width (int) -- (optional) used for line thickness or to indicate that the circle is to be filled
  • ...
  • (以下略)

さらに,どうやら 5 つめ以降の引数もあるようだとわかります.上では 5 つめの width まで引用して後は略しました.5 つめ以降の引数には「(optional)」,つまり省略可能という記載があります.Python の関数は,一部の引数が省略可能なように作ることができるのです.

省略された場合に使われる値 (デフォルト値) は,各引数の説明の中にもありますが,先に引用した呼び出し例の 2 つめにも

circle(surface, color, center, radius, width=0,
       draw_top_right=None, draw_top_left=None,
       draw_bottom_left=None, draw_bottom_right=None) -> Rect

のように示されています.width を省略すると 0 が使われるという意味です.もう一度引数 width の説明を読んでみると,0 なら塗りつぶしになり,正の数なら線の太さになると読み取れます.これで塗りつぶし以外の円が描けそうです.

pygame.draw.circle が説明されているページには,他にも ellipse, line, polygon などの関数が用意されています.他の図形も描けそうです.また,このページの最後にはいくつかの図形を描くサンプルコードがあります.

色についても見てみましょう.「Most useful stuff」の Color を開いてもよいですし,pygame.draw.circle の 2 つめの引数の説明のところから Color を開いても構いません.

pygame.Color

pygame object for color representations

Color(r, g, b) -> Color
Color(r, g, b, a=255) -> Color
Color(color_value) -> Color

これを見ると pygame.Color は引数 3 個の場合,4 個の場合,1 個の場合があるようです.この章で使ったときは引数 1 個でした.なので,下の方の引数の説明から color_value を見つけます.

color_value の説明を読むと「Supported color_value formats」として多数の記法が用意されていることがわかります.この章で使ったのは,そのうち「color name str」というものです.str というのは string,つまり文字列の型です.名前一覧が欲しければ以下のページを見ろと書いてあるので見てみましょう.

これはもはやドキュメントじゃなくて pygame を構成するプログラムの一部です.しかしプログラムとして読み解く必要はありません.色名として使える文字列がリストアップされていることだけわかればひとまず十分です.

こんなふうにリファレンスマニュアルを読めるようになると,できることがどんどん増えていきます.全部を完全に理解できる必要はありません.読み取れる情報に着目しながら,試行錯誤してみてください.

pygame.draw.circle の使い方は,リファレンスマニュアルの draw のページに「pygame.draw.circle」と書いてあるのでその通り書けばよいのだとわかるのですが,screen.fill とか screen.blit の方がよくわかりません.マニュアルの Surface のページには「pygame.Surface.fill」や「pygame.Surface.blit」と書かれているのに,なぜプログラムには pygame.Surface.fill とそのまま書かないのですか? この違いはどうやって見分ければよいですか?
ああ,この点 pygame のマニュアルはわかりにくいですね.

鍵になるのは,draw のページには冒頭に pygame module for drawing shapes と書かれており,Surface のページには冒頭に pygame object for representing images と書かれている点です.

draw はモジュールであり,そこには「普通の関数」が属していて,そのままの記法で呼び出せます.

これに対して Surface はオブジェクトなので,そこに属しているのは「メソッド」であり,メソッドの呼び出し記法を使って呼び出します.

リファレンスマニュアルを見ると circle とか Color とか,その前につける pygame.なんとか. の部分を省略して書かれていることが多いです.もしかしてソースコード上でも省略できるんですか?
import 文の使い方を変えることで可能です.すぐ後で説明します.
リファレンスマニュアルの Color のところに「pygame object」と書かれています.Color は関数じゃなかったんですか?
オブジェクト (の型名) でもあり,関数でもあります.この点を理解するには5章で学ぶ概念が必要です.

先回りしてざっとだけ説明すると,Color は pygame で色を表すオブジェクトの型 (クラス) 名です.Python では型名を,その型のオブジェクトを生成する関数の名前として使うことができます.

公式リファレンスマニュアルが難しいです.ググって出てきたページを参考にするのではだめですか?
だめではないですが,情報の正しさや新しさの保証がないことに注意してください (特に pygame はバージョン 1.x と 2.x で結構違います).

pygame に限らずですが,適当にウェブ検索して出てきた「わかりやすそうなページ」には,情報の古いものや信頼性の低いものが含まれている可能性があることに常に注意してください.何かおかしいと思ったら公式情報に当たるのが原則です.

信頼できるかできないかを判断するのはなかなか難しいのですが,少なくとも,ウェブ検索結果が上位だからといって信頼できるとは限らないことは認識しておく方がよいでしょう.特に,以下のように検索ランキングやページ閲覧数を上げることにやたら心血を注いでいそうなページは要注意です:

  • 情報量の無いフリー画像素材でコンテンツが増量されている
  • 謎キャラクタの情報量の無い会話でコンテンツが増量されている
  • ページタイトルにやたらと「?」とか「!」が含まれている
  • 最後の方で「いかがでしたか?」とか言ってる

2.10. import 文

このプログラムの先頭に import pygame と書き,pygame の機能を使えるようにしました.import 文では,pygame のような外部ライブラリのほか, math, sys などのような Python 言語に標準付属するライブラリを読み込むのにも使います.Python ではライブラリはモジュールと呼ばれる単位で管理されます:

import math          # Python 標準付属モジュール
import pygame        # 標準外のモジュールもインポート方法は同じ

import 文の使い方にはいくつかのバリエーションがあります:

1import pygame as pg            # モジュール名に別名をつける
2from pygame import Color       # モジュール名を略して使えるようにする
3from pygame.draw import circle

1行目のようにすることで,例えば pygame.init() の代わりに pg.init() と書けるようになります (その代わり pygame.init() とは書けなくなります).

2行目のように from を使うことで,pygame.ColorColor という名前で参照できるようになります.同様に,3行目によって pygame.draw.circle の代わりに circle という関数名を使えるようになります.pygame のリファレンスマニュアル等で単に Colorcircle とだけ書かれていたのにはこういう文法的背景があったわけです.

自作のモジュールを読み込むこともできます.例えば my_module.py というファイルを作って関数などを定義しておき, import my_module とすることができます.具体例は後の章で見ることにします.

2.11. 演習

以下の例題に取り組む前に,いったん Git でコミットしておいてください.次の章では,例題の前の時点から再開します.

あるいは hello_pygame.py を別ファイルにコピーしてそちらを改変するのでもよいです.その場合は,コピーした時点でいったんコミットしておくと改変箇所がわかりやすくて便利です.

例題2-1

「hello, pygame」を表示する文字を黄色に変えてください.

例題2-2

マウスポインタ位置を中心とする青い十字線を描画し,「hello, pygame」の周りを取り囲むような赤い楕円を表示してください.

線の太さや楕円の位置・サイズは適宜調整してください.

_images/ex2-2.png

例題2-3

pygame.draw.circle で円を描いたときはマウスポインタの位置が円の中心になりましたが,Surfaceblit メソッドでプレイヤ画像を貼り付けたときはポインタ位置が左上になりました.

マウスポインタ位置にプレイヤ画像の中心が来るように修正してください.プレイヤ画像と「hello, pygame」画像の相対位置関係は変わらないようにしてください.

(ヒント: Surface のメソッド get_widthget_height を調べるとよいです)