フェーズ 8 パッケージ化して Web で公開する

step0:星空を飛ぶ宇宙船(最終フェーズ)

トップページstep0 概要 / フェーズ 8

ゴール

完成した Star Voyager を、Python も Pyxel も入っていない人のブラウザで遊べる形にして、 GitHub Pages で公開します。URL を渡すだけで人に遊んでもらえる状態がゴールです。

このフェーズはコードを書きません 書くのは コマンド 2 行と、リンクを張るための HTML の追記だけです。 Python のコードには一切手を入れません。そのかわり「作ったものを人に届ける」という、 これまでとは種類の違う作業をします。

1. 考え方

1-1. いまの作品は、そのままでは渡せない

いま step0 フォルダには、次の 2 つのファイルが入っています。

step0/
├── star_voyager.py    プログラム本体
└── assets.pyxres      絵と音のファイル

この 2 つを友達に渡しても、たいていは動きません。相手のパソコンに Python が入っていて、さらに pip install pyxel も済んでいて、 2 つのファイルを同じフォルダに置いてもらう必要があるからです。 「ちょっと見て」と気軽に渡せる状態ではありません。

そこで Pyxel には、この問題を解く 2 つのコマンドが用意されています。

1-2. 2 段階で変換する

フォルダ → 1 ファイル → HTML という順に変換します。1 回でやらず 2 段階に 分かれているのは、途中の .pyxapp だけでも配布に使えるからです(後述)。

① いま手元にあるもの step0/ star_voyager.py assets.pyxres Python と Pyxel が 入っていないと動かない pyxel package ② 1 ファイルにまとめる step0.pyxapp 中身は ZIP Pyxel があれば これ 1 つで遊べる pyxel app2html ③ ブラウザ用 step0.html たった 4 行 何も入れていない人でも ブラウザだけで遊べる ③ を GitHub の docs/ フォルダに置いて push すると、GitHub Pages が公開してくれる https://mrgarita.github.io/learning_pyxel/site/step0/star_voyager.html
2 段階の変換。②で止めても配布できるが、相手に Pyxel が必要。③まで進めると誰でも遊べる

1-3. なぜブラウザで Python が動くのか

ここが不思議なところです。ブラウザが動かせるのは本来 JavaScript だけのはずなのに、 Python で書いた Pyxel のゲームがなぜ動くのでしょうか。

用語:WebAssembly(ウェブアセンブリ、略して wasm) ブラウザで動く、JavaScript 以外のプログラムのための形式です。C や Rust で書かれた プログラムを wasm に変換しておくと、ブラウザがそれを高速に実行できます。 Pyxel の本体は Rust で書かれているため、Pyxel と Python の実行環境をまるごと wasm に 変換したものが用意されていて、ネット上(CDN)に置かれています。

つまり app2html が作る HTML は、Python を JavaScript に翻訳しているのではありません。 「ネット上に置いてある Pyxel 一式(wasm)を読み込んで、そこに自分の作品を渡して動かして」と 書いてあるだけです。作品のコードは Python のまま運ばれます。

1-4. できあがる HTML の中身を見てみる

実際に生成される HTML は、これで全部です。改行を除けば 4 行しかありません。

<!doctype html>
<script src="https://cdn.jsdelivr.net/gh/kitao/pyxel@2.9.9/wasm/pyxel.js"></script>
<script>
launchPyxel({ command: "play", name: "step0.pyxapp", gamepad: "enabled", base64: "UEsDBBQAAAAI..." });
</script>

読み方は次のとおりです。

部分意味
<script src="https://cdn.jsdelivr.net/..."> Pyxel 一式(wasm)をネットから読み込む。 URL に pyxel@2.9.9 と書かれているとおり、 作ったときの Pyxel のバージョンが埋め込まれる
base64: "UEsDBBQAAAAI..." 作品(.pyxapp)そのもの。ZIP ファイルを文字列に変換して、 HTML の中に丸ごと埋め込んである
command: "play" 埋め込まれた作品を再生せよ、という指示
gamepad: "enabled" ゲームパッドがつながっていれば使えるようにする
用語:Base64(ベースろくじゅうよん) 画像や ZIP のような文字ではないデータを、英数字と記号だけの長い文字列に変換する 方法です。HTML やメールのように「文字しか運べない場所」にデータを載せたいときに使います。 そのかわりサイズは約 1.33 倍にふくらみます。
ここが大事:HTML 1 枚を置けば公開が終わる 作品が HTML の中に埋め込まれているので、.pyxapp を一緒に置く必要はありません。 公開作業は「この HTML を 1 枚アップロードするだけ」で済みます。

