正しくプログラム可能。 ポリモーフィズムを使用します。 ゲームキャラクターの一般的なロジック

Unity3D。 OOPの利点を使用します。 多形



注意!
すぐにあなたに警告したい、記事のコメントを読んで、そこで雲海楼は記事で提起された問題を解決するためのより完璧なアプローチを提案しました。




良い一日。 このチュートリアルでは、Unity 3Dエンジンの重要なプログラミングトリックをいくつか見ていきます。 この記事は、初心者と経験豊富なゲームメーカーの両方に役立つと思います。



この記事の目的は、 OOPのすべての魅力の力と無限の使いやすさ認識することです。 特に、多形。 さらに、他のいくつかの重要な問題にも触れます。



また、独自のモーターを作成する概念、ゲームキャラクターの一般的なロジック、およびそれらが互いにおよび外界と相互作用する方法についても少しお話します。 これを簡単かつ論理的に、簡単に編集可能(近代化可能)にする方法について説明します。 ゲームに新しい要素を追加するときに、すでに記述されている内容を書き換える(補足する)必要がないことを確認する方法。



将来的には記事の続きがあると思います。 そこで、独自のモーターの記述を詳細に調べ、ベクターとベクターで必要な操作について話し、本格的な在庫モデルを作成します。 古いアイテムを変更せずに新しいアイテムを簡単に追加できる非常に優れたインベントリ。



この記事は、Unity3Dを理解している人を対象としています。 このビューがない場合は、次の記事を読むことを強くお勧めします。



1)非常に良い記事: habrahabr.ru/post/112287、habrahabr.ru/post/113859

2番目の記事、JSのスクリプトの例。 JSとC#の両方に慣れていない場合でも、C#に翻訳しても問題はないと思います。 私が言う唯一のことは、JSの行:

bull_clone = Instantiate(bullet, transform.position, transform.rotation);
      
      





C#では、次のようになります。

 bull_clone = (GameObject)Instantiate(bullet, transform.position, transform.rotation);
      
      





そして、JSのそのような行:

 var ml = GameObject.Find("Player").GetComponent(MouseLook);
      
      





C#では、次のようになります。

 GameObject ml = GameObject.Find("Player").GetComponent<MouseLook>();
      
      







2)記事: habrahabr.ru/post/141362この記事の続きもサイトにあります。 それらは「Unity3d」と呼ばれます。 Unity 3Dの生徒からの教訓(B00-B16)»たった4つの記事。 現時点では、少なくとも。 右上のサイトに検索エンジンがあります。 対処できると思います。



3) habrahabr.ru/post/148410読み、理解することも必要です。 この記事には別の部分があります。 サイトの検索エンジンが再び役立ちます。



4)これは、ディレクトリhabrahabr.ru/post/128711、habrahabr.ru/post/128948のようなものです



5)このフォーラムには多くの興味深い記事があります。 必要に応じて自分を探してください。

だから。 あなたは読んだもの、実験したもの、すべてすでに小さなゲームを作ったものをすべて学びました。 たぶん小さなものではありません。 これらの記事を読んだだけで自分で何も書いていないのは悪いことです。 練習と再練習



しかし、最初に、二次的な質問をいくつか考えてください。 (念のため)



記事の前半はデモンストレーション用です。 コードを書き換える必要はありません。

次の単語を同義語として使用します: スクリプトクラス



クラス、クラス



オブジェクト指向プログラミング(OOP)に取り組んでいます。 クラスとクラスインスタンスが存在します。 クラスは、オブジェクトのフォームまたは描画のようなものです。 金型上の成形図は、クラスのインスタンスです。 すべてのバスはBusクラスのインスタンスです。 あなたの隣人であるバスヴァシャは、クラスバスのインスタンスです。 バスヤのバスには左側にステッカーがあり、体全体に傷があり、右前輪が下がっており、ガスタンクの床があり、ガラスが破損しており、ドアの1つが機能していません。 このバスには多くの個別のプロパティがあります。 他の隣人にもバスがあります。 また、傷、ステッカーがありますが、壊れた窓はなく、車輪は平らではなく、この特定のバスのより多くの個々の機能です。 両方のバスはBusクラスのインスタンスです。 クラスバスは図面です。 図面はまだ作成されていないバスであり、乗ることはできません。 傷やパンクしたタイヤはありません。 図面なしでは、単一のバスを作成できません。 クラスの説明がないと、クラスの単一のインスタンスを作成できません。 このクラスは、インスタンスを作成するだけでなく、たとえば、それによってバスの長さを調べることができます。 しかし、ここでは図面に乗ることはできません。 Unityでは、クラス名は大文字になり(GameObject、Transform、Animationなど)、クラスインスタンス名は大文字になります。



クラス名と区別するために、すべての変数名を小文字で開始することは理にかなっています。 これがUnityが私たちに命じるものです。



キャッシング



ウィキペディアから:作業結果のキャッシュ -
多くのプログラムは、必要なたびに計算しないように、中間または補助的な作業結果を書き込みます。 これにより作業が高速化されますが、追加のメモリ(RAMまたはディスク)が必要です。




このスクリプトを検討してください。 彼の何が悪いの? すべてがうまくいくようです。 オブジェクトは毎秒10ユニットの一定速度で左に移動します。 Time.deltaTimeのおかげで、特定のコンピューターの現在のパフォーマンスに依存しない均一な動きが発生します。



