Unity3Dで簡単なタワーディフェンスゲームを作成する方法、パート1

こんにちは 私は長い間この記事を公開したかったのですが、時間を割く時間はありませんでした。 この記事はUnity3Dをあまり知らないユーザー向けに設計されているため、本文に豊富な説明があることを事前に警告したいと思います。



パート2



誰でも興味がある-カットへようこそ!





最初のステップ



ベースシーン


実際に、銃、mobなどを配置するサーフェスを作成するときです。 [地形]-> [地形の作成]をクリックします。 同時にペイントしましょうか? オブジェクトで地形を選択し、プロパティでブラシの画像が表示されたボタンをクリックし、[テクスチャを編集]-> [すぐ下にテクスチャを追加]をクリックします。

テクスチャを追加するためのウィンドウが表示されます。最初の行の右側に小さな円があります。これは[参照]ボタンに直接似ており、クリックしてサーフェスのテクスチャを選択します。





テクスチャ選択ウィンドウを閉じて、テクスチャ設定ウィンドウに戻り、その中の適用ボタンをクリックします。 出来上がり、私たちのテレーンは、私たちが選んだテクスチャで描かれています。 同じ方法でテクスチャを追加し、より詳細に色付けすることができます。 ボタンを試して、スライドや穴などを作成してください;)



銃を追加


