Unity3dのコード構造-個人的な意見といくつかのトリック

画像

Unity3dをベースにしたモバイルゲームの開発に関する個人的な印象を共有したいと思います。 最初は、最近Unity3dで作業しているときに出会った小さな「Tip&Trick」すべてを1つの投稿に収めようと考えていました。 しかし、それらは多すぎました。 そのため、この投稿では、コードの作成に直接関係するもののみを取り上げます。



投稿の主なトピックは、クラスを「レイヤー」に分離し、イベントを通じてクラスをリンクすることと、ステージ上のオブジェクトの相互作用を確立する方法について少し説明します。

誰も気にしない-キャットへようこそ!





アプリケーション構造



画像

当初、私はどのような構造も考えていませんでした。Unityの知識が乏しく、何から何を分離できるのか、何を組み合わせるのか、どのように接続するのかを理解していませんでした。 ただし、アプリケーションの開発プロセス中に、次の疎に相互接続された層が何らかの形で際立っていました。

  1. インターフェース-これには、あらゆる種類のメニューとコントロールが含まれます。
  2. ゲームオブジェクト-レベルのステージ上にあり、ジオゲームに直接関連するすべてのもの。
  3. ゲームの状態は、ゲームを渡すプロセスを担当するクラスです。 プレーヤーはどのレベルをパスしましたか? 今何時? 何点獲得しましたか? 彼は今どのレベルをダウンロードする必要がありますか? コンテンツのロックを解除する必要がありますか? 等
  4. Googleサービスとの相互作用-結果の投稿と成果のロック解除を担当するクラス。
  5. 広告-広告の表示を担当するクラス。
  6. ソーシャルサービス-Facebook、VKontakteなどへの投稿


このリストは、いくつかの特定のアプリケーションの場合の個人的な例です。 他のアプリケーションでは、非常に異なる場合があります。 しかし、それの一般的な考え方は明らかです。



これらの層が何らかの形で私の頭の中に形成されたとき、疑問が生じました-それらの間の相互作用を確立する方法は? つまり 次のレベルのロードを担当するクラスは、メニューの対応するボタンに結び付けてはならないことを理解しました。 しかし、それを行う方法は?

この問題は、静的クラスとイベントによって解決されました。



一般的な考え方は、各レイヤーのオブジェクトは他のレイヤーのオブジェクトと相互作用しないということです。 それらはレイヤー上で静的イベントのみを引き起こし、それを必要とするすべての人はすでにそれらにサブスクライブしています。

イベントが静的なのはなぜですか?



実際、シーン内の特定のオブジェクトのイベントをサブスクライブするには、そのオブジェクトへのリンクが必要です。 そして、これは不必要な接続の束を追加し、「レイヤー」を分離するという考えは失われます。

場合によっては、ソリューションはさらに単純でした。シングルトンを追加してレイヤーと通信するだけで十分であり、相互作用は大幅に簡素化されました。

次に、これが各レイヤーでどのように機能するかを例を挙げて説明します。



インターフェース



画像

Unityチュートリアルには、次のような例がたくさんあります。

public class ExampleClass : MonoBehaviour { public GameObject projectile; public float fireRate = 0.5F; private float nextFire = 0.0F; void Update() { if (Input.GetButton("Fire1") && Time.time > nextFire) { nextFire = Time.time + fireRate; Instantiate(projectile, transform.position, transform.rotation) as GameObject; } } }
      
      





ちなみに、これは公式ドキュメントからのものです。

ここで何が間違っていますか?



Unityでの開発およびデバッグ中に、ボタンを使用すると非常に便利です。 ただし、モバイルデバイスに切り替えると、インターフェイス要素に置き換えられます。 ボタンを固定した場合、後でゲームオブジェクトのクラスを編集する必要があります。 さらに、ボタンの押下に複数のオブジェクトが応答する必要がある場合はどうすればよいですか?

UserInterfaceなどの個別のクラスを作成し、そのクラスでOnFireButtonClickグローバルイベントを宣言し、必要な場所でサブスクライブする方が便利であることが判明しました。



インターフェースの主なアイデアは次のとおりです。ゲームオブジェクトで入力処理を実行しないでください。 ユーザーからのすべてのコマンドを受け入れる個別のインターフェイスオブジェクトを作成し、ゲームオブジェクトのイベントをサブスクライブします。 特定のインターフェイスオブジェクトは変更される可能性があるため、シングルトンでイベントを生成することをお勧めします。これはゲームオブジェクトに影響を与えません。

