【Unity】RPGを作るチュートリアルその126 WebGLのセーブが消える問題を修正

【Unity】RPGを作るチュートリアルその126 WebGLのセーブが消える問題を修正

シンプルなRPGをUnityで作るチュートリアルシリーズの126回目です。

第125回ではWebGLのビルドとApacheの構成を行いました。

今回はWebGLでセーブが保存されない点など、確認できたバグを修正します。

 

 

制作環境

MacBook Pro 2023 Apple M2 Max

Unity6 (6000.0.30f1) Silicon

 

作業内容と順序

シンプルなRPGを作る上でどんな作業が必要か、どんな順番で作っていくと良さそうか、別ページで検討しました。基本的にこの流れに沿って進めていきます。

 

チュートリアルの一覧

このシリーズ全体の一覧は以下のページにまとめています。

 

前回の内容

前回はWebGLのビルドとApacheの構成を行いました。

 

WebGLの動作で気付いた点

前回の作業でローカルの検証環境でWebGLのビルドの動作を確認できるようになりました。一通り遊べたものの、以下のように気になる点もありました。

  • メニュー画面で魔法を使った時の効果音がない
  • メニュー画面で最後の1つのアイテムを使うとエラーで止まる
  • ブラウザをリロードするとセーブが消える

これらについて対応していきたいと思います。

 

メニュー画面で魔法を使った時の効果音がない

シンプルな方から修正していきます。メニュー画面では魔法を使った時の効果音が再生されなかったので、それを修正していきます。また、アイテムについても効果音が再生されなかったので、合わせて修正しておきたいと思います。

効果音の再生処理は戦闘でアイテムの処理、魔法の処理を行うときと同様に実装していきます。まずはアイテムの処理クラスである「MenuProcessorItem」から修正していきます。

ShowItemHealMessage()のメソッドにて、回復の効果音の再生処理を追加しました。GenerateHpHealMessage()でメッセージを表示する処理の直前に入れておきます。

 

続いて「MenuProcessorMagic」のクラスも修正します。

ProcessMagicActionCoroutine()のコルーチンでは、先頭に魔法を使った時の効果音の再生処理を入れました。

 

ShowMagicHealMessage()のコルーチンでは回復の効果音の再生処理を入れます。アイテムの時と同様に、GenerateHpHealMessage()でメッセージを表示する処理の直前に入れておきます。

 

効果音の動作確認

ゲームを開始して、メニュー画面でアイテムを使った時、魔法を使った時にそれぞれ効果音が再生されることを確認しましょう。

 

メニュー画面で最後の1つのアイテムを使うとエラーで止まる

次に、メニュー画面で最後の1つのアイテムを使うとエラーで止まる問題を修正します。これはWebGL特有の問題ではなく、気付いていなかっただけの問題です(すみません)

私の癖として、回復アイテムと回復魔法がある時には、ゴールドを使わずに回復できる魔法を優先して使う傾向があります。そのため薬草を使い切ることがなくて気付いていなかったようです。テストプレイをする時にも普段の癖が出てしまうので、他の人に遊んでもらうというのはとても大切ですね。

デバッグ用のログを仕込んで確認したところ、「MenuItemWindowItemController」のクラスにあるSetPageItem()のメソッドにて、現在のページ数がマイナスの状態で引数として渡されていました。その結果、不正なインデックスとなり、エラーが出て止まっていたようです。

ではなぜ現在のページ数がマイナスになっていたかと言えば、同じクラス内にあるVerifyPage()の処理で、条件式が間違っていたからでした。

現在のコードだと、「else if (currentPage < 0)」の条件式ではcurrentPageが0の場合は合致せず、else句の方に進んでしまいます。アイテム数が0の場合はGetMaxPageNum()が0になるので、そこから1を引いてマイナスの値になってしまっていました。

そこで、以下のように、「else if (currentPage <= 0)」とすることで現在のページ数が0の場合も合致するように修正しました。

 

currentPageが0未満としていた条件を0以下に変更しました。これでcurrentPageが0の場合にマイナスにならずに済みます。

また、「MenuItemWindowMagicController」のクラスでも同じくVerifyPage()のメソッドがあるので、同じように修正しましょう。

記憶の中ではこの問題に対処していたような気がしていたのですが、お店の処理の方でまさしくVerifyPage()のメソッドがあり、そちらはcurrentPageが0以下の条件になっていました。この時メニューの方にも気を配れていたら……と若干の後悔があります(反省)

 

動作確認

スクリプトを保存したら、ゲームを実行してメニュー画面からアイテムを全部使い切ります。「ArgumentOutOfRangeException」のエラーが表示されず、ゲームが進行すればOKです。

 

ブラウザをリロードするとセーブが消える

さて、問題はこちらですね。現在のセーブファイルの保持の仕組みだと、ファイルシステムにJSONファイルを保存するようになっていますが、実はWebGLではファイルシステムに書き込むことができません。ブラウザ上で実行されていてサンドボックス(隔離環境)になっているためで、別の仕組みを用意する必要があります。

