Unity3Dでシンプルなタワーディフェンスゲームを作成する方法、パート2

こんにちは 非常に長い間、資料の準備は私のために広がっていました(人生はお尻に多くのキックを与えました)が、私はそれをやったし、最初の記事の続きをあなたと共有する準備ができています。



パート1





物理テストに失敗しました



このパートでは:

-前の記事のコードを最適化します。

-オブジェクト「ベース」を作成し、時々修復することを教えます。

-銃を追加し、銃をリロードします。

-「不便な」変数gvを取り除きます。



そして、記事の終わりに小さなボーナスがあなたを待っています:)



誰もが興味を持っている-待望の猫へようこそ!





最適化、バグ修正、ステージ上の再配置、その他すべて





チュートリアルのこの部分では、以前に記述したたわごとコードを最適化します。これにより、ゲームのパフォーマンスのマージンが得られます。



まず、銃のAIスクリプト、距離の計算方法に影響を与えた変更、カートリッジのあるカートリッジの出現、指定された時間続くリロードから始めます。



PlasmaTurretAI.cs

using UnityEngine; public class PlasmaTurretAI : MonoBehaviour { 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 float reloadCooldown = 2.5f; //  ,  public float rotationSpeed = 1.5f; //    public int FiringOrder = 1; //    (    2) public int upgradeLevel = 0; public int ammoAmount = 64; public int ammoAmountConst = 64; public float ammoReloadTimer = 5.0f; public float ammoReloadConst = 5.0f; public LayerMask turretLayerMask; //  Unity3D             .    Monster.       . public Transform turretHead; //     private void Start() { turretHead = transform.Find("pushka"); //      } //      private void Update() { if (curTarget != null) //      { float squaredDistance = (turretHead.position - curTarget.transform.position).sqrMagnitude; //    if (Mathf.Pow(attackMinimumDistance, 2) < squaredDistance && squaredDistance < Mathf.Pow(attackMaximumDistance, 2)) //          { turretHead.rotation = Quaternion.Lerp(turretHead.rotation, Quaternion.LookRotation(curTarget.transform.position - turretHead.position), rotationSpeed * Time.deltaTime); //     if (reloadTimer > 0) reloadTimer -= Time.deltaTime; //     -   if (reloadTimer <= 0) { if (ammoAmount > 0) //     { MobHP mhp = curTarget.GetComponent<MobHP>(); switch (FiringOrder) //,     { case 1: if (mhp != null) mhp.ChangeHP(-attackDamage); //   FiringOrder++; //  ammoAmount--; //  break; case 2: if (mhp != null) mhp.ChangeHP(-attackDamage); FiringOrder = 1; ammoAmount--; break; } reloadTimer = reloadCooldown; //        "" } else { if (ammoReloadTimer > 0) ammoReloadTimer -= Time.deltaTime; if (ammoReloadTimer <= 0) { ammoAmount = ammoAmountConst; ammoReloadTimer = ammoReloadConst; } } } if (squaredDistance < Mathf.Pow(attackMinimumDistance, 2)) curTarget = null;//    ,      } } else { curTarget = SortTargets(); //     } } //     private GameObject SortTargets() { float closestMobSquaredDistance = 0; //       GameObject nearestmob = null; //    Collider[] mobColliders = Physics.OverlapSphere(transform.position, attackMaximumDistance, turretLayerMask.value); //              foreach (var mobCollider in mobColliders) //     { float distance = (mobCollider.transform.position - turretHead.position).sqrMagnitude; //    ,  closestMobDistance    if (distance < closestMobSquaredDistance && (distance > Mathf.Pow(attackMinimumDistance, 2)) || closestMobSquaredDistance == 0) { closestMobSquaredDistance = distance; //    nearestmob = mobCollider.gameObject;//    } } return nearestmob; //    } private void OnGUI() { Vector3 screenPosition = Camera.main.WorldToScreenPoint(gameObject.transform.position); //       Vector3 cameraRelative = Camera.main.transform.InverseTransformPoint(transform.position); //     if (cameraRelative.z > 0) //     { string ammoString; if (ammoAmount > 0) { ammoString = ammoAmount + "/" + ammoAmountConst; } else { ammoString = "Reloading: " + (int)ammoReloadTimer + " s left"; } GUI.Label(new Rect(screenPosition.x, Screen.height - screenPosition.y, 250f, 20f), ammoString); } } }
      
      







どうやら、ここで計算は距離の2乗と銃の最大距離の2乗との比較を通して使用されます。 より速く動作します Sqrtは使用されません。 ヒントをくれたレオポタムに感謝します:)



