はじめに
ebitengineでゲームを作ったら、是非公開したくなります。ebitengineではマルチプラットフォームに対応しているので好きなプラットフォームで公開できるわけですが、実際に公開するにはプラットフォームごとに異なる大きなハードルを超えないといけません。
そこで、今回はWebブラウザでebitengineで作ったゲームを遊べるようにするまでの方法を公開します。今回ご紹介する方法を実践後に、後はAWSやherokuなどにデプロイすればすぐに全世界に公開できます。
さらに、ログインユーザーごとに異なる値をebitengine側に渡すという実装も含まれているので、ebitengine本体に備わっていない「ゲームデータを保存する」ということもできます。今回はその取っ掛かりの部分を実装しているので、是非拡張してみてください。
ざっくり全体の説明
- ebitengineで作ったアプリをWebAssemblyで書き出す
- 書き出したアプリをlaravel上に設置する
- ログインユーザー名をebitengineで表示できるようにする
Laravel部の作成
今回作成する環境
dockerを使用します。未インストールの方はインストールをお願いします。
- Laravel バージョン:v8.83.23
- PHP バージョン:7.4.30
1. dockerfileなどの作成
ディレクトリ構造
┣ nginx
┃ ┣ default.conf
┃ ┗ mime.conf
┃
┣ php-fpm
┃ ┗ Dockerfile
┃
┣ src (一旦ディレクトリのみ作ってください)
┃
┗ docker-compose.yml
各ファイルの中身
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html/public;
index index.html index.htm index.php;
try_files $uri $uri/ /index.php$is_args$args;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
root /var/www/html/public;
fastcgi_pass php-fpm:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
types{
application/wasm wasm;
}
FROM php:7.4-fpm
#pdoインストール
RUN docker-php-ext-install pdo_mysql
#composerインストール
RUN curl -sS https://getcomposer.org/installer | php
RUN mv composer.phar /usr/local/bin/composer
#いろんなものをインストール
RUN apt-get update
RUN apt-get install -y git vim zip unzip nodejs npm
version: '3'
services:
nginx:
image: nginx:1.15
ports:
- 80:80
volumes:
- ./src:/usr/share/nginx/html
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./nginx/mime.conf:/etc/nginx/conf.d/mime.conf
php-fpm:
build: ./php-fpm
volumes:
- ./src:/var/www/html
mysql:
image: mysql:5.7
environment:
MYSQL_DATABASE: ebiten
MYSQL_ROOT_PASSWORD: password
MYSQL_USER: user
MYSQL_PASSWORD: password
TZ: Asia/Tokyo
ports:
- 3306:3306
volumes:
- ./mysql/data:/var/lib/mysql
2. Laravelプロジェクトの作成
$マークはコンテナの外、>マークはコンテナ内でのコマンドを表します
- ルートディレクトリにてコンテナ立ち上げ
$ docker-compose up -d
- php-fpmコンテナへ入る
$ docker exec -it ebiten-web_php-fpm_1 /bin/bash
- プロジェクトを作成
> composer create-project laravel/laravel ebiten
- src/ebiten 以下のファイルをすべて src 配下へ移動させて、ebitenフォルダを削除
- .envを開き、データベース関連の値を以下のように修正
DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=ebiten DB_USERNAME=user DB_PASSWORD=password
- コンテナ内でマイグレーション実施
> php artisan migrate
-
- laravel ui (ログイン周り)の導入
> composer require laravel/ui
> php artisan ui bootstrap --auth
> npm install && npm run dev
エラーがここで出るので、 下記を実行
> npm install resolve-url-loader@^5.0.0 --save-dev --legacy-peer-deps
> npm run dev - http://localhost/ にアクセスして新規登録できることを確認する
ebitengine部の作成
go言語のバージョン
- 1.19
サンプルの作成
-
適当なディレクトリ(ebitengineのプロジェクト名)を作成
-
1で作ったディレクトリ内でプロジェクトの作成
$ go mod init github.com/yourname/yourgame
yourenameやyourgameの部分は各自入力してください。
-
2で作られた
go.mod
と同じディレクトリにmain.go
を作成して、以下のように記述main.gopackage main import ( "log" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" ) type Game struct{} func (g *Game) Update() error { return nil } func (g *Game) Draw(screen *ebiten.Image) { ebitenutil.DebugPrint(screen, "Hello World !!") } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth, screenHeight int) { return 320, 240 } func main() { ebiten.SetWindowSize(640, 480) ebiten.SetWindowTitle("Hello, World!") if err := ebiten.RunGame(&Game{}); err != nil { log.Fatal(err) } }
-
一度
main.go
を実行$ go run main.go
このとき、go get ~~~ というエラーが出たら一通り実行して、Hello Worldが表示されるのを確認する
-
WebAssemblyへ書き出す
$ GOOS=js GOARCH=wasm go build -o main.wasm main.go
-
WebAssemblyを動かすためのjsファイルを取得
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
なんか斜体になっていますが、
$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .
です
Laravelにebitengineのアプリを移植
-
ebitengine部の5番目の手順で
main.wasm
ファイルが生成されているので、このファイルをlaravel側のsrc/public/wasm/main.wasm
となるように配置する。(wasmフォルダがないので作ってください) -
ebitengine部の6番目の手順で
wasm_exec.js
ファイルが生成されているので、このファイルをlaravel側のsrc/public/js/wasm_exec.js
となるように配置する。
ここまででこのようなディレクリ構成になっているはずです。
-
ログイン後のhomeのページにebitengineのアプリを埋め込みます。iframeの中の記述がebitengineのアプリの実行部分になります。
src/resources/views/home.blade.php@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Dashboard') }}</div> <div class="card-body d-flex justify-content-center"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif <iframe src="" allow = "autoplay" width="640" height="480" srcdoc=" <!DOCTYPE html> <script src='js/wasm_exec.js'></script> <script> // Polyfill if (!WebAssembly.instantiateStreaming) { WebAssembly.instantiateStreaming = async (resp, importObject) => { const source = await (await resp).arrayBuffer(); return await WebAssembly.instantiate(source, importObject); }; } const go = new Go(); WebAssembly.instantiateStreaming(fetch('wasm/main.wasm'), > go.importObject).then(result => { go.run(result.instance); }); </script>"> </iframe> </div> </div> </div> </div> </div> @endsection
-
http://localhost/ にアクセスして、ページ内でebitengineのアプリが実行されていることを確認する
ebitengineにLaravelで取得したデータを受け渡す
ここまででebitengineで作ったアプリをブラウザで遊ぶことができますが、プレイデータの保存や読み出し
ができないため、いつも同じところからゲームがスタートしてしまいます。
そこで、Laravel側でデータベースからプレイデータを取得してをebitengine側に渡したり、ebitengine側からLaravelのデータセーブAPIを叩くことでプレイデータの保存や読み出しが可能になります。
今回は、プレイデータの読み出し
の例として、ログインユーザー名をeibtengineに渡して表示させてみましょう。
-
ebitengine側にログインユーザー名を保存する変数とセットする関数の準備と、ユーザー名を画面に表示させるロジックを追加する
main.gopackage main import ( "log" "syscall/js" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/ebitenutil" ) type Game struct{} + var ( + userName string = "" + ) func (g *Game) Update() error { return nil } func (g *Game) Draw(screen *ebiten.Image) { + ebitenutil.DebugPrint(screen, "Hello "+userName+"!!") } func (g *Game) Layout(outsideWidth, outsideHeight int) (screenWidth,screenHeight int) { return 320, 240 } func main() { ebiten.SetWindowSize(640, 480) ebiten.SetWindowTitle("Hello, World!") + js.Global().Set("setUserName", js.FuncOf(func(this js.Value, args []js.Value) interface{} { + userName = args[0].String() + // set return value + args[1].Invoke(userName) + return nil + })) if err := ebiten.RunGame(&Game{}); err != nil { log.Fatal(err) } }
-
WebAssemblyで書き出して、Laravelプロジェクトのwasmファイルを入れ替える
$ GOOS=js GOARCH=wasm go build -o main.wasm main.go
-
Laravelのコントローラーからviewにユーザーデータを渡す
src/app/Http/Controllers/HomeController.php/** * Show the application dashboard. * * @return \Illuminate\Contracts\Support\Renderable */ public function index() { + $user = Auth::user(); + return view('home', compact('user')); }
-
Laravel側でログインユーザー名をセットする関数を実行する
src/resources/views/home.blade.php@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Dashboard') }}</div> <div class="card-body d-flex justify-content-center"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif <iframe src="" allow = "autoplay" width="640" height="480" srcdoc=" <!DOCTYPE html> <script src='js/wasm_exec.js'></script> <script> // Polyfill if (!WebAssembly.instantiateStreaming) { WebAssembly.instantiateStreaming = async (resp, importObject) => { const source = await (await resp).arrayBuffer(); return await WebAssembly.instantiate(source, importObject); }; } const go = new Go(); WebAssembly.instantiateStreaming(fetch('wasm/main.wasm'), > go.importObject).then(result => { go.run(result.instance); + setUserName('{{ $user->name }}', function(userName){ + console.log('ebitengine set userName :', userName) + }); }); </script>"> </iframe> </div> </div> </div> </div> </div> @endsection
-
http://localhost/ にアクセスして、ページ内でebitengineのアプリでユーザー名の表示ができている確認する
ざっくり解説
- LaravelのHomeControllerでユーザーデータを$userに格納して、viewに渡す
- Laravelのviewでebitengineのユーザーネームを設定する関数に、Cotrollerから渡されたユーザーデータからユーザーネームを渡す
- ebitengine側のユーザーネームを設定する関数で、Laravelから渡されてきたユーザーネームを変数に格納
- ebitengine側で変数に格納されたユーザーネームを画面に表示する
ユーザーネームの移動としては
DB→php→javascript→go
という感じでバケツリレーのようにユーザーネームを渡しています。
最後に
これでデータベースにある値をebitengine側に渡すことができたので、あとはユーザートークンやユーザーIDをLaravelからebitengineに渡してあげて、ebitengineは渡された値を元にLaravelのデータ取得や更新APIを叩くことでプレイデータの保存や更新ができるようになります。
(もしかしたらAPI叩くときにもいろんな壁があるかも)
みなさんも素敵なゲーム作成ライフを!