このスクリプトの悪い点は、すべてのフレームがtransform.positionと呼ばれることです。むしろ、悪い点はtransformが呼び出されることです。 実際、ここのUpdate()で記述された変換は、gameObject.transformを意味します。 そして、これは今度はgameObject.GetComponent()と同じです。 ただし、これはすでに、スクリプトがgameObjectにかかっているすべてのフレームの中で探しているすべてのフレームが変換されることを意味します。 しかし、なぜそれを一度見つけて覚える(キャッシュ)ことができるのに、毎回見てください(小さな文字で書かれたgameObjectに注意を払ってください。つまり、GameObjectクラスのインスタンスです)。



何も変わっていないようです。 しかし実際には、マシンのリソースをより合理的に使用します。

私を信じないでください。 Unityのドキュメントを開いて、キャッシュが必要だと表示されます!!!



キャッシュする必要があるもののいくつかの例を見てみましょう。



コード内のコメントを注意深く読んでください



もちろん、キャッシュしようとしているものが何度も使用される場合にのみ、キャッシュすることは理にかなっています。 (多くの場合、これは複数回です)。 さらに、以前に変数「A」にキャッシュされたコンポーネントをgameObjectから削除し、再度追加しても、「A」には何も含まれないことを理解することは価値があります。 したがって、常に削除および再追加されるものをキャッシュすることは意味がありません。 次の追加後すぐにキャッシュできますが。 クラスのインスタンスのみをキャッシュできます(まだ嘘をつかないかもしれません)が、データ型と構造をキャッシュする必要はありません。

たとえば、キャッシュすることができます







これはクラスであることを右に示し、左のアイコンも特別です。 (赤で囲む)

ただし、キャッシュすることはできません。



位置はクラスのインスタンスではないため。 これはVector3構造です



また、Visual StudioはVector3が構造であることを示しています。 彼女のアイコンも異なっています。

だから。 キャッシュクラスのみ。 float、byte、vector3などのすべての種類の変数 キャッシュしないでください! さて、あなたはそれを理解しています:

 Public Vector3 aaa; aaa = transform.position; //    transform.position.      aaa    transform.position.    ( )     , .         .
      
      





オブジェクトにハングしたスクリプトだけでなく、オブジェクト自体もキャッシュする必要があります。



他の例では、すべてがキャッシュされるため、例を理解できます。



MOBAの一般的な概念(文字)



Unityに付属している標準的なプロジェクトを分析し、少し頭を動かすと、キャラクターの正しい概念は次のとおりであることが明らかになります。動作スクリプトを有効または無効にするメインスクリプトがあります。 たとえば、動作スクリプトは次のとおりです。



もちろん、スキームを複雑にすることができます。 たとえば、「ターゲットが表示される、殺すことができない、逃げる」モードで動作を追加する(ターゲットから逃げる)

独自のスクリプトでの各動作の実装。 メインスクリプトには、特定の動作がどのように実装されているかわかりません。 そのタスクは、適切なタイミングでオンにすることです。 たとえば、プレイヤーが特定の距離でMobに近づいたとき、または最初にMobを攻撃したときに、「ターゲットが表示され、殺すことができ、近づくことができます」モードのBehaviorスクリプトをオンにできます。 2つのほぼ同一のMobが必要な場合、スクリプト全体をやり直すのではなく、Behaviorスクリプトの1つを変更します。 これは、開発段階だけでなく、実行時にも実行できます。 (これにはポリモーフィズムが必要です)



これらのスクリプトはすべて、キャラクター(mob)の動きを制御できます。 彼らは自分でそれを管理するのではなく、MOTORと呼ばれる中間スクリプトを通して管理します。 義理のモーターには、キャラクターを制御するために必要ないくつかの方法があります。 例:



なぜこれが必要なのですか? 2つの主な理由があります。 まず、論理的であり、モーターで何かを変更したい場合は、キャラクターを移動するさまざまなスクリプトのコードを変更する必要はありません。 それらはすべて1つのスクリプトを介して行われます。 モーターとすべてのコードを変更します。 さらに、アニメーションはモーターからの動きによってもトリガーされます。 プロジェクトがチームによって開発されている場合、これは特に重要です。 モーターを作成する人は、モーターメソッドがどこから呼び出されるかわかりません。 また、Behaviorスクリプトを作成する人は、MobMotor.MoveLeft()メソッドがどのように実装されているかわかりません。主なことは、キャラクターを左に移動することです。 彼が必要なのはそれだけです。 したがって、調整された作業には、2つの単純な事実のみを知る必要があります。 暴徒は左右に歩くことができます。 それだけです! これ以上。 第二に... ...最も重要なことは第二です。 しかし、ポリモーフィズムが何であるかはまだわかりません。 後でこの利点に戻ります。



続けます。 状況を監視し、必要なときに必要なスクリプトを含むメインスクリプトがあります。 そして、それらはモーターを通してのみキャラクターを動かします。 キャラクターの健康を担当するスクリプトがあります。 例:







ところで、私はあなたにパブリックを思い出させます-宣言された変数(メソッドなど)は一般的にすべてのスクリプトで利用できることを意味します。 スクリプト自体の内部でのみプライベート利用可能



