JamstackだけどJamstackじゃないクソアプリ

[クソアプリ Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kuso-app) 1日目の記事です。

# 前置き
おはようございます。DE-TEIUです。
ポインセチアが美しい紅色を見せる時期となりましたが、皆様いかがお過ごしでしょうか。
ところでポインセチアって何ですかね。

## 過去にアドベントカレンダー用に作ったクソアプリ
- [p5.jsで作ったダメなソリティア](https://qiita.com/de_teiu_tkg/items/c938fbb52218185659e7)
- [絶対に作業できない作業用BGMサイト](https://qiita.com/de_teiu_tkg/items/6ebcef3550507258edaf)
- [社会に一石を投じるクソアプリ開発](https://qiita.com/de_teiu_tkg/items/e82870eca1f265807dbe)
- [鼻毛が生えるカメラアプリ](https://qiita.com/de_teiu_tkg/items/213aa2ade2ef840a33b5)
- [映画館のスクリーンでYouTubeが見られるクソアプリ](https://qiita.com/de_teiu_tkg/items/94259a0f90d1db861237)
- [電卓の成長を体験できるクソアプリ](https://qiita.com/de_teiu_tkg/items/5d878f01207f408ee919)

# Jamstackについて
Jamstackとは、JavaScript、API、Markupを組み合わせたWeb開発アーキテクチャの一種です。
WebアプリをHTML、CSS、JavaScriptを用いて静的サイトとして開発し、動的コンテンツはすべてAPIを通じて取得する、という構成で開発する手法です。
また、静的サイトジェネレーターなどを用いて、ビルドのタイミングでAPIから動的コンテンツの取得を行い、表示させたいデータをあらかじめ埋め込んだ静的サイトを出力する、というやり方もあるようです。


## 余談(Jamstackの表記について)
Jamstackについて検索すると、「*JAMstack*」と「**Jamstack**」の二種類の表記が見つかります。
これは表記ゆれなどでは無く、どうやら2019年ぐらいまでは「*JAMstack*」、2020年頃から「**Jamstack**」と書かれるようになったらしいです。今後は「**Jamstack**」の方を使うのが良さそうです。

## 注意事項
この記事ではこれ以降、**正しい意味でのJamstackの話はしません**。ご了承下さい。

# 成果物
stackという単語には、積み重ねるという意味があります。
ということで、Jamをstackするゲームを作りました。

[JAMをSTACK](https://jamstack-higher.vercel.app/)
![クリップボード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173272/af3e8948-9741-155a-2fd8-004baf03ffc4.png)


## JAMが
STACKしたと思います。
15メートル以上積めたらプロです。

# 解説など
以下、今回使用したビルドツールやフレームワークの話などをします。
- [Vite](#vite)
- [Phaser](#phaser)

## Vite
[Vite](https://ja.vitejs.dev/)(ヴィート)とは、フロントエンドのビルドツールの一種です。
以下のような特徴があります。
- わりと高速でビルドできる
- Vue、React、Preact、Lit、Svelteを組み込んだテンプレートがあり、これらのフレームワークを使った開発環境がすぐに作れる
- TypeScriptにも対応している

### 導入方法
[はじめに | Vite](https://ja.vitejs.dev/guide/#%E6%A6%82%E8%A6%81) に書いてある手順を実行するだけです。普通にnpmやyarnでいけます。

```
$ npm create vite@latest
```
あとは画面表示に従い、
- プロジェクト名の入力
- 使用するフレームワークの選択
- JavaScriptとTypeScriptのどちらで開発するか選択

を実施します。

その後、作成されたフォルダに移動し、

```
$ npm install
```
```
$ npm run dev
```
を実行すればローカルサーバーが起動します。これで導入は完了です。

ちなみに、今回のアプリ開発には使用していませんが、プロジェクトルート直下に*vite.config.js*というファイルを作ると、ビルドオプションなどを細かく設定できます。

## Phaser
[Phaser](https://phaser.io/)(フェイザー)とは、JavaScriptで2Dゲームを開発するためのフレームワークの一種です。
- スプライトの管理
- 視点(カメラ)の移動
- シーンの管理
- リソース(画像、効果音など)の管理
- 物理演算(Matter.js)

など、ゲーム開発に必要な機能は一通り揃っています。
あと公式サイトで公開されているサンプルソースの量もそこそこ多いです。

### 導入方法

npmやyarnでインストールするだけです。
```
$ npm install phaser
```

### 使い方(ざっくり)

GameConfigクラスのインスタンスを作成し、ゲームの各種設定を定義しておきます。
設定可能な項目は[APIドキュメント](https://photonstorm.github.io/phaser3-docs/Phaser.Core.Config.html)に書いてあります。

```javascript:index.ts
const config: Phaser.Types.Core.GameConfig = {
  parent: "canvas-parent",
  type: Phaser.AUTO,
  width: canvasWidth, //キャンバスの幅
  height: canvasHeight, //キャンバスの高さ
  backgroundColor: "#4488aa",
  physics: {
    default: "matter",
    matter: {
      debug: false,
      gravity: { y: 0.9 },
    },
  },
  audio: {
    disableWebAudio: true,
  },
  scene: [TitleScene, MainScene], //この2つのシーンクラスは別途用意する
  fps: {
    target: 60,
    forceSetTimeOut: true,
  },
};
```
あとはGameクラスのインスタンス作成時にconfigを引数として渡してやれば、実行時にcanvasが生成されてゲーム画面が表示されます。
```javascript:index.ts
const game = new Phaser.Game(config);
```

### シーンクラスについて
PhaserにはSceneクラスというものがあり、このクラスを継承することで1画面分の処理を実装できます。
Sceneクラスには主に
- init
- preload
- create
- update
の4つのメソッドがあり、これらに必要な処理を記述していきます。
(不要であれば省略しても良い)

```javascript

class SampleScene extends Phaser.Scene {
  constructor() {
    super("titleScene");
  }
  
  /**
   * シーン生成開始時、最初に実行される処理
   */
  init() {
  }
  
  /**
   * init実行後に実行される処理
   * (画像や効果音などの各種リソースの読み込みはここで行う)
   */
  preload() {
  }
  
  /**
   * preload実行後(リソースの読み込み完了後に実行される処理
   * (画面に最初に表示するスプライトの定義などはここで行う)
   */
  create() {
  }
  
  /**
   * フレーム更新時に毎回実行する処理
   * @param time このシーンを開始してからの経過時間
   * @param delta 前回のフレーム更新からの経過時間
   */
  update(time: number, delta: number) {
  }
}
```

### 実行中のシーンに画像やテキストを表示

例えば、画像を読み込んで表示させたい場合、
preloadメソッドに
```javascript
this.load.image("jam", "/image/jam.png");
```
と書いておき、createメソッドに
```javascript
const canvasWidth = this.sys.game.canvas.width; //キャンバスの幅
const canvasHeight = this.sys.game.canvas.height; //キャンバスの高さ
this.add.image(canvasWidth / 2, canvasHeight / 2, "jam");
```
みたいな記述をすれば、キャンバスの中央にジャムの画像を表示させたりできます。
ちなみに画像ファイルは、 *プロジェクトのルートディレクトリ/public/* 以下に配置します。
(上記の例だと */public/image/jam.png* となります)

他にも、テキストを表示させたい場合は、createメソッドに
```javascript
this.add
  .text(100, 100, "表示させるテキスト")
  .setFont("32px Arial")
  .setColor("#ffffff")
  .setAlign("center")
  .setLineSpacing(10)
  .setOrigin(0.5, 0.5);
```
などと記述することで、フォントの色や大きさ、配置の設定も含めて実現できます。

また、これらの記述で生成したスプライトに対し、後ろに
```javascript
.setInteractive({
  useHandCursor: true, //ホバー時にマウスカーソルを手に変える
})
.addListener("pointerup", () => {
  alert("タップしました")
});
```
と書いていけば、特定のインタラクションに対するイベントを持たせることもできます。
**※setInteractive無しでaddListenerを付けても動かないので注意**

### 効果音の再生

まずは画像と同様に、preloadメソッドでリソースを読み込みます。
```javascript
this.load.audio("start", "/se/bell00.wav");
```

そして適当なメンバ変数を用意し、createメソッドで再生用のインスタンスを作成します。
```javascript
this.bellSE = this.sound.add("start");
```
あとは何かしらのイベント処理やupdateメソッドの中などでplayメソッドを呼びます。
```javascript
bellSE.play();
```
ちなみに音量の調節とかループ再生のOn/Offなども設定できます。
```javascript
this.bgm = this.sound.add("bgm", {
  volume: 0.5,
  loop: true,
});
```
※ちなみに、スマートフォンで実行する場合、ユーザーが何かしらのインタラクション(画面タップとか)を1度実行した後でないと、**効果音が再生されません**。

というわけで、スマートフォン向けのブラウザゲームを作る際は、スタートボタンをタップしたタイミングで効果音の再生を開始する、などの実装にしておく必要があります。createメソッドにこんな感じで書いておくと良いでしょう。
```javascript
this.add
  .image(canvasWidth / 2, canvasHeight / 2 + 350, "start")
  .setInteractive({ useHandCursor: true })
  .addListener("pointerup", () => {
    (this.sound as Phaser.Sound.HTML5AudioSoundManager).unlock(); //効果音の再生を許可する(iOSでの実行時に必須)
    startSE.play(); //効果音再生
    this.startGame();//ゲーム開始処理
  });
```
あと環境によってはWeb Audio APIが使えず、HTML5 Audioで効果音の再生を行う必要があります。その場合は更に、GameConfigクラスのインスタンスを作る際に以下のオプションを追加しておきましょう。
```javascript
audio: {
  disableWebAudio: true,
},
```


# ソースコード
[Github](https://github.com/de-teiu/jamstack-public) で公開しています。
このクソアプリでは物理演算を行うスプライトの作成や、視点の移動処理なども行っているので、その辺りのコードが参考になれば幸いです。

# 参考文献
- [WTF is Jamstack?](https://jamstack.wtf/)
- [はじめに | Vite](https://ja.vitejs.dev/guide/)
- [Phaser 3 API Documentation - Index](https://photonstorm.github.io/phaser3-docs/)
- [Sounds not worked on iOS - Phaser 3 - Phaser](https://phaser.discourse.group/t/sounds-not-worked-on-ios/12193)
- [ポインセチア - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BB%E3%83%81%E3%82%A2)