phina.jsでモンストっぽいゲームを作る【２】ー矢印の表示ー

# はじめに
モンストもどきを作るという試みの第２回となる今回は、モンストの象徴とも言えるプレイヤーを引っ張ると表示される矢印を作っていきます。

![monst-tut-03.gif](https://qiita-image-store.s3.amazonaws.com/0/67114/0ac5b2d1-7b58-0964-7ce3-55bf43667ff0.gif)

## 矢印画像の読み込み
矢印は**Canvas**で描画できないこともありませんが、コードが複雑になるので今回は以下のような画像を用意しました。

![arrow.png](https://rawgit.com/alkn203/piko_strike/master/assets/arrow.png)

そして、いつも通りにASSETSに定義します。

```js
// アセット
var ASSETS = {
  // 画像
  image: {
    'bg': 'https://rawgit.com/alkn203/piko_strike/master/assets/bg.png',
    'tomapiko': 'https://rawgit.com/phinajs/phina.js/develop/assets/images/tomapiko.png',
    'arrow': 'https://rawgit.com/alkn203/piko_strike/master/assets/arrow.png',
  },
};
```
## 矢印の表示

矢印はメインシーンに追加します。

```js
// コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit();
    // 背景
    Sprite('bg').addChildTo(this)
                .setPosition(this.gridX.center(), this.gridY.center());
    // 矢印
    var arrow = Sprite('arrow').addChildTo(this).hide();
    arrow.alpha = 0.75;
    // プレイヤー
    this.player = Player().addChildTo(this);
    this.player.setPosition(this.gridX.center(), this.gridY.span(13));
    arrow.setPosition(this.player.x, this.player.y);
    // シーン全体から参照できるようにする
    this.arrow = arrow;
  },
```

* 矢印はスプライトとして追加しますが、追加するタイミングは、背景の後でプレイヤーよりも先にします。**phina.js**では、基本的にシーンに追加した順番で重なり順が決まるからです。
* 追加した後は、**hide**メソッドで一旦非表示にします。
* 背景が少し透けるように**alpha**値を指定しています。
* 矢印の位置はプレイヤーと同じ位置にします。（プレイヤーは少し上に移動させました）
* 最後に**this.arrow = arrow**としているのは、色々な設定は一旦プライベート変数のままで行って、後にシーン全体から参照できるようにするためです。

## 矢印の特性
先に矢印の特性について整理しておきます。

* タッチ時から表示される
* 引っ張った方向とは逆の方向が矢印の頭になる
* 引っ張った距離に応じて縦方向に拡大
* タッチが終わると消える

これらの特性を踏まえて、**タッチ開始、タッチ中移動、タッチ終了**に分けて処理を書いていきます。

#### タッチ開始時

```js
  // タッチ開始時処理
  onpointstart: function(e) {
    // タッチ位置を記録
    this.startPos = Vector2(e.pointer.x, e.pointer.y);
    // 矢印表示
    this.arrow.setPosition(this.player.x, this.player.y).show();
    // 縦を0に縮小
    this.arrow.scaleY = 0;
  },
```

* **onpointstart**の引数eにタッチ関係の情報が入ってきますので、**e.pointer**からタッチされた位置を取得して、変数に代入しておきます。
* 矢印をプレイヤーの位置に表示します。突然出現するように見せるために、縦サイズを0に縮小しています。

#### タッチ中移動時

```js
  // タッチ移動時処理
  onpointmove: function(e) {
    // 矢印の方向を求める
    var pos = Vector2(e.pointer.x, e.pointer.y);
    this.arrow.rotation = this.getDegree(this.startPos, pos) + 90;
    // 距離に応じて矢印を拡大縮小
    var distance2 = Vector2.distanceSquared(this.startPos, pos);
    this.arrow.scaleY = distance2 / 10000;
  },
```

* **onpointstart**で代入したタッチ開始位置と現在のタッチ位置から、別途作った**getDegree**関数を使って矢印の方向を求めます。
* **rotation**に代入する時には、計算上とcanvas上の角度の開始位置に90度のズレがあるので補正しています。
* **Vector2.distanceSquared**は、引数で与えられた２つの座標間の距離の2乗を返す関数です。距離そのものを返す**distance**でも良いのですが、平方根の計算は処理が重たいと言われていますので、今回は使用を避けています。
* 最後に矢印を拡大する時に、計算した値をそのまま使うととてつもない大きさになってしまいますので、適用な値で除してサイズを調整しています。

##### 参考：getDegree関数
```js
// 2点間の角度を求める
  getDegree: function(from, to) {
    return Math.radToDeg(Math.atan2(from.y - to.y, from.x - to.x));
  },
```

* 2点間の角度は**atan2**関数を使って求めることができます。引数のfromとtoの順番で矢印の頭の向きが変わるので注意します。
* **atan2**はラジアン値を返すので、**radToDeg**関数でラジアンから度に変換しています。

#### タッチ終了時

```js
  // タッチ終了時処理
  onpointend: function(e) {
    // 矢印非表示
    this.arrow.hide();
  },
```

* 矢印を**hide**で非表示にしているだけです。

## 動作確認

[[runstantプロジェクトへのリンク]](http://runstant.com/alkn203/projects/ee690eec)

* 画面上の何処でも良いので、タッチしながら移動するとプレイヤーの下に矢印が表示されます。
* 実際のゲームでは画面端から矢印がはみ出さないように対策していると思いますが、今回はそこまでは考慮していません。

## 全体コード
```js
// グローバルに展開
phina.globalize();
// アセット
var ASSETS = {
  // 画像
  image: {
    'bg': 'https://rawgit.com/alkn203/piko_strike/master/assets/bg.png',
    'tomapiko': 'https://rawgit.com/phinajs/phina.js/develop/assets/images/tomapiko.png',
    'arrow': 'https://rawgit.com/alkn203/piko_strike/master/assets/arrow.png',
  },
};
// 定数
var SCREEN_WIDTH = 640;
var SCREEN_HEIGHT = 960;
/*
 * メインシーン
 */
phina.define("MainScene", {
  // 継承
  superClass: 'DisplayScene',
  // コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit();
    // 背景
    Sprite('bg').addChildTo(this)
                .setPosition(this.gridX.center(), this.gridY.center());
    // 矢印
    var arrow = Sprite('arrow').addChildTo(this).hide();
    arrow.alpha = 0.75;
    // プレイヤー
    this.player = Player().addChildTo(this);
    this.player.setPosition(this.gridX.center(), this.gridY.span(13));
    arrow.setPosition(this.player.x, this.player.y);
    // シーン全体から参照できるようにする
    this.arrow = arrow;
  },
  // タッチ開始時処理
  onpointstart: function(e) {
    // タッチ位置を記録
    this.startPos = Vector2(e.pointer.x, e.pointer.y);
    // 矢印表示
    this.arrow.setPosition(this.player.x, this.player.y).show();
    // 縦を0に縮小
    this.arrow.scaleY = 0;
  },
  // タッチ移動時処理
  onpointmove: function(e) {
    // 矢印の方向を求める
    var pos = Vector2(e.pointer.x, e.pointer.y);
    this.arrow.rotation = this.getDegree(this.startPos, pos) + 90;
    // 距離に応じて矢印を拡大縮小
    var distance2 = Vector2.distanceSquared(this.startPos, pos);
    this.arrow.scaleY = distance2 / 10000;
  },
  // タッチ終了時処理
  onpointend: function(e) {
    // 矢印非表示
    this.arrow.hide();
  },
  // 2点間の角度を求める
  getDegree: function(from, to) {
    return Math.radToDeg(Math.atan2(from.y - to.y, from.x - to.x));
  },

});
/*
 * プレイヤークラス
 */
phina.define("Player", {
  // 継承
  superClass: 'Sprite',
  // コンストラクタ
  init: function() {
    // 親クラス初期化
    this.superInit('tomapiko');
  },
});
/*
 * メイン処理
 */
phina.main(function() {
  // アプリケーションを生成
  var app = GameApp({
    title: 'Piko Strike',
    // メインシーンから開始
    startLabel: 'main',
    // アセット読み込み
    assets: ASSETS,
  });
  // 実行
  app.run();
});
```

# 次回予定
次回は、プレイヤーを引っ張った後の動きと画面との反射処理を実装したいと思います。