MobHpスクリプトを詳細に検討してください。 キャラクターが表示され、すぐにヘルスが100ユニットになります。 キャラクターがダメージを受けると(ダメージを受けるのはMobHp自体ではなく、メインスクリプトによって監視されます)、メインスクリプトはMobHp.SetDamage(float damage)メソッドを呼び出します。 このメソッドは、trueまたはfalseを返します。 trueが返された場合、Mobは損傷を乗り切りました。 そして、メインスクリプトは引き続き機能します。 falseが返された場合、暴徒は死んでいます。 死後に暴徒に何が起こるか、メインスクリプトは気にしません。 彼はこれを知りません。 私たちの場合、死亡すると、サウンドファイルが再生され(キャラクターの音が消える)、5秒後にキャラクターが消えます(写真を参照)。 メインスクリプトの役割は単純です。 falseの場合、すべての動作スクリプトが無効になります。 キャラクターのモーターを無効にします。 彼はすべてをオフにします。 つまり、Mobがフリーズし、死にかけているMobのサウンドが再生され、その後消えます。 もちろん、フラグメントの生成をMobHpスクリプトに追加して、地面に落ちるキャラクターのアニメーションを再生することができます。 あなたが欲しいもの。 美しさは、MobHpスクリプトが完全に独立していることです。 メインスクリプトとMobHpが異なる人によってプログラムされている場合、協調作業のために知っておく必要があるのは、キャラクターがMobHp.SetDamage(float dfamage)メソッドを介してダメージを受けることだけです。 メソッドがfalseを返す場合、mobは無効です。 それだけです! 不要な情報はありません。 MobHpの作成者がスクリプトの内容を変更したい場合、これはメインスクリプトに影響しません!



His下の多態性



だからそこに着いた。 読書をやめなかった人たちを祝福します。 ポリモーフィズムに関するstory話の話は、いくつかの簡単な人生の例から始まり、それから私たちはそれらを暴徒(キャラクター)の一般的な概念に拡張します。



スイッチを想像してください。 通常のスイッチ。 部屋の照明をオン(オフ)にします。 それとも彼はファンをオンにしますか? または、多分彼は旋盤を始めます。 しかし、従来のスイッチは核爆弾を引き起こすことができます。 おそらく、スイッチが何をオン(オフ)にするかを気にしないのは明らかです。 連絡先を開くだけです。 彼はどこかでエネルギーを放出します。 彼はどこでも構いません



今、タンクシェルを想像してください。 飛んで、落ちて、爆発して、周りのすべてにダメージを与えます。 彼は周りに何も気にしません。 彼は気にしない。 彼は自分の周りに何があるのか​​わかりませんが、それでも、彼はダメージを与えます。



スクリプトに戻りましょう。 MobHp.SetDamage(float damage)メソッドがあります。 キャラクターの隣に落ちた架空のタンクシェルは、キャラクターにダメージを与えるためにこのメソッドを呼び出す必要があります。 ただし、私たちのコンセプトによれば、MobHp.SetDamage(float damage)メソッドはMainスクリプトから呼び出す必要があります。 したがって、戦車からの発射物はメインスクリプトと正確に相互作用する必要があります。 Mainスクリプトでグローバルメソッドを作成するとします。 また、落下中の発射物は、キャラクター(mob)が爆発の半径内にあるかどうかを確認し、必要に応じてこのメソッドを呼び出します。 マップ上に絶対に同一のMob(キャラクター)をたくさん置いてみましょう。 落下するとき、発射物はそれらのすべてを通過し、ダメージの範囲内にあるものを選択し、それらにダメージを与える必要があります(各Mobのメインスクリプトで対応するメソッドを呼び出します)。 Unityでは、これは次のように実装できます:すべてのMobにはMobタグが割り当てられます



このような特定のタグを持つすべてのオブジェクトの配列を取得できます

 GameObject[] allMobs= GameObject.FindGameObjectsWithTag ("Mob" ); //    //    
      
      





明確にするために、メインスクリプトをMyBehaviourと呼びます。



そして今、すべてのMobをソートし、それらに損害を与えます(注意してください。メインスクリプト(MyBehaviour)には、メソッドMyBehaviour.SetDamage(float damage)があり、これはMobHp.SetDamage(float damage)メソッドをMobHpスクリプトで呼び出します。スクリプトはキャラクター(mob)でハングします)

シェル上のスクリプト内のコード。

 foreach ( GameObject mob in allMob ) //      { mob.GetComponent<MyBehaviour>().SetDamage(50); //     SetDamage }
      
      





この例では、発射物は50のダメージを与えます。

すべてが素晴らしい! これは動作します。 しかし、2つのタイプの多くのMobが必要な場合、 2番目のタイプでは、Mainスクリプトを異なる方法で実装できます。 たとえば、ロボットと銃。 それらは非常に異なる動作をします。 彼らは、「見る、殺す、近づける」モードで動作を実装しました(新しいターゲットに実行します)。 銃にはまったくありません。 したがって、タワーでは、メインスクリプトはMyBehaviour2と呼ばれ(異なるスクリプトは異なる方法で呼び出す必要があります)、すべてのBehaviorスクリプトは異なる方法で呼び出されます。 そして、MobHpでさえ別の銃を持っています。 たとえば、MobHp2は、独自の方法で「銃死」を実装します(問題は死の音とアニメーションの置き換えに限定されない場合があります)。 すべてのMobとTowerを損傷するために、タンクシェルはどのように動作する必要がありますか



