Pyxel クラスを使って複数のキャラクターを表示する

Pyxelでプログラミング練習(クラスを使う 第1回)

 Python向けレトロゲームエンジン「Pyxel(ピクセル)」を使ってのゲーム作成を通してプログラミングを学習します。

 Pythonの「クラス」という仕組みを使うことで「ソフトウェアの開発が楽になる」を実感できるような記事にしたいと思います。

ローカル変数とグローバル変数

 本題に入る前に,いくつか用語を説明します。次のコード例(1)と(2)を見てください。

 (1) 自作の関数add()の外で定義された変数 c は,グローバル変数と呼ばれます。関数add()の中で定義された変数 c はローカル変数と呼ばれ,その関数の中でだけ有効です。
 (2) 関数の中でグローバル変数に値を代入するには,global文で変数名を指定する必要があります。(値の参照だけならglobal文は不要)
 (1)も(2)もPythonの文法的に正しく,add()の中でだけ使いたい変数cなのか,他の場所でも合計を参照したい変数cなのか,で記述を変える必要があります。

 

複数のキャラクター表示とグローバル変数

 グローバル変数を使うと,関数をまたいでデータを利用できるので便利なように見えます。本ブログの記事でアクションゲームを作成した際も,プレイヤーキャラクターを表示する座標用にグローバル変数 x,y を定義して,update()関数とdraw()関数から利用しました。しかし,画面に表示するキャラクターを増やすには,それだけ多くの座標データを管理する必要が出てきます。

 変数名が衝突しないように名前を変えたり,配列にして処理するなどの工夫をすることで,過去記事のコードでも対応は可能ですが,「オブジェクト指向」と呼ばれるプログラミング技術や考え方を知ると,「同じようなものを整理して扱う」ことができるようになります。

 

クラスとインスタンス

 「オブジェクト指向」の仕組みの基本的なものに「クラス」があります。
 クラスそれ自身は種類を示す「型」のようなもので,実際にクラスを使うときには「インスタンス」という具体的なものを作成します。
 よくある例えでは,クラスがクッキーの型のようなもので,インスタンスは型抜きした生地というものがあります。1つのクラスから複数のインスタンスが作成できます。
 
  pixabay silviarita 画像を加工して使用)
 

クラスを作ってキャラクターを表示する

 前置きが長くなりましたが,「同じようなものを整理して扱う」プログラムを作成してみましょう。クラスを使うと「関連する変数や処理にわかりやすい名前を付けて,まとめて扱う」ことができるので,キャラクターを指定位置に表示するクラスを作ります。

 ※リソースファイルはPyxelのサンプルを使用します。以下の記事を参照してください。kinutani.hateblo.jp

【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)
list08_01.py

import pyxel
pyxel.init(32,32)
pyxel.load("sample.pyxres")

# イメージバンク0上の各キャラクター座標
character_list =[(8,0),(0,16)]

# スプライトクラスの定義 ==================
# クラス名は通常先頭を大文字で記述
class Sprite:
    # 初期化のメソッド  init前後の__はアンダーバーを2個続けて記述
    def __init__(self,x,y,idx):
        self.x = x
        self.y = y
        self.u , self.v = character_list[idx]

    # メソッドの第一引数には通常 self を記述する
    def draw(self):
        pyxel.blt(self.x,self.y, 0, self.u,self.v, 8,8, 0)
        return

# ここからメインの処理 ===================
# スプライトクラスのインスタンスを作成する
hero = Sprite(0,0,0)    # __init__(self,0,0,0)で初期化が実行される
boss = Sprite(10,10,1)  # __init__(self,10,10,1)で初期化が実行される

def update():
    return

def draw():
    pyxel.cls(0)
    hero.draw()     # インスタンスを代入した変数名を使ってメソッドを呼び出す
    boss.draw()     # 呼び出し側の記述では self は省略する
    return

pyxel.run(update, draw) # 以降update()とdraw()が実行され続ける(Pyxelの機能)

実行結果
 

・class Sprite:
 スプライトクラスの定義です。(一般にゲーム等で表示する画像をスプライトと呼ぶことがあります)
 画面上の表示座標 x,y と,イメージバンク0の座標 u,v をクラス内の変数に持ちます。
 クラスの関数(Pythonではメソッドと呼ばれます)として,draw()メソッドを作りました。メソッドが実行されるとPyxelのblt命令でドット絵を描画します。