次のステップは、シーンを次の形式にすることです。







赤い点で赤い点をマークしました。中央には標準のマックスティーポットの形の「ベース」があります:)







Baseタグをベースに配置して、簡単に見つけられるようにします。

銃を無視してMobをベースに直行させる必要があります。 これを行うには、特定の間隔で損傷と修理を理解するようにベースに教える必要があります。

さあ、始めましょう:



Basehp.cs

 using UnityEngine; public class BaseHP : MonoBehaviour { public float maxHP = 1000; public float curHP = 1000; public float regenerationDelayConstant = 2.5f; //      public float regenarationDelayVariable = 2.5f; //    public float regenerationAmount = 10.0f; //       private GlobalVars gv; private void Awake() { gv = GameObject.Find("GlobalVars").GetComponent<GlobalVars>(); if (maxHP < 1) maxHP = 1; } public void ChangeHP(float adjust) { if ((curHP + adjust) > maxHP) curHP = maxHP; else curHP += adjust; if (curHP > maxHP) curHP = maxHP; //just in case } private void Update() { if (curHP <= 0) { Destroy(gameObject); } else { if (regenarationDelayVariable > 0) regenarationDelayVariable -= Time.deltaTime; //     -       if (regenarationDelayVariable <= 0) //       { ChangeHP(regenerationAmount); //     regenarationDelayVariable = regenerationDelayConstant; //        } } } }
      
      







ベースを使用してオブジェクトにスクリプトを掛けます。 彼女は準備ができています、あなたはモブの再訓練を開始できます!



AIモブスクリプトでは、Updateメソッドのみが変更される可能性があるため、残りのコードは提供しません。



MobAI.cs

 private void Update() { if (Target != null) { 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 squaredDistance = (Target.transform.position - mob.position).sqrMagnitude; //    Vector3 structDirection = (Target.transform.position - mob.position).normalized; float attackDirection = Vector3.Dot(structDirection, mob.forward); if (squaredDistance < attackDistance * attackDistance && attackDirection > 0) { if (attackTimer > 0) attackTimer -= Time.deltaTime; if (attackTimer <= 0) { BaseHP bhp = Target.GetComponent<BaseHP>(); //   HP  if (bhp != null) bhp.ChangeHP(-damage); //    HP   attackTimer = coolDown; } } } else { GameObject baseGO = GameObject.FindGameObjectWithTag("Base"); //    ,    if (baseGO != null) Target = baseGO; //   -     . } }
      
      







すべてが順調で、mobはクロールしてベースを噛み、銃は巧妙に撃ちます。 しかし、カメラは静的です! 障害、修正:



CameraControl.cs

 using UnityEngine; public class CameraControl : MonoBehaviour { public float CameraSpeed = 100.0f; //   public float CameraSpeedBoostMultiplier = 2.0f; //      Shift //     ,    -    public float DefaultCameraPosX = 888.0f; public float DefaultCameraPosY = 50.0f; public float DefaultCameraPosZ = 1414.0f; private void Awake() { //     ,     transform.position = new Vector3(DefaultCameraPosX, DefaultCameraPosY, DefaultCameraPosZ); } private void Update() { float smoothCamSpeed = CameraSpeed * Time.smoothDeltaTime; //       Time.deltaTime //  -    WASD     ,       (WA      ),  Shift    . if (Input.GetKey(KeyCode.W)) transform.position += Input.GetKey(KeyCode.LeftShift) ? new Vector3(0.0f, 0.0f, smoothCamSpeed * CameraSpeedBoostMultiplier) : new Vector3(0.0f, 0.0f, smoothCamSpeed); // if (Input.GetKey(KeyCode.A)) transform.position += Input.GetKey(KeyCode.LeftShift) ? new Vector3(-smoothCamSpeed * CameraSpeedBoostMultiplier, 0.0f, 0.0f) : new Vector3(-smoothCamSpeed, 0.0f, 0.0f); // if (Input.GetKey(KeyCode.S)) transform.position += Input.GetKey(KeyCode.LeftShift) ? new Vector3(0.0f, 0.0f, -smoothCamSpeed * CameraSpeedBoostMultiplier) : new Vector3(0.0f, 0.0f, -smoothCamSpeed); // if (Input.GetKey(KeyCode.D)) transform.position += Input.GetKey(KeyCode.LeftShift) ? new Vector3(smoothCamSpeed * CameraSpeedBoostMultiplier, 0.0f, 0.0f) : new Vector3(smoothCamSpeed, 0.0f, 0.0f); // } }
      
      







もちろん、スクリプトはカメラにかかっています。 これですべてが動いているので、ベースに近づいているMobを見て回り、銃を途中に置くことができます。



次のバグ修正は、「クレジットで」銃を購入できることです。 はい、プレイヤーのお金と銃のコストを簡単に確認する必要があります。 この問題を修正します。

Graphic.cs

 private void OnGUI() { GUI.Box(buyMenu, "Buying menu"); //     buyMenu  ,   "" if (GUI.Button(firstTower, "Plasma Tower\n" + (int)TowerPrices.Plasma + "$")) //      { if (gv.PlayerMoney >= (int)TowerPrices.Plasma) //     gv.mau5tate = GlobalVars.ClickState.Placing; //     " " } if (GUI.Button(secondTower, "Pulse Tower\n" + (int)TowerPrices.Pulse + "$")) //   { //same action here } if (GUI.Button(thirdTower, "Beam Tower\n" + (int)TowerPrices.Beam + "$")) { //same action here } if (GUI.Button(fourthTower, "Tesla Tower\n" + (int)TowerPrices.Tesla + "$")) { //same action here } if (GUI.Button(fifthTower, "Artillery Tower\n" + (int)TowerPrices.Artillery + "$")) { //same 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")) { //same action here } } //   private enum TowerPrices { Plasma = 100, Pulse = 150, Beam = 250, Tesla = 300, Artillery = 350 }
      
      







さらに、以前のすべてのコードを記述した後、GlobalVarsオブジェクトを取り除き、そのオブジェクトとそのすべての静的変数を作成しました。



GlobalVars.cs

 using System.Collections.Generic; using UnityEngine; public static class GlobalVars { public static List<GameObject> MobList = new List<GameObject>(); //    public static int MobCount = 0; //    public static List<GameObject> TurretList = new List<GameObject>(); //    public static int TurretCount = 0; //    public static float PlayerMoney = 200.0f; //  ,        -   200$,     public static ClickState mau5tate = ClickState.Default; //   public enum ClickState //    { Default, // Placing, //  Selling, //  Upgrading //  } }
      
      







GlobalVarsが使用されたすべてのクラスで、gv変数を削除し、Awake()で初期化します。 すべてのgvをGlobalVarsに置き換えます。 nullの無駄なGlobalVarsチェックを削除します。 GlobalVarsコンポーネントを同じ名前のGOから削除します(GO自体の名前をcfgなどのわかりやすい名前に変更できます)。

この操作の結果を比較するために、変更があるクラスの完全なリストを提供します。



注意、次の部分のネタバレ! :)



bitbucket.org/andyion/habratd-tutorial/commits/db7c1bc0c10c89f45be187e59e0608a2fbb3083d



これで交換は完了です。

次の瞬間に、銃とMobの両方の攻撃範囲を調整する際に人生を大きく促進する少しのボーナスを追加します: bitbucket.org/andyion/habratd-tutorial/commits/18ec053f5f5697abbd3598890aa40306e038d472



使用方法:オブジェクトにスクリプトを配置し、インスペクターで範囲を調整します。 強調表示すると、GOの周りに黄色の円が表示されます。これは指示された範囲です。



おわりに


結論として、コードにまだ浅瀬があるにもかかわらず、完全に機能するゲームのプロトタイプを作成できると言いたいです。 NavMeshをいじる時間はありませんでしたが、一見すると複雑なことはありませんでした。



All Articles