2. hello, pygame¶
この章では,pygame がどのようなものかを知ってもらうための簡単なプログラム例を作成します.その後,3章と4章にかけて,Python の基本事項を確認しながら拡充していきます.出来上がりは以下の GIF アニメーションのようなものになります.
Python の知識は前提とはしませんが,何らかの言語で簡単なプログラミングをしたことがあると想定しています.
これはマウス入力に反応して動くインタラクティブなアプリケーションで,以下のような操作を受け付けます.
- マウスポインタ移動: プレイヤ移動
- 左ボタン押下: 文字列表示
- ESCキー: 終了
2.1. フォルダの準備¶
このプロジェクト用のフォルダを準備します.
VSCode の でフォルダ選択ダイアログを開き,projects フォルダの中 (前回作った hello フォルダがあるところ) で右クリックして hello_pygame フォルダを作成してください.作成したフォルダを選び,フォルダの選択をクリックしてください.
アクティビティーバーから Explorer サイドバーを開くと,
hello_pygame フォルダが開かれた状態になっていると思います.
hello_pygame.py というファイルを新規作成し,ダブルクリックして開いてください.VSCode のタイトルバーには
hello_pygame.py - hello_pygame - VSCodium (Computer Seminar I) と表示されるはずです.
2.2. 図形の描画¶
まず最初に,ウィンドウを開いて適当な図形を表示するだけのプログラムを書いてみることにします.
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 の内容が表示されている状態で を実行してください.赤い円が描かれた黒いウィンドウが開き,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.circle と pygame.Color
だけです.このどちらかが怪しいぞとアタリをつけられるようになるとエラー探しは早くなります.
例えば整数型のデータというのは,値として整数のうちいずれかを取ることができて,加算,減算,乗算…などなどの演算を適用することができます.真偽値型のデータは,値として True または
False を取ることができ,AND や OR などの論理演算を適用することができます.
同様に,Surface 型のデータは,ディスプレイに表示できる2次元の画像を値として取ることができて,図形を描画したり,画像の一部を取り出したり,ディスプレイに表示したりといった操作を適用することができます.
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 キーが押されると終了することにしましょう.
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 には真と偽を表す定数
TrueとFalseが用意されています.ここでは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キーで終了するように変更」などとでもしましょうか.
7行目あたりの説明で書いた通り,このプログラムではイベント処理を無視しているので × ボタンは効きません (驚きましたか? × ボタンが押されたというのもイベントの一種です).
どう書き間違えたかにもよりますが,× ボタンを連打すると終了できる場合があります.
もう一つの方法は VSCode から終了することです.画面の上部にデバッガの制御ボタンが表示されていると思います.そのうち赤い四角 (Stop) を押してみてください.
それでもだめな場合は,タスクマネージャーから強制終了してください. Windows のタスクバーで右クリックしてタスクマネージャーをを起動します.「プロセス」タブで「アプリ」の中に Python という名前のものがあると思います.右クリックして「タスクの終了」を選択してください.
key_pressed[キーの名前] って何だか C 言語の配列みたいですね?C の配列に相当するものは Python ではシーケンスと呼ばれます.ここで出てきた key_pressed は実はシーケンスの一種です.次の章以降で少しずつ詳細を見ていきます.
2.4. マウス入力対応¶
ユーザ入力が終了キーだけではつまらないですよね.赤い円がマウスに追従して動くようにしてみます.
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 を使っているなら,ver 2.0 の時点のコードがコミットされていると思います.現在のコードとの差分を Git Graph で表示すれば (前章を参照してください),変更すべき行が正しく変更されているか,それ以外の行が書き換わっていないか確認することができます.
2.5. マウス入力対応 (再挑戦)¶
さて,「円が動く」ようなプログラムにするには,毎回の描画前に画面をクリアしてあげればよいです.そのためには,以下のように 1 行追加すれば OK です.
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.init や
pygame.display.update などのように pygame パッケージに属するものでした.
では screen.fill とは何でしょう? screen というのはこのプログラム中で勝手に作った変数でした.代入されているのは Surface 型のデータです.どうやらここでは Surface 型のデータに属する関数 fill を呼んでいるようです.
このように Python では,特定の型のデータに属する関数のようなものを定義できて,メソッド (method) と呼びます.あるデータ xyz に属するメソッド abc は xyz.abc という形式で参照することができます.ここで使った fill というメソッドは,それが属する
Surface 型データ,つまり画面を引数で指定された色で塗りつぶす操作として pygame が定義しています.
多くのプログラミング言語で,このようにメソッドを持っているデータのことをオブジェクトと呼んでいます.Python ではもう少し広い意味で用いられますが,基本的には同じだと理解してください.「変数 screen には
Surface 型のオブジェクトが代入されている (割り当てられている)」といった言い方をします.
screen.fill というメソッドを呼び出すことで screen が塗りつぶされるという点がよく理解できません.なぜ screen の状態が変化するのですか? fill を呼び出した結果が screen に代入されるということですか?screen というオブジェクトに『fill しなさい』と指示している」のだと考えてみてください.代入とは関係ありません.例えば Python の print 関数は,Python に対して「print しなさい」と指示するものだと理解することができます.同様に
pygame.display.update 関数は,pygame.display モジュールに対して「update しなさい」と指示するものと理解できます.メソッドもこれと同様で,screen オブジェクトに「fill しなさい」と指示しています.
指示された結果 screen が何をするのかは pygame の開発者がどのように定義したかで決まります.この場合は自身の全面を指定色で塗りつぶすように定義されています.
一般に,xyz.abc というメソッド呼び出しを行ったからといってオブジェクト xyz の状態が変化するとは限りません.変化するように abc が定義されていたら変化する,それだけのことです.
例えば x と y を加算するという演算は,演算子を使って
x + y と書くこともできるし,関数 add を定義して add(x,
y) と書くこともできます.これとは別に x.add(y) と書けるような定義のしかたが Python には (Python に限らず,オブジェクト指向プログラミングという考え方を採用する多くの言語には) 用意されているのだと理解するのがよいでしょう.「Python に対して,x と y
を足した結果を返すように指示する」代わりに,「x に対して,自身に y を足した結果を返すように指示する」わけです.
C 言語の構造体に慣れている人は,構造体がメンバ変数だけでなくメンバ関数を取れるようになったのだという見方をしてもよいでしょう.あるデータを独立した変数で表すべきか,構造体のメンバ変数として表すべきかはケースバイケースです.関数の場合も同様です.
2.6. 画像の表示¶
円形の代わりにファイルから読み込んだ画像を表示してみましょう.
projects フォルダの隣に assets というフォルダがあり,画像ファイルをいくつか置いています.ゲーム用のフリー素材配布サイト
から頂きました.
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引数で指定します.
これは最早知らなくてもよい古い用語です.しかし 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 に表示されるメッセージをよく見ると,プログラムファイルの場所に移動してから実行されていることがわかります)
.convert() を外してみたのですが普通に動いちゃいました.これ必要なんですか?ここで convert しておかないと,blit するたびに convert 相当の処理が走ります.本来一回だけやればよい処理を毎回やるのは無駄ですよね.
pygame.draw.circle(screen,
...) なのに,どうして画像の貼り付けは pygame.blit(screen,
image, ...) のようには書いてはいけないのですか?回答として言えるのは,pygame の設計者がそのような選択をしたから,ということだけです.何か理由があるのかも知れませんし,ただの気まぐれかも知れません.
実際,こういうことはよくあります.同じことを実現するための方法が一通りではない場合に,設計者がよいと思った選択と,利用者が自然だと感じる選択は同じでないかも知れません.
最終的には,ライブラリあるいはプログラミング言語のマニュアルをよく調べて,それに従うしかありません.
navy にしてみたら,キャラクターを囲む黒い四角が見えて格好悪いのですが,なんとかなりませんか.convert を convert_alpha に置き換えてください.PNG ファイルの透過色が保持されます.2.7. 文字の表示¶
文字の描画をしたいときも,考え方は画像表示と同じです.まず文字が描かれた小さな Surface を作成します.あとは全く同じように貼り付ければよいです.
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. 文字と画像の同時表示¶
画像と文字を同時に表示してみることにしましょう.両者が重なると残念な感じになるので,文字は,画像の場所からちょっと右にずれたところに置くことにします.
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行目
- これまでは貼り付ける
Surfaceはimageという変数に入れていましたが,画像と文字の両方を使うので区別する必要があります.どんな名前でもよいですが,text_image,player_imageという名前にしておきます. - 14行目
pygame.mouse.get_pos関数は,これまでは図形を描画したり Surface を貼り付けたりする時点で呼んでいましたが,画像と文字の両方で使うのであらかじめ呼んでおくことにします.- 17-19行目
プレイヤ画像の貼り付けはこれまでと同様です.文字はプレイヤ画像より 100 ピクセル右に表示することにします.
ここでは x 座標と y 座標の値を変数
mouse_xとmouse_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 とそのまま書かないのですか? この違いはどうやって見分ければよいですか?鍵になるのは,draw のページには冒頭に pygame module for drawing shapes と書かれており,Surface のページには冒頭に pygame object for representing images と書かれている点です.
draw はモジュールであり,そこには「普通の関数」が属していて,そのままの記法で呼び出せます.
これに対して Surface はオブジェクトなので,そこに属しているのは「メソッド」であり,メソッドの呼び出し記法を使って呼び出します.
circle とか Color とか,その前につける pygame.なんとか. の部分を省略して書かれていることが多いです.もしかしてソースコード上でも省略できるんですか?import 文の使い方を変えることで可能です.すぐ後で説明します.Color のところに「pygame object」と書かれています.Color は関数じゃなかったんですか?先回りしてざっとだけ説明すると,Color は pygame で色を表すオブジェクトの型 (クラス) 名です.Python では型名を,その型のオブジェクトを生成する関数の名前として使うことができます.
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.Color を
Color という名前で参照できるようになります.同様に,3行目によって
pygame.draw.circle の代わりに circle という関数名を使えるようになります.pygame のリファレンスマニュアル等で単に Color や
circle とだけ書かれていたのにはこういう文法的背景があったわけです.
自作のモジュールを読み込むこともできます.例えば
my_module.py というファイルを作って関数などを定義しておき,
import my_module とすることができます.具体例は後の章で見ることにします.
2.11. 演習¶
以下の例題に取り組む前に,いったん Git でコミットしておいてください.次の章では,例題の前の時点から再開します.
あるいは hello_pygame.py を別ファイルにコピーしてそちらを改変するのでもよいです.その場合は,コピーした時点でいったんコミットしておくと改変箇所がわかりやすくて便利です.
例題2-1
「hello, pygame」を表示する文字を黄色に変えてください.
例題2-3
pygame.draw.circle で円を描いたときはマウスポインタの位置が円の中心になりましたが,Surface の blit メソッドでプレイヤ画像を貼り付けたときはポインタ位置が左上になりました.
マウスポインタ位置にプレイヤ画像の中心が来るように修正してください.プレイヤ画像と「hello, pygame」画像の相対位置関係は変わらないようにしてください.
(ヒント: Surface のメソッド get_width と get_height を調べるとよいです)
