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



(9) 回転方向を左右のみにする

Scratchでは、スプライトの「回転方法を左右のみにする」を設定できます。 この設定にすると、スプライトが画面の端で跳ね返ったときに、上下が逆さまにならず、 左右に向きを変えて表示されます。たとえば、キャラクターが右向きに進んで画面の端にぶつかると、 今度は左向きになって戻ってきます。走る人や動物などを自然に動かしたいときに便利な設定です。



Scratch での動き

Arcadeでは、スプライトの左右の向きを scale_x で切り替えることができます。
self.player_sprite.scale_x = -1
とすると右向きに、
self.player_sprite.scale_x = 1
とすると左向きになります。また、左右の壁をスプライトとして用意します。
self.wallR_sprite
self.wallL_sprite
そして、プレイヤーが壁にぶつかったかどうかは、collides_with_sprite() を使って判定します。
self.player_sprite.collides_with_sprite(self.wallR_sprite)
self.player_sprite.collides_with_sprite(self.wallL_sprite)

Arcade での動き

このように、「壁をスプライトとして用意する」→「ぶつかったかを判定する」→「scale_x で向きを変える」 という方法で、Scratchの「端で跳ね返る」動きをArcadeで再現できます。

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)

        # 魚の初期化
        image_source = ":resources:/images/enemies/fishGreen.png"
        self.player_sprite = arcade.Sprite(image_source, 1.0)
        self.player_sprite.center_x = 64
        self.player_sprite.center_y = 128
        self.player_sprite.angle = 0
        self.player_sprite.change_x=2       # 右へ進む
        self.player_sprite.scale_x = -1.0   # 右を向く

        self.player_list = arcade.SpriteList()
        self.player_list.append(self.player_sprite)

        # 左右の壁を作る
        wall = ":resources:/images/tiles/boxCrate.png"
        self.wallR_sprite = arcade.Sprite(wall, 1.0)
        self.wallR_sprite.center_x = 680
        self.wallR_sprite.center_y = 240
        self.wallR_sprite.scale_y = 4.0
        self.player_list.append(self.wallR_sprite)
        self.wallL_sprite = arcade.Sprite(wall, 1.0)
        self.wallL_sprite.center_x = -40
        self.wallL_sprite.center_y = 240
        self.wallL_sprite.scale_y = 4.0
        self.player_list.append(self.wallL_sprite)

    # 1秒間に60回、この関数が呼び出され再描画します。
    def on_draw(self):
        self.clear()
        self.player_list.draw() # スプライトの再描画

    def on_update(self, delta_time):
        self.player_sprite.update()             # 位置をアップデートする
        if self.player_sprite.collides_with_sprite(self.wallR_sprite):  #右の壁にぶつかった
            self.player_sprite.change_x=-2      # 左へ進む
            self.player_sprite.scale_x = 1.0    # 左を向く
        if self.player_sprite.collides_with_sprite(self.wallL_sprite):  #左の壁にぶつかった
            self.player_sprite.change_x= 2      # 右へ進む
            self.player_sprite.scale_x = -1.0   # 右を向く

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

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


(10) スペースバーでジャンプする

PCのキーボードが押されたときに何かを実行する場合は、on_key_press(self, key, modifiers)を使います。 ここでは、スペースバーが押されたら、プレイヤーのスプライトをジャンプさせるために、Y方向の移動距離を 「4」に設定しています。さらに、スプライトの画像をジャンプに変更しています。 次にplayer_sprite.update()が実行されると、 この移動距離に従ってスプライトが上方向へ移動し、ジャンプします。