シンプルな方法だと、JSONの文字列をPlayerPrefsに文字列として保存する方法があります。今回のチュートリアルの規模だと、フラグ数やデータ量も多くなく、出力されているファイルを確認しても2KB程度なので、PlayerPrefsを使う方式で進めたいと思います。WebGLでPlayerPrefsを使う場合、内部的に「IndexedDB API」を使って保存していて、最大1MBまで保存できます。

本番リリースするゲームであれば、バックエンド環境を用意して、そちらでゲームデータを保存する方法もあります。ブラウザ側で保存するのはIDやパスワードだけで済むので、最大容量を気にせずに実装できます。

 

スクリプトの修正

PlayerPrefsを使うようにしますが、WebGLでのみこの動作を行うようにしたいと思います。それ以外の環境であればファイルシステムに保存できるので、プラットフォーム別の処理を行うようにします。

Unityエディタで、かつSwitch PlatformでWebGLを選択している場合には、エディタ側の環境を優先したいので、

  • WebGLで実行している
  • Unityエディタでない

の2つの条件を満たす形で分岐させたいと思います。

まずは「SaveSettings」のクラスで、PlayerPrefsで文字列を保存するためのキーを定義します。

定義クラスの末尾に「WebGLSaveKey」のフィールドを追加しました。このキー名を使ってPlayerPrefsで文字列を保存します。

 

続いてファイルからの読み込み、ファイルへの書き込みを行なっている「SaveDataHolder」を変更していきます。

既存のLoad()とSave()のメソッドでは、冒頭にプラットフォーム依存コンパイルの分岐を入れています。WebGLで、かつUnityエディタでない場合にこの分岐を通るようになっていて、PlayerPrefsからロードするメソッドのLoadFromPlayerPrefs()、PlayerPrefsに保存するメソッドのSaveToPlayerPrefs()を呼び出すようにしています。呼び出し後はreturnで抜けるので、ファイルシステムとのやりとりを行う処理は通らなくなります。

LoadFromPlayerPrefs()では、JSONを保存するためのキーを使ってPlayerPrefsから文字列を取得し、それを変換してセーブファイルの形にしています。もしロードに失敗した場合はInitializeSaveFile()を通るようにします。

SaveToPlayerPrefs()では同様にキーを使って文字列を保存します。SetString()の後は明示的にPlayerPrefs.Save()を呼んで保存するようにしています。

 

動作確認

スクリプトを保存したら、まずはUnityエディタでゲームを実行して、セーブファイルが読み込めていることを確認します。問題がなさそうであればWebGL用にビルドしましょう。ビルドには時間がかかるので、Unityエディタで動作を確認してからビルドを行うとスムーズです。

ビルドが完了したら、Docker DesktopからApacheのコンテナを起動し、「View files」のメニューを選択します。「/usr/local/apache2/htdocs」のディレクトリに移動して、WebGLのディレクトリ(私の場合は「SimpleRpg」)を選択してメニューを開き、「Delete」を選択して削除します。続いて「/usr/local/apache2/htdocs」のディレクトリでメニューを開き、「Import」を選択しましょう。表示されたダイアログでは先ほどビルドしたフォルダを選択します。インポートされたら、念の為コンテナを再起動しましょう。

上書きするとファイルの不整合が起こる可能性があるので、念の為削除してから再度インポートする形にしています。

任意のブラウザを使って、「http://localhost:8080/SimpleRpg/」にアクセスします。「8080」の部分はコンテナ作成時に指定したポート番号、「SimpleRpg」の部分はUnityから書き出したフォルダの名前にしましょう。

「はじめから」を選択して最初からゲームを開始し、任意の場所でセーブを行います。ページをリロードして、再度タイトル画面が表示されたら「つづきから」を選択してデータが存在することを確認します。

セーブデータが残っている
セーブデータが残っている

 

ひとまず気付いた問題については対応できたので、ゲームとしては完成としたいと思います。残りは、テスト時のポイントの総括やリリースビルドに向けた準備、チュートリアル全体のまとめと拡張案、で合計128回ぴったりで終わりそうです。キリよく終われそうで良かったです。実は124回くらいで終われるかも……みたいな予測もしていましたが、WebGL君が空気を読んでくれてちょうど良い回数になるようにバグを検知させてくれました。ありがたいですね(白目)

 

今回のブランチ

 

まとめ

今回はWebGLでセーブが保存されない点など、確認できたバグを修正しました。ひとまず自分で確認できた範囲ではバグを潰せたので、完成扱いで進めたいと思います。確認できた範囲は日々広がっていくのでまだまだバグや不具合が見つかりそうですが区切りをつけておきたいと思います。

次回はテスト時のポイントの総括やリリースビルドに向けた準備について確認していきましょう。

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!