Arcadeでゲームプログラミングの準備

Arcadeは、Pythonを使って絵を描いたり、キャラクターを動かしたりして、 2Dゲームやアニメーションを作るためのライブラリです。Scratchで学んだ考え方をそのまま活かせるので、 Pythonを楽しく学び始めるのにぴったりです。

Scratchでは、ブロックパレットからブロック(プログラムの命令)を選び、 スクリプトエリアに並べます。ブロックを組み合わせることで、 スプライト(キャラクター)の動きやゲームの動作を作ることができます。

一方、Pythonでは、ブロックの代わりに文字や記号を使って命令を書きます。 このように、プログラムを書くための文字や記号の並びを「コード」と呼びます。 Scratchでブロックをつなげていくように、Pythonでは命令を表すコードを上から順番に書き並べて、 コンピューターにしてほしいことを伝えます。たとえば、キャラクターを動かしたいときは、「右に進む」 「10歩動く」といった動きを、Pythonの命令を使ってコードにします。

なを、このページは、Arcade Academy - Learn Python「18. Using the Window Class」参照し、 実際にコードを動かして試しながら書いています。

ゲームを動かす仕組み

Arcadeで2Dの絵を描くプログラムはPython Arcade「絵を描く方法」にも書きました。 次のコードでWindowを開いて絵を描きました。このプログラムでは幅640、高さ480のWindowを開いています。

import arcade
arcade.open_window(640, 480, "描画サンプル") # Width x Height
arcade.run()

これで、640×480ピクセルのゲーム画面を作ることができます。でも、画面ができただけでは、まだゲームにはなっていません。 ゲームを作るには、

  1. キャラクターを画面に表示する
  2. キャラクターを動かす
  3. キーボードで操作する
  4. 敵を動かす
  5. 当たり判定をする
  6. 点数を計算する

など、たくさんの仕組みが必要です。 そこで、ArcadeのWindowをそのまま使うのではなく、ゲームに必要な機能を追加した、自分専用のWindowを作ります。 そのために使うのが、

class MyGame(arcade.Window):

という書き方です。 これは、Arcadeが用意したWindowを土台にして、自分のゲーム用のMyGameを作るという意味です。 このクラスは、「キャラクターを画面に表示する」などゲームを作る上で必要な機能を持っています。

(1) MyGameを定義し、初期化する

__init__(self, width, height, title)は、 このクラスを作成した時に、一番最初に実行される関数です。 この中で、super().__init__(width, height, title)は親である arcade.Window の初期設定を実行しています。

import arcade

# MyGame クラスの定義 ---
class MyGame(arcade.Window):
    
    def __init__(self, width, height, title):
        # 親クラスの初期化関数をコールする
        super().__init__(width, height, title)

# --- クラスの定義終わり

# MyGame を作成
mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()

(2) 描画の更新

on_draw(self)は1秒間に約60回、定期的に呼ばれる関数です。 この関数で再描画してアニメーションを作成します。 この中でself.clear()で背景以外を全部消して、その上で draw_lrbt_rectangle_filledで Windowの下の4分の1を緑色に塗り、さらにボールを draw_circle_filledで描き加えています。この場合は ボールが毎回、同じ位置に描画されるので、描画が更新されても静止画になります。



import arcade

# MyGame クラスの定義 ---
class MyGame(arcade.Window):
    
    def __init__(self, width, height, title):
        # 親クラスの初期化関数をコールする
        super().__init__(width, height, title)
        # 背景色
        arcade.set_background_color(arcade.color.SKY_BLUE)

    # 1秒間に60回、この関数が呼び出され再描画します。
    def on_draw(self):
        self.clear()    #全部消す
        arcade.draw_lrbt_rectangle_filled(0, 640, 0, 240, arcade.csscolor.GREEN)
        arcade.draw_circle_filled(50, 300, 15, arcade.color.AUBURN)

# --- クラスの定義終わり

# MyGame を作成
mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()

(3) アニメーション

ボールのX座標を self.ball_x = 0 と定義します。 on_update(self, delta_time) は1秒間に約60回、定期的に呼ばれる関数です。この暗数の中でこのX座標をアップデートします。 右端に到達したら、左端に戻すようにしています。



import arcade

# MyGame クラスの定義 ---
class MyGame(arcade.Window):
    
    def __init__(self, width, height, title):
        # 親クラスの初期化関数をコールする
        super().__init__(width, height, title)
        # 背景色
        arcade.set_background_color(arcade.color.SKY_BLUE)
        self.ball_x = 0

    # 1秒間に60回、この関数が呼び出され再描画します。
    def on_draw(self):
        self.clear()
        arcade.draw_lrbt_rectangle_filled(0, 640, 0, 120, arcade.csscolor.GREEN)
        arcade.draw_circle_filled(50, 160, 15, arcade.color.AUBURN)

    # 1秒間に60回、この関数が呼び出されます。
    def on_update(self, delta_time):
        self.ball_x += 2
        if(self.ball_x>=640):
            self.ball_x=0

