PhaserJSでパンを撃って敵を討つゲームを作った
Summary
- パンを撃って敵を討つシューティングゲームを作ってみた
- いつもお世話になっているPdMや社内に向けたネタ作だが、割とちゃんと作った
- Googleログインでランキング機能を入れている
あそぶ
こちらから遊べます。キーボード操作が必要なため、PCブラウザでのみ遊べます。 推奨環境はChrome。
- URL
Tech Stack
- Frontend: PhaserJS, Vite
- Backend: Golang
- Hosting: Fly.io with PostgreSQL server
モチベーション
Viteの学習も兼ねて、ブラウザゲームを作ってViteでBuild した結果を、サーバから静的配信したい、というモチベーションでネタを探していた。
ちょこちょこ小ネタのゲームを作るので、今回も小ネタとして作ってみた。
感想もろもろ
Fly.io
個人開発の置き場を Heroku からどこに変えるかを悩んでいて、今回は Fly.io に置いてみた。
- 無料枠内で DBサーバとして PostgreSQL も使える
flyctl
でアプリの構築〜デプロイ〜管理すべてコマンドラインベースで勝手が良い- Dockerfileに記述しておき、Fly.io 上でDockerイメージを立ち上げてくれる
構成管理が toml なのが慣れないともやもやするが、簡単なサービスなら今後はこれで行こうかなと思った。
Stable Diffusion
今回のメインビジュアルは背景を Stable Diffusion で作ってみた。
ローカルやGoogle Colaboratory に置いて試すのが一般的だが、 mage.space というサービスで今回は作成した。
画像生成系モデルのAIについては全然詳しくないのだが、 Example も多いのでそのプロンプトを見つつ作ってみた。
いい感じのアラビアンな画像ができたと思う。
Google ログイン
もともとはViteでブラウザゲームをビルドしたいだけだったので Firebase Hosting あたりに置くつもりだった。
しかし、やっぱり展開するならランキング機能欲しいなという欲が出てきて、 Google ログインを突っ込むことにした。
Auth0や Firebase Authorication も検討したものの、アカウント情報を基本的には保持したくなかったため、IDと名前以外は持たないように内製した。
スコア保持
ゲームはすべてCanvas 上で動いている。
普段の仕事だとSPAのWEBアプリを作っているのだが、Canvas上での画面をまたぐ際の情報保持がイマイチ分からず苦戦した。
結局、CryptoJS を用いて暗号化して、SessionStorageに保持 → 画面遷移後に認証、スコアを複合→DB保存後、破棄 という手順を踏んだ。
https://github.com/daitasu/pan-shoot/blob/main/frontend/src/scenes/mypage.ts#L32-L46
もっといい方法がありそうだが、そんなに大事な情報でもないのでフロント側で管理することにした。
Static Server 配信のための src リプレイス
今回のVite側の静的ファイルのパス解決とGolangサーバ側のパス解決を合わせるのに中々詰まった。
今回のフロントエンドをビルドした結果は下記のようになる。
dist └ assets └ main-xxxxxxxxx.js └ images └ xxx.png # 各種の画像 └ index.html
このとき、
- Golang側で静的ファイルサーバを
/
直下には置かず、/static
配下にしたかった index.html
から js を<script type="module" src="/static/assets/main-xxx.js"></script>
のように取得したい- 画像群もまた、
href="/static/images/chocopan.ico"
のようにして取得したい
という条件があるのだが、Golang側で /static/
を静的ファイルサーバとして配信すると、画像ファイルは正常に上記のパスになる。
一方、Viteでビルドした時点で index.html の script タグは src="/assets/main-xxx.js"
となっており、ここにずれがあった。
prefix をつけると画像側のパスが今度はずれてしまうため、ここだけ個別書き換えるようにした。
pan-shoot/buildPlugin.ts at main · daitasu/pan-shoot · GitHub
これを vite.config.ts
上でプラグインとして入れ込むことで解决した。
import updateIndexHtml from "./src/scripts/buildPlugin"; export default defineConfig({ // ... build: { // ... rollupOptions: { input: { main: resolve(root, "index.html"), }, }, }, plugins: [updateIndexHtml()], server: { port: 9000, }, });
Viteにおける環境変数の持ち方
Viteを使って個人開発してる中で地味に詰まったのでメモ。
Summary
- Vite の環境変数は
vite.config.ts
におけるenvDir
で指定したディレクトリパスにある.env
を参照する .env
の情報はimport.meta.env
内に保持されるVITE_
prefix がつく環境変数のみを.env
ファイルから取得し、それ以外は undefined となる- TS 用の型補完は
vite/client.d.ts
で型定義を提供しており、自前型定義はImportMetaEnv
を補う形で追加する
ref: Env Variables and Modes | Vite
Contents
① Vite の環境変数は vite.config.ts
における envDir
で指定したディレクトリパスにある .env
を参照する
- ref: https://ja.vitejs.dev/config/shared-options.html#envdir
- デフォルトでは
root
となっている - これは
vite.config.ts
で指定しているroot
をベースとしているため、下記のようなディレクトリ構成、設定の場合は指定が必要
- public/ - src/ └ main.ts └ index.html - .env - package.json - yarn.lock - vite.config.ts
vite.config.ts
import { defineConfig } from "vite"; export default defineConfig({ root: "src", // src 配下を root としたい envDir: "../", // src がroot のため、envDir は1つ戻す publicDir: "../public", build: { outDir: "../dist", }, server: { port: 9000, }, });
うっかりで地味に気づくのに時間かかった。。
② .env
の情報は import.meta.env
内に保持される
Vite でビルドした結果において、環境変数は import.meta.env
に保持される。
標準で下記のような情報を保持している。
import.meta.env.BASE_URL
... アプリのベースURL。import.meta.env.DEV
... 開発環境で動作しているかどうかimport.meta.env.PROD
... 本番環境で動作しているかどうかimport.meta.env.MODE
... アプリのモード。yarn build --mode {mode}
のように文字列指定できる。yarn dev
はdevelopment
モード、build
はproduction
モードとして動作するimport.meta.env.SSR
... SSR での動作かどうか
③ VITE_
prefix がつく環境変数のみを .env
ファイルから取得し、それ以外は undefined となる
Vite は標準で dotenvを内包している。
現在のモードに合わせて、下記のように読み込む条件が変わる。
- .env # 全ての場合に読み込まれる - .env.local # 全ての場合に読み込まれ、gitには無視される - .env.[mode] # 指定されたモードでのみ読み込まれる - .env.[mode].local # 指定されたモードでのみ読み込まれ、gitには無視される
少し変わった特徴として、Viteでは環境変数が誤ってクライアントサイドに漏れないように、 VITE_
prefix がつく変数のみをViteで処理されたコードに公開する。
sample
VITE_HOGE=aaa HOGE=bbb
これに対し、 クライアントサイドで取得を試みると下記のようになる。
import.meta.env.VITE_HOGE
... aaaimport.meta.env.HOGE
... undefined
デフォルトは VITE_
という指定だが、この prefix は enxPrefix
を vite.config.ts
に指定して変更可能。
Viteのコードを追ってみると、シンプルに .env
で読み取った情報の中から prefix
が一致するものだけを吸っているっぽい。
④ TS 用の型補完は vite/client.d.ts
で型定義を提供しており、自前型定義は ImportMetaEnv
を補う形で追加する
Viteの import.meta.env
の型定義は vite/client.d.ts で提供されている。
しかし、自前の環境変数には自動補完が効かないので、下記のような env.d.ts
を作成する。
/// <reference types="vite/client" /> interface ImportMetaEnv { readonly VITE_APP_TITLE: string; readonly VITE_HOGE: string; readonly VITE_FUGA: string; /// etc... } interface ImportMeta { readonly env: ImportMetaEnv; }
robotgo(Go) でカラーピッカーを作る
はじめに
Goでキーボードやマウス操作ができる robotgo というライブラリを知り、定常的に使っているカラーピッカーを自前で作り直したかったので今回挑戦してみました。
完成物
先に、出来上がったものはこちらです。
robotgo とは
キーボードやマウス操作、画面キャプチャなどをGoから扱えるライブラリです。 例えば、下記のようにマウス操作やスクリーン操作が行なえます。(サンプルコードより拝借)
package main import ( "github.com/go-vgo/robotgo" ) func main() { // マウス操作 robotgo.ScrollDir(10, "up") robotgo.ScrollDir(20, "right") robotgo.Scroll(0, -10) robotgo.Scroll(100, 0) robotgo.MilliSleep(100) robotgo.ScrollSmooth(-10, 6) robotgo.Move(10, 20) robotgo.MoveRelative(0, -10) robotgo.DragSmooth(10, 10) robotgo.Click("wheelRight") robotgo.Click("left", true) robotgo.MoveSmooth(100, 200, 1.0, 10.0) robotgo.Toggle("left") robotgo.Toggle("left", "up") // スクリーン操作 x, y := robotgo.Location() fmt.Println("pos: ", x, y) color := robotgo.GetPixelColor(100, 200) fmt.Println("color---- ", color) sx, sy := robotgo.GetScreenSize() fmt.Println("get screen size: ", sx, sy) bit := robotgo.CaptureScreen(10, 10, 30, 30) defer robotgo.FreeBitmap(bit) img := robotgo.ToImage(bit) imgo.Save("test.png", img) num := robotgo.DisplaysNum() for i := 0; i < num; i++ { robotgo.DisplayID = i img1 := robotgo.CaptureImg() path1 := "save_" + strconv.Itoa(i) robotgo.Save(img1, path1+".png") robotgo.SaveJpeg(img1, path1+".jpeg", 50) img2 := robotgo.CaptureImg(10, 10, 20, 20) robotgo.Save(img2, "test_"+strconv.Itoa(i)+".png") } }
カラーピッカーを作ってみる
robotgoを用いて、各種イベントに対して処理を登録していきます。 robotgo.EventHookというメソッドがあるので、これを用いてクリックイベントとKeyDownイベントを渡して処理の登録をしています。
fmt.Println("Please mouse left click to pick the color of current mouse position.") fmt.Println("To pick the color of mouse positions, please enter ctrl+Shift+c.") fmt.Println("To quit checking mouse event, please enter ctrl+Shift+q.") // Stop event robotgo.EventHook(hook.KeyDown, []string{"q", "shift", "ctrl"}, func(e hook.Event) { robotgo.EventEnd() }) // Pick the color of event robotgo.EventHook(hook.KeyDown, []string{"c", "shift", "ctrl"}, func(e hook.Event) { posx, posy := robotgo.GetMousePos() color := robotgo.GetPixelColor(posx, posy) robotgo.WriteAll(color) fmt.Println("Pick the color: ", color) }) // Get color of current mouse position robotgo.EventHook(hook.MouseDown, []string{}, func(e hook.Event) { posx, posy := robotgo.GetMousePos() color := robotgo.GetPixelColor(posx, posy) fmt.Println("pos-> ", posx, posy) fmt.Println("color-> ", color) })
マウスクリックの度にカラーコードをクリップボードに取得されるのはモヤモヤするので、クリップボードへのコピーは処理を分けてます。 下記のようなメソッドを利用しています。
- func GetMousePos() (int, int)... マウスの現在地の取得
- func GetPixelColor(x, y int) string... 引数位置のカラーコード取得
- func WriteAll(text string) error... clipboardへのコピー
下記のように動きます。
$ go run main.go Please mouse left click to pick the color of current mouse position. To pick the color of mouse positions, please enter ctrl+Shift+c. To quit checking mouse event, please enter ctrl+Shift+q. // マウス左クリック時 pos-> 968 703 color-> 2d313b // ctrl + shit + c 押下時 Pick the color: efefef
robotgo を用いることで、さくっとマウスの現在地とそのカラーコードの取得ができました。 CLIだけだとどんな色を取得するか分かりにくく扱いづらいので、次回はもう少し改良していこうと思います。
参考
今回の制作にあたり、下記の記事も参考にさせて頂きました。