Pythonロボティクスコース レッスン 26

テーマ.7-1 忍者ゲームを制作しよう

モジュールを利用してゲームを制作しよう!

このレッスンで学ぶこと

テーマ.7では、これまで学習してきた文法を活用して、少し処理が複雑なゲームのプログラムを作成します。このレッスンでは、ゲームの製作に共通で必要な機能をモジュール化したり、モジュールをパッケージにまとめて管理したりする方法について学びます。

チャプター2

新しいPython文法の学習

はじめに、モジュールやパッケージについて、それぞれどういったものであったか改めて確認していきましょう。

2. 1 モジュールとは?

モジュールとは「関連性のあるプログラムの部品をひとつにまとめたもの」です。プログラム中に何度も出てくるコードをひとつのファイルにまとめ、名前を付けてモジュール化します。これまで使用してきた、Python/Studuino:bit/ArtecRoboのそれぞれのモジュール例を下の表にまとめていますので、おさらいしましょう。

【 Pythonで標準的に用意されているモジュール例 】
モジュール名説明
time現在の日時を取得したり、時間の経過を計測したりする関数がまとめられたモジュール
randomランダムな処理を行う場合に使用できる関数がまとめられたモジュール
【 Studuino:bit専用のモジュール例 】
モジュール名説明
dsplyLEDディスプレイを制御するためのクラスが定義されているモジュール
imageLEDディスプレイ上に表示するイメージを扱うためのクラスが定義されているモジュール
bzrブザーを制御するためのクラスが定義されているモジュール
buttonボタンを扱うためのクラスが定義されているモジュール
boardStuduino:bitの各種パ―ツを扱うクラスから作成したインスタンスが定義されているモジュール
【 ArtecRobo専用のモジュール例 】
モジュール名説明
partsロボット用の各種パーツを制御するためのクラスが定義されているモジュール

2. 2 パッケージとは?

パッケージとは複数のモジュールを束ねたものです。

パッケージの構造を工具棚に例えると、工具棚(パッケージ)に複数の工具ケース(モジュール) があり、さらにその中にそれぞれの工具(変数/関数/クラス)が収納されているという関係です。つまり、「パッケージ > モジュール > 変数・関数・クラス」の階層構造になっています。

さきほど確認したStuduino:bit用のモジュールやArtecRobo用のモジュールも、それぞれ以下のパッケージに梱包されています。

【 Studuino:bitやArtecRobo用のモジュールを梱包しているパッケージ 】
パッケージ名梱包されている代表的なモジュール
pystubitdsply、image、bzr、button、board
pyatcrobo2parts

2. 3 パッケージの中身の確認

では、実際にMuでpyatcrobo2パッケージの内容を確認してみましょう。

Studuino:bitをUSBケーブルで接続し、メニューの「ファイル」をクリックします。開いたウィンドウからpyatcrobo2フォルダを探し、クリックして展開すると、その中にpartsを含むいくつかのモジュールが存在していることが確認できます。

このように、フォルダをパッケージとして扱いたいときは、__init__.pyという名前のファイルをフォルダ内に含めます。これでそのフォルダはPythonからパッケージとして認識されるようになります。

※ __init__.pyの中身は空でも構いません。

2. 4 モジュールやモジュール内の変数・関数・クラスのインポートについて

ここでは、改めてモジュールやその中の変数・関数・クラスを読み込む方法について確認しましょう。

モジュール内の変数や関数、クラスを使用したい場合、モジュールをインポートして呼び出すのか、それともモジュール内で使用したい特定の変数や関数、クラスのみをインポートするのかを選択できます。

例として、pystubitパッケージのdsplyモジュールにあるStuduinoBitDisplayクラスを使用する場合のサンプルプログラムで確認しましょう。

【 モジュールをインポートしてクラスを呼び出す 】
【 モジュール内のクラスのみをインポート 】
from pystubit.dsply import StuduinoBitDisplay display = StuduinoBitDisplay()display.scroll("Hello!")