on_update(self, delta_time)の中で、スプライトが最高到達点にとどいたかを判定し、もし届いた時には スプライトの画像をフォールに変更し、Y方向の移動距離を「-3」に設定しています。 さらに、スプライトが下り切って元の位置までくると、スプライトの画像を通常状態に戻し Y方向の移動距離を「0」に設定しています。

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.dest= [0,0]    # 目的地

        # スプライトの初期化
        image_source = ":resources:/images/animated_characters/female_person/femalePerson_idle.png"
        self.player_sprite = arcade.Sprite(image_source, 0.8)
        self.player_sprite.center_x = 64
        self.player_sprite.center_y = 80
        self.player_sprite.angle = 0

        # 通常状態/ジャンプ中/降下中の3つのスプライトを準備する
        self.texture_idel = arcade.load_texture(":resources:/images/animated_characters/female_person/femalePerson_idle.png")
        self.texture_jump = arcade.load_texture(":resources:/images/animated_characters/female_person/femalePerson_jump.png")
        self.texture_fall = arcade.load_texture(":resources:/images/animated_characters/female_person/femalePerson_fall.png")

        # 「リスト」はスプライトを管理するためのものです。スプライトは、いずれかのリストに入れる必要があります。
        self.player_list = arcade.SpriteList()
        self.player_list.append(self.player_sprite)

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

    # 1秒間に60回、この関数が呼び出されます。
    def on_update(self, delta_time):
        self.player_sprite.update()         # 位置をアップデートする
        # 一番上に到達したか?
        if(self.player_sprite.center_y>280):
            self.player_sprite.texture = self.texture_fall
            self.player_sprite.change_y= -3
        # 降りたきったか?
        if(self.player_sprite.center_y<80):
            self.player_sprite.center_y = 80
            self.player_sprite.texture = self.texture_idel
            self.player_sprite.change_y= 0

    # もし、スペースバーが押されたらジャンプする
    def on_key_press(self, key, modifiers):
        if key == arcade.key.SPACE:
            self.player_sprite.texture = self.texture_jump
            self.player_sprite.change_y= 4

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

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


(11) 敵のスプライトに当たったら音が出る

プレイヤーのスプライトと、敵のスプライトがぶつかった時に音が出るようにします。 この例では、当たった時にプレイヤーのスプライトのコスチュームが変わります。 on_update(self, delta_time)の中で、 arcade.check_for_collision_with_listで衝突判定をしています。

arcade.check_for_collision_with_listは、プレイヤーのスプライト(リストではなく) と敵のリスト(スプライトではなく)の衝突を判定しています。この関数は、スプライトとリストが重なっている ところがあると、Trueになります。 最初にぶつかった時だけ音をならしたいので、arcade.check_for_collision_with_listが Trueにセットされた瞬間を捕まえて音を鳴らします。 このため、フラグself.was_collisionは一つ前の衝突判定を持っています。

import arcade

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

        # プレイヤー
        self.texture_idel = arcade.load_texture(":resources:/images/animated_characters/female_person/femalePerson_idle.png")
        self.texture_fall = arcade.load_texture(":resources:/images/animated_characters/female_person/femalePerson_fall.png")
        self.player_sprite = arcade.Sprite(
            self.texture_idel,
            scale=1.0
        )

        self.player_sprite.center_x = 128
        self.player_sprite.center_y = 240

        # プレイヤーの「リスト」
        self.player_list = arcade.SpriteList()
        self.player_list.append(self.player_sprite)

        # 敵
        self.enemy_sprite = arcade.Sprite(
            ":resources:images/enemies/slimeBlue.png",
            scale=1.0
        )
        self.enemy_sprite.center_x = 512
        self.enemy_sprite.center_y = 240
        self.enemy_sprite.change_x = -2

        # 的の「リスト」
        self.enemy_list = arcade.SpriteList()
        self.enemy_list.append(self.enemy_sprite)

        # 音声ファイルを読み込む
        self.hit_sound = arcade.load_sound(
            ":resources:sounds/coin1.wav"
        )

        self.was_collision = False

    def on_draw(self):
        self.clear()

        self.player_list.draw()
        self.enemy_list.draw()

    def on_update(self, delta_time):
        # 敵を動かす
        self.enemy_list.update()

        # プレイヤーと敵が衝突したか調べる
        collision= arcade.check_for_collision_with_list(
            self.player_sprite,
            self.enemy_list
        )

        # 衝突していない状態から、衝突する時を判定する
        if(collision and not self.was_collision):
            # 衝突したスプライトに変更
            self.player_sprite.texture = self.texture_fall
            # 衝突したら音を鳴らす
            arcade.play_sound(self.hit_sound)
            # 今回の衝突判定を保存する
            self.was_collision = collision

        # 敵が左端に到達したら、右へ戻す
        if(self.enemy_sprite.center_x<=20):
            # 普通のスプライトに変更
            self.player_sprite.texture = self.texture_idel
            self.enemy_sprite.center_x=512
            self.was_collision = False

mywindow = MyGame(640, 480, "MyGame Example")
arcade.run()

(12) 背景の変更

背景は、 arcade.draw_texture_rectを on_drawで呼ぶことで描画します。 何を描画するか?を衝突の前後で書き換えています。 背景の画像は、__init__(self, width, height, title)の中で保存しています。


