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

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

第三回です。前回は敵キャラを出したので、今回は自機を出しましょう。

## 画像を用意する

Undertaleの戦闘画面ではプレイヤーはハートの姿になるので、ハートっぽい画像を作りました。こういうのってMacの場合何で作るのが楽なんでしょうね？今回はLibreOffice Drawで作りましたが(「記号シェイプ」にハートがあったのでそれを1個置いて、赤色にして、選択してファイル→エクスポートという手順です。エクスポート時、「選択範囲」にチェックしておくとうまくハートだけpngにすることができます。)

![heart.png](https://qiita-image-store.s3.amazonaws.com/0/43/ec0da30d-f0b0-a649-84a8-45788d283907.png)

## プレイヤーを表すクラスを作る

前回、「動くものはクラスにしよう」と書きました。プレイヤーも動くものなので、クラスにしましょう。Enemyクラスを作ったときと同じようにすればOKです。

例によって変更した箇所だけコメントを入れています。

```rb
require 'dxopal'
include DXOpal
# ハートの画像を宣言
Image.register(:player, "images/heart.png")
# ...

# プレイヤーを表すクラス
class Player < Sprite
  def initialize
    x = 300
    y = 300
    super(x, y, Image[:player])
  end
end

Window.load_resources do
  Window.bgcolor = C_BLACK
  enemy = Enemy.new
  # Playerクラスのオブジェクトを作る
  player = Player.new
  
  # ...

    enemy.update
    enemy.draw
    # プレイヤーを描画する
    player.draw
    
  # ...
```

これでハートが出るはず…

<img width="650" alt="スクリーンショット 2018-12-14 1.06.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/43/7978f4b3-10cf-7cc2-48cf-933ea6a44c68.png">

あれ、出ませんね。

## 描画順序について

しばし考えたのち、以下の箇所が悪いということに気づきました。draw_thick_boxは[初回](https://qiita.com/yhara/items/c9a2a21ff24c65978205)で定義した太い枠線を引くメソッドですが、外側の四角を描く→内側を背景色で塗りつぶす という動作なので、プレイヤーごと塗りつぶされてしまったようです。

```rb
    player.draw

    draw_thick_box(260, 240, 380, 360, C_WHITE, 4)
```

ということで順序を入れ替えてやると：

```rb
    draw_thick_box(260, 240, 380, 360, C_WHITE, 4)
    player.draw
```

ハートが出ました。

<img width="646" alt="スクリーンショット 2018-12-14 1.14.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/43/188c4167-c555-91f4-d9fb-d388937d7958.png">

このようにDXOpalでは基本的に実行した順に描画が行われるので、順番には注意しましょう。(別解として[z値で描画順を指定する](http://mirichi.github.io/dxruby-doc/tips/zindex.html)方法もありますが、今回のような簡単なプログラムなら単に順番に気をつけるだけで良いでしょう)

## プレイヤーを動かす

次はキー操作でプレイヤーを動かせるようにしてみましょう。DXOpalでは`Input.key_push?(K_LEFT)`のようにしてキーの状態が取れます…が、カーソルキーに限っては、もっと簡単に`Input.x`, `Input.y`で状態が取れるようになっています。これを使ってみましょう。

```rb
class Player < Sprite
  def initialize
    x = 300
    y = 300
    super(x, y, Image[:player])
  end

  # キー操作に応じて座標を変化させる
  def update
    self.x += Input.x
    self.y += Input.y
  end
end

# ...

    enemy.update
    enemy.draw
    draw_thick_box(260, 240, 380, 360, C_WHITE, 4)
    # キー入力を処理する
    player.update
    player.draw
```

Playerを動かす処理は、Enemyのときと同様、updateというメソッドでやることにします。`Input.x`はカーソルキーの左が押されたら-1、右が押されたら+1になるので、それをそのまま座標に足してやることで、自機が動くようになります。

## 範囲を制限する

とはいえこれだけだと白線の外にも出ていってしまうので、範囲をチェックする処理を追加しましょう。まず下準備として、プレイヤーの移動範囲を定数にしておきます。というのは「プレイヤーの移動範囲」と「白線で囲む範囲」は同じなので、定数を使って共通化しておけば、位置を微調整したくなったときに、定数の値を変えるだけで済むので楽になります。

```rb
require 'dxopal'
include DXOpal
Image.register(:player, "images/heart.png")
Image.register(:enemy, "images/computer_typing_hayai.png")
C_ORANGE = [233, 115, 51]
FONT_HP = Font.new(18)
# プレイヤーの移動範囲
PLAYAREA = [260, 240, 380, 360]

# ...

    enemy.update
    enemy.draw
    # 移動範囲を囲むように白線を引く
    draw_thick_box(PLAYAREA[0]-4, PLAYAREA[1]-4, PLAYAREA[2]+4, PLAYAREA[3]+4, C_WHITE, 4)
```

これで準備はOKです。では、この`PLAYAREA`を使ってプレイヤーの移動範囲を制限しましょう。Playerのupdateメソッドを修正します。

```rb
class Player < Sprite
  def initialize
    x = 300
    y = 300
    super(x, y, Image[:player])

    # x, yの最小値と最大値
    @min_x = PLAYAREA[0]
    @max_x = PLAYAREA[2] - self.image.width   # xは「プレイヤー画像の左端」なので、画像の幅を引かないと右端に移動したとき枠に被ってしまう
    @min_y = PLAYAREA[1]
    @max_y = PLAYAREA[3] - self.image.height
  end

  def update
    self.x += Input.x
    self.x = @min_x if self.x < @min_x  # 小さすぎたら@min_xにする
    self.x = @max_x if self.x > @max_x  # 大きすぎたら@max_xにする
    self.y += Input.y
    self.y = @min_y if self.y < @min_y
    self.y = @max_y if self.y > @max_y
  end
end
```

こんな感じでしょうか？これでもいいのですが、「数値をある範囲に制限したい」というときはRuby 2.4から追加された[clampメソッド](https://docs.ruby-lang.org/ja/search/query:clamp/)を使うとシンプルに書けます。

```rb
  def update
    self.x = (self.x + Input.x).clamp(@min_x, @max_x)
    self.y = (self.y + Input.y).clamp(@min_y, @may_y)
  end
```

## まとめ

今回は自機の表示と移動を実装しました。こんな感じでぴたっと止まってくれます。

<img width="634" alt="スクリーンショット 2018-12-22 20.52.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/43/8dd81de9-6a40-3a5d-3cac-f2abaee0122c.png">

実際に動いているところは以下で見れます。カーソルキーで自機が移動します。

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

次回があれば敵の攻撃パターンを実装して、完成としたいです。