2023年自作ゲーム総括

# 本題について
2023年も自作シューティングゲームと、ステージ編集ツールをそれぞれ改修しました。

# 目的

- 好きなシューティングゲームを自分で楽しむ
- 変遷が大きいフロントエンドの技術等を学習する

# 改修履歴

```mermaid
---
displayMode:
---
gantt
    title 自作シューティングゲームと、ステージ編集ツール改修履歴
    dateFormat YYYY-MM-DD
    axisFormat %Y-%m
    tickInterval 6month
    section Shooting Game<br>(自作シューティングゲーム)
        Start with JavaScript :done, a1, 2017-04-01, 10M
        Changed to TypeScript in React :done, after a1, 2021-10-01, 4M
    section Edit Stage Tool<br>(ステージ編集ツール)
        Start with JavaScript :done, 2018-01-01, 7M
        Changed to Vue2.x :done, 2020-01-01, 7M
        Changed to Nuxt3 :done, 2022-12-01, 10M
```

改修期間と改修内容は、大体こんな感じです。

|| 改修期間 | 改修内容 |
|-|-|-|
| Shooting Game | 2017/4〜2018/1 | JavaScriptで実装 |
| ↑ | 2021/10ごろ〜2022/1 | フレームワーク学習のためにReactを採用し、TypeScriptに換装。<br>テストツールはVitestを採用し、テストコードを作成 |
| Edit Stage Tool | 2018/1ごろ〜2018/7 | JavaScriptで実装 |
| ↑ | 2020/1ごろ〜2020/8 | フレームワーク学習のためにVue(2.x)を採用。JavaScriptで実装 |
| ↑ | 2022/12ごろ〜2023/9 | フレームワーク学習のためにNuxt3を採用。TypeScriptに換装<br>テストツールはVitestを採用し、テストコードを作成 |

過去の実装は以下に投稿してます。

https://qiita.com/TakahashiMasaya/items/eb7386aa11f158cb6064

https://qiita.com/TakahashiMasaya/items/6c67b82977ce5d2aadde

https://qiita.com/TakahashiMasaya/items/f578b350603c80f01f85

# 1. 自作シューティングゲームとは

https://www.youtube.com/watch?v=sjslgfhLv4Q

TypeScriptで実装したグラディウス風の横スクロールシューティングのブラウザゲームです
一部制約はありますが、基本的にPC・スマホでゲームを楽しめます。

※この動画ではiPad mini6, ゲームコントローラー（Xbox Elite ワイヤレス コントローラー シリーズ 2）使ってプレーしてます。

### 対応端末

2023/12/1時点での対応状況です

| Device | OS Version | Browser |
|-|:-:|-|
| MacBook Air(M2) | latest | <ul><li>Safari</li><li>Chrome</li></ul> |
| Windows | latest | <ul><li>Edge</li><li>Chrome</li></ul> |

| Device | OS Version | Browser |
|-|:-:|:-|
| iPhone 14Pro Max | 17.1 | <ul><li>Safari</li><li>Chrome</li></ul> |
| iPhone 14 Plus | 17.1 | <ul><li>Safari</li><li>Chrome</li></ul> |
| iPad Pro(2022) | 17.1 | <ul><li>Safari</li><li>Chrome</li></ul> |
| iPad Air 4 | 17.1 | <ul><li>Safari</li><li>Chrome</li></ul> |
| iPad mini 6 | 17.1 | <ul><li>Safari</li><li>Chrome</li></ul> |

# 2. 2023年自作シューティングゲームの改修

## 2-1. 画面リサイズ処理の見直し

### iPhoneでのリサイズのイメージ

ブラウザのウィンドウサイズを変更する幾つかのケースで、
ゲーム画面の表示サイズを調整してます。

1. ブラウザの画面サイズ変更した時
    - PCでブラウザの画面サイズを変更
    - iOS Safariにてツールバーの表示・非表示切替
2. スマホにて画面回転した時

![gamerotation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/c6a42866-2e15-22bf-23f6-7b51dff2d674.gif)

また2.において、ポートレートからランドスケープモードへの回転時、以下を考慮して改修しました。

1. タイミングを遅らせて画面サイズを取得してゲーム画面サイズを調整する
2. 1.でタイミングを遅らせた際、ゲーム画面が一瞬レイアウト崩れになるので、フェードインすることで自然にゲーム画面を表示させる（...つもり）

### リサイズの改修はiOS、iPadOSを考慮する必要があった

iOS、iPadOS(ともに17.2系) Safariで画面回転した直後にウィンドウサイズがうまく拾えず、
以下のように、端末開店後に少しタイミング遅らせてリサイズする必要がありました。

```resize.ts
// 画面リサイズ処理
const timerId = 0
const resize = () => {
  window.clearTimeout(timerId)
  window.setTimeout(() => {
    リサイズ処理  
  }, 500) // <- 任意の値
}

// イベントリスナーの登録
window.addEventListener('resize', resize);
// イベントリスナーの解除
window.removeEventListener('resize', resize);
```