2. 使うコマンド

このフェーズで使うのは、次の 3 つです。すべて pyxel コマンドで、 Python のコードの中に書くものではありません。ターミナル(VS Code のターミナル)で打ちます

コマンドやること
pyxel package APP_DIR SCRIPT.py APP_DIR フォルダの中身をまとめて APP_DIR.pyxapp を作る。 SCRIPT.py は「起動時に実行するファイル」の指定
pyxel play FILE.pyxapp できた .pyxapp をそのまま実行する。中身の確認用
pyxel app2html FILE.pyxapp .pyxapp から FILE.html を作る
コマンドの一覧を見たいとき 引数なしで pyxel とだけ打つと、使えるコマンドの一覧が出ます。 忘れたらこれで確認できます。

3. 手順

3-1. パッケージ化する

まず、ターミナルの現在地をリポジトリのルートstep0 フォルダの 1 つ上)にします。VS Code でこのプロジェクトを開いていれば、 ターミナルは最初からルートにいるはずです。

pyxel package step0 step0/star_voyager.py

成功すると、次の 3 行が表示されます。

added 'step0\.pyxapp_startup_script'
added 'step0\assets.pyxres'
added 'step0\star_voyager.py'

そしてルートに step0.pyxapp ができます

いちばん間違えやすいところ:2 番目の引数 2 番目の引数は「step0 フォルダの中でのファイル名」ではなく、 いまいる場所(ルート)から見たパスで書きます。
pyxel package step0 star_voyager.py       ← ✕ no such file: 'star_voyager.py'
pyxel package step0 step0/star_voyager.py ← ○
step0 を 2 回書くので重複しているように見えますが、これが正しい書き方です。

3-2. できた .pyxapp を確かめる

HTML にする前に、この段階でちゃんと動くかを確かめます。

pyxel play step0.pyxapp

いつもどおりのウィンドウが開き、星が流れ、宇宙船が矢印キーで動き、BGM が鳴れば成功です。 Esc で閉じます。ここで動かないものは HTML にしても動きません。

.pyxapp の中身が気になったら .pyxapp は正体が ZIP なので、拡張子を .zip に変えれば中を覗けます (備忘録.pyxres の話と同じ仕組みです)。中身はこうなっています。
step0/.pyxapp_startup_script   中身は「star_voyager.py」の 1 行だけ
step0/assets.pyxres
step0/star_voyager.py
つまり pyxel package がやっているのは「フォルダを ZIP にして、 どれを最初に実行するかを書いたメモを 1 枚入れる」だけです。

3-3. HTML にする

pyxel app2html step0.pyxapp

何も表示されませんが、ルートに step0.html ができています。 サイズは 4 KB ほどしかありません。

3-4. ブラウザで動作確認する

できた HTML を、公開する前に手元のブラウザで確かめます。ここで ファイルをダブルクリックして開くのはおすすめしません。 ブラウザには「パソコンの中のファイルを直接開いたページ」に強い制限をかける 仕組みがあり、環境によっては真っ黒なまま動かないためです。

確実なのは、その場に簡易的な Web サーバーを立てて、そこから開く方法です。 Python に最初から付いている機能で立てられます。ターミナルで次を打ちます。

python -m http.server 8765

Serving HTTP on ... と表示されたら、ブラウザで次の URL を開きます。

http://127.0.0.1:8765/step0.html

すると、黒い画面の中央に CLICK TO START と出ます。

CLICK TO START クリックするまでゲームは始まらない
Web 版だけに出る起動画面。ネイティブ実行(pyxel run)では出ない

クリックすると、いつもの Star Voyager が始まります。

なぜ CLICK TO START が出るのか ブラウザには「ユーザーが操作していないのに勝手に音を鳴らしてはいけない」という ルールがあります。ページを開いた瞬間に BGM が鳴りだすと迷惑だからです。 そこで Pyxel は、最初のクリックを待ってから音を含めた実行を始めます。 これは Pyxel の仕様ではなく、ブラウザ側のルールに合わせた作りです。

確認が済んだら、ターミナルで Ctrl + C を押してサーバーを止めます。

ブラウザ版とネイティブ版の違い 同じ作品でも、ブラウザで動かすと次の点が変わります。公開する前に知っておいてください。
項目pyxel run(手元)ブラウザ
起動すぐ始まるCLICK TO START のクリックが要る
キー入力すぐ効く画面をクリックして選択してからでないと効かない
Esc終了する終了しない(タブを閉じる)
起動の速さすぐ初回は wasm の読み込みで数秒かかる
ネット接続不要必要(Pyxel 本体を CDN から読み込むため)