タワーに新しいタグを導入する必要があります。 そして、シェルスクリプト自体は次の形式を取ります。

 //   GameObject[] allMobs= GameObject.FindGameObjectsWithTag ("Mob" ); //    //     foreach ( GameObject mob in allMob )//     { mob.GetComponent<MyBehaviour>().SetDamage(50); //     } //   GameObject[] allTower= GameObject.FindGameObjectsWithTag ("Tower" ); foreach ( GameObject tower in allTower )//     { tower.GetComponent<MyBehaviour2>().SetDamage(50); //     }
      
      





このアプローチの欠点は何ですか。 明らかに、シェルスクリプトはMobの種類の数に依存します。 したがって、1種類の暴徒を追加する場合は、シェルスクリプトにアクセスしてアップグレードする必要があります。 ゲームに500種類のMobがあると想像してください。 シェルスクリプトには、約1,500行のコードが含まれます。 ゲームには確かに数十種類のシェルがあることを考慮する価値があります。 自分で数える。 シェルスクリプトが1人で行われ、MobとGunsおよび他のキャラクター(MyBehaviour、MyBehaviour2、MyBehaviour3など)のメインスクリプトが別の人である場合、調整作業のために、Mobの数と正確な名前(およびコンテンツ)を知る必要があります主なスクリプト(名前がMyBehaviNNではなく、RobotBehaviour、TowerBehaviour、MonsterBehaviour、TigerBehaviourなどの形式になると想像してください)



スイッチに戻りましょう。スイッチは何がオンになるかを気にしません。 ゲームに5種類のドアがある場合、各ドアには独自のスイッチ(ボタン)が必要です。 1種類のドアを追加する場合は、ボタンを追加するか、既存のドアをアップグレードする必要があります。 そして、異なる人がボタンとドアをプログラムする場合、すべてのドアとボタンのすべてのスクリプトのすべての名前(およびおそらく内容)を知っている必要があります。



状況は、キャラクターの武器、鎧(ゲームへの影響が強さ、速度などの変化に限定されない場合)、複雑に相互作用するオブジェクトによるものとまったく同じです。



もちろん、この問題を解決する方法があります。 しかし、私の意見では、最もエレガントで理解しやすいのは多形です。 厳密な定義は書きません。自分で見つけることができます。



継承、カプセル化、多型が3つのOOP象です。

以下は定義なしです! むしろ、おおよその説明

カプセル化とは、クラスが外部コードから内臓を隠す機能です。 コードクラスに関連して、外部からPrivate、Protectedキーワードで宣言された変数(メソッド、プロシージャなど)を参照することはできません。これは適切です。

継承 -クラスがその親からメソッド、変数などを継承(コピー)する能力。



なるほど! MobHpはMonoBehaviourの継承者です。 MonoBehaviourはMobHpの親です。 親はコロンを介して書き込まれます。

そして今、トリック。 新しい空のスクリプトを作成し、NewMobHpと呼び、MonoBehaviourの代わりに(スクリプト自体で)MobHpを記述します(つまり、この新しいNewMobHpの親はMonoBehaviourではなくMobHpになることを示しました)(下の図を参照)。

NewMobHpスクリプトで、newMobHp型の変数をnewMobHpという名前で(小さな文字で)宣言します。 NewMobHpスクリプト(「スクリプト」と「クラス」は同義語です)で、クラス自体と同じ型の変数を宣言したことを心配しないでください。 はい。クラスには、クラス自体と同じ型の変数を含めることができます。 Start()に「newMobHp」と入力し、ドットを押します。 通常のコードエディタを使用している場合は、コードのこの場所で使用可能なすべてのメソッド、プロパティなどが表示されます。 SetDa ... Magicを書き始めましょう



そして魔法! NewMobHpクラスには、MobHpクラスと同様に、NewMobHp .SetDamage(フロートダメージ)メソッドがあります。 スクリプトは作成されたばかりで、SetDamage(float damage)メソッドの宣言はありませんが、このメソッドは親クラスで宣言されています。 これが遺産です。 NewMobHpクラスは、MobHpのキーワードPublicで宣言されたため、SetDamage(float damage)メソッドを継承しました。



ところで、ProtectedとPrivateの違いは何ですか? プライベートはすべて継承されません。 Protectedが継承するすべて。 したがって、グローバルに宣言された(パブリック)メソッドまたは変数などを継承する場合は、何もする必要はありません。 すでに継承されています。 このクラスのローカル変数を継承する場合、Privateではなく、親クラスにProtectedを記述する必要があります。

そのため、この瞬間からコマンドCODEを入力します! 後で私が書くすべてのものはあなたが書かなければなりません。 実験し、理解し、ペンを詰め込みます。



NewMobHpおよびその他のスクリプトは私にとって便利です;それらを削除します(完了している場合)。 ちなみに、NewMobHpクラスはMobHpの相続人であるだけでなく、MonoBehaviourの相続人でもあります。 MobHpはMonoBehaviourクラスの継承者であるため。



このように:MonoBehaviour-> MobHp-> NewMobHp。



MobHpは、NewMobHpの直接の親です。

Unityを開き、コーディングを開始します! おそらく何かが明確ではないかもしれませんが、小さなゲームの作成に着手すると、すべてが明確になります。



