メインコンテンツへスキップ
見出し画像

Pyxelで作成したWebアプリをPWAにする

    Pyxelで作ったゲームはWebアプリとして公開できるので、SNSなどでシェアしてスマホから気軽にアクセスしてもらうことが可能ですが、PWA要件を満たすことでよりスマホアプリっぽく使ってもらうことができます。
    この記事のサムネイル画像(↑)が実際にPWAとしてホーム画面に表示したイメージです。

    PWA対応のやり方

    PWA要件を満たすのは大して難しくなく、

    • Service Worker と呼ばれるJSファイル

    • manifest.json というアプリのメタ情報やアイコンなどを指定する設定ファイル

    を用意して、index.htmlから上記のファイルを参照すればOKです。
    この2つについては詳しく解説している記事がいくらでもあるので、ここでは触れません(というか、私が詳しくありません)。
    とりあえず最低限のものを準備してみましょう。

    まずmanifest.jsonです。
    ホーム画面のアイコンが必要なので、ここでは「icon-192.png」を指定しています。

    {
      "name": "Pyxel-web-template",
      "short_name": "PyxelApp",
      "description": "This is PyxelApp",
      "icons": [
        {
          "src": "./icon-192.png",
          "sizes": "192x192",
          "type": "image/png"
        }
      ],
      "start_url": "/index.html",
      "display": "standalone",
      "scope": "/",
      "theme_color": "#000",
      "background_color": "#000"
    }

    次にService Worker、sw.jsです。

    self.addEventListener("fetch", (event) => {})

    何のこっちゃ意味不明ですが、とりあえずこれで大丈夫でした。

    これらをindex.htmlから参照させます。Pyxelの標準コマンドで生成したHTMLファイルは以下のような構造になっています。

    <!DOCTYPE html>
    <script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
    <script>
      launchPyxel({
        command: "play",
        name: "app.pyxapp",
        gamepad: "enabled",
        base64: "略",
      })
    </script>

    ここにmanifest.jsonやsw.jsを読み込むコードを入れればOKです。

    <!DOCTYPE html>
    <link rel="manifest" href="/manifest.json" />
    <script>
      if ("serviceWorker" in navigator) navigator.serviceWorker.register("/sw.js")
    </script>
    <script src="https://cdn.jsdelivr.net/gh/kitao/pyxel/wasm/pyxel.js"></script>
    <script>
      launchPyxel({
        command: "play",
        name: "app.pyxapp",
        gamepad: "enabled",
        base64: "略",
      })
    </script>

    ついでに画面サイズを最適化する

    本題は以上ですが、ついでにPyxelアプリをWeb化するときに気になりがちな画面サイズの問題を修正します。

    Pyxelでは画面サイズが最大256pxまで指定できますが、Webブラウザで動かす場合、アプリの画面はデバイスやブラウザのサイズに収まる範囲で「アプリの大きさ x N(整数)」のサイズが選ばれるようです。

    たとえば横幅375pxのiPhoneSEだと、256x1 < 375, 256x2 > 375なので、ブラウザ内のサイズは256 x 256となります。
    すると次の画像のように左右にマージンができて、小さいスマホ画面のさらに一部しか使えない状態になってしまいます。
    (表示しているアプリはPyxel公式のサンプルです。)

    画像

    これはちょっともったいないというか、できるだけ画面を大きく使いたいので、HTMLのmetaタグで画面幅を指定することでマージンをつぶします。
    具体的には画面幅を512pxと指定すると・・

    画像

    このように画面いっぱいに広がってくれました。
    やり方は、HTMLファイルにさらに以下のコードを追加するだけです。

    <meta name="viewport" content="width=512" />

    画面幅が512より大きいタブレットやPCの場合は上の記述は無視されますが、十分な大きさを確保できているのでそれでOK、と考えます。
    一般的なスマホは横幅が360〜450くらいなはずなので、アプリの横幅が256pxならwidth=512、240pxならwidth=480、192pxならwidth=576、といったように「一般的なスマホの横幅を少し超える、アプリの横幅の倍数値」を指定するのがいいかなと思っています。

    なお、画面を横回転してしまうとこの設定は無意味になってしまいます。
    何か手の打ちようもあるかもしれませんが、今のところ私は諦めています。

    一連の手順をシェルスクリプト化する

    さて、開発したアプリは一度リリースしたら終わりとは限らず、バグ修正したりバージョンアップすることがあるかと思います。

    そのたびに、Pyxelの標準コマンドが吐き出したHTMLファイルを手修正するのは面倒ですね。
    また、先ほどのmanifest.jsonやらアイコン画像やらもいちいちHTMLファイルと同じフォルダにコピーしたりする必要があります。

    ということで、シェルスクリプトを使ってそのあたりを自動化すると便利です(自分はMacユーザです)。
    「makehtml.sh」と命名しました。

    pyxel package ./app ./app/main.py
    pyxel app2html app.pyxapp
    rm -rf public/*
    mv app.html public/index.html
    OLD="<!DOCTYPE html>"
    NEW="<!DOCTYPE html><meta name='viewport' content='width=512' /><link rel='manifest' href='/manifest.json' /><script> if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js')</script>"
    OLD_ESCAPED=$(echo "$OLD" | sed 's/[\/&]/\\&/g')
    NEW_ESCAPED=$(echo "$NEW" | sed 's/[\/&]/\\&/g')
    sed -i '' "s/${OLD_ESCAPED}/${NEW_ESCAPED}/g" public/index.html
    cp static/* public/
    rm -f app.pyxapp
    cd public
    python3 -m http.server 8000

    Windowsの場合はこうなるようです。
    ※ChatGPTさんに出力してもらいました。すみませんが動作未確認です。
    「makehtml.bat」としておきましょう。

    @echo off
    REM パッケージングとHTML変換
    pyxel package .\app .\app\main.py
    pyxel app2html app.pyxapp
    
    REM 古いファイルの削除と新しいファイルの移動
    if exist public\ del /Q public\*
    move app.html public\index.html
    
    REM HTMLファイルの更新
    setlocal enabledelayedexpansion
    set "OLD=<!DOCTYPE html>"
    set "NEW=<!DOCTYPE html><meta name='viewport' content='width=512' /><link rel='manifest' href='/manifest.json' /><script> if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js')</script>"
    set "FILE=public\index.html"
    set "TEMPFILE=%FILE%.tmp"
    
    REM ファイルの置換
    (
        for /f "tokens=*" %%a in ('type "%FILE%"') do (
            set "line=%%a"
            set "line=!line:%OLD%=%NEW%!"
            echo !line!
        )
    ) > "%TEMPFILE%"
    move /Y "%TEMPFILE%" "%FILE%"
    
    REM 静的ファイルのコピー
    xcopy /Y static\* public\
    
    REM 不要なファイルの削除
    del app.pyxapp
    
    REM サーバの起動
    cd public
    python -m http.server 8000

    かなり汚いシェルですが、pyxelのコマンドを入力したり、sedコマンドで強引にHTMLファイルを書き換えたりしています。

    全体の構造

    .
    ├── app # ここにソースファイルやアセットを置く
    │   └── main.py
    ├── makehtml.bat # デプロイ用スクリプト(Win、未検証)
    ├── makehtml.sh # デプロイ用スクリプト(Mac)
    ├── public # makehtml.shを実行するとここが更新されるので、フォルダごとサーバーにアップする。
    │   ├── favicon.ico
    │   ├── icon-192.png
    │   ├── index.html
    │   ├── manifest.json
    │   └── sw.js
    └── static # アイコンやファビコンなどのWebリソースをここに置く
        ├── favicon.ico
        ├── icon-192.png
        ├── manifest.json
        └── sw.js

    こんな感じになりました。
    それなりに汎用性があると思いますので、テンプレートとしてGitHubに置いておきます。
    よろしければどうぞお使いください。もちろんお好みに応じて加工いただいて全く問題ありません。


     
     
    レトロゲームエンジン「pyxel」を使ってゲーム制作しています。Steam / App Store / Google Playで販売実績あり。

    あなたへのおすすめ