変数や関数、クラスを指定してインポートした場合、モジュール名が省略できるというメリットがあります。しかし一方で、複数のモジュールから変数や関数、クラスをインポートした場合、同じ名前のものが含まれているとプログラム上で衝突してしまい、エラーの原因となる可能性があります。もし、同じ名前の変数や関数、クラスをインポートしたい場合は、モジュールをインポートして、そこから呼び出すようにしましょう。

2. 5 モジュール化やパッケージ化するメリット

1つのファイルに何百、何千行と続くコードを書くよりも、まとまったコードの固まりをモジュール化したり、パッケージにまとめたりする事で、コードが整理でき、管理しやすくなります。また、それによってプログラムの修正を行うときの作業が効率化できたり、機能を拡張しやすくなったりするなどのメリットもあります。

忍者ゲームの製作

ここからは、新しいモジュールを利用して、ゲームを製作していきます。このゲームでは、Muのターミナルウィンドウをディスプレイとして利用します。ゲームのテーマは「忍者」で、ランダムにディスプレイに表示される敵と味方を見分けて、敵が表示されたら手裏剣を投げて倒します。手裏剣を投げるときは、Studuino:bitの光センサーの上で手を通過させます。

まずは下の動画を見て、どのようにこのゲームをプレイするのかを確認しましょう。

【 ゲームのプレイ動画 】

3. 1 ゲームで登場するキャラクターの紹介

プレイヤーはターミナルウィンドウに表示された「アスキーアート」のキャラクターが「敵」か「味方」かを瞬時に判断して攻撃を行います。

※ アスキーアート(ASCII art)とは・・・コンピューター上で扱える文字や記号を用いた表現のこと。1行の顔文字から複数行に渡る大掛かりな絵まで様々なものが作られています。
【 敵のキャラクター 】
  • 敵忍者A
       X
  ∠ ̄\∩
   |/゚U゚Lノ
 ~~( ニ⊃
   ( ヽ/
   ノ>ノ
  • 敵忍者B
  ∩
⊂ \
 /ヽ )
⊂二 ,)~~
/|,∩,/|
U\_フ
【 味方のキャラクター 】
  • 姫様
  ∧M∧
  (゚ー゚*)
⊂(|0|)⊃
  ノ~~~ヽ
  ノ~~~~~ヽ
   ∪ ∪
  • 殿様
   ∧_∧
  (  ゚∀゚)
  /~~〉〈/つ
 ノ ノ| |、
 ~(__)__)ゝ~~~
~~~~~~~~~~~

また、手裏剣を投げる動作を行うと、プレイヤーが操作する忍者のキャラクターが手裏剣を投げる姿が表示されます。

【 プレイヤーのキャラクター 】
   ∧_∧ ∩
  <#・+・>彡  -==卍
  / 三三つニつ-=≡卍
 /  /ミヽ -=卍 -==卍
//~> > ∪ -=≡卍
\) (_)

3. 2 ゲームのルール説明

敵が表示されたときは手裏剣を投げて攻撃し、味方が表示されたときは攻撃をしなければ、それぞれポイントが入ります。反対に、敵を攻撃しなかったり、誤って味方を攻撃してしまったりすると減点されます。

 敵忍者A敵忍者B姫様殿様
攻撃した+1+1-1-1
攻撃していない-1-1+1+1

はじめは、プレイ時間を30秒に設定し、ゲーム終了時に7点以上のポイントを獲得できていればゲームクリアとしましょう。

3. 3 ゲーム製作用に使うモジュールの準備

ゲーム開発用のソフトウェアには、あらかじめキャラクターの画像やゲーム内で流す音楽など様々な素材が用意されているものがあります。これらの素材を利用することで、絵を描いたり、曲を制作したりすることができない人でも、ゲーム製作ができるようになっています。また、インターネット上で自分の描いた絵や作曲した音楽を無償で提供するサイトもあり、それらを利用して多くのゲームが開発されています。

そこで今回は、ゲームを素早く制作できるように、以下のアスキーアートと簡単な効果音をまとめたモジュールを利用してみましょう。

■ ターミナルエリアに表示するアスキーアートのモジュール

以下のリンクから「picture.py」をダウンロードして、Studuino:bit内に保存してください。