ただ、これでは端末によって挙動が異なる可能性もあると思い、
新たに`deviceorientation`イベントタイプによるイベントリスナーの登録・解除を追加してみました。

https://developer.mozilla.org/ja/docs/Web/API/Window/deviceorientation_event

```diff_typescript
// 画面リサイズ処理
- const timerId = 0
const resize = () => {
-  window.clearTimeout(timerId)
-  window.setTimeout(() => {
    リサイズ処理  
-  }, 500) // <- 任意の値
}

// イベントリスナーの登録
window.addEventListener('resize', resize); // 上記 1. の対応
+ window.addEventListener('deviceorientation', resize); // 上記 2. の対応
// イベントリスナーの解除
window.removeEventListener('resize', resize);
+ window.removeEventListener('deviceorientation', resize);
```

こんな感じで実装してみたのですが、結果は変わらずでした。

もう少し楽な方法があればなぁ...と。


## 2-2. ゲームコントローラーを使ってプレーする

ブラウザのGamepadAPIを使って、PC・iPhone・iPadでゲームパッドやコントローラーを使ってプレーできます

### 接続方法

接続方式に従い以下のコントローラーでプレーできます。

| 接続方式 | ゲームコントローラー |
|:-:|:-|
| Bluetooth | <ul><li>[RAZER KISHI V2 FOR IPHONE](https://www.razer.com/jp-jp/mobile-controllers/razer-kishi-v2-for-iphone)</li><li>[Xbox Elite ワイヤレス コントローラー シリーズ 2](https://www.xbox.com/ja-JP/accessories/controllers/elite-wireless-controller-series-2)<li></li>[8BitDo Arcade Stick](https://www.8bitdo.com/arcade-stick/)</li></ul> 
| ケーブル | <ul><li>8BitDo Arcade Stick<br>Windows PCのみ<br>キーマッピングアプリを使う</li></ul> |

以下のツイートは、MacBookのChromeブラウザで8BitDo Arcade StickをBluetooth接続してプレーしてます。

https://twitter.com/i/status/1739290883424698403

### iPhone16 Plus、iOS17.1.x以降、Nintendo SwitchのJoyConが利用できるようになった

iPhone12 Pro Max、iOS 16.xで、Nintendo SwitchのJoyConを使用したら以下の問題がありました。

- 長時間プレーすると端末が熱くなり、ブラウザが落ちてしまう
- ボタンの反応が遅い

それがiPhone16 Plus、iOS17.1.x以降解決された感じですが、もう少し様子見しようと思います

https://twitter.com/i/status/1577653519061962752

## 2-3 CSPの設定

学習がてら、CSPに関して以下を参照し、必要なアクセスのみ許可する設定を行いました。

https://developer.mozilla.org/ja/docs/Web/HTTP/CSP


```mermaid
---
title: Structure for CSP(only production mode)
---
stateDiagram-v2
    direction LR
    state "Web Server" as w1 {
        state "Shooting Game<br>(自作シューティングゲーム)" as ga {
            direction LR
            s0: script tag
            s1: script tag (www.googletagmanager.com)
            s2: inline script (from googletagmanager)
        } 
    }
    state "Web Server" as w2 {
        state "Edit Stage Tool<br>(ステージ編集ツール)" as edit
    }
    state Google {
        GoogleAnalytics
    }
    s0 --> s0: self
    s0 --> edit: connect
    s1 --> GoogleAnalytics: connect
    s2 --> GoogleAnalytics: connect
    s2 --> s2: SHA-256

```

上記構成図により、Viteを使って本番環境用のみmetaタグを使ってCSP設定をしました。

```index.html
<html>
<head>
  ...
  <!-- # INSERT CSP HERE -->
  ...
</head>
<body>
  ...
</body>
</html>
```

```vite.config.ts
import crypto from 'crypto';
...
// プラグイン
const insertContentSecurityPolicy = (env): Plugin => {
  return {
    name: 'insert-content-security-policy',
    apply: 'build',
    transformIndexHtml: async (html) => {
      // インラインスクリプトタグ内の文字列を取り出す
      const scriptTag = html.replace(/.*<script>(.*)<\/script>.*/g, '$1');
      // インラインスクリプト文字列をsha256形式に変換する
      const hashString = crypto
        .createHash('sha256')
        .update(scriptTag, 'utf8')
        .digest('base64');
      return html.replace(
        '<!-- # INSERT CSP HERE -->',
        `<meta http-equiv="Content-Security-Policy" content="default-src 'self' *.googletagmanager.com 'sha256-${hashString}'; connect-src 'self' *.google-analytics.com ${(Shooting Editのドメイン名)}" />`,
      );
    },
  };
};
...
export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, process.cwd(), '');
  const isBuild = () => command === 'build';
  return {
    ...
    plugins: [
      ...
      isBuild() && insertContentSecurityPolicy(env),
    ]
    ...
  }
}
```

# 3. ステージ編集ツール

## 3-1. 機能と開発

### ツール概要
ステージを編集するツールで、主に以下の機能を持ちます。Nuxt3で開発しました。

### 機能一覧

| 機能 | 操作イメージ |
|-|-|
| ステージの作成 | ![Monosnap screencast 2023-12-30 17-02-02.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/4686a678-f4dd-b5a8-8055-85556d32c772.gif) |
| ステージの編集 | ![Monosnap screencast 2023-12-30 17-05-20.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/53df9fb7-ea44-5f1e-e7f9-e023d315b8ff.gif) |
| ステージの削除 | ![Photos Library.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/852850a4-6dac-7f0a-c2fb-da31c563e3f8.gif) |
| ステージの並替え | ![test.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/1a3d5abc-f64e-aac6-86e5-1b4952944e85.gif) |

編集したステージは、以下外部リポジトリに保存するよう実装してます。
※保存自体は、対象リポジトリをあらかじめ一つ選択する形になります。

- Firebase
- microCMS
- MovableType

## 3-2. 開発環境とデプロイ

### 開発環境(2024/12/24時点)

- パッケージ
  - Nuxt3(v3.8.2) ※TypeScriptで実装
  - Vitest
  - eslint
  - prettier
  - stylelint
- パッケージ管理マネージャー
  - pnpm

### デプロイ

#### Amazon Lightsailでの運用がうまくいかなかった

Amplifyデプロイ前までは、Amazon Lightsail（nginx）へデプロイしたのですが、
サーバー起動しても以下のように、CPU使用率も一気に上がって、サーバーダウン...。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/130301/218baeae-cde5-8288-c223-77b00ff9290e.png)

