僕の財布にはいつだってお金がない。
食後に毎回プリンを食べているのが原因のようだ。
ただお金は無くともゲームは作りたい。
そんな僕に朗報だ。
【結果発表】山分け方法はみんなで決める!総額 $3,000USD分のアセットストアバウチャー山分けキャンペーン! – Unity公式 Asset Portal
Unityさんがバウチャーコードをくれるらしい。
このイベントに参加したらね。
なるほど。
完成プロジェクトのアセットを購入して、そのプロジェクトを改造するゲームジャムですか。
そして、その完成プロジェクトのアセット代金分のバウチャーコードは頂けると。
よし参加しよう。
バウチャーコードのために!
というわけで今回は完成プロジェクトを改造していた際に「この処理にハマっちまった!」ということがあったお話です。
自分自身で一から作っているときには、なかなかその作りなどの不便な点には気づかないものですからね。
この記事にはUnity2018.1.0f2を使用しています。
.Netのバージョン設定には.Net4.x (4.6相当)を使用しています。
- 変数をシリアライズするということ
- 関数がアニメーションから呼ばれることをスクリプトからは読み取れないということ
- いろいろなところでPlayerPrefsを使うということ
- TimeScaleを0にしてゲームを停止させるということ
- 最後だということ
変数をシリアライズするということ
エディタ上でパラメーターを設定したいときは、コンポーネントの変数をシリアライズすればエディタ上で設定できるようになりますよね。
アクセス修飾子がpublic
の変数は、シリアライズされるのでエディタ上に表示されます。
ただ、エディタ上で設定しなくてもいい変数までエディタ上で表示されてしまうと、情報を設定する人は混乱してしまいます。
余計な情報は表示しないようにすれば、混乱を避けられるはずです。
System.NonSerialized
アトリビュートを使うのはどうでしょう。
これを付けた変数はシリアライズされないので、エディタ上に表示されません。
アクセス修飾子がprivate
の変数は、シリアライズされないのでエディタ上に表示されません。
でも他のクラスには公開したくないけど、エディタ上ではパラメーターを設定したい。ということは多々あるはず。
そういう場合は、SerializeField
アトリビュートを使いましょう。
これを付けた変数はシリアライズされるので、エディタ上に表示されます。
GameManager.cs
using UnityEngine; public class GameManager : MonoBehaviour { // エディタ上に表示される。 public string m_valueA = null; // エディタ上に表示されない。 [System.NonSerialized] public string m_valueB = null; // エディタ上に表示されない。 private string m_valueC = null; // エディタ上に表示される。 [SerializeField] private string m_valueD = null; } // class GameManager
関数がアニメーションから呼ばれることをスクリプトからは読み取れないということ
これこそが、今回この完成プロジェクトを改造していて。一番ハマっちまったところだ。
Unityではアニメーションそのものに、イベント関数を仕込むことが出来る。
アニメーションがアタッチされているコンポーネントの関数を、アニメーションの任意のタイミングで呼び出すことが出来るのだ。
エディタ上で設定できるので、「アニメーションのこのフレームで攻撃処理を呼ぼう!」ということが簡単にできて便利ではあるんだ。
だが、この設定をしていない人にとっては、このやりかたは非常にフローが追いづらいものになってしまう!
この完成プロジェクトには以下のような処理があったんだ。
Character.cs
private void Shoot() { // 弾を撃つ処理 // ...... }
プレイヤーキャラが弾を撃つ処理なんだけど、スクリプトを見る限りどこからも呼ばれていないんだ!
文字列検索しても引っかからないし、そもそもprivate
関数だから、このクラス内からしか呼ばれないはずだし、ボタンのイベントにも設定できないはずだ!
ここがアニメーションのイベント関数の恐ろしいところだ。
private
関数でも容赦なく呼べるのだ!
SeneMessage()
と同じ方式かよ……。
このことに気づかなかったり、そもそもアニメーションのイベント関数のことを知らなかったりすれば、ここで詰みます。
ただ、「アニメーションの任意のタイミングで処理を呼ぶ」ということをやりたいときも出てくると思うので、アニメーションのイベント関数の使用を禁止するのも難しそうだ。
とすると、スクリプト側で関数名をOnAnimationEvent()
などアニメーションから呼ばれることを明示的に示すようにしたり、コメントなどを記述するようにしたりして対応するしかなさそうなんだよなぁ。
いろいろなところでPlayerPrefsを使うということ
うーん、PlayerPrefs
かぁ……。
PlayerPrefs
をセーブデータとして使うことは、推奨していないという話は聞くよね。
本格的にセーブデータを作るのならば、Jsonやバイナリ形式にしてファイル読み書きした方がいいだろうさ。
ただ、このPlayerPrefs
を使うのが楽なのは事実だよね。
だから個人の裁量で使う分に、兎角言うつもりはないのさ。
ただ問題なのは、いろんなところでPlayerPrefs
を呼ぶってことなんだよ。
この完成プロジェクトでは、ゲーム内課金をする場合の対応も考えてくれているので、武器の数やコインの数などをPlayerPrefs
を使ってセーブデータとして保存しているんだよね。
GameManager.cs
private void Purchase() { if( PlayerPrefs.GetInt( "coin" ) > 250 ) { PlayerPrefs.SetInt( "coin", PlayerPrefs.GetInt( "coin" ) - 250 ); PlayerPrefs.SetInt( "hp", PlayerPrefs.GetInt( "hp" ) + 1 ); } }
だが、いろんなところでPlayerPrefs
を呼びまくっているせいで、ゲーム全体として何の情報が保存されているかを把握しづらい。
何がゲームを立ち上げたら初期化される一時的な情報で、何がゲームをやめても保存されている永続的な情報なのかがわからんのだ!
せめてセーブデータ関連の処理は、特定のクラスなどにまとめてほしいな。
TimeScaleを0にしてゲームを停止させるということ
僕はあまり使ったことがなかったんですが、Time.timeScale
を0にすることで、ゲームの更新を止める方法を使っていました。
リザルト画面やポーズ画面などを表示するときに使っているみたいですね。
GameManager.cs
public void EndGame( int i_index ) { switch( i_index ) { case 0: Time.timeScale = 0; // Pauseメニュー表示処理... break; case 1: Time.timeScale = 1; // Pauseメニュー非表示処理... break; case 2: Time.timeScale = 0; // GameOverメニュー表示処理... break; case 3: Time.timeScale = 1; // 別シーンに移動処理... break; default: break; } }
なるほど、Time.timeScale
を0にすれば、FixedUpdate()
は呼ばれなくなるし、Time.deltaTime
も0になるみたいですね。
Update()
が呼ばれなくなるわけではないですが、ゲームの更新を止める方法の一つとして利用できそうだ。
だがこの方法には大きな問題が!
誰かがTime.timeScale
を1に戻さなくてはいけないということだ。
この完成プロジェクトのゲームフローをそのまま使う場合は、要所にTime.timeScale
を1に戻す処理が記述されているだろうから、問題にはならないだろう。
ただ、このゲームフローに新たなフローを追加する場合に問題が発生しそうだ。
Time.timeScale
が0のまま、別の処理やシーンに移動してしまう可能性がありえるのだ。
Time.timeScale
が0になっていることに気づかなければ、ゲームが正常に動作しなくなってしまう!
コンポーネントはついているし、Update()
も呼ばれているし、エラーも出ていない。
なのにゲームが動かない!
この状況は、不具合の原因を突き止めるのになかなか苦労しそうだ!
Time.deltaTime
を0にする際には注意したい。
最後だということ
今回挙げたことは、自分一人でゲームを作っているときには、あまり不便に感じないことばかりなんですよね。
作った本人は全体を把握しているわけですから、問題になりにくいんですよ。
ただ、自分自身が作ったプロジェクトでも、時間が経てばどんな処理を書いたか忘れてしまいますからね。
このあたりのことは気を付けていきたいものです。
今回の完成プロジェクトを改造することは、いい勉強になりました。
さて、勉強も終わったことですし、プリンでも食べようかな。