パパMyScriptを作成します。 その中で、MyBehaviourスクリプトを作成します。 これは、すべてのMobのすべてのメインスクリプト(一般に、ダメージを受ける方法を知っているすべて)の親になります。 SetDamageメソッドを作成できます。 そして将来、新しいメインスクリプトがそれを継承します。 そして、すべてのMobにこのメソッドがあります。 しかし、Mobのタイプごとに、ダメージを受けるプロセスはさまざまな方法で実装できます。 メソッドをスクリプトごとに異なる方法で実装するにはどうすればよいですか?



多型-定義を与える方法がわかりません。 たぶんあなた自身が後でそれを与えるでしょう。 例としてすべてを詳しく見ていきます。 これをMyBehaviourで書きましょう。



空のSetDamageメソッド。 なぜ必要なのですか? はい、彼は必要ありません。 Virtualキーワードに注意してください(図を参照)。つまり、このメソッドは、MyBehaviourスクリプトの相続人となる他のスクリプトでオーバーライドできます。つまり、それらは継承しますが、独自の方法で実装します。次に、MobBehaviourスクリプトを作成します。



そして、ここで何が起こるかを分析しましょう

1- MobBehaviourはMyBehaviourクラスの継承であることを明示的に示しました

2-「上書き」という言葉を書き、スペースを入れて、Visual Studio自体が再定義できることを提案します。ご覧のとおり、SetDamage(フロートダメージ)があります。それが必要です。他のすべてのメソッドは、MyBehaviourよりもはるかに親スクリプトで宣言されたメソッドです。たとえば、ToString()。 ToString()をオーバーライドし、このメソッドがMobBehaviourクラスに対してどのように機能するかをプログラムできます。しかし、私たちはそれを必要としません。 SetDamage(float damage)を選択し、このメソッドがMyBehaviourで最初に定義された(つまり、MyBehaviouクラスから継承されている)ことを確認します(図の図3)。 ToString()などが定義されている場所を確認できます。 SetDamage(float damage)の行を選択し、Enterを押してください。これがVisual Studio自身が描いたものです。



Base.SetDamage(損傷); -ここで、BaseはSetDamage(float damage)メソッドが定義された親(クラス)のようなものです。別の言い方をすれば、このメソッドが継承されたクラスへの参照です。 MyBehaviourで定義されています。忘れていない場合は空です。つまり、そこに何でも追加できます。そして、Base.SetDamage(損傷);このコードを呼び出し、その後、特定の相続人に何かを追加できます。これは、SetDamage(フロートダメージ)メソッドのMyBehaviour継承者のマイルストーンに共通点がある場合に必要です。しかし、私たちはそれを必要としません。今のところ、MobBehaviourから不要なものをすべて削除します。そのままにしておきます



MobBehaviourから休憩します。新しいスクリプトMyHpを作成しましょう。



これは、キャラクターの健康に関与するすべてのスクリプトが継承されるスクリプトです

別のスクリプトを作成しましょう。それをMobHpと呼びましょう。私たちは、上記のことを考えられて



最後の時間など、すべてが、再オーバーライドメソッドの宣言SetDamage(フロート損傷)のワードがあります。また、クラスがMyHpクラスの継承であることを示しています。死にかけている暴徒をもう少し変えました。音はありません、暴徒は単に死によって3回増加します。



繰り返しますが、MobBehaviourについて考えてください。このようにしましょう:



SetDamfge(float damage)メソッドを再宣言しました。ヘルススクリプトはMobHpと呼ばれますが、thisHpはMyHpとして宣言されていることに注意してください。ここは多形です。 MobHpおよびMyHpで作業できます。 MyHpの継承者であるため、MobHpクラスのインスタンスにMyHp型の変数を割り当てることができます。したがって、MobBehaviourスクリプトは、myHpクラスのどの継承者がthisHp変数にあるかを気にしません。主なものは、thisHpにSetDamfge(float damage)メソッドがあることです。そして、それを使用することができます! MobHpスクリプトを別のスクリプト(MyHpの後継者)に変更できます。そして、すべてが機能します。



さて、mobをボールの形にしましょう(最初のモブのサイズを2倍にします)。地球(私は平らな長方形を作ります)とある種の光を作りましょう。それはこのように私に起こった:



このボールは私たちの最初の暴徒になります。 MobBehaviourおよびMobHpスクリプトをそれに掛けます。



MobBehaviourスクリプトのThis Hpに注意してください(図を参照)。ここでは何も割り当てられていません(なし)。ただし、開始時に(MobBehaviourのStart()プロシージャで)MyHp(またはその後継)は

thisHp = GetComponent();になります。

だから、私たちはあなたが殺すことができる暴徒を持っています。確かに、それは「スフィア」と呼ばれますが、問題ではありません。モブでボールを発射するプレーヤーを作成する必要があります。やってみましょう。

既存のカメラを削除します。ファーストパーソンコントローラーを作成しましょう(フォルダー内にあります。記事の冒頭で参照されているレッスンを思い出してください)。ボール(小さい)を作成し、Rigibodyをアタッチします。 Bulletと呼び、MyPrefabという新しいフォルダーにドラッグしてみましょう。



これが私たちの将来のパトロンです。ところで、彼が高速で壁を飛び越えないようにするために



、物理エンジンがカートリッジの動きを補間します。これはおそらくかなり複雑なアルゴリズムなので、Interpolateをインストールするだけの価値はありません。高速で移動するオブジェクトのみ(非常に高速。1フレームで0.5メートル以上から飛行できるオブジェクト)。ウィキペディアで、補間と外挿について説明します。