# --- クラスの定義終わり

# MyGame を作成
mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()


(4) 壁で跳ね返るボール

Scratchのプログラムでは以下のようなプログラムです。 MyGameの場合は、ぶつかった壁が左右なのか、上下なのかで跳ね返った後の動きを変更しています。 左右の場合はX方向の動きを反転し、上下の場合はY方向の動きを反転しています。



Scratch での動き



MyGameでの動き



import arcade
import numpy as np

Angle = 45.*np.pi/180

# MyGame クラスの定義 ---
class MyGame(arcade.Window):
    
    def __init__(self, width, height, title):
        # 親クラスの初期化関数をコールする
        super().__init__(width, height, title)
        # 背景色
        arcade.set_background_color(arcade.color.SKY_BLUE)
        self.xinc = 4*np.cos(Angle)
        self.yinc = 4*np.sin(Angle)
        self.ball_x = 50
        self.ball_y = 160

    # 1秒間に60回、この関数が呼び出され再描画します。
    def on_draw(self):
        self.clear()
        arcade.draw_lrbt_rectangle_filled(0, 640, 0, 120, arcade.csscolor.GREEN)
        arcade.draw_circle_filled(self.ball_x, self.ball_y , 15, arcade.color.AUBURN)

    # 1秒間に60回、この関数が呼び出されます。
    def on_update(self, delta_time):
        self.ball_x += self.xinc
        self.ball_y += self.yinc
        if(self.ball_x>=640):   # 右の壁にぶつかった
            self.xinc=-self.xinc
        if(self.ball_x<=0):     # 左の壁にぶつかった
            self.xinc=-self.xinc
        if(self.ball_y>=480):   # 上の壁にぶつかった
            self.yinc=-self.yinc
        if(self.ball_y<=0):     # 下の壁にぶつかった
            self.yinc=-self.yinc

# --- クラスの定義終わり

# MyGame を作成
mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()

(4) ボールの数を増やす

ボールをいくつも作って動かすために、ボールの設計図となる「ボールクラス」を作ります。 ボールクラスには、それぞれのボールが持っている情報として、位置、色、大きさ、X方向の移動量、Y方向の移動量などを用意します。 また、ボールの位置を計算する処理と、ボールを画面に描くための描画処理も用意します。 ゲームが進むとon_update()からupdate()が呼び出され、 ボールの位置を計算し直します。そして、画面を描くときにはon_draw()から描画処理draw()が呼び出されます。 このように、ボールに必要な「情報」と「動作」をひとまとめにしておくことで、同じボールをいくつでも作ることができます。


import arcade
import numpy as np

# ボールのクラスの定義 ---
class Ball(arcade.Window):
    def __init__(self, xposs, yposs, bsize, bangle, bspeed, bcolor):
        self.ball_x = xposs
        self.ball_y = yposs
        self.ball_size = bsize
        self.xinc = bspeed*np.cos(bangle*np.pi/180)
        self.yinc = bspeed*np.sin(bangle*np.pi/180)
        self.color = bcolor

    def updata(self,delta_time):
        self.ball_x += self.xinc
        self.ball_y += self.yinc
        if(self.ball_x>=640):   # 右の壁にぶつかった
            self.xinc=-self.xinc
        if(self.ball_x<=0):     # 左の壁にぶつかった
            self.xinc=-self.xinc
        if(self.ball_y>=480):   # 上の壁にぶつかった
            self.yinc=-self.yinc
        if(self.ball_y<=0):     # 下の壁にぶつかった
            self.yinc=-self.yinc      

    def draw(self):
        arcade.draw_circle_filled(self.ball_x, self.ball_y , self.ball_size, self.color)
# --- クラスの定義終わり

# MyGame クラスの定義 ---
class MyGame(arcade.Window):
    
    def __init__(self, width, height, title):
        # 親クラスの初期化関数をコールする
        super().__init__(width, height, title)
        # 背景色
        arcade.set_background_color(arcade.color.SKY_BLUE)
        # ボールを創る
        self.ballr = Ball(50,  150, 10,  43, 12, arcade.color.RED)
        self.ballg = Ball(350, 360, 15, -42,  9, arcade.color.YELLOW)
        self.ballb = Ball(250, 250, 20,  64,  6, arcade.color.BLUE)

    # 1秒間に60回、この関数が呼び出され再描画します。
    def on_draw(self):
        self.clear()
        arcade.draw_lrbt_rectangle_filled(0, 640, 0, 120, arcade.csscolor.GREEN)
        self.ballr.draw()
        self.ballg.draw()
        self.ballb.draw()

    # 1秒間に60回、この関数が呼び出されます。
    def on_update(self, delta_time):
        self.ballr.updata(delta_time)
        self.ballg.updata(delta_time)
        self.ballb.updata(delta_time)
# --- クラスの定義終わり

# MyGame を作成
mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()