LoginSignup
2
0

More than 1 year has passed since last update.

Laravel上でebitengineのゲームを遊べるようにする

Posted at

はじめに

ebitengineでゲームを作ったら、是非公開したくなります。ebitengineではマルチプラットフォームに対応しているので好きなプラットフォームで公開できるわけですが、実際に公開するにはプラットフォームごとに異なる大きなハードルを超えないといけません。

そこで、今回はWebブラウザでebitengineで作ったゲームを遊べるようにするまでの方法を公開します。今回ご紹介する方法を実践後に、後はAWSやherokuなどにデプロイすればすぐに全世界に公開できます。

さらに、ログインユーザーごとに異なる値をebitengine側に渡すという実装も含まれているので、ebitengine本体に備わっていない「ゲームデータを保存する」ということもできます。今回はその取っ掛かりの部分を実装しているので、是非拡張してみてください。

ざっくり全体の説明

  1. ebitengineで作ったアプリをWebAssemblyで書き出す
  2. 書き出したアプリをlaravel上に設置する
  3. ログインユーザー名を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

各ファイルの中身

nginx/default.conf
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;
    #}
}
nginx/mime.conf
types{
    application/wasm wasm;
}
php-fpm/Dockerfile
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
docker-compose.yml
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プロジェクトの作成

$マークはコンテナの外、>マークはコンテナ内でのコマンドを表します

  1. ルートディレクトリにてコンテナ立ち上げ

    $ docker-compose up -d

  2. php-fpmコンテナへ入る

    $ docker exec -it ebiten-web_php-fpm_1 /bin/bash

  3. プロジェクトを作成

    > composer create-project laravel/laravel ebiten

  4. src/ebiten 以下のファイルをすべて src 配下へ移動させて、ebitenフォルダを削除
  5. .envを開き、データベース関連の値を以下のように修正
    DB_CONNECTION=mysql
    DB_HOST=mysql
    DB_PORT=3306
    DB_DATABASE=ebiten
    DB_USERNAME=user
    DB_PASSWORD=password
    
  6. コンテナ内でマイグレーション実施

    > php artisan migrate

    1. 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

  7. http://localhost/ にアクセスして新規登録できることを確認する

ebitengine部の作成

go言語のバージョン

  • 1.19

サンプルの作成

  1. 適当なディレクトリ(ebitengineのプロジェクト名)を作成

  2. 1で作ったディレクトリ内でプロジェクトの作成

    $ go mod init github.com/yourname/yourgame

    yourenameやyourgameの部分は各自入力してください。

  3. 2で作られたgo.modと同じディレクトリにmain.goを作成して、以下のように記述

    main.go
    package 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)
      }
    }
    
  4. 一度 main.goを実行

    $ go run main.go

    このとき、go get ~~~ というエラーが出たら一通り実行して、Hello Worldが表示されるのを確認する

  5. WebAssemblyへ書き出す

    $ GOOS=js GOARCH=wasm go build -o main.wasm main.go

  6. WebAssemblyを動かすためのjsファイルを取得

    $ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .

    なんか斜体になっていますが、$ cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" .です

Laravelにebitengineのアプリを移植

  1. ebitengine部の5番目の手順でmain.wasmファイルが生成されているので、このファイルをlaravel側のsrc/public/wasm/main.wasm となるように配置する。(wasmフォルダがないので作ってください)

  2. ebitengine部の6番目の手順でwasm_exec.jsファイルが生成されているので、このファイルをlaravel側のsrc/public/js/wasm_exec.jsとなるように配置する。
    ここまででこのようなディレクリ構成になっているはずです。
    Monosnap home.blade.php — ebiten-web 2022-08-24 19-23-26.png

  3. ログイン後の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
    
  4. http://localhost/ にアクセスして、ページ内でebitengineのアプリが実行されていることを確認する

    Monosnap Laravel 2022-08-24 22-46-23.png

ebitengineにLaravelで取得したデータを受け渡す

ここまででebitengineで作ったアプリをブラウザで遊ぶことができますが、プレイデータの保存や読み出しができないため、いつも同じところからゲームがスタートしてしまいます。

そこで、Laravel側でデータベースからプレイデータを取得してをebitengine側に渡したり、ebitengine側からLaravelのデータセーブAPIを叩くことでプレイデータの保存や読み出しが可能になります。

今回は、プレイデータの読み出しの例として、ログインユーザー名をeibtengineに渡して表示させてみましょう。

  1. ebitengine側にログインユーザー名を保存する変数とセットする関数の準備と、ユーザー名を画面に表示させるロジックを追加する

    main.go
    package 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)
      }
    }
    
  2. WebAssemblyで書き出して、Laravelプロジェクトのwasmファイルを入れ替える

    $ GOOS=js GOARCH=wasm go build -o main.wasm main.go

  3. 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'));
    }
    
  4. 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
    
  5. http://localhost/ にアクセスして、ページ内でebitengineのアプリでユーザー名の表示ができている確認する

    Monosnap Laravel 2022-08-24 23-32-54.png

ざっくり解説

  1. LaravelのHomeControllerでユーザーデータを$userに格納して、viewに渡す
  2. Laravelのviewでebitengineのユーザーネームを設定する関数に、Cotrollerから渡されたユーザーデータからユーザーネームを渡す
  3. ebitengine側のユーザーネームを設定する関数で、Laravelから渡されてきたユーザーネームを変数に格納
  4. ebitengine側で変数に格納されたユーザーネームを画面に表示する

ユーザーネームの移動としては
DB→php→javascript→go
という感じでバケツリレーのようにユーザーネームを渡しています。

最後に

これでデータベースにある値をebitengine側に渡すことができたので、あとはユーザートークンやユーザーIDをLaravelからebitengineに渡してあげて、ebitengineは渡された値を元にLaravelのデータ取得や更新APIを叩くことでプレイデータの保存や更新ができるようになります。
(もしかしたらAPI叩くときにもいろんな壁があるかも)

みなさんも素敵なゲーム作成ライフを!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0