phina.jsでモンストっぽいゲームを作る【３】ープレイヤーの移動と反射処理ー

# はじめに
モンストもどきを作るという試みの第３回となる今回は、プレイヤーの移動と反射処理を作っていきます。

![monst-tut-04.gif](https://qiita-image-store.s3.amazonaws.com/0/67114/cfcc3018-72ff-e34f-ecda-656658b09a9e.gif)

## 定数追加
最初にプレイヤーのスピードを定数に追加します。

```js
var PLAYER_SPEED = 50;
```

## 矢印の方向にプレイヤーを移動させる
タッチ終了後にプレイヤーを移動させますので、**onpointend**に処理を追加します。

```js
  // タッチ終了時処理
  onpointend: function(e) {
    // 矢印非表示
    this.arrow.hide();
    // 矢印の角度から方向を決める
    var deg = this.arrow.rotation - 90;
    // 角度からベクトルを求めてプレイヤーに設定
    this.player.physical.velocity = Vector2().fromDegree(deg, PLAYER_SPEED);
  },
```

* 矢印の表示上の角度はすでに決まっているので、それを計算上の角度に補正します。
* **Vector2**クラスの**fromDegree**は、角度と大きさからベクトルを求めて返すメソッドで、今回はこれを利用します。
*  **fromDegree**で求めたベクトルを**physical**の**velocity**に設定します。これで矢印の方向にプレイヤーが移動します。

## 画面端との反射処理
このままだとプレイヤーが画面外に出てしまいますので、画面端との反射処理を追加します。
今回は、**MainScene**の**update**関数の中に追加します。

```js
  // 毎フレーム更新処理
  update: function() {
    var velocity = this.player.physical.velocity;
    var player = this.player;
    // 画面端反射処理
    if (player.left < 0) {
      player.left = 0;
      velocity.x *= -1;
    }
    if (player.right > SCREEN_WIDTH)  {
      player.right = SCREEN_WIDTH;
      velocity.x *= -1;
    }
    if (player.top < 0) {
      player.top = 0;
      velocity.y *= -1;
    }
    if (player.bottom > SCREEN_HEIGHT) {
      player.bottom = SCREEN_HEIGHT;
      velocity.y *= -1;
    }
  },
```

* **left, right, top, bottom**プロパティを使うことで端との判定が楽にできます。
* 横方向の反射は、**velocity.x**、縦方向の反射は**velocity.y**にそれぞれ**-1**を乗じて、移動方向を反転させています。

## プレイヤーの減速処理
プレイヤーは画面からはみ出なくなりましたが、このままだと永久に反射し続けていくだけです。
そこで、**physical**を使って、プレイヤーを徐々に減速させる処理を追加します。

## 定数追加
摩擦度（減速度）を定数に追加します。

```js
var FRICTION = 0.98;
```

減速処理はプレイヤーが移動した直後に設定するので、**onpointend**に追加します。

```js
  // タッチ終了時処理
  onpointend: function(e) {
    // 矢印非表示
    this.arrow.hide();
    // 矢印の角度から方向を決める
    var deg = this.arrow.rotation - 90;
    // 角度からベクトルを求めてプレイヤーに設定
    this.player.physical.velocity = Vector2().fromDegree(deg, PLAYER_SPEED);
    // 摩擦をかけて徐々に減速させる
    this.player.physical.friction = FRICTION;
  },
```

* **physical.friction**に定数で定義した摩擦度(0.98)を設定しています。
* この場合、毎フレームの処理で前の速度の**0.98**倍となっていくので、結果的に徐々に減速していくことになります。

## 画面タッチ制限
一見良さそうにみえますが、このままだとプレイヤーが移動途中でも次のタッチ入力ができていまいます。
そこで、プレイヤーが完全に止まったのを確認してからでないと次の移動操作が出来ないように制限します。

```js
  // タッチ終了時処理
  onpointend: function(e) {
    // 画面タッチを無効にする
    this.setInteractive(false);
```

* プレイヤーを移動させた後に画面タッチを無効にしています。

```js
  // タッチ終了時処理
  onpointend: function(e) {
    // 画面タッチを無効にする
    this.setInteractive(false);
```

画面タッチ再開の判断は、プレイヤーが完全に止まった後にしたいので、以下の処理をシーンの**update**に追加します。

```js
// 一定速度を下回ったら強引に停止させる
    if (velocity.length() > 0 && velocity.length() < 2) {
      velocity.set(0, 0);
      // 画面タッチを有効にする
      this.setInteractive(true);
    }    
```

* **Vector2**の**lentgh**関数で速度ベクトルの大きさを求めて、一定の速度を下回ったら速度を0にしています。
* その後、**setInteractive**で画面タッチを有効にしています。

## 動作確認

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

## 全体コード
```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;
var PLAYER_SPEED = 50;
var FRICTION = 0.98;
/*
 * メインシーン
 */
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;
  },
  // 毎フレーム更新処理
  update: function() {
    var velocity = this.player.physical.velocity;
    var player = this.player;
    // 画面端反射処理
    if (player.left < 0) {
      player.left = 0;
      velocity.x *= -1;
    }
    if (player.right > SCREEN_WIDTH)  {
      player.right = SCREEN_WIDTH;
      velocity.x *= -1;
    }
    if (player.top < 0) {
      player.top = 0;
      velocity.y *= -1;
    }
    if (player.bottom > SCREEN_HEIGHT) {
      player.bottom = SCREEN_HEIGHT;
      velocity.y *= -1;
    }
    // 一定速度を下回ったら強引に停止させる
    if (velocity.length() > 0 && velocity.length() < 2) {
      velocity.set(0, 0);
      // 画面タッチを有効にする
      this.setInteractive(true);
    }    
  },
  // タッチ開始時処理
  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.setInteractive(false);
    // 矢印非表示
    this.arrow.hide();
    // 矢印の角度から方向を決める
    var deg = this.arrow.rotation - 90;
    // 角度からベクトルを求めてプレイヤーに設定
    this.player.physical.velocity = Vector2().fromDegree(deg, PLAYER_SPEED);
    // 摩擦をかけて徐々に減速させる
    this.player.physical.friction = FRICTION;
  },
  // 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();
});
```

</div></div>

# 次回予定
次回は、敵の追加と敵との当たり判定を実装したいと思います。