※ リンクの上にカーソルを合わせて右クリックし、「名前を付けてリンク先を保存」を選択してください。

picture.py

このモジュールには、以下のアスキーアートの文字列が格納された変数(定数)が定義されています。

  • 数字
    左から「ZEROONETWOTHREEFOURFIVESIXSEVENEIGHTNINE
┏━━┓ ┏┓ ┏━━┓ ┏━━┓ ┏┓┏┓ ┏━━┓ ┏━━┓ ┏━━┓ ┏━━┓ ┏━━┓
┃┏┓┃ ┃┃ ┗━┓┃ ┗━┓┃ ┃┃┃┃ ┃┏━┛ ┃┏━┛ ┃┏┓┃ ┃┏┓┃ ┃┏┓┃
┃┃┃┃ ┃┃ ┏━┛┃ ┏━┛┃ ┃┗┛┃ ┃┗━┓ ┃┗━┓ ┗┛┃┃ ┃┗┛┃ ┃┗┛┃
┃┃┃┃ ┃┃ ┃┏━┛ ┗━┓┃ ┗━┓┃ ┗━┓┃ ┃┏┓┃   ┃┃ ┃┏┓┃ ┗━┓┃
┃┗┛┃ ┃┃ ┃┗━┓ ┏━┛┃   ┃┃ ┏━┛┃ ┃┗┛┃   ┃┃ ┃┗┛┃ ┏━┛┃
┗━━┛ ┗┛ ┗━━┛ ┗━━┛   ┗┛ ┗━━┛ ┗━━┛   ┗┛ ┗━━┛ ┗━━┛
  • キャラクター
    左から「NINJA_ANINJA_BPRINCESSKINGSHURIKEN
       X     ∩         ∧M∧      ∧_∧             ∧_∧ ∩
  ∠ ̄\∩   ⊂ \       (゚ー゚*)     (  ゚∀゚)        <#・+・>彡  -==卍
   |/゚U゚Lノ   /ヽ )   ⊂(|0|)⊃    /~~〉  〈/つ     / 三三つニつ-=≡卍
 ~~( ニ⊃  ⊂二 ,)~~  ノ~~~ヽ     ノ ノ| |、      /  /ミヽ -=卍 -==卍
   ( ヽ/  /|,∩,/|     ノ~~~~~ヽ   ~(__)__)ゝ~~~  //~> > ∪ -=≡卍
   ノ>ノ    U\_フ       ∪ ∪    ~~~~~~~~~~~     \) (_)
  • 文字
    左から「OKNG
 *****  *  *    *   *  *****
 *   *  * *     **  *  *   
 *   *  **      * * *  *  ***
 *   *  * *     *  **  *   *
 *****  *  *    *   *  *****
  • 結果の表示用
    上から「SUCCESSFAILED
     ∧_∧         / ̄ ̄ ̄ ̄
 /\( ・∀・)/ヽ < {}points! Congratulation!
( ☆ ⊂   つ ☆ ) \____
 \/⊂、  ノ  \ノ
     し’
	 
  ∧_∧   / ̄ ̄ ̄ ̄
(  ・_・) < {}points. Please retry.
( つ と) \____
 と_)_)

■ 簡単な効果音のモジュール

ゲームで使用できる簡単な効果音をまとめたモジュールです。以下のリンクから「sound.py」をダウンロードして、Studuino:bit内に保存してください。このモジュールは次回以降のレッスンでも使用します。

※ リンクの上にカーソルを合わせて右クリックし、「名前を付けてリンク先を保存」を選択してください。

sound.py

このモジュールには以下の関数が定義されています。

関数名内容
success()「ピンポンピンポーン」という成功音
failed()「ブッブー」という失敗音
operation()「ビ」という操作音
incorrect()「ブー」という不正解音
congratulation()「ドレミファソラシド」が順番に鳴るメロディ
countdown()「3・2・1・0」とLEDディスプレイに数字を表示しながら音を鳴らすカウントダウン

3. 4 忍者ゲームのプログラムの作成

上記のモジュールを利用して、ゲームのプログラムを作成します。まずは、このゲームに必要な機能を確認しましょう。