3-5. 公開する場所に置く

このリポジトリでは、docs/ フォルダの中身がそのまま Web に公開される設定に なっています(仕組みは次の章で説明します)。作品の HTML も、その中に置きます。

置き場所は解説ページと同じ docs/site/step0/ にして、名前を作品名に変えますstep0.html のままだと、URL を見ても何のページか分からないためです。

docs/site/step0/star_voyager.html

移動は VS Code のエクスプローラ(左側のファイル一覧)でドラッグ&ドロップし、 F2 で名前を変えるのが簡単です。ターミナルでやるなら次のとおりです。

Move-Item step0.html docs\site\step0\star_voyager.html

3-6. 中間ファイルを Git の管理から外す

ルートに残った step0.pyxapp は、いつでも作り直せる中間ファイルです。 こういうものはリポジトリに入れません。.gitignore に 1 行足します。

# pyxel package が作る中間ファイル(いつでも作り直せるので追跡しない)
*.pyxapp
なぜ生成物をリポジトリに入れないのか .pyxapp.py.pyxres から自動で作られるものです。 これを入れてしまうと、コードを直すたびに 中身が同じか違うか分からない古いファイルが リポジトリに残り続けます。「元になるものだけを管理し、作られるものは管理しない」が Git の基本的な考え方です。

では star_voyager.html も生成物なのに、なぜ入れるのか。 それはこれが公開物そのものだからです。GitHub Pages は リポジトリに置かれたファイルをそのまま配るだけなので、これは置く必要があります。

3-7. 作品へのリンクを張る

HTML を置いただけでは、URL を知っている人しかたどり着けません。 解説サイトから行けるようにします。2 か所に追記します。

docs/site/step0/index.html(step0 概要ページ)の「完成イメージ」の下

<p style="text-align:center">
  <a href="star_voyager.html">▶ 完成した作品をブラウザで遊ぶ</a>
</p>

docs/site/index.html(トップページ)の step0 カードの中

カードの説明文のあとに、遊べることが分かる 1 行を足します。

さらに、リポジトリの README.md にも公開 URL を書いておくと、 GitHub をのぞいた人がすぐ遊べます。

リンクの書き方 star_voyager.htmlindex.html同じフォルダにあるので、 リンクはファイル名だけで書けます(href="star_voyager.html")。 ../https:// から始める必要はありません。

3-8. コミットしてプッシュする

ここまでの変更を GitHub に送ります。VS Code のソース管理(左の枝分かれアイコン)を開くと、 変更されたファイルが一覧で出ます。

  1. 一覧に star_voyager.html.gitignore、リンクを足した HTML が並んでいることを確認する
  2. step0.pyxapp一覧に出ていないことを確認する(出ていたら .gitignore の書き方を見直す)
  3. メッセージ欄に step0 フェーズ8:作品をパッケージ化して Web 公開 と入力
  4. コミット変更の同期(プッシュ)

3-9. 公開を確認する

プッシュしてから 数十秒〜数分待つと、次の URL で遊べるようになります。

https://mrgarita.github.io/learning_pyxel/site/step0/star_voyager.html
すぐに反映されないことがあります push した瞬間に公開されるわけではありません。GitHub 側でページを組み立てる時間が要ります。 404 が出てもすぐ慌てず、1〜2 分待ってから再読み込みしてください。 ブラウザが古い内容を覚えている場合は Ctrl + F5 で強制的に読み直せます。

4. GitHub Pages の仕組み

GitHub Pages は、リポジトリの中の HTML をそのまま Web サイトとして配ってくれる GitHub の無料機能です。サーバーを借りる必要も、アップロード作業も要りません。 push すれば公開されるのが最大の利点です。

手元のパソコン learning_pyxel/ docs/ site/step0/ star_voyager.html push GitHub リポジトリ Pages の設定 branch: main folder: /docs 自動 世界中から見える 🌐 公開 URL 誰でもアクセス可 docs/ の中の階層が、そのまま URL の階層になる: docs/site/step0/star_voyager.html → .../learning_pyxel/site/step0/star_voyager.html
docs/ より下のフォルダ構成が、そのまま URL の形になる

設定を確認する

このリポジトリではすでに設定済みなので、今回は変更する必要はありません。 確認するには GitHub のリポジトリページで Settings → Pages を開きます。

