DXOpalでUndertaleっぽい画面を作る(終) 

- [DXOpalでUndertaleっぽい画面を作る \- Qiita](https://qiita.com/yhara/items/c9a2a21ff24c65978205)
- [DXOpalでUndertaleっぽい画面を作る\(続\) \- Qiita](https://qiita.com/yhara/items/2de1d0d39cf595a42088)
- [DXOpalでUndertaleっぽい画面を作る\(続2\) \- Qiita](https://qiita.com/yhara/items/6b6c15143edc2a434306)

前回は自機を出すところまでやりました。今回は敵が弾を撃ってくるようにして、完成とします。

## 敵弾クラスを作る

例によって、Spriteを継承して敵弾クラスを作ります。画像は、適当に丸を描いて敵弾ということにする手もありますが、せっかく「[タイピングが速い人のイラスト](https://www.irasutoya.com/2018/12/blog-post_83.html)」を使っているので、文字が弾として飛んでくるようにします。

といってもいろんな文字の画像を用意するのは大変なので、`Image.new`を使って自動で生成しましょう。`Image#draw_font`を使うと、Windowではなく画像オブジェクトに文字を描画できます。

```rb
class EnemyBullet < Sprite
  FONT_BULLET = Font.new(20)

  def initialize(x, y)
    img = Image.new(20, 20)
    # 「あ」から「ん」までの文字をランダムに1つ選ぶ
    str = ("あ".."ん").to_a.sample
    img.draw_font(0, 0, str, FONT_BULLET, C_WHITE)
    super(x, y, img)
  end
end
```

うまく生成できているか、描画して確かめてみましょう。

```rb
Window.load_resources do
  Window.bgcolor = C_BLACK
  enemy = Enemy.new
  # 敵弾を一つ生成する
  enemy_bullet = EnemyBullet.new(0, 0)
  player = Player.new

# ...

    enemy.update
    enemy.draw
    # 敵弾1つを描画する
    enemy_bullet.draw
```

左上に「つ」という文字が出ています。良さそうです。

<img width="639" alt="スクリーンショット 2018-12-22 21.17.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/43/74b3a312-0b7b-39a2-ddc1-ebe452334cfb.png">

## 弾をたくさん出す

弾が1個だとたぶん簡単すぎるので、敵弾は複数出したいですよね。DXOpalでは複数のスプライト(Spriteを継承したオブジェクト)をまとめて扱うメソッドがあるので、これらを使うと便利です。

- Sprite.update: まとめてupdateする
- Sprite.draw: まとめてdrawする
- Sprite.clean: vanishフラグが立っているものを削除する

使い方は、以下のようになります。

```rb
class EnemyBullet < Sprite
  # ...

  def update
    self.y += 8
    # 画面外に出たらvanishフラグを立てる
    self.vanish if self.y > Window.height
  end
end

# ...

Window.load_resources do
  Window.bgcolor = C_BLACK
  enemy = Enemy.new
  # 敵弾の配列
  enemy_bullets = []
  player = Player.new

# ...

    player.draw

    # 敵弾が減ったら補充する
    if enemy_bullets.length < 8
      enemy_bullets.push(EnemyBullet.new(enemy.x, enemy.y))
    end
    Sprite.clean(enemy_bullets)   # 画面外に出たものを配列から削除する
    Sprite.update(enemy_bullets)  # 敵弾のupdateメソッドを順に呼ぶ
    Sprite.draw(enemy_bullets)    # 敵弾のdrawメソッドを順に呼ぶ
```

敵弾が複数になるので、`enemy_bullet =`としていた部分を`enemy_bullets = []`と配列にしています。メインループでは、敵弾が8個未満のときに新しい弾を作っています。

敵弾は`EnemyBullet#update`を呼ぶごとに下に移動します。画面外に出たら、Spriteクラスのvanishメソッドを呼んで、「vanishフラグ」を立てるようにします。こうすると、`Sprite.clean`でvanishフラグが立ったものをまとめて削除できます。

実行すると、こんな感じになりました。

![beam1.gif](https://qiita-image-store.s3.amazonaws.com/0/43/1750cfb2-08c5-9f42-bcb0-e01f90bf4fd0.gif)

いくつか問題がありそうなので、順に解決していきます。

## 弾が敵の肩から出ている

まず、弾が敵の肩のあたりから出ているように見えます。これは、弾を作るときに

```rb
      enemy_bullets.push(EnemyBullet.new(enemy.x, enemy.y))
```

としているのが原因です。`enemy.x`, `enemy.y`は敵の画像の左上を指すので、`enemy.hand_pos`で手の間くらいの位置を取得できるようにしましょう。

```
class Enemy < Sprite
  # ...

  def hand_pos
    [self.x + self.image.width / 2,
     self.y + self.image.height * 0.8]
  end

# ...

      enemy_bullets.push(EnemyBullet.new(*enemy.hand_pos))
```

これでいい感じの位置から弾が出るようになりました。

## 弾がダンゴになる

弾を生成している箇所は以下です。DXOpalのメインループは1秒におよそ60回実行されるので、このコードだと弾があっという間に8個作られてしまいます。

```rb
    if enemy_bullets.length < 8
      enemy_bullets.push(EnemyBullet.new(*enemy.hand_pos))
    end
```

そこで、弾を作る条件を以下のようにします。[rand](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/rand.html)は0.0から1.0未満の数を返すので、この例だと約1/10の確率で弾が生まれることになります。

```rb
    if enemy_bullets.length < 8 && rand < 0.1
      enemy_bullets.push(EnemyBullet.new(*enemy.hand_pos))
    end
```

## 弾が速すぎる

`EnemyBullet#update`ではy座標を8ずつ増やしていましたが、これだとちょっと避けきれない気がします。実際の速度を見ながら、値を適当に調整します。2くらいだとちょうどいいでしょうか。

```rb
  def update
    self.y += 2
    self.vanish if self.y > Window.height
  end
```

ここまでの修正を入れると、こんな感じになりました。

![beam2.gif](https://qiita-image-store.s3.amazonaws.com/0/43/999a9615-b2b1-5e91-a257-84becfcc2aee.gif)

## 弾をばらけさせる

だいぶマシになりましたが、ちょっと攻撃が単調ですよね。今は弾のy座標だけ変化させていますが、x座標も変化するようにしてみましょう。

```rb
class EnemyBullet < Sprite
  FONT_BULLET = Font.new(20)

  def initialize(x, y)
    img = Image.new(20, 20)
    img.draw_font(0, 0, ("あ".."ん").to_a.sample, FONT_BULLET, C_WHITE)
    super(x, y, img)

    # ｘ方向の動きを決める(-2,-1,0,+1,+2のいずれかになる)
    @dx = rand(-2..2)
  end

  def update
    # x方向にも動かす
    self.x += @dx
    self.y += 2
    self.vanish if self.y > Window.height
  end
end
```

実行するとこうなります。

![beam3.gif](https://qiita-image-store.s3.amazonaws.com/0/43/20f06a05-d221-821c-df33-2ee6ad148557.gif)


どうでしょう？ぐっとゲームっぽくなりましたね。

## 当たり判定を付ける

最後にひと頑張りして、敵弾と自機の当たり判定を実装しましょう。当たり判定に使えるのは[Sprite.check](http://mirichi.github.io/dxruby-doc/api/Sprite_check.html)です。

Sprite.checkには配列を2つ渡します。1つ目は「当たる側のスプライト」、2つ目は「当たられる側のスプライト」です。今回は「弾がプレイヤーに当たったとき」に処理をしたいので、1つ目を`enemy_bullets`、2つ目を`[player]`(playerだけが入った配列)にします。

```rb
    if enemy_bullets.length < 8 && rand < 0.1
      enemy_bullets.push(EnemyBullet.new(*enemy.hand_pos))
    end
    # 当たり判定をチェック
    Sprite.check(enemy_bullets, [player])
    Sprite.clean(enemy_bullets)
    Sprite.update(enemy_bullets)
    Sprite.draw(enemy_bullets)
```

こうすると、敵弾がplayerと接触したときに以下が起こります。

- 敵弾の`shot`メソッドが呼ばれる
- プレイヤーの`hit`メソッドが呼ばれる

## 当たったときの処理を書く

ということで`EnemyBullet#shot`と、`Player#hit`を実装しましょう。敵弾については、ヒットしたらvanishを呼んで消えることにしましょう。

```rb
  def shot
    self.vanish
  end
```

プレイヤーの方はHPが減ることにしましょう。そういえば体力をまだ変数にしていなかったので、プレイヤーのインスタンス変数として持つことにしましょう。また、メインループから`player.hp`で取得できるように`attr_reader`を書いておきます。

```rb
class Player < Sprite
  def initialize
    # ...
    # 初期体力
    @hp = 20
  end
  attr_reader :hp

  # ...

  def hit
    @hp -= 1
  end
end
```

メインループにHPを描画する箇所があるので、以下のようにして`player.hp`が反映されるようにします。

```rb
    Window.draw_font(270, 370, "HP #{player.hp}/20", FONT_HP, color: C_WHITE)
```

ためしに弾に当たってみて、HPが減ることを確認してください。

## まとめ

今回は敵弾を実装して、当たるとHPが減るようにしました。本シリーズはこれで終わりですが、「ゲーム」として考えるとやれることはまだまだあるでしょう。HPが0になったらゲームオーバーにするとか、当たり判定を改善するとか。(デフォルトでは画像全体が判定エリアになりますが、`collision=`で画像の一部だけを設定することもできます。画像より一回り小さい円に設定すると自然な感じになります。) このあたりの話は、Rubyist Magazineの以下の記事が参考になると思います。ぜひいろいろ試してみてください。

- [Rubyで始めるゲームプログラミング \- DXOpal編 \-](https://magazine.rubyist.net/articles/0057/0057-GameProgramingWithDXOpal.html)

完成したものは以下にあります。

- [デモ](https://yhara.github.io/adcal2018/11/)
- [ソース](https://github.com/yhara/adcal2018/blob/master/11/main.rb)

ソースはMITライセンスとしますので、このページへのリンクを貼ってもらえれば自由に改造・再利用などして構いません。動くものができたら、ぜひ公開にもチャレンジしてください:-)

- [DXOpalで作ったゲームを無料で公開する \- Qiita](https://qiita.com/yhara/items/24141953af652ad96db0)