・def __init__(self,x,y,idx):
 初期化に使う特殊なメソッドです。init前後の__は「_ アンダーバー」を2つ続けたものです。
 第一引数のselfがインスタンス自身を指す変数で,「self.変数名」で自身の持つ変数にアクセスできます。

 ※クラス定義の文法的な説明は別途Pythonの記事を作成予定ですが,「Python クラス定義」等で調べるとわかるかと思います。

 
・hero = Sprite(0,0,0)
 クラスは定義しただけでは実行されません。クラス名にかっこをつけて実行すると__init__()メソッド(初期化処理)が呼び出され,クラスの「インスタンス」が作成されます。

・hero.draw()
インスタンス」を変数に代入しておくと,その変数に「. ピリオド」をつけてクラスの機能を利用することができます。変数 hero と変数 boss に代入されたインスタンスは,それぞれ別のもので,変数x,y,u,vもインスタンスごとに独立しています。

 ※Pythonクラス内のメソッド定義は第一引数に self という名前の変数をつけるのが一般的です。
 ※メソッド内の処理記述時に,自身の変数やメソッドを扱うときは第一引数( self のこと)に「. ピリオド」をつけて記述します。(「.」はドット演算子とよばれることが多いです)
 ※メソッド呼び出し側の記述では第一引数 self は省略します。
 ※「. 」は「~の」と読み下すと理解しやすいです。import文でモジュール読み込みをしたもの「pyxel.blt() ピクセルのblt()」と同じ。


 

リストにインスタンスを格納する例

 インスタンスを格納する変数をたくさん用意するのが効率的ではない場合があります。シューティングゲームで撃った弾を1つずつインスタンスにする場合など)
 インスタンスをリスト(配列)に追加して,機能を使うときはそのリストの要素を処理することでコードの記述をまとめることができます。

【ソースコード】はこちらのリンクから確認してください(Googleドキュメント)
list08_02.py

import pyxel
pyxel.init(32,32)
pyxel.load("sample.pyxres")

# イメージバンク0上の各キャラクター座標
character_list =[(8,0),     # hero
                 (0,16),    # evil lord
                 (0,8),     # ghoul
                 (64,8),    # skeleton warrior
                 (64,16)]   # princess
            
class Sprite:
    def __init__(self,x,y,idx):
        self.x = x
        self.y = y
        self.u , self.v = character_list[idx]
   
    def draw(self):
        pyxel.blt(self.x,self.y, 0, self.u,self.v, 8,8, 0)
        return

# ここからメインの処理
# スプライトクラスのインスタンスをリストに格納
objlist = []
for i in range( len(character_list) ):
    objlist.append( Sprite(6*i,6*i,i) )

def update():
    return

def draw():
    pyxel.cls(0)
    # リスト内の要素を1個ずつ変数objに代入して,変数objでメソッドを呼び出し
    for obj in objlist:
        obj.draw()
    return

pyxel.run(update, draw)

実行結果
 

・for i in range( len(character_list) ):
 len(character_list) len()関数はリストの長さを返します。
 range()関数 range(6)で,[0,1,2,3,4,5]のリストを返します。
 character_listと同じ長さのリストを作って,インデックスの値となる i を取得した処理です。単純に i を0から5まで増やすループで行っても問題ありません。

・objlist
 オブジェクトという言葉を話題に出すために配列の変数名に使いました。プログラムで扱う変数などをオブジェクトと呼ぶことがあります。Pythonのリファレンスでは「Python における オブジェクト (object) とは、データを抽象的に表したものです。」となっています。(オブジェクトは「データ(変数)と手続き(メソッド)を合わせたまとまり」と説明されることもあります)
 Pythonでは整数やリストもオブジェクトで,type()関数を使うとオブジェクトの型(クラス)がわかります。

>>>print(type(1))
<class 'int'>
>>>print(type([0,1]))
<class 'list'>

 
 グローバル変数 x,y をたくさん用意せずに,クラスに管理を任せることで「同じようなものを整理して扱う」プログラムにできることがわかりました。次回はクラスを使って簡単なゲームを作成します。

 次の記事
kinutani.hateblo.jp

 

関連記事

 kinutani.hateblo.jp