項目設定値意味
SourceDeploy from a branchブランチの中身をそのまま配る方式
Branchmainmain ブランチの内容を公開する
Folder/docsdocs フォルダの中だけを公開する
なぜ docs/ だけを公開するのか リポジトリ全体を公開する設定にもできますが、そうすると step0/star_voyager.pyCLAUDE.md まで Web からたどれる URL を持ってしまいます。 「見せたいものだけを 1 つのフォルダに集める」ほうが、 うっかり公開してしまう事故を防げます。

なお、コードが GitHub のリポジトリページから読めること自体は問題ありません (このリポジトリは公開リポジトリです)。ここで分けているのは 「Web サイトとして配るもの」と「ソースコードとして置いてあるもの」の役割です。

5. うまくいかないとき

症状原因と対処
no such file: 'star_voyager.py' 2 番目の引数をいまいる場所からのパスで書く。 pyxel package step0 step0/star_voyager.py
pyxel コマンドが見つからない pip install pyxel が済んでいるか確認する。 仮想環境を使っているなら、それを有効にしてから実行する。 python -m pyxel package ... のように python -m を付けても実行できる
pyxel play では動くのに、 ブラウザでは真っ黒のまま ① HTML をダブルクリックで開いていないかpython -m http.server 経由か、公開後の URL で開く。
ネットにつながっているか。Pyxel 本体を CDN から読み込むため、 オフラインでは動かない
CLICK TO START が出ない/ クリックしても始まらない wasm の読み込みに数秒かかる。少し待つ。 それでも変わらなければ、ブラウザの開発者ツール(F12)の Console タブに赤いエラーが出ていないか見る
矢印キーを押しても宇宙船が動かない ゲーム画面を一度クリックする。ブラウザは、選択されている場所にだけ キー入力を送るため
音が鳴らない CLICK TO START をクリックする前は鳴らない仕様。 クリック後も鳴らないなら、ブラウザのタブがミュートになっていないか確認する (タブのスピーカーアイコン)
Esc で終了しない ブラウザでは終了できない(仕様)。タブを閉じる。 画面の PRESS ESC TO QUIT の表示は、手元で実行したとき向けの案内
公開 URL が 404 ① push から数分待つ。② ファイルが本当に docs/にあるか。 ③ URL の綴りとフォルダ階層が一致しているか。 ④ GitHub のリポジトリページでファイルが見えているか (見えなければ push できていない)
更新したのに古い内容が出る ブラウザが前の内容を覚えている。Ctrl + F5 で強制再読み込み
step0.pyxapp がコミット一覧に出てくる .gitignore*.pyxapp を書いたか確認する。 すでに一度コミットしてしまった場合は .gitignore だけでは消えないので、 git rm --cached step0.pyxapp で追跡から外す
Pyxel を新しくしたら ブラウザ版だけ動きが変わった/動かない HTML には作ったときのバージョンpyxel@2.9.9)が 埋め込まれている。Pyxel を更新したら package からやり直して HTML を作り直す
pyxel.load("assets.pyxres") のパスは大丈夫? フェーズ 5 で「pyxel.load の相対パスは実行するスクリプトのある場所を基準に 解決される」と学びました。パッケージ化しても同じです。 .pyxapp の中で star_voyager.pyassets.pyxres同じ step0/ フォルダに並んだまま収められるので、コードは 1 文字も直さずに動きます。

6. step0 をふりかえる

これで step0 は完了です。8 つのフェーズで、次のことを一通り通りました。

フェーズ身についたこと
1Pyxel の骨格(initrun、update と draw の役割分担)
2画面座標系と 16 色パレット、文字の描画
3リストで多数のものを管理し、毎フレーム動かすアニメーションの考え方
4リソースエディタでのドット絵制作、コードをクラスにまとめる設計
5スプライトの転送と透過色、リソースファイルの読み込み
6キー入力の判定と、画面外に出さないための値の制限
7サウンドとミュージックの作成、BGM のループ再生
8作品のパッケージ化と Web 公開、生成物と原本の区別

ここまでで「画面に出す・動かす・操作する・音を鳴らす・人に届ける」という、 ゲーム制作の一周分がそろいました。step1 からは、これらを組み合わせて ルールのあるゲームを作っていきます。

次にやること step0 で学んだ道具を使って、自分だけの作品を 1 つ作ってみるのがおすすめです。 お手本をなぞるのと、自分で考えて作るのとでは、身につき方がまったく違います。 題材は何でもかまいません。作ったら、このフェーズの手順でそのまま公開できます。