珈琲焙煎者の観察

地下部屋の珈琲焙煎者

インベーダーゲームをつくる。

youtu.be

manuke.hateblo.jp

インベーダーゲームをつくることを目標にして、pythonプログラムを学びます。前回からの続きです。

何もしないpythonプログラムコード。 gist.github.com

次は、4行目から

pygame.init()

pygameを初期化ですね。pygameの公式の解説を見てみます。

Pygame Tutorials - Import and Initialize — pygame v2.0.0.dev5 documentation

pygameで多くのことを行う前に、初期化する必要があります。 これを行う最も一般的な方法は、1回の呼び出しのみです。

pygame.init()

これにより、すべてのpygameモジュールが初期化されます。 すべてのpygameモジュールを初期化する必要はありませんが、これにより自動的に初期化されるモジュールが初期化されます。 各pygameモジュールを手で簡単に初期化することもできます。 たとえば、フォントモジュールのみを初期化するには、呼び出すだけです。

pygame.font.init()

pygame.init()で初期化するときにエラーが発生した場合、エラーが発生することに注意してください。このようなモジュールを手動で初期化すると、エラーが発生すると例外が発生します。初期化する必要のあるモジュールにはget_init()関数もあり、モジュールが初期化されている場合はtrueを返します。

DISPLAYSURF = pygame.display.set_mode((400, 300))

5行目は、ウィンドウの pygame.Surfaceオブジェクト を返すpygame.display.set_mode()関数の呼び出しです。 関数に2つの整数のタプル値を渡す。 このタプルは、ピクセル単位でウィンドウを作成する幅と高さをset_mode()関数に伝えます。(400, 300)は、幅400ピクセル、高さ300ピクセルのウィンドウを作成します。

単に2つの整数そのものではなく、2つの整数の タプル をset_mode()に渡していることに注意してください。関数を呼び出す正しい方法は、

pygame.display.set_mode((400, 300))

です。2つの整数値を()で囲んで、さらに関数の引数とするために()で囲んでいますね。

pygame.display.set_mode(400, 300)のような関数呼び出しは、次のようなエラーを引き起こします。

TypeError:引数1は、intではなく2アイテムのシーケンスでなければなりません。

変数DISPLAYSURF は幅400ピクセル、高さ300ピクセルpygame.Surfaceオブジェクト を指すことになります。

pygame.Surfaceオブジェクトってなんだ?

Surfaceオブジェクト1は、長方形の2D画像を表すオブジェクト。 Surfaceオブジェクトのピクセルは、Pygameの描画関数を呼び出して変更し、画面に表示できる。 ウィンドウの境界、タイトルバー、およびボタンは、Surfaceオブジェクトの一部ではない。 特に、pygame.display.set_mode()によって返されるSurfaceオブジェクトは、ディスプレイSurfaceと呼ばれます。 ディスプレイのSurfaceオブジェクトに描画されるものはすべて、pygame.display.update()関数 が呼び出されたときにウィンドウに表示されます。

Surfaceオブジェクトをコンピューターのモニター画面に描画するよりも、Surfaceオブジェクト(コンピューターのメモリにのみ存在する)に描画する方がはるかに高速です。

pygame.dislpay.set_caption('Hello World')

pygame.display.set_caption()関数 によってウィンドウのトップにキャプションテキストをセットします。'Hello World'の文字列の値が、関数の呼び出しに渡されてテキストがキャプションにつくられます。

f:id:honda-satoru:20191219045515p:plain
window001.py ウィンドウキャプション

game loop

ここでとり上げるゲームプログラムコードには、メインループ と呼ばれる絶えず繰り返されるループ命令のブロックがあります。

ゲームとはどのような処理をしているでしょうか。

多くのゲームでは、プレーヤーの体力と位置、敵の体力と位置、盤面に付けられたマーク、スコアなどが追跡されます。プレイヤーがダメージを受ける、敵がどこかに移動する、ゲーム世界で何かが発生するなど、何かが起こるたびに、ゲームの状態 が変化したと言います。 ゲームの状態 には変数の値が含まれていて、ゲームの状態の変化はゲームプログラム内のすべての変数の値のセットを参照することによって表現されるわけです。 保存できるゲームをプレイしたことがある場合、「保存状態」は保存した時点のゲーム状態です。