新しいBulletScriptスクリプトを作成します。これはカートリッジスクリプトです。



何かにヒットすると、このスクリプトは、この何かからMyBehaviourコンポーネント(またはその後続バージョン)を取得します。そして、(これにMyBehaviourがある場合)ダメージ= 30でSetDamfge(フロートダメージ)メソッドを呼び出します。したがって、1つのカートリッジが30のダメージを与えます。 POLYMORPHISMのおかげで、カートリッジは何を正確に気にせず、どのMyBehaviour相続人が攻撃したいかを気にしません。このスクリプトを記述した後、相続人のMyBehaviour(TowerBehaviourなど)を使用して新しいタイプのMobをゲームに追加しても、この新しいMobに入るとカートリッジは損傷します。戦車砲の例を思い出してください。このスクリプトをカートリッジプレハブにドラッグすることを忘れないでください。



プレイヤーにこれらのカートリッジを撃つことを強制するだけです。新しいPlayerShootスクリプトを作成する



同様のスクリプトが作成され、記事の1つで詳細に説明されています。リンクは記事の冒頭にあります。彼だけがJSにいた。主な点だけを見てみましょう:

1-撮影するカートリッジのプレハブ、

2-初期カートリッジ速度、

3-ショット間の一時停止、

4-カメラ変換、マウスを動かすと回転するため、

5-最後のショット、

6-カメラ変換をキャッシュします。私たちがそれをどのように行ったかに注意してください。このスクリプトは、FirstPersonControllerでハングします。したがって、変換によって(スクリプトでこの単語を記述する場合)は、カメラではなくFirstPersonController変換を意味します。



スケールに注意してください。ファーストパーソンコントローラーのスケールは1,1,1です



そこに100,100,100がある場合、mobまたはその将来の動きに気付かないかもしれません。



サブ変換(子変換と呼ばれることもありますが、クラスのような相続人という意味ではありません)内で、transform.FindChild(「メインカメラ」)メソッドを使用してカメラ変換を検索します。このメソッドは、FirstPersonControllerサブオブジェクトの中から「Main Camera」という名前のオブジェクトの義理の父を見つけ、それからTransformを取得します。そして、このTransformをcameraTransform変数にキャッシュします。

7-最後のショットからshootPause以上が経過した場合は、撮影できます。

8-カートリッジのコピーを作成します。

9-rigiBodyカートリッジを入手します。

10-カートリッジに速度を追加します。

11-ショットの時間を覚えておいてください。

このスクリプトをプレーヤーにスローします。必要に応じて、カートリッジのプレハブをドラッグします



ゲームを開始します。暴徒を見つける。左マウスボタンを使用して彼を撃ちます。ショットを数えます。 4ヒットで膨らむはずです。コンソールに「Mob is dead !!!」と表示されました。暴徒は膨張し、5秒後に消えます。



MobHpスクリプトでthisTransform.localScale = new Vector3(3、3、3)を記述したため、mobは肥大化しています。モブが十分に膨らんでいない場合は、3つのポイントを大きな値に変更します。さて、ここでは、おそらくあなたができることをしました。多型の美しさは何ですか?しかし、ここにあります。もう1つの暴徒を作成しましょう。また、ボール。新しいMonsterHpスクリプトをそれに掛けます:



ご覧のとおり、MobHpスクリプトとほとんど違いはありません。すべての違いに赤の下線が引かれています。マテリアルをキャッシュし、健康状態に応じて色を変更しました。希望の色を作成する方法がわからない場合は、RGBカラーモデルについてインターネットで確認してください。



主人公の「Player」タグを



作成して、MonsterBehaviourスクリプトを作成します。







これは、MobBehaviourスクリプトに類似しています。ただし、このスクリプトを使用したMobは、プレーヤーが十分近い場合、プレーヤーの後に実行することもできます。一般に、暴徒の動きの方法はモーターで行う必要があります(理由は既に説明しました)が、例を無駄にせず、脳を吹き飛ばさないために、MonsterBehaviour自体で行います。 SetDamage(float damage)メソッドは、MobHpのように実装されていないことに注意してください。 IsLive = false;を追加しました。 Debud.Log()のテキストが変更されました。もちろん、さらにコードを追加できます。 FindGameObjectWithTag( "Player")メソッドが "Player"タグを持つ最初の1つのgameObjectのみを返すという事実に注意を喚起したいと思います。しかし、私たちと、そして彼は一人です。すべてのオブジェクトを取得する必要がある場合は、FindGameObjectsWithTagメソッドが必要です。

 FindGameObjectWithTag("Tag") //  gameObject FindGameObjectsWithTag("Tag")//   gameObject[]    .    .
      
      





gameObject全体ではなく、プレーヤーのトランスフォームをキャッシュしたことに注意してください。具体的にはトランスフォームを参照します。gameObjectを使用すると、player.getComponent()と同等のplayer.transformのようになります。そして、一度だけすぐにキャッシュできる場合、毎回これを行うのはなぜですか。もちろん、数十個の余分なgetComponentからはほとんど何も変わりません。しかし、プロジェクトが非常に大きい場合。そして、各プログラマーは、getComponentとGameObject.Find(...)およびGameObject.FindGameObjectWithTag(...)がヒットする場所などを突きます。これにより、プログラムの速度が低下する可能性があります。 FPSを下げます。必要ですか?