【 ゲームに必要な主な機能 】
  • ゲーム開始までをカウントダウンする機能
  • ゲーム開始から経過した時間を計測する機能
  • ランダムにキャラクターを選択し、ターミナルウィンドウに表示する機能
  • 光センサーの上を手が通過したかどうかを検知する機能
  • ポイントの獲得や減点を判定する機能
  • 制限時間の経過後にポイントが目標点を超えたかどうかを判定する機能

また、このプログラムは以下の流れで処理を行います。プログラムの主な処理はstart_game()関数としてまとめ、ターミナルから呼び出して実行できるようにします。

【 プログラムの処理の主な流れ 】

では、順番に各機能を作成していきましょう。

■ 各種モジュールのインポート

はじめに、プログラム内で使用する各種オブジェクトやモジュールをインポートしましょう。新たにStuduino:bit上に保存したモジュールpicture(.py)sound(.py)に加えて、ランダムな動作を行わせるためにrandomモジュールと、経過時間を確認するためにtimeモジュールをインポートします。

■ ゲーム開始までをカウントダウンする機能

ゲーム開始の合図として、ターミナルウィンドウに3から0までのアスキーアートの数字を順番に表示します。このカウントダウンを行うプログラムを短くまとめるために、使用するアスキーアートの数字を表示順にタプルnumbersに格納します。

追加【7行目~12行目】

次に、start_game()関数を宣言して、作成したタプルnumbersから順番に要素を取り出してターミナルに表示するコードを追加します。このときターミナルウィンドウへの表示以外に、ブザー音("C6")を600ミリ秒間鳴らし、次の数字の表示までさらに600ミリ秒待つようにしてみましょう。

追加【14行目~18行目】

このstart_game()関数は、ターミナルから呼び出します。動作を確認する前に、以下の手順でターミナルウィンドウの高さを調整しましょう。

  1. エディタエリアの下部にある横線にカーソルを合わせると形が変化します。そこでカーソルをドラッグすると、ターミナルウィンドウの高さを調整することができます。
  2. あらかじめ、「Enterキー」を何度か押して「>>>」でターミナルウィンドウを埋めておき、ちょうど7行分の高さになるように「1の作業」を行って調整します。

アスキーアートはすべて6行の高さで用意しており、print()関数で表示すると最後に1つ改行文字が追加されるため、ターミナルエリアの高さを7行とすることで、常に1つのアスキーアートだけが見えるようになります。

■ 経過時間を確認する機能

次に、ゲーム開始から経過した時間を確認する機能を追加します。まずは、制限時間の30秒(30000ミリ秒)を定数LIMIT_TIMEとして定義します。

追加【7行目】

続けて、start_game()関数の中でカウントダウンを終えた後に、ゲームを開始した時間を記録するコードを追加します。そして、経過時間が制限時間を超えない間は処理を繰り返すコードも追加します。このwhile文の中にキャラクターの表示やポイントの獲得や減点などを行うコードを書いていきます。

追加【22行目~24行目】

■ ランダムに選んだキャラクターを表示する機能

ターミナルウィンドウに表示するキャラクターをランダムに選ぶため、randomモジュールのchoice()関数を使います。このchoice()関数は引数としてタプルを受け取り、その要素の中からランダムにひとつを選びます。

そこで、ターミナルに表示するキャラクターをタプルcharactersにまとめます。ただし、後の判定で使用するために、敵の場合は"enemy"を、味方の場合は"ally"を2つめの要素として持たせたタプルとして追加します。

※ enemyは英語で「敵」を意味し、allyは英語で「味方」を意味します。
追加【14行目~19行目】

続けて、キャラクターをランダムに選び表示します。キャラクターを表示するときはsoundモジュールからoperation()関数を実行して、効果音も加えてみましょう。

追加・変更【29行目~31行目】
※ while文の下にあったpass文は削除しています。

さらに、キャラクターを表示するまでに待つ時間も0.5秒(500ミリ秒)から1.5秒(1500ミリ秒)の間の50ミリ秒きざみの時間からランダムに選んでみましょう。

追加【30行目、31行目】