通常、ゲームの状態はイベント(マウスのクリックやキーボードの押下など)または時間の経過に応じて更新されるため、発生した新しいイベントを1秒間に何度も常にチェックおよび再チェックしています。 ゲームループは、これを受け持ちます。

メインループ内には、作成されたイベントを調べるコードがあります(Pygameでは、これは pygame.event.get()関数 を呼び出すことで行われます)。

メインループには、作成されたイベントに基づいてゲームの状態を更新するコードもあります。これは通常、イベント処理 と呼ばれます。

このプログラムの中のメインループを、 game loop ゲームループ と呼びます。

game loop は、大まかに分けると3つのことをしています。

  1. イベントを処理 .
  2. ゲームの状態を更新 .
  3. ゲームの状態を画面に描画 .

この3つがグルグルとずっと回り続けているというわけです。これがゲームループです。このゲームループというブロックがゲームのプログラムコードの中にあるということは重要です。

このゲームループは、7行目に現れてきます。

while True: #ゲームループのブロック
  for event in pygame.event.get(): # イベント処理ブロック
    if event.type == QUIT:  # ユーザーからのプログラム終了のイベントを受け取ったら
      pygame.quit() # pygameの終了
      sys.exit() # プログラムを終了させる
    pygame.display.update() # ゲームの状態を画面に描画

ユーザーがキーボードキーを押す、プログラムのウィンドウ上でマウスを動かすなど、いくつかのアクションのいずれかを行うと、pygame.event.EventオブジェクトPygameライブラリによって作成され、 イベント として記録されます。(これは、pygameモジュールの中の、eventモジュールに存在するEventというタイプのオブジェクト。)pygame.event.get() 関数を呼び出して、どのイベントが発生したかを調べることができます。 pygame.event.Eventオブジェクト(単にEventオブジェクトと呼びます)。

Eventオブジェクトのリスト は、最後に呼び出された pygame.event.get()関数 から後に発生した各イベントごとにできます。(または、pygame.event.get() が一度も呼び出されていない場合、プログラムの開始以降に発生したイベントが Eventオブジェクトのリスト になります。)

ここで リスト(Lists) という概念がでてきました。さきほど、タプル(Tuples) というのも出てきました。タプルの方は、(400, 300)というかたちでした。

リストは、 配列 と呼ばれる別のデータ構造に似ています。 リストのサイズは変更できますが、配列は変更できません。

IDLEもしくは、spyderを使っている場合、IPythonコンソールでコマンドラインを使ってリストを作って確認することが出来ます。

f:id:honda-satoru:20191219064645p:plain
IPythonコンソール

>>> x = [1,2]
>>> print(x)
[1, 2]

リスト内の個々の要素をprintするには

>>> print(x[0])
1

f:id:honda-satoru:20191219073151p:plain
list プリント

アイテムの場所を含むこの番号は、 インデックス と呼ばれます。 リストの場所はゼロから始まります。10個の要素を持つリストまたは配列にはインデックス10番番目[10]に要素がありません。 [0]から[9]で10個です。10個のアイテムのリストを作成してからインデックス10番を持たないのは、ほとんどのコンピューター言語で、1ではなく0でカウントを開始するのと同じです。 数字のリストを操作する際に考慮すべき数字のセットは2つあることを憶えておいてください。位置(インデックス)と値です。インデックスとも呼ばれる位置は 、値がある場所を指します。値は、その場所に保存されている実際の数値です。

window001.pyにもどって、8行目

for event in pygame.event.get():

このforブロックは、pygame.event.get() によって返された Eventオブジェクトのリスト を反復処理するforループです。最後の行の pygame.display.update() までが、このforのブロックに含まれます。

forループの各反復で、event という名前の変数に、このリスト内の次のイベントオブジェクトの値が割り当てられます。 pygame.event.get() から返されるイベントオブジェクトのリストは、イベントが発生した順になります。ユーザーがマウスをクリックしてキーボードキーを押すと、マウスクリックのイベントオブジェクトがリストの最初の項目になり、キーボードを押すイベントオブジェクトが2番目になります。イベントが発生していない場合、pygame.event.get() は空のリストを返します。