Brrr ...どういうわけか紛らわしいことが判明しましたが、私は一般的な考えを持ってきたことを望みます。



ところで、イベントをサブスクライブする場合は、「返信」を追加することを忘れないでください。

 void OnDestroy() { <_> -= <_>; }
      
      







そして、シーンが閉じられ、オブジェクトが生きてイベントを処理するとき、それは面白い状況になります。 また、ガベージコレクターは正しく機能しません。 イベントを生成するオブジェクトがハンドラーと同じシーンにあり、それとともに削除される場合にのみ、サブスクライブを解除できません。



理解できない状況があります-シーンを閉じることは、そのオブジェクトをすべて破壊し、すべてから自動的に「サブスクライブ解除」することを意味すると思いました。 これはそうではないことが判明しました。 アプリケーションがシーンの責任を負うブロックを単に残す可能性が高くなります。 オブジェクトは参照されなくなり、ガベージコレクターによって削除されると想定されています。 これがバグなのか機能なのかわかりません。 誰もがこの状況の本質を理解しているなら-共有、プリズ、知識。 とても好奇心が強い。



ゲームオブジェクト



画像

これが、ステージ上で実行、ジャンプ、スピン、シュート、および相互作用するすべてです。

Unityは、コミュニケーションのためにいくつかのアプローチを提供しています。



1.パブリックプロパティを介してリンクします。 次のようになります。

 public class PlatformMoveController : MonoBehaviour { public GameObject Player; ... void OnCollisionStay(Collision col) { if (col.gameObject.name == Player.name) onTheGround = true; } ... }
      
      





欠点は、オブジェクトを接続する必要があることです。 そして、それらを動的に生成する場合は?



ここで、2つ目のアプローチが役立ちます。



2.名前またはタグでシーン上のオブジェクトを検索します。 例:

 private GameObject _car; ... void Start () { _car = GameObject.Find ("Car"); if (_car == null) throw new UnityException ("Cannot find 'Car' object!"); _car.OnCrash += DoSomething(); } …
      
      





このメソッドのマイナスは、例自体に表示されます-シーン内の目的のオブジェクトが表示されない場合があります。 まあ、彼がまったくそこにいなくて、私たちが何もできないなら、彼がそこに追加されたが、別の名前でどうしたらいいでしょうか?



ここでも、静的イベントを回避できます。 各Carインスタンスが静的クラスイベント(CarManager)を介して衝突をスローする場合、オブジェクトを探したりバインドしたりする必要はありません。



次のようになります:

 class Car { void Crash() { ... CarManager.CrashCar(); } } public static class CarManager { public static void CrashCar() { if (OnCarCrash != null) OnCarCrash(); } }
      
      







ハンドラー:

 void Start () { CarManager.OnCarCrash += DoSomething(); } void OnDestroy() { CarManager.OnCarCrash -= DoSomething(); }
      
      







このアプローチには欠点と落とし穴があるので、慎重に使用する必要があり、イベントのサブスクライブを忘れないでください。 しかし、時にはそれは大いに役立ちます。



ゲームの状態



ゲームの状態は、レベルの通過の修正、次のレベルのロード、得点などを担当するクラスの一般名です。

ここで考慮すべき主なことは、Application.LoadLevel(<シーン名>)を実行する必要がないことです。 遅かれ早かれ、ゲームオブジェクトから、追加のチェック、計算、中間画面などを入力する必要があります。これらすべてを別のクラスに入れることをお勧めします。 そこで、ゲームの一時停止や復元コマンドなどを行うことができます。



例えば
 public static class GameController { public static event EmptyVoidEventType OnPause; public static event EmptyVoidEventType OnResume; public static event EmptyVoidEventType OnReplay; public static void PauseGame() { if (OnPause != null) OnPause (); } public static void ResumeGame() { Time.timeScale = 1; if (OnResume != null) OnResume (); } public static void ReplayGame() { LevelManager.LoadLastLevel(); if (OnReplay != null) OnReplay (); } public static void FinishLevel () { ... } public static void LoadLastLevel () { ... } public static void GoToMenu() { ... } ... }
      
      







つまり プレイヤーが突然リプレイしたい場合、シーンはレベルが完了したときに何をすべきか、前のシーンの名前は何であるかを心配する必要がなくなりました。 マネージャークラスを呼び出すだけです。

同様に、たとえば、シーン内のゲームの中断に対応する必要がある場合は、Time.timeScale = 0;を設定します。 対応するイベントを購読するだけです。