MonsterBehaviourおよびMonsterHpスクリプトを新しいmob(New Ball)に掛けます。私は次のシーンを得ました:



小さなボールはカートリッジ(弾丸)です。とにかく、削除することができます、それは既にプレハブ(MyPrefabフォルダー)にあります。両方のMobが同じ(Shape)と呼ばれることに注意してください。変更できます。 New Mob MonsterとOld Towerを呼び出しましょう。



ゲームを開始します。新しい暴徒に近づく。彼は私たちの方向に動き始めます。逃げて、彼は止まります。暴徒を撃つと、色が変わります。私たちは再び撃つ、彼は赤面し、もはや私たちを追いかけません。 5秒後に消えます。古い暴徒を撃ち、彼がまだダメージを受けて死ぬことを確認することができます。



だから、多形の美しさは何ですか。暴徒を追加し、彼に台本を書きました。しかし、プレイヤーのスクリプトとカートリッジのスクリプトには何も書きませんでした。



繰り返しになりますが、タンクシェルを思い出してください。この場合、スクリプトはどのようになりますか?とても簡単です。ただし、最初に



両方のMobに「Mob」タグを追加します。次に、カートリッジからタンクの爆発性シェルを作成します。破片や爆発などは発生しません。これはレッスンとは関係ありません。吊り下げられたカートリッジをシーンから取り外します(まだ取り外されていない場合)。 MyPrefabフォルダーにのみ残してください。新しいBigBulletScriptスクリプトを作成します







これは、BulletScriptスクリプトに類似しています。これは戦車カートリッジのスクリプトです。何かと衝突する場合、GameObject.FindGameObjectsWithTag( "Mob")を使用して、タグ "Mob"を持つすべてのゲームオブジェクトの配列を取得します。このオブジェクトにMyBehaviourコンポーネント(またはその後続)があるかどうかを確認し、ある場合は、このオブジェクトまでの距離を確認します。この距離がatacDistanseより小さい場合、このオブジェクトはMyBehaviour(またはその相続人)のインターフェース(SetDamageメソッド)を介して損傷を受けます。



次に、カートリッジのプレハブからBulletScriptスクリプトを削除し、代わりにBigBulletScriptスクリプトを切断します。



互いに遠くないステージでMobを作成します。ゲームを開始します。カートリッジがモブの間に落ちるように撮影します。4つのショットと両方のMobの準備ができました。カートリッジのスクリプトを完全に置き換えましたが、Mobとプレイヤーのスクリプトには影響しませんでした。BigBullrtScriptスクリプトは、侵入するmobに依存しません。モブが一人で作成され、カートリッジが別の人で作成される場合、協調作業のために知っておく必要があるのは、すべてのモブ(一般的に、損傷を受ける可能性のあるもの)が何かを運ぶことです)クラスMyBehaviourの相続人。そして、この相続人にはSetDamage(float damage)メソッドがあります。とても便利です。したがって、プロジェクトコードの残りの部分を考慮することなく、100500種類のMobをリベットできます。



まとめると



この単純なプロジェクトでは、2つの場所でポリモーフィズムを使用しました。



1)任意のMobからMyHpを削除して、新しいMob(別のMyHpの後継者)をハングさせることができます。これは、集団開発に特に便利です。特定のプロジェクトヘッドが最適な動作スクリプト(または、この場合は健康に関与するスクリプト)を選択するとします。彼は提供されたオプションを変更します。そして、メインスクリプトを変更することなくすぐに動作します。プロジェクトの責任者は、これらのスクリプトがどのように機能するかを知らず、それらのスクリプトで目的の変数の宣言をすばやく見つけてそのタイプを変更することはできません。しかし、ポリモーフィズムを使用する場合、これは必要ありません。 MyHpスクリプトの新しい子孫を編集および追加でき、それらはプロジェクトに完全に適合します。最も重要なことは、これらのスクリプトは開発段階だけでなく実行時にも変更できることです。



2)カートリッジは任意の暴徒を攻撃できます。カートリッジ自体の作成後でも作成されます。新しいカートリッジを作成できますが、Mobのスクリプトは変更しないでください。



Unity unity3d.com/learn/tutorials/modules/intermediate/scripting/polymorphismの作成者からのポリモーフィズムレッスンへのリンクを次に示します。私の意見では、それは嫌で理解できない。私は彼が英語でいるという事実について話していません。ポリモーフィズムの詳細については、sharp-generation.narod.ru / C_Sharp / methods.htmlをご覧ください。ただし、最初に、このチュートリアルの最初の2、3の章(リンクが記載されています)を読んでください。そうでないと、明確になりません。



大規模なプロジェクトを作成するためのチームアプローチを再考します。



デザインはさまざまな目的から始めることができます。しかし、上から始めたほうがいいです。つまり、最初にゲームで一般的に何をするかを最初に決定します。どのモブ、どの武器がプレイヤーに利用可能になるかなど。考えたすべてをクラスとサブクラスに分解します。例:



図では、楕円はオブジェクト自体を意味するのではなく、スクリプトがオブジェクトにかかっています。例えば



