黒ひげ危機一〇髪で触るphina.js

# はじめに
コミックマーケット95にて拓殖大学ディジタルコンテンツ研究愛好会様にて頒布させていただきました、『黒ひげ危機一〇髪で触るphina.js』というコピー本の内容を[拓殖大学ディジタルコンテンツ研究愛好会 Advent Calendar 2019 23日目](https://adventar.org/calendars/4622)の、この場を借りて公開されて頂きます。
頒布という形であるので、購入者から反感を買うのではないか、と心配の声があるかもしれませんが、悲しいことに一部も売れなかったので問題ありません。

コピー本では開発環境を用意させていただいていたのですが、サービスが終了してしまったので、その部分はカットして公開させていただきます。

# 表紙
![C95表紙_pages-to-jpg-0001.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/299962/09a1938d-0dae-22fe-01b1-9c8ccd51b2c5.jpeg)


# 前書き
はじめまして。ご購入頂きありがとうございます。

この本はJavaScriptの基本的な文法を理解しており、Web上で動くゲームをつくりたい方向けの本になります。

皆さんは、phina.jsというゲームフレームワークをご存知でしょうか？　このフレームワークはブラウザ上で動くゲームが比較的簡単につくれて、それをSNSに載せることで承認欲求の満たされる私が愛用しているフレームワークの一つです。

今回は、そんないつも
承認欲求を満たすのに
お世話になっているphina.jsを知らない方に認知してもらえるように、まだ使ったこと無い方にも使ってもらえるように、今回初めて筆を執らせていただきました。

phina.jsの１ユーザーに過ぎませんが、もしよろしければ最後までご一読頂けるよう、phina.jsを使っていただけるよう、書かせて頂きました。

今回は『黒ひげ危機一〇髪もどき』をつくりながら解説していこうと思います。よろしくお願いします。

## 参考
参考にさせていただきました。ありがとうございます。

- [phina.js API Documentation](http://phinajs.com/docs/)
- [とりあえず試してみたいって方のための phina.js 入門](http://phiary.me/phinajs-tutorial-try/)
- [本日 phina.js version 0.2 をリリースしました](http://phiary.me/phinajs-release-v0-2/)
- [phina.js Tips集](https://qiita.com/alkn203/items/bca3222f6b409382fe20)
- [phina.js チュートリアル集](https://qiita.com/alkn203/items/2150f739d383b4fd5a92)
- [phina.js superMethodの使い方](https://qiita.com/Negiwine_jp/items/2fb5258f42a8a401c126)

# 今回作るもの

黒ひげ危機一〇髪のブラウザで遊べるスマートフォン向けゲームをつくります。

完成したゲームは以下のリンクより遊べます。
https://masayasviel.github.io/blackBeard-phina_js/

追記：以下のリンクよりコードを公開しています。
https://github.com/masayasviel/blackBeard-phina_js

六つある樽の中に一つある、黒ひげが隠れているハズレの樽を選ばないようにタッチしていくゲームです。どれだけ空の樽を選べたかをリザルト画面で表示します。

また、スコアをツイートできるようにもします。

# phina.jsとは

国産のJavaScriptのゲーム制作向けフレームワークです。HTML5に対応しているので、JavaScriptの動作するブラウザであればphina.jsで書かれたプログラムを動かすことが出来ます。

phina.jsの強みは、[公式サイト](http://phinajs.com/)には

>- アイディアを即座に形にできるゲームライブラリです
>- 初心者でも手軽にゲームを開発できます
>- 様々な Web コンテンツ, アプリで多数の採用実績があります
>- 大学や専門学校といった教育機関で利用されています
>- 国産かつオープンソースでTwitter 駆動開発なので気軽にコントリビューターになれます

と書かれています。加えて個人の初見として、

+ JavaScriptという実行環境を整えやすい言語のフレームワークである
+ HTML、CSSの細かな知識が必要ない
+ PCとスマートフォンどちらでも動く
+ [公式サイト](http://phinajs.com/)にサンプルが複数用意されている

が挙げられます。

ここでいくら説明しても、良いところを書き連ねても、『百聞は一見に如かず』とはいったもので実際に手を動かしてみるのが一番だと考えます。

それでは早速始めて行こうと思います。

# 実行環境

今回は実行環境として、[jsdo.it](http://jsdo.it/)を使わせて頂きました。また、今回はバージョン:71.0.3578.98のGoogle chromeで動作確認をしました。
ですが、[jsdo.it](https://jsdo.it/)はサービスが終了してしまったので、登録及び簡単な使い方の説明はカットさせていただきます。

# テンプレート
## HTML

まずはHTMLの部分に以下のコードを書きましょう。

```html
<!doctype html>

<html>
  <head>
    <meta charset='utf-8' />
    <meta name="viewport" content="width=device-width, user-scalable=no" />
    <meta name="apple-mobile-web-app-capable" content="yes" />

    <title>黒ひげ危機一〇髪</title>
    <!-- phina.js を読み込む -->
    <script src='https://cdn.rawgit.com/phi-jp/phina.js/v0.2.0/build/phina.js'></script>

  </head>
  <body>

  </body>
</html>
```

これは[phina.js](http://phinajs.com/)に書かれた`index.html`のコードをコピペしても構いません。ただし、その場合は

```html
<title>Getting started | phina.js</title>
<script src='http://cdn.rawgit.com/phi-jp/phina.js/v0.2.0/build/phina.js'></script>
```

の部分を、

```html
<title>黒ひげ危機一〇髪</title>
<script src='https://cdn.rawgit.com/phi-jp/phina.js/v0.2.0/build/phina.js'></script>
```

と書き直しましょう。`http`ではなく`https`に、`<title>`部分を`黒ひげ危機一〇髪`にしましょう。(`<title>`部分は好きにしていいと思いますが、あえてここでは`黒ひげ危機一〇髪`で統一することにします。)

今後、HTML部分は書き換えません。理由は後述します。

CSSの部分はそのままにしておきましょう。

HTML同様、CSSも今後イジることはありません。

## JavaScript
`phina.js`では描写や背景なども全てJavaScriptの部分に記述していきます。これがHTML、CSSの細かな知識が必要ない、書き換えない理由です。

`phina.js`には雛型があり、それを元に不要な部分は削除し、必要な部分をつけ足していく、といった流れでコードを書いていきます。

これが雛型となります。

```javascript
// phina.js をグローバル領域に展開
phina.globalize();

// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene', // 継承元
  // コンストラクタ
  init: function() {
    this.superInit(); // 親クラスのコンストラクタに値を渡す
  },
});

// メイン処理
phina.main(function() {
  // アプリケーション生成
  let app = GameApp({
    startLabel: 'main', // メインシーンから開始する
  });
  // アプリケーション実行
  app.run();
});
```

これは[phina.js](http://phinajs.com/)の`main.js`から不要な部分を除いたものになります。また、`superClass: CanvasScene`は`superClass: 'DisplayScene'`直しましょう。

バージョンが変わったときに変更されたみたいです。

【ソース】[本日 phina.js version 0.2 をリリースしました](http://phiary.me/phinajs-release-v0-2)

さて、以上で前知識の説明は終わりです。ここからゲームをつくっていきます。

# ゲームをつくる

## 画面をつくる

以下、全てJavaScriptに書いていきます。

まずはタイトル画面を作ってゲームの画面サイズを決めちゃいます。ゲームの画面サイズはスマートフォンに合わせたサイズにします。

```javascript
// phina.js をグローバル領域に展開
phina.globalize();

// 定数
const SCREEN_WIDTH = 640;            // 画面横サイズ
const SCREEN_HEIGHT = 960;           // 画面縦サイズ

// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene', // 継承元
  // コンストラクタ
  init: function() {
    this.superInit(); // 親クラスのコンストラクタに値を渡す
  },
});

// メイン処理
phina.main(function() {
  // アプリケーション生成
  let app = GameApp({
    title: '黒ひげ危機一〇髪',
    startLabel: location.search.substr(1).toObject().scene || 'title', // 'title'から開始する
    width: SCREEN_WIDTH,
    height: SCREEN_HEIGHT,
  });
  // アプリケーション実行
  app.run();
});
```

これだけで画面をタッチするとメインシーンへ移行するタイトル画面は完成です。簡単ですね。

ゲームの画面サイズはウィンドウによって自動伸縮されます。

`let app = GameApp({});`内の`title:`部分の文字を変えると表示される文字が変わります。

これは`phina.js`が用意してくれたテンプレートです。リザルトシーンのもあります。また、タイトルシーンもリザルトシーンも自分でつくることもできますが今回はやりません。

## 画像を表示

まず、画像を読み込みます。

画像は[いらすとや](https://www.irasutoya.com/)様のを使わせていただきます。ありがとうございます。

画像は以下の画像を使用します。

[海賊](https://2.bp.blogspot.com/-vic8sGz8fv0/W6XJc9aEH1I/AAAAAAABPDA/zhl2g1Nq2qMRJGflTeOEim_IFdUffz9PwCLcBGAs/s450/kaizoku_man.png)

<img src="https://2.bp.blogspot.com/-vic8sGz8fv0/W6XJc9aEH1I/AAAAAAABPDA/zhl2g1Nq2qMRJGflTeOEim_IFdUffz9PwCLcBGAs/s450/kaizoku_man.png" width="100" height="100">

[樽](https://1.bp.blogspot.com/-BtjaBaaAKNw/VkxNbVZhsLI/AAAAAAAA0vs/roxlxfuXIgE/s400/drink_taru.png)

<img src="https://1.bp.blogspot.com/-BtjaBaaAKNw/VkxNbVZhsLI/AAAAAAAA0vs/roxlxfuXIgE/s400/drink_taru.png" width="100" height="100">

JavaScriptで画像を取り扱う場合、通常は`Image`コンストラクタを使用します。

例：

```javascript
let myImage = new Image();
myImage.src = "hoge.jpg";
```

では、`phina.js`ではどうするのでしょう？　こうします。

```javascript
// phina.js をグローバル領域に展開
phina.globalize();

// 定数
const SCREEN_WIDTH = 640;            // 画面横サイズ
const SCREEN_HEIGHT = 960;           // 画面縦サイズ

const ASSETS = {
  image: {
    pirate:"https://2.bp.blogspot.com/-vic8sGz8fv0/W6XJc9aEH1I/AAAAAAABPDA/zhl2g1Nq2qMRJGflTeOEim_IFdUffz9PwCLcBGAs/s450/kaizoku_man.png",
    barrel:"https://1.bp.blogspot.com/-BtjaBaaAKNw/VkxNbVZhsLI/AAAAAAAA0vs/roxlxfuXIgE/s400/drink_taru.png",
  }
};

// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene', // 継承元
  // コンストラクタ
  init: function() {
    this.superInit(); // 親クラスのコンストラクタに値を渡す
  },
});

// メイン処理
phina.main(function() {
  // アプリケーション生成
  let app = GameApp({
    title: '黒ひげ危機一〇髪',
    startLabel: location.search.substr(1).toObject().scene || 'title', // 'title'から開始する
    width: SCREEN_WIDTH,
    height: SCREEN_HEIGHT,
    assets: ASSETS,
    backgroundColor: '#191970',
  });
  // アプリケーション実行
  app.run();
});
```

`let app = GameApp({});`内に`assets: ASSETS,`と`backgroundColor: '#191970',`を加えましょう。

`assets`はアセットを有効にします。
ここでアセットとは画像や音声データなどのバイナリデータのことです。  
また、画像を読み込ませる場合は`assets`に以下のような書式のオブジェクトを渡します。

```
const ASSETS = {
  image: {
    キー名１:画像の場所１,
    キー名２:画像の場所２,
  }
};
```

【参考】[[phina.js-Tips-015] Spriteを表示する](https://qiita.com/alkn203/items/55ee7642f051d1b5ea58)

`backgroundColor`はそのまんま、背景色のことを指します。入れる書式はRGBカラーコードで指定します。今回は黒ひげということで海をイメージして深い青にしてみました。

次に画像を表示させてみたいと思います。

以下、コード中の`//...`はコードが省略されていることを示します。

```javascript
//...
// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene',
  init: function() {
    this.superInit();
      let pirate = Sprite('pirate',100,100).addChildTo(this); 
      pirate.x = SCREEN_WIDTH / 2 - 100;
      pirate.y = SCREEN_HEIGHT / 2;
      let barrel = Sprite('barrel',100,100).addChildTo(this); 
      barrel.x = SCREEN_WIDTH / 2 + 100;
      barrel.y = SCREEN_HEIGHT / 2;
  },
});

// メイン処理
//...
```

タイトル画面でタッチして、次のシーンへ行くと小さい海賊と樽が真ん中に並んでいますね。  
`Sprite('画像キー名',横サイズ,縦サイズ)`で渡して、`.addChildTo()`でthisを指定してMainSceneの子要素にしています。  
また、それぞれのx、yプロパティに数値を直接入れることによって位置を指定しています。

ですが、これからこの海賊と樽を何度も使っていくとなると、このやり方は面倒ですよね？　私は面倒だと思います。  
ですので、pirateとbarrelそれぞれクラス化して再利用できるようにします。

`phina.js` でのクラス・メソッドのつくり方は

```javascript
phina.define('クラス名', {
  superClass: '親クラス',
  // コンストラクタ
  init: function() {
    this.superInit();
  },
  メソッド名１: function() {
    // ...
  }, // カンマを忘れずに！
  メソッド名２: function(){
    // ...
  } // 一番後ろのメソッドはカンマは要らないがあっても問題ない
});
```

このようにします。

上のように`phina.js`で定義したクラスはグローバルのメンバになります。

【参考】[[phina.js-Tips-39] クラスを作成してみる](https://qiita.com/alkn203/items/f37b3e7d422dfb933773)

では、これを踏まえた上で、pirateとbarrelそれぞれをクラスとして定義します。

```javascript
// ...
// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene',
  init: function() {
    this.superInit();
    // 海賊インスタンス作成
    this.pirate = Pirate().addChildTo(this).setPosition(SCREEN_WIDTH / 2 - 100,SCREEN_HEIGHT / 2);
    // 樽インスタンス作成
    this.barrel = Barrel().addChildTo(this).setPosition(SCREEN_WIDTH / 2 + 100,SCREEN_HEIGHT / 2);
  },
});

// 海賊クラスを定義
phina.define('Pirate',{
  superClass: 'Sprite',
  init:function() {
    this.superInit('pirate',300,300);
  },
});

// 樽クラスを定義
phina.define('Barrel',{
  superClass: 'Sprite',
  init:function() {
    this.superInit('barrel',200,200);
  },
});

// メイン処理
//...
```

海賊、樽と並んで表示されたはずです。

`setPosition(横,縦)`で位置を指定して決定することもできます。

また`phina.js`で定義したクラスはコンストラクタを`new`なしで呼び出します。

【参考】[[phina.js-Tips-002] Shapeの位置を指定する](https://qiita.com/alkn203/items/971b1763037cbd972b23)

また、海賊クラス、樽クラスで継承している、Spriteクラスの中身を少しだけ覗いてみましょう。

```javascript
phina.define('phina.display.Sprite', {
  superClass: 'phina.display.DisplayElement',

  init: function(image, width, height) {
    this.superInit();
    this.srcRect = phina.geom.Rect();
    this.setImage(image, width, height);
  },

  draw: function(canvas) {
    //...
  },

  setImage: function(image, width, height) {
    //...
  },
  //...
```

と、このようになっています。

コンストラクタで`setImage()`メソッドにそのまま引数を渡していることからわかる通り、`this.superInit('画像',横サイズ,縦サイズ);`と渡すことで画像と大きさの要素を持ったクラスを簡単につくることができます。

だいぶ端折りましたが、

[phina.js API Documentation](http://phinajs.com/docs/)

の

[phina.display.Sprite](http://phinajs.com/docs/#!/api/phina.display.Sprite)

から全文が見られます。

## ゲームにする

まず、先に海賊クラスと樽クラスから書いていきます。先ほどのコードを書き直します。

```javascript
// 海賊クラスを定義
phina.define('Pirate',{
  superClass: 'Sprite',
  init:function(x, y) {
    this.superInit('pirate',300,300);
    this.setPosition(x, y);
  },
});

// 樽クラスを定義
phina.define('Barrel',{
  superClass: 'Sprite',
  init:function(number, x, y) {
    this.superInit('barrel',200,200);
    this.number = number;
    this.setInteractive(true); // タッチを有効にする
    this.setPosition(x, y);
  },
});
```

樽クラスの`this.number`については後述するのでひとまず置いておきます。

樽クラスにある`this.setInteractive()`は`true`を入れることでタッチを有効にします。逆に`false`を入れるとタッチが無効になります。

次にMainSceneのほうを書いていきます。

```javascript
// MainScene クラスを定義
phina.define('MainScene', {
  superClass: 'DisplayScene',
  // コンストラクタ
  init: function() {
    this.superInit();
    let score = 0;
    let rand = Random.randint(1, 6);
    let barrel_num = 0; // 樽に番号をつける
    // 位置
    let position_x = [150, 450];
    let position_y = [200, 500, 800];
      
    // 樽グループ
    this.barrelGroup = DisplayElement().addChildTo(this);
      
    const self = this;
    for(let x of position_x){
      for(let y of position_y){
        barrel_num++;
        // 樽作成
        let barrel = Barrel(barrel_num, x, y).addChildTo(self.barrelGroup);
        barrel.onpointend = function() {
          this.remove();
          if(rand == barrel.number){
            self.invalid();
            self.popUp(self, x, y);
            setTimeout(function() {
              self.exit({
                score: score,
                message: "黒ひげ危機一〇髪で触るphina.js",
                hashtags: "C95_DCRC"
              });
            }, 500);
          }else{
            score++;
          }
        };
      }
    }
  },
  // 黒ひげインスタンスを後から作成する
  popUp: function(self, position_x, position_y) {
    Pirate(position_x, position_y).addChildTo(self);
  },
  // 樽それぞれのタッチを無効化
  invalid: function(){
    for (const barrel of this.barrelGroup.children) {
      barrel.setInteractive(false);
    }
  }
});
```

### 解説

`Random.randint(a, b)`はa以上b以下のランダムな値を出すメソッドです。
これと`barrel.number`を比較して、同じ値の時に黒ひげが出るようにしています。

【参考】[[phina.js-Tips-006] Shapeをランダムな位置に表示する](https://qiita.com/alkn203/items/b3471138323efd33587c)

`barrel.onpointend`の関数では、タッチされた樽が、ハズレであるかを判定しています。ところが、MainSceneの`init`が実行し終わった時点で、`barrel_num`の値は必ず`6`になってしまいます。
このため、ランダムの値が`6`の時以外黒ひげが出てこなくなってしまいます。
また、ランダムの値が`6`の時、どこを押しても黒ひげが出てくるようになってしまいます。
そのため、`barrel.number`にその樽が何個目の樽かを格納しています。

`element.onpointend`は`element`のタッチ終了時に代入された関数が実行されます。  
他にも、

```javascript
// タッチ開始時
element.onpointstart = function() {
  // ...
}

// タッチ移動時
element.onpointmove = function() {  
 // ...
}
```

などがあります。

また、シーン内で

```javascript
onpointstart: function() {
  // ...
},
```

このようにメソッドを定義したら、画面をタッチしたとき、そのメソッドが実行されます。

`element.remove()`は指定された`element`を消去するメソッドです。  
上記のコードで使われている`remove()`メソッドは`this.remove()`と書かれていることから分かる通り、タッチされた樽だけを消すようにしています。

`MainScene`では`popUp`と`invalid`というメソッドを定義しています。  
これらは`MainScene`に属しているため、このままだと`barrel.onpointend`内から`this`を用いて参照できません。  
そのため、`MainScene`の参照を`const self = this`で保存する必要があります。

【参考】[とりあえず試してみたいって方のための phina.js 入門](http://phiary.me/phinajs-tutorial-try/#post-h2-id-8)

`this.barrelGroup = DisplayElement().addChildTo(this)`はグループ化というもので、つくられたインスタンスをひとまとめにしてイジれるようにするものです。

`.addChildTo()`引数につくったグループを入れてやることで、そのグループの子要素となります。  
子要素それぞれをイジりたい場合は

```javascript
invalid: function(){
  for (const barrel of this.barrelGroup.children) {
    barrel.setInteractive(false);
  };
}
```

このようにそれぞれ取り出して変えてやる必要があります。

また、`MainScene`の初期化後、新しくインスタンスを召喚したい場合は、別に召喚用メソッドを定義してやる必要があります。

```javascript
self.popUp(self, position_x[x], position_y[y]);
```

```javascript
popUp: function(self, position_x, position_y) {
  // 海賊作成
  Pirate(position_x, position_y).addChildTo(self);
},
```

ここからわかる通り、`self`を渡しています。  
これは`.addChildTo(self)`で`init`内の`this`の子要素とするためです。こうすることで後からインスタンスをつくって画面に出すことが出来ます。

最後に`this.exit()`についてです。  
これで次のシーンへと飛べます。`phina.js`ではデフォルトだと『title➡main➡result➡title』の順番で飛びます。  
したがって、`MainScene`で`exit()`メソッドが発火すると`ResultScene`へと飛びます。  
`ResultScene`はつくっていないので、`phina.js`でデフォルトで用意された`ResultScene`へ飛びます。
デフォルトで`exit()`メソッドにどういう値を渡せるのか、ResultSceneクラスを少し覗いて確認してみます。以下はデフォルトの部分です。

```javascript
_static: {
  defaults: {
    score: 16,

    message: 'this is phina.js project.',
    hashtags: 'phina_js,game,javascript',
    url: phina.global.location && phina.global.location.href,

    width: 640,
    height: 960,

    fontColor: 'white',
    backgroundColor: 'hsl(200, 80%, 64%)',
    backgroundImage: '',
  },
},
```

[phina.game.ResultScene](http://phinajs.com/docs/#!/api/phina.game.ResultScene)

うおお、色々渡すことができますね……今回この本を書くにあたり、初めてソースを見ましたが、知っている以上にたくさん値を渡すことができてビックリしました。

今回はこの中の`score`、`message`、`hashtags`のプロパティに値を渡したいと思います。

```javascript
this.exit({
  score: score,
  message: "黒ひげ危機一〇髪で触るphina.js",
  hashtags: "C95_DCRC"
});
```

どれもそのままの意味なのですが、`score`はスコア、`message`はメッセージ、`hashtags`は共有ボタンを押したとき作成されるテキストにつくハッシュタグのことです。`,`で区切ることで複数ハッシュタグをつけられます。

# あとがき
拙い文章ですが、ここまで読んでいただきありがとうございました。個人のブログで度々技術系の記事は書かせていただいているのですが、これほどの文章を書くのは初めてでとても良い経験となりました。ありがとうございます。

初学者でも触れやすく、すぐに動くものがつくれてプログラミングの楽しさを伝えるには最適なフレームワークである、この`phina.js`を触ってみて良いなと思ったら他の人にも是非教えてあげてください。

最後になりますが、**`this`の参照に気を付けましょう。「なぜか動かない」と思ったら、ひょっとするとそれは`this`の参照が間違っているかもしれません。**

以上です。二度目になりますが、ご購入いただき、最後まで読んでいただきありがとうございました！　終わり！！

# おくづけ

2018年12月30日　初版発行

著者：masayasviel
サークル：拓大ディジコン愛好会
印刷：学内某所

## スペシャルサンクス
名前（敬称略）
- alkn203
- なかひこくん
- みっきぃ
- えふてい

名前（敬称略）：サイトURL
- 高尾技研：https://takao-giken.github.io/