これらのささいなことはすべて、開発を大幅に簡素化します。



Googleサービスとの相互作用



画像

Googleは、モバイルグッズから呼び出すことができるすばらしいサービスに寛大になりました。 これで、独自のリーダーボード、実績などを書く必要がなくなりました。 さらに、これらのサービスは開発中であり、ある種のスーパードゥーパーワンダーワッフルになると約束されています。

これらすべてを考慮すると、アプリケーション全体で薄い層で呼び出しを塗りつぶすのではなく、1つまたは2つのクラスに集中し、イベントを介してアプリケーションの残りの部分と接続することは理にかなっています。 これにより、ゲームプレイに大きな影響を与えることなく、ゲームの「ソーシャル」コンポーネントを開発できます。



Googleとの接続があるかどうかをすばやく簡単に確認する方法
 public static bool HasConnection() { try { using (var client = new WebClient()) using (var stream = new WebClient().OpenRead("http://www.google.com")) { return true; } } catch { return false; } }
      
      





これが最良の選択肢であるかどうかはわかりませんが、最もシンプルで明確に見えます。





広告



画像

各広告プロバイダーは、独自の表示ツールを提供します。 このすべての動物園でゲームクラスを理解することは望みません。 私自身のために、1つのパブリックメソッドShowBanner()を使用して、静的で単純なクラスAdsControllerを作成し、広告を表示する場所で呼び出します。 AdMobでのすべての作業は内部に隠されており、別のものに切り替えることにした場合、コードを登って電話をかける必要はありません。 些細なことですが、素晴らしい。

イベントを介して参加することもできますが、私の場合、広告ユニットを呼び出すためのインターフェースを変更する可能性は低いため、シングルトンがより多く登場しましたが、ゲームイベントは変更される可能性があります。



プレイヤーの神経細胞を救うためのちょっとしたトリック
 public static class AdsController { static DateTime lastAdCloseTime; static TimeSpan AdsInterval = new TimeSpan(0,3,0); ... //   -      static void AdClosed(object sender, System.EventArgs e) { lastAdCloseTime = DateTime.Now; } public static void ShowBanner() { if (DateTime.Now - lastAdCloseTime > AdsInterval) <_> } ... }
      
      







つまり 成功したインプレッションの頻度を制限します。 バナーを表示するための実際。 たとえば、レベルまたは死亡の終わりにそれらを表示します。 しかし、最初のレベルは数十秒で完了します。 プレイヤーを苛立たせないために、彼はそのような制限を追加しました。

主なことは、成功した印象を正確に考慮することです。 そして、接続に不具合があると、プレーヤーは広告を見ることはなく、開発者に注意を払わなかったことに失望します。





社会サービス



この「レイヤー」の役割は広告のものと非常に似ていますが、その本質はさらにシンプルです:ソーシャルネットワークのすべてのコマンドをヒープに配置し、PostToFacebook、PostToVkなどのメソッドを持つ1つの静的クラスを配置します。クラス内。 パラメーターに最小値を渡します。 私はそれを1つのパラメーターに減らしました-得点の数。 原則として、このクラスの使用は、ユーザーが[共有]ボタンを押したときに目的のメソッドを呼び出すだけになります。

このフォームでは、実質的に変更を加えずにプロジェクト間でドラッグできます。



おわりに



この考えの流れが誰かに役立つことを願っています。 私は特別なことを何も言わなかったこと、そしてUnityを使用している人々がこれをすべて、そして同じことをするためのさらに100のより適切な方法を長く知性的に知っていることを知っています。 私は初心者を助け、専門家がコメントで彼らの経験を共有することを願っています。



興味があれば、3Dオブジェクトをインポートしてプロジェクト構造を構築するときに入力したバンプについて説明できます。



好奇心の強い人のための投稿の背景
約1年前、モバイルゲームの開発が私の趣味になった経緯について、Habrに書きました。 この投稿の後、私はまだ多くのこと、そして何が「燃えた」かを学びました。 「予想外の」経験をするたびに、座ってそれについて書きたいという願望がありましたが、怠wasだったか、アイデアをよりよく確認して共有したかったのです。

自分用の「didline」をインストールしました-おもちゃを2番目のバージョンに更新し、座って投稿します。 しかし、別のプロジェクトが並行して進行し、仕事に負担がかかったため、このプロセスは6か月間続きました。 したがって、多くの考えがあり、投稿はかなり厄介でした。



All Articles