50ミリ秒きざみとするために、乱数は10から30の間で選び、それに50ミリ秒をかけています。これによって、「500, 550, …. , 1450, 1500」の中からランダムに選ぶのと同じ結果となります。

■ 光センサーの上を手が通過したかどうかを感知する機能

光センサーの上を手が通っている間はセンサー値が小さくなっています。このことから、一度でも設定したしきい値を下回った場合、手が通過したとみなすことにします。

では、しきい値を決めるために、新しいプログラムを作成して以下の【 サンプルコード 3-4-1 】を実行しましょう。手で覆い隠したときと、そうでないときの光センサーの値を確認してください。

【 サンプルコード 3-4-1 】

調べた値から決めたしきい値は、元のプログラムに定数THRESHOLDとして定義しましょう。

追加【8行目】

続けて、定義したしきい値を使って光センサーの上を手が通過したかどうかを判定する処理を書いていきます。

光センサーの上を手が通過した場合は、手裏剣を投げて攻撃したことになります。攻撃が行われたかどうかを判断するためのフラグとして変数is_attackedを用意します。この変数is_attackedには、はじめFalseを入れておき、光センサーの上を手が通過した場合はTrueを入れます。

また、プレイヤーへ攻撃するかどうかを瞬時に判断させるために、600ミリ秒間だけこの検知を行います。この間に光センサーの上を手が通過しなかった場合は、攻撃しなかったものとして処理します。

追加【36行目~39行目】 下の4行

while文の中で、光センサーの値を取得し、しきい値と比較します。光センサーの上を手が通過した場合は、is_attackedTrueに変え、手裏剣を投げるアスキーアートpicture.SHURIKENを600ミリ秒間表示しましょう。

追加・変更【39行目~43行目】

■ ポイントの獲得と減点を判定する機能

攻撃(手裏剣を投げたこと)を検知したときは、表示したキャラクターによってポイントを獲得するのか、それとも減点するのかを判定します。この判定の組み合わせは以下の表のようになります。

 攻撃した is_attacked == True攻撃していない is_attacked == False
敵 "enemy"+1-1
味方 "ally"-1+1

では、獲得したポイントを記録しておくための変数scoreを定義します。start_game()関数の先頭で初期値として「0」を入れておきましょう。

追加・変更【23行目】

47行目からポイントの獲得と減点の判定を行うコードを追加します。上の表を参考にして、分岐処理を書きましょう。また、ポイントを獲得したときはアスキーアートpicture.OKを表示し、soundモジュールからcorrect()関数を実行しましょう。反対に、減点になったときはアスキーアートpicture.NGを表示し、soundモジュールからincorrect()関数を実行しましょう。

追加【47行目~64行目】 ※if is_attacked: # 攻撃した場合 から追加

そして、次のループに移るまで600ミリ秒待ち、さらに5行分の改行文字を表示してターミナルウィンドウの表示を空にするコードを追加しましょう。

追加【66行目、67行目】 ※下の2行

■ 結果を判定して表示する機能

最後に、獲得したポイントがゲームクリアの基準とする「7点」以上かどうかを判定し、結果を表示する処理を追加します。まずは、このゲームクリアの基準となる得点を定数CLEAR_SCOREとして定義しましょう。

追加【9行目】 ※最後の1行

経過時間が制限時間を超えたかどうかを判定しているwhile文を抜けたあとに、ゲームクリアの判定を行います。ここで、ゲームの結果を表示するためのアスキーアートとしてpicture.SUCCESSpicture.FAILEDを用意していたことを思い出してください。

  • picture.SUCCESS
     ∧_∧         / ̄ ̄ ̄ ̄
 /\( ・∀・)/ヽ < {}points! Congratulation!
( ☆ ⊂   つ ☆ ) \____
 \/⊂、  ノ  \ノ
     し’
  • picture.FAILED
  ∧_∧   / ̄ ̄ ̄ ̄
(  ・_・) < {}points. Please retry.
( つ と) \____
 と_)_)

このアスキーアートの文字列の中には、「{}」が含まれています。{}は文字列のformat()メソッドで指定した別の数値や文字列に変換することができました。そこで、獲得したポイントをformat()メソッドの引数として渡し、その戻り値をターミナルウィンドウへ表示します。