プログラムは上のプログラムの変更箇所のみ記載しています。 動作するプログラムはこちらのリポジトリにあります。

import arcade
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480

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

        # 背景
        self.background1 = arcade.load_texture("background1.png")
        self.background2 = arcade.load_texture("background2.png")
        self.background = self.background1

--- 略 ---

    def on_draw(self):
        self.clear()

        # 背景の描画
        arcade.draw_texture_rect(
            self.background,
            arcade.XYWH(
            SCREEN_WIDTH / 2,
            SCREEN_HEIGHT / 2,
            SCREEN_WIDTH,
            SCREEN_HEIGHT
            )
        )
        self.player_list.draw()
        self.enemy_list.draw()

    def on_update(self, delta_time):
        # 敵を動かす
        self.enemy_list.update()

        # プレイヤーと敵が衝突したか調べる
        collision= arcade.check_for_collision_with_list(
            self.player_sprite,
            self.enemy_list
        )

        # 最初に衝突した時
        if(collision and not self.was_collision):
            # 衝突したスプライトに変更
            self.player_sprite.texture = self.texture_fall
            # 衝突したら音を鳴らす
            arcade.play_sound(self.hit_sound)
            # 背景の変更
            self.background = self.background2
            # 衝突状態を保存する
            self.was_collision = collision

        # 衝突が解除された時
        if(not collision and self.was_collision):
            # 普通のスプライトに変更
            self.player_sprite.texture = self.texture_idel
            # 背景をもとに戻す
            self.background = self.background1
            # 衝突状態を保存する
            self.was_collision = collision

        # 敵が左端に到達したら、右へ戻す
        if(self.enemy_sprite.center_x<=20):
            self.enemy_sprite.center_x=512

--- 略 ---


(13) 同心円上にボールを放出

このプログラムは、真ん中から15度間隔で24個のボールが放出されます。 放出されたボールが端へ到達するとボールが一つずつ消えます。 全部のボールが消えると、0.8s後にもう一度ボールが真ん中からボールが放出されます。 全部のボールを消えたことを反転する判断文はself.player_spriteに含まれているballの全てがvisibleでは無い、と 以下の一行で判断しています。


if all(not ball.visible for ball in self.player_sprite):

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.player_list = arcade.SpriteList()

        # 表示されているスプライトの数
        self.visible_count=24

        # 時間のカウンント
        self.wait_time = 0
        self.waiting = True

        # 24個のスプライトの初期化
        image_source = "./ballarrow.png"
        self.player_sprite=[]
        for i in range(24):
            self.player_sprite.append(arcade.Sprite(image_source, 0.5))   # サイズ0.8
        
        # 24個のスプライトを円の真ん中に並べる
        for i in range(24):
            self.player_sprite[i].center_x = 320
            self.player_sprite[i].center_y = 240
            self.player_sprite[i].angle = i*15
            self.player_sprite[i].visible = False
            self.player_list.append(self.player_sprite[i])

    def on_draw(self):  --- 略 ---

    def on_update(self, delta_time):

        # 発射待ち
        if self.waiting:
            self.wait_time += delta_time

            if self.wait_time >= 0.8:
                self.waiting = False
                self.wait_time = 0

                for ball in self.player_sprite:
                    ball.visible = True
            return

        # ボールを移動
        for ball in self.player_sprite:

            if not ball.visible:
                continue

            ball.forward(2)

            # 端に到達
            if (ball.left <= 0 or ball.right >= self.width or
            ball.bottom <= 0 or ball.top >= self.height):

                # 円の真ん中に戻す
                ball.center_x = 320
                ball.center_y = 240
                ball.visible = False

        # 全部のボールが端に到達した
        if all(not ball.visible for ball in self.player_sprite):
            self.waiting = True
            self.wait_time = 0

--- 略 ---


(14) ジャンプで敵を避ける

右手から進んでくる敵のヘビをジャンプで避けるゲームです。 スペースバーを押すとプレーヤはジャンプします。 着地すると、次のヘビが現れます。 ヘビの速度はだんだん早くなります。 プレーヤと敵がぶつかると終了です。




(15) シューティングゲームの例

プレーヤーは左右の矢印キーで左右に動きます。 スペースバーを押すとボールを投げ上げます。 敵は右から左へ、かべに当たると方向を反転します。 ボールを投げる時に音が出ます。 また、敵に当たると音が出て敵の色が変わります。