terraneで十分プレイしましたか? 深刻なおもちゃの素晴らしい時間:私たちの唯一の銃をダウンロード(5Mb、 リンク

アーカイブをプロジェクトのAssetsフォルダーまたはエディターのプロジェクトウィンドウに解凍すると、効果は同じになります。

また、別のフォルダーを作成する必要があります。同じプロジェクトの上部に[作成]ボタンがあります。 それをクリックして、フォルダを選択します。 プレハブフォルダーに名前を付けます。 このフォルダーを右クリックして、[作成]-> [プレハブ]を選択します。 そして、彼の名前はgun_prefabになります。



ちょっとした理論:プレハブは、その設定を持つ他のオブジェクトを含む特別な種類のオブジェクトであり、ステージ上ですばやく生成することもできます(これは将来私たちにとって有用です)。 シーンオブジェクトのリストのプレハブは青です。



キャノンファイル(cannon2)をクリックします。ガンインポートプラグインのプロパティがプロパティインスペクターに読み込まれます。

次のように値を設定します。

0.1に設定されたスケール係数

コライダーの生成をチェック

残りは変更せず、下の[適用]ボタンをクリックするだけです。



したがって、銃を適切なサイズに設定し(最初の0.01は小さすぎ、ユニットは非常に巨大です)、銃が地下に落ちないようにコライダーを生成しました。



プロジェクトウィンドウで、大砲(cannon2)を新しく作成したプレハブ(gun_prefab)にドラッグします。



これで、プレハブをステージに直接ドラッグできます。銃は複製されますが、新しいインスタンスはそれぞれ個別のオブジェクトになります。 試してみてください! そして、十分にプレイしたら、シーンからすべての銃を削除します(それらを選択してDeleteキーを押します)。



モブを作成する




最も単純なTDがあるため、Mobは私たちにとって単純なボールになります。 3Dモデリングに頼らずにそれらを作成することは、梨を砲撃するのと同じくらい簡単です。

GameObject-> Create Other-> Sphereをクリックします。 Sphereという名前のオブジェクトがシーンとオブジェクトインスペクターに表示されます。 既知の方法で、monster01という名前のプレハブを作成し、モンスターをプレハブにドラッグします。 その後、モンスターをシーンから削除することができます。それは、そこではもう必要ないからです。 コードから直接生成します。



カード、お金、 2つのトランク



プレハブガンをProjectからステージに直接ドラッグし、任意の便利な場所に配置します(そうすると、スポーンの実装方法が異なりますが、スターターの場合はそうします)。 AIガンを書く時間です!

プロジェクトにスクリプトフォルダーを作成し、その中にaiを作成します。 次に、aiフォルダーを右クリックして、[作成]-> [C#スクリプト]を選択します。 このスクリプトは、PlasmaTurretAIと呼ばれます。

ダブルクリックで開くと、このスクリプトを使用したIDEがロードされ、スクリプトのフレームワークになります。

PlasmaTurretAI.cs

using UnityEngine; using System.Collections; public class PlasmaTurretAI : MonoBehaviour //       ,    MonoBehaviour    ""    GameObject. { //     void Start () { } //      void Update () { } }
      
      







そして今、実際には、AIコード自体がコメントにあります:

PlasmaTurretAI.cs

 using System.Collections.Generic; using System.Linq; using UnityEngine; //       ,    MonoBehaviour    ""    GameObject (     ). public class PlasmaTurretAI : MonoBehaviour { public GameObject[] targets; //   public GameObject curTarget; public float towerPrice = 100.0f; public float attackMaximumDistance = 50.0f; //  public float attackMinimumDistance = 5.0f; public float attackDamage = 10.0f; // public float reloadTimer = 2.5f; //  ,   public const float reloadCooldown = 2.5f; //  ,  public float rotationSpeed = 1.5f; //    public int FiringOrder = 1; //    (    2) public Transform turretHead; public RaycastHit Hit; //     private void Start() { turretHead = transform.Find("pushka"); //      } //      private void Update() { if (curTarget != null) //      { float distance = Vector3.Distance(turretHead.position, curTarget.transform.position); //    if (attackMinimumDistance < distance && distance < attackMaximumDistance) //          { turretHead.rotation = Quaternion.Slerp(turretHead.rotation, Quaternion.LookRotation(curTarget.transform.position - turretHead.position), rotationSpeed * Time.deltaTime); //     if (reloadTimer > 0) reloadTimer -= Time.deltaTime; //     -   if (reloadTimer < 0) reloadTimer = 0; //     -     if (reloadTimer == 0) //  { MobHP mhp = curTarget.GetComponent<MobHP>(); switch (FiringOrder) //,     { case 1: Debug.Log("  "); //   FiringOrder++; // FiringOrder  1 break; case 2: Debug.Log("  "); //   FiringOrder = 1; // FiringOrder    break; } reloadTimer = reloadCooldown; //        } } } else // { curTarget = SortTargets(); //     } } //    ,    ! public GameObject SortTargets() { float closestMobDistance = 0; //       GameObject nearestmob = null; //    List<GameObject> sortingMobs = GameObject.FindGameObjectsWithTag("Monster").ToList(); //     Monster      foreach (var everyTarget in sortingMobs) //     { //    ,  closestMobDistance    if ((Vector3.Distance(everyTarget.transform.position, turretHead.position) < closestMobDistance) || closestMobDistance == 0) { closestMobDistance = Vector3.Distance(everyTarget.transform.position, turretHead.position); //     ,     nearestmob = everyTarget;//    } } return nearestmob; //   } }
      
      







コメントは、コードを非常に明確に説明していると思います。 四元数のようなモンスターは、唯一理解できないように見えるかもしれません。 恥ずかしがらないで、グーグルで、読んで、このトピックは誰にとっても簡単ではない。 そしてここでは、Unity3Dの四元数について独自のサイトで読むことができます。



変更を保存し、Unity3Dに切り替えます。



作成したばかりのスクリプトを銃に「引っ張る」には、スクリプトファイルをそのプレハブに直接ドラッグする必要があります。 その後、銃のプレハブをクリックすると、スクリプト内のセクションがプロパティインスペクターに表示され、コード内のすべてのパブリックフィールドを設定できます!





次に、コードをテストするために、モンスターにMonsterタグを添付する必要があります。 プロジェクトでそれをクリックしてから、オブジェクトインスペクターを確認します。上部に[タグ]ドロップダウンフィールドがあり、値は[タグなし]になっています。 このリストをクリックして、下部にある[タグを追加]をクリックします。





[タグ]リストを展開し、[要素0]フィールドに「モンスター」と入力します(スクリーンショットのように引用符なし)。





モンスターをもう一度クリックし、可能性のあるタグのリストを再度展開します-それらの中にはモンスターがあります。 選択します。



初心者モブスクール



これまで私たちのMobは単なるオブジェクトでしたが、今度は銃に向かってって喜び、幸福、そして何よりもそれにダメージを与えることを教えます。

既知の方法で、新しいC#スクリプトを作成します:MobAI、GlobalVars、MobHP、TurretHP、SpawnerAI。 順番に始めましょう:



MobAI.cs

 using UnityEngine; using System.Collections.Generic; public class MobAI : MonoBehaviour { public GameObject Target; //  public float mobPrice = 5.0f; //    public float mobMinSpeed = 0.5f; //   public float mobMaxSpeed = 2.0f; //   public float mobRotationSpeed = 2.5f; //   public float attackDistance = 5.0f; //  public float damage = 5; //,   public float attackTimer = 0.0f; //     public const float coolDown = 2.0f; //,         private float MobCurrentSpeed; // ,   private Transform mob; //    private GlobalVars gv; //     private void Awake() { gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); //  mob = transform; //     ( ) MobCurrentSpeed = Random.Range(mobMinSpeed, mobMaxSpeed); //         } private void Update() { if (Target == null) //    { Target = SortTargets(); //      } else //     { mob.rotation = Quaternion.Lerp(mob.rotation, Quaternion.LookRotation(new Vector3(Target.transform.position.x, 0.0f, Target.transform.position.z) - new Vector3(mob.position.x, 0.0f, mob.position.z)), mobRotationSpeed); //-,    ! mob.position += mob.forward * MobCurrentSpeed * Time.deltaTime; //  ,    float distance = Vector3.Distance(Target.transform.position, mob.position); //    Vector3 structDirection = (Target.transform.position - mob.position).normalized; //   float attackDirection = Vector3.Dot(structDirection, mob.forward); //   if (distance < attackDistance && attackDirection > 0) //         { if (attackTimer > 0) attackTimer -= Time.deltaTime; //    0 -   if (attackTimer <= 0) //         { TurretHP thp = Target.GetComponent<TurretHP>(); //     if (thp != null) thp.ChangeHP(-damage); //   ,   (      ,   ) attackTimer = coolDown; //     } } } } //    ,    ! private GameObject SortTargets() { float closestTurretDistance = 0; //       GameObject nearestTurret = null; //    List<GameObject> sortingTurrets = gv.TurretList; //    foreach (var turret in sortingTurrets) //     { //    ,  closestTurretDistance    if ((Vector3.Distance(mob.position, turret.transform.position) < closestTurretDistance) || closestTurretDistance == 0) { closestTurretDistance = Vector3.Distance(mob.position, turret.transform.position); //     ,     nearestTurret = turret;//    } } return nearestTurret; //   } }
      
      







GlobalVars.cs-グローバル変数のクラス

 using System.Collections.Generic; using UnityEngine; public class GlobalVars : MonoBehaviour { public List<GameObject> MobList = new List<GameObject>(); //    public int MobCount = 0; //    public List<GameObject> TurretList = new List<GameObject>(); //    public int TurretCount = 0; //    public float PlayerMoney = 200.0f; //  }
      
      







MobHP.cs

 using UnityEngine; public class MobHP : MonoBehaviour { public float maxHP = 100; //  public float curHP = 100; //  public Color MaxDamageColor = Color.red; //   public Color MinDamageColor = Color.blue; //   private GlobalVars gv; //     private void Awake() { gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); //  if (gv != null) { gv.MobList.Add(gameObject); //      gv.MobCount++; //   } if (maxHP < 1) maxHP = 1; //      -   } public void ChangeHP(float adjust) //    { if ((curHP + adjust) > maxHP) curHP = maxHP;//     adjust   ,    -      else curHP += adjust; //   adjust } private void Update() { gameObject.renderer.material.color = Color.Lerp(MaxDamageColor, MinDamageColor, curHP / maxHP); //       .  :  -    ,  - . if (curHP <= 0) //       { MobAI mai = gameObject.GetComponent<MobAI>(); //   AI  if (mai != null && gv != null) gv.PlayerMoney += mai.mobPrice; //   -          Destroy(gameObject); //  } } private void OnDestroy() //  { if (gv != null) { gv.MobList.Remove(gameObject); //      gv.MobCount--; //     1 } } }
      
      







そして、次のクラスについてはコメントしませんでした。MobHPのほぼ完全なコピーであり、いくつかの違いがあります(たとえば、独自の色を持つ必要はありません)。



TurretHP.cs

 using UnityEngine; public class TurretHP : MonoBehaviour { public float maxHP = 100; //  public float curHP = 100; //  private GlobalVars gv; //     private void Awake() { gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); //  if (gv != null) { gv.TurretList.Add(gameObject); gv.TurretCount++; } if (maxHP < 1) maxHP = 1; } public void ChangeHP(float adjust) { if ((curHP + adjust) > maxHP) curHP = maxHP; else curHP += adjust; if (curHP > maxHP) curHP = maxHP; } private void Update() { if (curHP <= 0) { Destroy(gameObject); } } private void OnDestroy() { if (gv != null) { gv.TurretList.Remove(gameObject); gv.TurretCount--; } } }
      
      







SpawnerAI.cs

 using UnityEngine; public class SpawnerAI : MonoBehaviour { public int waveAmount = 5; //   1      public int waveNumber = 0; //   public float waveDelayTimer = 30.0F; //    public float waveCooldown = 20.0F; // (  !)    ,     public int maximumWaves = 500; //     public Transform Mob; //     Unity public GameObject[] SpawnPoints; //   private GlobalVars gv; //     private void Awake() { SpawnPoints = GameObject.FindGameObjectsWithTag("Spawnpoint"); //      gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); //  } private void Update() { if (waveDelayTimer > 0) // h     { if (gv != null) { if (gv.MobCount == 0) waveDelayTimer = 0; //     -     else waveDelayTimer -= Time.deltaTime; //   } } if (waveDelayTimer <= 0) //      { if (SpawnPoints != null && waveNumber < maximumWaves) //           { foreach (GameObject spawnPoint in SpawnPoints) //    { for (int i = 0; i < waveAmount; i++) // i    ,          { Instantiate(Mob, new Vector3(spawnPoint.transform.position.x, spawnPoint.transform.position.y, spawnPoint.transform.position.z + i * 10), Quaternion.identity); //  } if (waveCooldown > 5.0f) //    5  { waveCooldown -= 0.1f; //  0.1  waveDelayTimer = waveCooldown; //   } else // { waveCooldown = 5.0f; //     5  waveDelayTimer = waveCooldown; } if (waveNumber >= 50) // 50  { waveAmount = 10; //   10     } } waveNumber++; //   } } } }
      
      







そして今、あなたは銃のAIコードを修正する必要があります。 そこでスイッチ(FiringOrder)を見つけ、ブロック全体をこれで完全に置き換えます。



  switch (FiringOrder) //,     { case 1: if (mhp != null) mhp.ChangeHP(-attackDamage); //   FiringOrder++; // FiringOrder  1 break; case 2: if (mhp != null) mhp.ChangeHP(-attackDamage); //   FiringOrder = 1; // FiringOrder    break; }
      
      







また、同じクラスの最後の行を置き換える必要があります



 return nearestmob;
      
      







そのような



 return closestMobDistance > attackMaximumDistance ? null : nearestmob;
      
      







これは三項演算子と呼ばれます。 条件が「?」の前にある場合 true-その後nullを返します。それ以外の場合、nearestmobは戻ります。 この表現の意味は、銃が到達できないターゲットをつかまないということです。



一般に、コードは準備ができています。 次に、ゲームオブジェクトを準備する必要があります。 MobSpawnerオブジェクトを作成します。将来的に干渉しなければ、その場所は重要ではありません。 SpawnerAIスクリプトをその上に置き、目的の変数値を設定します。 mobプレハブを変数Mobの値にドラッグします。



Spaunerはもう触れません。



GlobalVarsという名前のオブジェクトを作成し、同じ名前のスクリプトをその上にドラッグし、プレーヤーの開始金額を指定します。

次に、スポーンのポイントに必要な数のオブジェクト(便宜上、「name_orderNumber」の精神で名前を付けます)を作成し、Mobのスポーンの目的の場所に配置します。 スポーンポイントタグを割り当て、同時にタレットタグを作成してガンプレハブに割り当てます。



Mobに2つのMobAIおよびMobHPスクリプトを、大砲にTurretHPを掛けます。 変数の値にふけることを忘れないでください。

銃のプレハブをMobAIの目標値に引く必要はありません。AI自体が目標を検索します。 非常に原始的で、ゆっくりですが、見ています。



モンスタープレハブにRigidbodyコンポーネントを追加します(コンポーネント->物理-> Rigidbody)。







ガイエム?



GUIを作成するには、Graphicという新しいC#スクリプトが必要です。



Graphic.cs

 using UnityEngine; public class Graphic : MonoBehaviour { private GlobalVars gv; //     public Rect buyMenu; //   public Rect firstTower; //     public Rect secondTower; //     public Rect thirdTower; //     public Rect fourthTower; //     public Rect fifthTower; //     public Rect towerMenu; //    (/) public Rect towerMenuSellTower; //    public Rect towerMenuUpgradeTower; //    public Rect playerStats; //   public Rect playerStatsPlayerMoney; //     public GameObject plasmaTower; //  ,     public GameObject plasmaTowerGhost; //  ,     private RaycastHit hit; //   public LayerMask raycastLayers = 1; //    / - ,    private GameObject ghost; //     private void Awake() { gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); //  if (gv == null) Debug.LogWarning("gv variable is not initialized correctly in " + this); //  ,  gv  buyMenu = new Rect(Screen.width - 185.0f, 10.0f, 175.0f, Screen.height - 100.0f); //  ,   X, Y, , . X  Y       firstTower = new Rect(buyMenu.x + 12.5f, buyMenu.y + 30.0f, 150.0f, 50.0f); secondTower = new Rect(firstTower.x, buyMenu.y + 90.0f, 150.0f, 50.0f); thirdTower = new Rect(firstTower.x, buyMenu.y + 150.0f, 150.0f, 50.0f); fourthTower = new Rect(firstTower.x, buyMenu.y + 210.0f, 150.0f, 50.0f); fifthTower = new Rect(firstTower.x, buyMenu.y + 270.0f, 150.0f, 50.0f); playerStats = new Rect(10.0f, 10.0f, 150.0f, 100.0f); playerStatsPlayerMoney = new Rect(playerStats.x + 12.5f, playerStats.y + 30.0f, 125.0f, 25.0f); towerMenu = new Rect(10.0f, Screen.height - 60.0f, 400.0f, 50.0f); towerMenuSellTower = new Rect(towerMenu.x + 12.5f, towerMenu.y + 20.0f, 75.0f, 25.0f); towerMenuUpgradeTower = new Rect(towerMenuSellTower.x + 5.0f + towerMenuSellTower.width, towerMenuSellTower.y, 75.0f, 25.0f); } private void Update() { switch (gv.mau5tate) //    { case GlobalVars.ClickState.Placing: //      { if (ghost == null) ghost = Instantiate(plasmaTowerGhost) as GameObject; //    -       else // { Ray scrRay = Camera.main.ScreenPointToRay(Input.mousePosition); // ,         if (Physics.Raycast(scrRay, out hit, Mathf.Infinity, raycastLayers)) //        (..  ) { Quaternion normana = Quaternion.FromToRotation(Vector3.up, hit.normal); //    ghost.transform.position = hit.point; //          ghost.transform.rotation = normana; //    ,    ,    if (Input.GetMouseButtonDown(0)) //   { GameObject tower = Instantiate(plasmaTower, ghost.transform.position, ghost.transform.rotation) as GameObject; //     if (tower != null) gv.PlayerMoney -= tower.GetComponent<PlasmaTurretAI>().towerPrice; //    Destroy(ghost); //   gv.mau5tate = GlobalVars.ClickState.Default; //      } } } break; } } } private void OnGUI() { GUI.Box(buyMenu, "Buying menu"); //     buyMenu  ,   "" if (GUI.Button(firstTower, "Plasma Tower\n100$")) //      { gv.mau5tate = GlobalVars.ClickState.Placing; //    } if (GUI.Button(secondTower, "Pulse Tower\n155$")) //   { //action here } if (GUI.Button(thirdTower, "Beam Tower\n250$")) { //action here } if (GUI.Button(fourthTower, "Tesla Tower\n375$")) { //action here } if (GUI.Button(fifthTower, "Artillery Tower\n500$")) { //action here } GUI.Box(playerStats, "Player Stats"); GUI.Label(playerStatsPlayerMoney, "Money: " + gv.PlayerMoney + "$"); GUI.Box(towerMenu, "Tower menu"); if (GUI.Button(towerMenuSellTower, "Sell")) { //action here } if (GUI.Button(towerMenuUpgradeTower, "Upgrade")) { //action here } } }
      
      







ああ、今はGlobalVarsスクリプトをうまく変更する必要があります。



GlobalVars.cs

 using System.Collections.Generic; using UnityEngine; public class GlobalVars : MonoBehaviour { public List<GameObject> MobList = new List<GameObject>(); //    public int MobCount = 0; //    public List<GameObject> TurretList = new List<GameObject>(); //    public int TurretCount = 0; //    public float PlayerMoney; //  public ClickState mau5tate = ClickState.Default; //   public enum ClickState //    { Default, Placing, Selling, Upgrading } public void Awake() { PlayerMoney = PlayerPrefs.GetFloat("Player Money", 200.0f); //  ,        -   200$,     } public void OnApplicationQuit() { PlayerPrefs.SetFloat("Player Money", PlayerMoney); //     PlayerPrefs.Save(); } }
      
      







次に、銃のゴーストを作成する必要がありますが、これは非常に簡単です:銃を複製し、空のGOをその中に投げ、削除し、銃をそのプレハブから解き放ちます、主なことはそれを保存しないことです! 次に、大砲内のオブジェクトの階層全体を調べて、シェーダーを透明拡散に変更します。 ちなみに、銃の構造全体を完全に見るには、銃をステージ上に置き、そこで階層を開く必要があります。 ゴーストの作成で問題が発生した場合は、準備が整った状態で投稿します。 パッケージのダウンロードへのリンクがまだあるものもあります (3.7 mb)。



理論的には動作するはずです。 もちろん、ジャムとそれらの最も明白なものがあります-各状態のガンスポーンメカニズムのコードを書く必要があります-これは、状態の形でオーバーロードを持つ独自のメソッドを作成し、メソッド内で既にそれらを解析することによって修正されますが、これは思慮深いコードのためにすぐに対処する必要があるコードの最適化です突然発生するアイデアの下で自分を支配するのが簡単になります。



レッスンのこの部分はそれほど長くはありませんが、ゲームの基本的なメカニズムの実装と理解において最も重要です。



そして、すべてがあなたにとって正しいと判明した場合、それは次のようになります:







続く!



07/26/2012 :コードのバグを修正

05/14/2018 :壊れたリンクを修復する



All Articles