プレーヤーは、プレーヤーにハングアップするスクリプトです。そのため、写真にはおなじみのMyHpとその相続人がいます。ご覧のように、破壊可能なアイテムには、行われたダメージに反応するMyHpの継承者がいます。スキームには「動的」クラスがあります(何らかの形で参加します)。シーン内でこのクラスのすべての相続人(プレーヤーに関連付けられているものを除く)を見つけてオフにした場合(切断方法は「動的」(何らかの方法で参加)で公表されます)、プレーヤー自身を除くゲーム内のすべてがフリーズします。そのため、時間を止める効果を簡単に実感できます。別の方法で「動的」クラスを作成せずに実行できますが、下位クラスから始めます。 「ダメージを受けることができる」は、ダメージを受ける方法を知っているすべてのものに固執するスクリプト(クラス)です。すでに実装しており、MyBehaviorという名前を付けています。しかし、ここでは、MyBehaviourはMonoBehaviourの相続人であり、「動的」ではありません。必要ありません(「動的」)。スキームは次のようになります。



すべてのスクリプト(クラス)はMonoBehaviourの継承者です。これがUnityが私たちに命じるものです。



大きなスキームに戻りましょう。 「アクション」は、子孫がイベントを実装するために必要ないくつかのメソッドを受け取るクラスです。たとえば、StartAction()、Open()、Close()メソッドなどです。 「アクション」クラスの相続人はすべて、プロットスクリプト、ドアのスクリプト、ハッチなどです。このアプローチにより、アクションスクリプト配列でこれらのメソッドのいずれか(または複数のメソッドを一度に)を呼び出すことができるユニバーサルトリガー(ボタンなど)を作成できます。とても便利です。 Unityには、このようなトリガー用の標準スクリプトもあります。それだけが少し改善されています。多態性のおかげで、Actionクラスの各子孫は独自のメソッドStartAction()、Open()、Close()を実装します。さまざまなドアがさまざまに開き、さまざまなストーリースクリプトがStartAction()をさまざまに実装します。ドアの例:プレーヤーがユニバーサルトリガーを入力し、ドアがトリガーに接続されます(より正確には、このドアからの「アクション」クラスの継承者)トリガーはOpen()メソッドを呼び出します。トリガーを設定するには、ボックスをチェックする必要があります。このメソッドは、「アクション」クラスの指定された後継者で呼び出す必要があります。つまり、ドアクラスごとにボタンクラスを作成する必要はありません。ボタンはユニバーサルです。そして、その中でパブリック変数が宣言されています:

 Public ActionScript action; //     ActionScript Public bool callStartAction=true; // ,     Public bool callOpen =false; Public bool callClose =false;
      
      





さらに、特定の状況(ボタンを押す、または目的のオブジェクトのトリガーを入力する)でのボタンのコード(トリガーなど)では、アクションオブジェクトの目的のメソッドが呼び出されます

 If(callStartAction) { action. StartAction(); } If(callOpen) { action. Open(); } If(callClose) { action. Close (); }
      
      





再び、大きな図を見てください。プレイヤーのモーター、ゾンビ、銃があります。モーターの概念についてはすでに説明しました。しかし今では、他のすべての人がメソッドを継承するメインモーターがあります。すべてのモーターに同じ方法があれば非常に良いでしょう。たとえば、Jump()、MoveDirection(Vector3方向)、Run(bool run)、Stop()、LookTo(Vector3方向)、GoToPoint(Vector3ポイント)などのメソッドがあります。プレーヤーには、最後を除くすべてのメソッドが必要です。そしてこれがMobの最後の1つです。パス検索アルゴリズムなどが含まれる場合があります。多相性のおかげで、メインモーターの各相続人はこれらの方法を独自の方法で実装でき、モーターを別のモーターに変更することもできます。それらは完全に交換可能です。



次に、動作スクリプトを検討します。 「ゾンビは忙しくありません」、「ゾンビ攻撃」など。これらは1つのスクリプトの相続人でもあります。一般的な方法を区別できます。たとえば、SetTarget(変換ターゲット)。このメソッドを通じて、mobの現在のターゲットを動作スクリプトに渡すことができます。結局のところ、状況の分析に従事しているのはMyBehaviourクラスの相続人であるため、目的の動作スクリプトをオンにする場合、必要な情報を彼に送信する必要があります。実際のゲームでは、おそらくこれらの方法がもっとあるでしょう。



3)同様のスキームを作成した後、その実装に進みます。最初に、スクリプトを「親」にします。他のすべてはそれらから継承されます。たとえば、モーターのスクリプト「親」は次のように作成できます。



赤は、このクラスの相続人が独自の方法でこれらのメソッドを再定義できるようにするキーワードを強調しています。アナウンスは次のようになります。



その後、MonsterBehaviourでMyMotor型の変数を作成し、このMobにハングアップしたMyMotorスクリプトの子孫を保存します。もちろん、暴徒は私たちのゲームではジャンプしません。これは単なる例です。 Mobの場合、MyMotorクラスの別のメソッドを考え出し、SetTarget(Transform target)と呼ぶことができます。プレーヤー(または別の暴徒)の後に移動する必要がある場合は、このメソッドを使用します。そして、Mob Mobility自体がジャンプなどの動きの方向を決定します。

最後の例は、ポリモーフィズムを実装するために必要なのは、VirtualキーワードとOverrideキーワード、およびクラスの親の明示的な指示だけです。



以上です。コード内のいくつかの「単語」により、スクリプトが独立し、交換可能になります。一般に、すべてのスクリプトは相互に独立させる必要があります。



次の記事では、ポリモーフィズムと他のOOPチャームを引き続き使用します。



All Articles