さらに、ゲームをクリアしたときと、失敗したときでそれぞれsoundモジュールに用意されている関数を実行して音を鳴らしましょう。

追加【69行目~74行目】
※ インデントの位置に注意してください。

完成したプログラムは以下のようになります。作成したプログラムと見比べて誤りがないかを確認して、プログラムを実行しましょう。

【 サンプルコード 3-4-2 】

課題:新しいアクションを追加してゲームの難易度を上げよう

【 サンプルコード 3-4-2 】では、味方の「姫様」や「殿様」が表示されたときは、何もしないことでポイントが入るようになっていましたが、この課題ではゲームの難易度を上げるために、姫様が表示されたときはボタンAを押すというアクションを追加してみましょう。

  • 姫様 ⇒ ボタンAを押す
  ∧M∧
  (゚ー゚*)
⊂(|0|)⊃
  ノ~~~ヽ
  ノ~~~~~ヽ
   ∪ ∪
  • 殿様 ⇒ 何もしない
   ∧_∧
  (  ゚∀゚)
  /~~〉〈/つ
 ノ ノ| |、
 ~(__)__)ゝ~~~
~~~~~~~~~~~

これによって、ポイントの獲得や減点の設定を以下のように変更します。攻撃したときは同じですが、攻撃していないときにボタンAを押したかどうかでポイントが変わります。また、味方には"ally"という値を設定していましたが、姫様と殿様を見分けるために、それぞれ"princess""king"に変更します。

 攻撃した攻撃していない かつ ボタンAを押した攻撃していない かつ ボタンAを押していない
敵 "enemy"+1-1-1
姫様 "princess"-1+1-1
殿様 "king"-1-1+1

4. 1 プログラムの作成例

【 サンプルコード 3-4-2 】へコードの追加と変更を順番に行います。まずは、ボタンAを新たに使用するため、プログラムの先頭でbutton_aオブジェクトをインポートしましょう。

変更【1行目】

次にキャタクターのアスキーアートに付けている値を変更します。"ally"からそれぞれ"princess""king"に変更しましょう。

変更【19行目、20行目】

ボタンAが押されたかどうかは、button_aオブジェクトのwas_pressed()メソッドを利用して判定します。このメソッドは過去にボタンAが押されたかどうかを調べるものですが、攻撃の判定と関係のないところでボタンAを押したときにも記録されてしまいますので、攻撃の判定処理の直前で実行しておき、記録を消去しておきましょう。

追加【40行目】

最後に攻撃判定によって、ポイントの獲得か減点かを判定するコードを変更します。攻撃があった場合(変数is_attackedTrueの場合)に変更はありません。攻撃がなかった場合(変数is_attackedFalseの場合)は、"enemy"だけでなく"princess""king"でも処理を分けます。

次の表を参考にして、コードを書きましょう。

 攻撃していない かつ ボタンAを押した攻撃していない かつ ボタンAを押していない
敵 "enemy"-1-1
姫様 "princess"+1-1
殿様 "king"-1+1
追加・変更【63行目~80行目】

これでプログラムの完成です。全体のプログラムは次のようになります。うまく動作しない場合は、こちらと見比べて、誤りがないかどうか確認してください。

【 サンプルコード 4-1-1 】

おわりに

5. 1 このレッスンのまとめ

このレッスンでは、モジュールとパッケージについて改めて学習をしました。そして、あらかじめ用意されたモジュールを利用することで、効率良くゲームを製作できることも確認しました。

Pythonでは作成したモジュールやパッケージをインターネット上に公開して共有するコミュニティがあります。その中には優れたものも数多くあり、必要なものを探して利用することで、効率的にプログラムを書くことができます。このコミュニティの利用方法についてはテーマ11以降で紹介していきます。

5. 2 次のレッスンについて

次回のレッスンでは、Studuino:bitの加速度センサーを使用したゲームを製作します。また、「1, 2, 3, 4, 5」のようにある一定の規則で並ぶ値をリストに格納するときに便利な「内包表記」について新たに学習します。

TOP