#### AWS Lambdaへデプロイする

...できず...。

#### AWS Amplifyへデプロイする

NitroのAmplifyゼロコンフィグ対応を受けて、Amplifyへのデプロイにリトライ。

https://developer.mamezou-tech.com/blogs/2023/11/22/nuxt3-on-amplify-hosting/

```mermaid
---
title: Amplifyへのデプロイフロー
---
stateDiagram-v2
    direction LR
    [*] --> s0: pr
    state GitHub {
        state "GitHub Action" as ga {
            direction LR
            s0 --> s1
            s1 --> s2
            s2 --> s3
            s0: do action
            s1: install
            s2: check lint
            s3: check test
            s3 --> プロビジョン: merge(if passed GA)
        } 
    }
    state AWS {
        state Amplify {
            direction LR
            プロビジョン --> 構築
            構築 --> デプロイ
            プロビジョン
            構築
            デプロイ
        }
    }
```

### AWS Amplify ビルド設定
開発環境ではpnpmパッケージマネージャーを使ってるので、
デフォルトのコードから該当箇所を変更して、無事にデプロイできました。

```amplify.yml
version: 1
frontend:
  phases:
    preBuild:
      commands:
        - nvm use 18
        - corepack enable
        - npx --yes nypm i
    build:
      commands:
        - npx pnpm build // <- ここでpnpmに変更
  artifacts:
    baseDirectory: .amplify-hosting
    files:
      - '**/*'
```

## 3-3 自作シューティングゲームとの連携

ステージ編集ツールと、自作シューティングゲームと連携することができます。
ステージのデータは、Firebase・microCMS・MovableTypeに保持させ、
いずれかのAPIを使って操作(保存・取得)するイメージです。

https://twitter.com/i/status/1721530248175427612

以下構成図では、各要素との関連を示してますが、
編集したステージをプレーすることができます。

```mermaid
stateDiagram-v2
    direction LR
    PC1: PC
    PC2: PC
    state "Shooting Game<br>(自作シューティングゲーム)" as ga {
        React
    } 
    state "Edit Stage Tool<br>(ステージ編集ツール)" as edit {
        Nuxt3
    }
    state repositories {
      direction TB
        Firebase
        microCMS
        MovableType
        [*]
        [*] --> Firebase
        [*] --> microCMS
        [*] --> MovableType
    }
    iPhone --> React: （３）playing game
    iPad --> React: （３）playing game
    PC1 --> React: （３）playing game
    PC2 --> Nuxt3: （１）edit stage(s)

    Nuxt3 --> repositories: （２）update stage(s)
    repositories --> Nuxt3: （５）fetch stages

    React --> Nuxt3: （４）request to fetch stages
    Nuxt3 --> React: （６）fetch stages
```

|#| | 説明 |
|-|-|-|
|(1)| edit stage(s) | ステージを編集する |
|(2)| update stage(s) | ステージを更新する |
|(3)| playing game | ゲームする |
|(4)| request to fetch stages | 全ステージ取得をリクエストする |
|(5)| fetch stages | ステージを取得する |
|(6)| fetch stages | ステージを取得する |

# 4. まとめ

今年も自作シーティングゲーム、ステージ編集ツールを紹介しました。
2017年からシーティングゲームを作成してたので、気づいたらもう7年以上も続いてます。

フロントエンドの変遷が大きい状況の中で、
何か新しい技術等あれば、これらのツールを通じて経験して業務に活かせればと思ってます。