階:數(shù)據(jù)驅(qū)動防御塔與抽卡養(yǎng)成系統(tǒng)實(shí)現(xiàn))
大家好我是專注于Unity游戲開發(fā)的博主。在上一篇文章中我們搭建了游戲的基礎(chǔ)框架實(shí)現(xiàn)了場景、敵人尋路和基礎(chǔ)戰(zhàn)斗循環(huán)。今天我們將深入游戲的核心玩法完成防御塔的多樣化建造與升級并實(shí)現(xiàn)一套完整的抽卡收集與養(yǎng)成機(jī)制。無論你是想學(xué)習(xí)Unity中如何設(shè)計(jì)復(fù)雜的游戲系統(tǒng)還是希望為自己的塔防游戲項(xiàng)目注入“抽卡”這樣的流行元素這篇文章都將提供從零到一的完整實(shí)現(xiàn)路徑和可復(fù)用的代碼方案。1. 防御塔系統(tǒng)進(jìn)階從單一到多樣化一個(gè)有趣的塔防游戲其靈魂在于多樣化的防御塔。我們將告別單一的攻擊塔設(shè)計(jì)一套包含多種攻擊類型、可升級、可配置的塔系統(tǒng)。1.1 防御塔數(shù)據(jù)與配置設(shè)計(jì)首先我們需要一個(gè)結(jié)構(gòu)化的方式來定義不同類型的塔。使用ScriptableObject來創(chuàng)建可配置的塔數(shù)據(jù)資產(chǎn)是最佳實(shí)踐它允許我們在不修改代碼的情況下調(diào)整塔的屬性。創(chuàng)建塔數(shù)據(jù) ScriptableObject在ScriptableObjects文件夾下創(chuàng)建腳本TowerData.cs。// 文件路徑Assets/Scripts/ScriptableObjects/TowerData.cs using UnityEngine; [CreateAssetMenu(fileName NewTowerData, menuName Tower Defense/Tower Data)] public class TowerData : ScriptableObject { [Header(基礎(chǔ)信息)] public string towerName; // 塔名稱 public string description; // 描述 public GameObject towerPrefab; // 塔的預(yù)制體 public Sprite icon; // UI圖標(biāo) public int baseCost; // 基礎(chǔ)建造花費(fèi) [Header(攻擊屬性)] public float baseDamage; // 基礎(chǔ)傷害 public float baseAttackRange; // 基礎(chǔ)攻擊范圍 public float baseAttackInterval; // 基礎(chǔ)攻擊間隔秒 public ProjectileType projectileType; // 攻擊類型子彈、激光、范圍等 public GameObject projectilePrefab; // 子彈預(yù)制體如果適用 [Header(升級配置)] public TowerUpgrade[] upgrades; // 升級配置數(shù)組 } // 攻擊類型枚舉 public enum ProjectileType { Bullet, // 直線子彈 Splash, // 范圍濺射 Laser, // 持續(xù)激光 Slow, // 減速效果 } // 升級配置類 [System.Serializable] public class TowerUpgrade { public int upgradeCost; // 本次升級花費(fèi) public float damageIncrease; // 傷害增加量 public float rangeIncrease; // 范圍增加量 public float attackSpeedIncrease; // 攻速增加量減少間隔 public string upgradeDescription; // 升級描述 public GameObject upgradedVisualPrefab; // 升級后的外觀預(yù)制體可選 }在Unity編輯器中右鍵點(diǎn)擊Project窗口 - Create - Tower Defense - Tower Data即可創(chuàng)建如“箭塔”、“炮塔”、“魔法塔”等不同配置的數(shù)據(jù)文件。你可以自由設(shè)置傷害、射程、花費(fèi)和升級路徑。1.2 重構(gòu)防御塔控制器接下來我們需要一個(gè)強(qiáng)大的TowerController來使用這些數(shù)據(jù)并管理攻擊、升級和狀態(tài)。// 文件路徑Assets/Scripts/Tower/TowerController.cs using System.Collections; using UnityEngine; public class TowerController : MonoBehaviour { [SerializeField] private TowerData towerData; // 關(guān)聯(lián)的塔數(shù)據(jù) private int currentLevel 0; // 當(dāng)前等級0為基礎(chǔ)等級 private float currentDamage; private float currentAttackRange; private float currentAttackInterval; private Transform targetEnemy; private bool isAttacking false; // 當(dāng)前屬性只讀供UI顯示 public float CurrentDamage currentDamage; public float CurrentAttackRange currentAttackRange; public float CurrentAttackInterval currentAttackInterval; public int CurrentLevel currentLevel; public TowerData TowerData towerData; public bool CanUpgrade currentLevel towerData.upgrades.Length; void Start() { InitializeTower(); } void InitializeTower() { if (towerData null) { Debug.LogError(TowerController: TowerData is not assigned!); return; } // 初始化基礎(chǔ)屬性 currentDamage towerData.baseDamage; currentAttackRange towerData.baseAttackRange; currentAttackInterval towerData.baseAttackInterval; // 可選根據(jù)數(shù)據(jù)生成塔身模型 if (towerData.towerPrefab ! null) { Instantiate(towerData.towerPrefab, transform.position, Quaternion.identity, transform); } // 開始檢測敵人 StartCoroutine(ScanForEnemies()); } IEnumerator ScanForEnemies() { while (true) { if (!isAttacking) { // 尋找范圍內(nèi)最近的敵人 Collider[] hitColliders Physics.OverlapSphere(transform.position, currentAttackRange); float shortestDistance Mathf.Infinity; Transform nearestEnemy null; foreach (var hitCollider in hitColliders) { if (hitCollider.CompareTag(Enemy)) { float distance Vector3.Distance(transform.position, hitCollider.transform.position); if (distance shortestDistance) { shortestDistance distance; nearestEnemy hitCollider.transform; } } } targetEnemy nearestEnemy; if (targetEnemy ! null) { // 開始攻擊 StartCoroutine(AttackRoutine()); } } yield return new WaitForSeconds(0.2f); // 每0.2秒掃描一次優(yōu)化性能 } } IEnumerator AttackRoutine() { isAttacking true; while (targetEnemy ! null Vector3.Distance(transform.position, targetEnemy.position) currentAttackRange) { // 根據(jù)攻擊類型執(zhí)行不同攻擊邏輯 switch (towerData.projectileType) { case ProjectileType.Bullet: FireBullet(); break; case ProjectileType.Splash: FireSplash(); break; case ProjectileType.Laser: // 持續(xù)傷害在協(xié)程中處理 ApplyLaserDamage(); break; case ProjectileType.Slow: ApplySlowEffect(); break; } yield return new WaitForSeconds(currentAttackInterval); } // 敵人離開范圍或死亡停止攻擊 isAttacking false; } void FireBullet() { if (towerData.projectilePrefab null || targetEnemy null) return; GameObject projectile Instantiate(towerData.projectilePrefab, transform.position Vector3.up, Quaternion.identity); Projectile projScript projectile.GetComponentProjectile(); if (projScript ! null) { projScript.Initialize(targetEnemy, currentDamage); } else { // 簡單直線飛行邏輯如果預(yù)制體上沒有Projectile腳本 Rigidbody rb projectile.GetComponentRigidbody(); if (rb ! null) { Vector3 direction (targetEnemy.position - projectile.transform.position).normalized; rb.velocity direction * 20f; // 假設(shè)速度20 // 需要另一個(gè)腳本或協(xié)程來處理碰撞和傷害 } } } void FireSplash() { // 范圍攻擊在目標(biāo)點(diǎn)產(chǎn)生一個(gè)爆炸效果對范圍內(nèi)所有敵人造成傷害 // 實(shí)現(xiàn)邏輯與Bullet類似但碰撞檢測是范圍性的 Debug.Log(${towerData.towerName} 發(fā)動了范圍攻擊); // 具體實(shí)現(xiàn)需要爆炸效果預(yù)制體和范圍傷害檢測 } void ApplyLaserDamage() { // 持續(xù)傷害每幀或每隔固定時(shí)間對鎖定敵人造成傷害 // 通常需要生成一個(gè)持續(xù)的激光視覺效果 if (targetEnemy ! null) { EnemyHealth enemyHealth targetEnemy.GetComponentEnemyHealth(); if (enemyHealth ! null) { enemyHealth.TakeDamage(currentDamage * Time.deltaTime); // 每秒造成currentDamage點(diǎn)傷害 } } } void ApplySlowEffect() { // 減速效果給敵人附加一個(gè)減速Debuff if (targetEnemy ! null) { EnemyMovement enemyMovement targetEnemy.GetComponentEnemyMovement(); if (enemyMovement ! null) { enemyMovement.ApplySlow(0.5f, 2.0f); // 減速50%持續(xù)2秒 } } } // 升級塔 public bool UpgradeTower() { if (!CanUpgrade) return false; TowerUpgrade nextUpgrade towerData.upgrades[currentLevel]; // 這里應(yīng)該檢查玩家資源是否足夠假設(shè)有一個(gè)GameManager管理資源 // if (GameManager.Instance.SpendGold(nextUpgrade.upgradeCost)) // { currentDamage nextUpgrade.damageIncrease; currentAttackRange nextUpgrade.rangeIncrease; currentAttackInterval Mathf.Max(0.1f, currentAttackInterval - nextUpgrade.attackSpeedIncrease); // 確保間隔不為負(fù) currentLevel; // 更新外觀可選 if (nextUpgrade.upgradedVisualPrefab ! null) { // 銷毀舊模型生成新模型 foreach (Transform child in transform) { if (child.CompareTag(TowerVisual)) Destroy(child.gameObject); } Instantiate(nextUpgrade.upgradedVisualPrefab, transform.position, Quaternion.identity, transform).tag TowerVisual; } Debug.Log(${towerData.towerName} 升級到等級 {currentLevel}); return true; // } // return false; } // 在Scene視圖中繪制攻擊范圍便于調(diào)試 void OnDrawGizmosSelected() { if (towerData ! null) { Gizmos.color Color.red; Gizmos.DrawWireSphere(transform.position, towerData.baseAttackRange); } } }這個(gè)控制器集成了數(shù)據(jù)驅(qū)動、多種攻擊邏輯和升級系統(tǒng)。你需要配套實(shí)現(xiàn)Projectile.cs子彈腳本、EnemyHealth.cs敵人生命和EnemyMovement.cs敵人移動包含減速方法。1.3 塔的建造與UI交互玩家需要在指定位置如空地建造塔。我們需要一個(gè)BuildManager和建造點(diǎn)BuildPoint。建造點(diǎn)腳本// 文件路徑Assets/Scripts/Tower/BuildPoint.cs using UnityEngine; using UnityEngine.EventSystems; public class BuildPoint : MonoBehaviour, IPointerDownHandler { public bool isOccupied false; private GameObject currentTower; // 當(dāng)玩家點(diǎn)擊這個(gè)建造點(diǎn)需要掛載Collider public void OnPointerDown(PointerEventData eventData) { // 通知BuildManager這個(gè)點(diǎn)被選中了 BuildManager.Instance.SelectBuildPoint(this); } public bool BuildTower(TowerData data) { if (isOccupied || data null) return false; // 檢查資源是否足夠調(diào)用GameManager // if (!GameManager.Instance.SpendGold(data.baseCost)) return false; GameObject towerObj Instantiate(data.towerPrefab, transform.position, Quaternion.identity); TowerController towerController towerObj.GetComponentTowerController(); if (towerController ! null) { towerController.TowerData data; // 如果預(yù)制體未預(yù)先賦值數(shù)據(jù) } currentTower towerObj; isOccupied true; return true; } public void SellTower() { if (!isOccupied || currentTower null) return; // 計(jì)算出售價(jià)格例如返回部分花費(fèi) // GameManager.Instance.AddGold(calculateRefund); Destroy(currentTower); currentTower null; isOccupied false; } }建造管理器// 文件路徑Assets/Scripts/Managers/BuildManager.cs using UnityEngine; public class BuildManager : MonoBehaviour { public static BuildManager Instance; [SerializeField] private TowerData selectedTowerData; // 當(dāng)前從UI選中的塔數(shù)據(jù) private BuildPoint selectedBuildPoint; void Awake() { if (Instance null) Instance this; else Destroy(gameObject); } // 從UI按鈕調(diào)用設(shè)置想要建造的塔 public void SelectTowerToBuild(TowerData towerData) { selectedTowerData towerData; Debug.Log($準(zhǔn)備建造: {towerData.towerName}); } // 被BuildPoint調(diào)用設(shè)置選中的建造點(diǎn) public void SelectBuildPoint(BuildPoint point) { selectedBuildPoint point; // 可以在這里顯示一個(gè)建造UI面板詢問是否建造 if (selectedTowerData ! null !point.isOccupied) { point.BuildTower(selectedTowerData); selectedTowerData null; // 建造后清空選擇 } else if (point.isOccupied) { // 顯示升級/出售UI UIManager.Instance.ShowTowerOptionsPanel(point); } } public TowerData GetSelectedTower() { return selectedTowerData; } }至此一個(gè)數(shù)據(jù)驅(qū)動、可升級、多類型的防御塔系統(tǒng)就搭建完成了。玩家可以通過UI選擇塔點(diǎn)擊空地建造并點(diǎn)擊已建造的塔進(jìn)行升級或出售。2. 抽卡與收集養(yǎng)成系統(tǒng)設(shè)計(jì)抽卡機(jī)制能為游戲帶來持續(xù)的目標(biāo)感和收集樂趣。我們將設(shè)計(jì)一個(gè)包含卡池、概率、卡牌合成與升星功能的完整系統(tǒng)。2.1 卡牌數(shù)據(jù)與卡池配置首先定義卡牌??ㄅ瓶梢源碛⑿?、技能、塔的變體或道具。// 文件路徑Assets/Scripts/Card/CardData.cs using UnityEngine; [CreateAssetMenu(fileName NewCardData, menuName Tower Defense/Card Data)] public class CardData : ScriptableObject { public string cardId; public string cardName; public CardRarity rarity; // 稀有度 public Sprite cardImage; public string description; // 卡牌效果根據(jù)游戲類型定義例如解鎖特定塔、提供全局增益、召喚單位等 public CardEffect effect; } public enum CardRarity { Common, // 普通 Uncommon, // 稀有 Rare, // 罕見 Epic, // 史詩 Legendary // 傳說 } // 卡牌效果基類用于多態(tài) public abstract class CardEffect : ScriptableObject { public abstract void ApplyEffect(); } // 示例解鎖塔的效果 [CreateAssetMenu(fileName UnlockTowerEffect, menuName Tower Defense/Card Effects/Unlock Tower)] public class UnlockTowerEffect : CardEffect { public TowerData towerToUnlock; public override void ApplyEffect() { // 將towerToUnlock添加到玩家可建造列表 BuildManager.Instance.UnlockTower(towerToUnlock); Debug.Log($解鎖了新防御塔: {towerToUnlock.towerName}); } }接著配置卡池??ǔ貨Q定了每次抽卡時(shí)不同稀有度卡牌出現(xiàn)的概率。// 文件路徑Assets/Scripts/Card/CardPool.cs using UnityEngine; [CreateAssetMenu(fileName CardPool, menuName Tower Defense/Card Pool)] public class CardPool : ScriptableObject { [System.Serializable] public class RarityProbability { public CardRarity rarity; [Range(0, 100)] public float probability; // 概率百分比總和應(yīng)為100 } public RarityProbability[] rarityProbabilities; public CardData[] allCards; // 池子里所有的卡 // 根據(jù)概率隨機(jī)獲取一張卡 public CardData DrawRandomCard() { float totalProb 0; foreach (var rp in rarityProbabilities) { totalProb rp.probability; } float randomPoint Random.Range(0, totalProb); CardRarity selectedRarity CardRarity.Common; float cumulative 0; foreach (var rp in rarityProbabilities) { cumulative rp.probability; if (randomPoint cumulative) { selectedRarity rp.rarity; break; } } // 從該稀有度的卡中隨機(jī)選一張 CardData[] cardsOfRarity System.Array.FindAll(allCards, card card.rarity selectedRarity); if (cardsOfRarity.Length 0) { Debug.LogWarning($卡池中沒有稀有度為 {selectedRarity} 的卡); return null; } return cardsOfRarity[Random.Range(0, cardsOfRarity.Length)]; } }2.2 抽卡管理器與UI實(shí)現(xiàn)抽卡需要消耗資源金幣、鉆石或抽卡券并管理玩家的卡牌收藏。// 文件路徑Assets/Scripts/Managers/GachaManager.cs using System.Collections.Generic; using UnityEngine; public class GachaManager : MonoBehaviour { public static GachaManager Instance; [SerializeField] private CardPool cardPool; // 主卡池 [SerializeField] private int singleDrawCost 100; // 單抽消耗 [SerializeField] private int multiDrawCost 900; // 十連消耗優(yōu)惠 [SerializeField] private int pityCounter 0; // 保底計(jì)數(shù)器 [SerializeField] private int pityThreshold 10; // 10抽必出稀有以上 private ListCardData playerCollection new ListCardData(); // 玩家擁有的卡 void Awake() { if (Instance null) Instance this; else Destroy(gameObject); } // 單次抽卡 public CardData PerformSingleDraw() { // 檢查資源 // if (!ResourceManager.Instance.SpendGold(singleDrawCost)) return null; pityCounter; CardData drawnCard; // 保底機(jī)制 if (pityCounter pityThreshold) { // 保底抽強(qiáng)制從稀有及以上卡池中抽取 CardData[] rarePlusCards System.Array.FindAll(cardPool.allCards, card (int)card.rarity (int)CardRarity.Rare); drawnCard rarePlusCards[Random.Range(0, rarePlusCards.Length)]; pityCounter 0; // 重置保底 Debug.Log(保底觸發(fā)); } else { drawnCard cardPool.DrawRandomCard(); } if (drawnCard ! null) { AddCardToCollection(drawnCard); // 觸發(fā)UI顯示抽卡結(jié)果 UIManager.Instance.ShowGachaResult(new CardData[] { drawnCard }); } return drawnCard; } // 十連抽 public ListCardData PerformMultiDraw() { // if (!ResourceManager.Instance.SpendGold(multiDrawCost)) return null; ListCardData results new ListCardData(); bool pityTriggeredThisBatch false; for (int i 0; i 10; i) { pityCounter; CardData drawnCard; if (!pityTriggeredThisBatch pityCounter pityThreshold) { // 十連內(nèi)觸發(fā)一次保底 CardData[] rarePlusCards System.Array.FindAll(cardPool.allCards, card (int)card.rarity (int)CardRarity.Rare); drawnCard rarePlusCards[Random.Range(0, rarePlusCards.Length)]; pityCounter 0; pityTriggeredThisBatch true; Debug.Log(十連保底觸發(fā)); } else { drawnCard cardPool.DrawRandomCard(); } results.Add(drawnCard); AddCardToCollection(drawnCard); } UIManager.Instance.ShowGachaResult(results.ToArray()); return results; } void AddCardToCollection(CardData card) { playerCollection.Add(card); // 立即應(yīng)用卡牌效果如果是即時(shí)生效型 card.effect?.ApplyEffect(); Debug.Log($獲得新卡牌: {card.cardName} ({card.rarity})); } public ListCardData GetPlayerCollection() { return new ListCardData(playerCollection); } }抽卡結(jié)果UI示例你需要創(chuàng)建一個(gè)UI面板用于展示抽到的卡牌。這里提供一個(gè)簡單的UI控制器思路。// 文件路徑Assets/Scripts/UI/GachaResultPanel.cs using System.Collections; using UnityEngine; using UnityEngine.UI; public class GachaResultPanel : MonoBehaviour { public GameObject cardDisplayPrefab; // 卡牌展示預(yù)制體 public Transform resultContainer; // 卡牌排列的父物體 public Button closeButton; void Start() { closeButton.onClick.AddListener(() gameObject.SetActive(false)); } public void ShowResults(CardData[] cards) { gameObject.SetActive(true); // 清空舊結(jié)果 foreach (Transform child in resultContainer) { Destroy(child.gameObject); } // 生成新卡牌顯示 foreach (var card in cards) { GameObject cardObj Instantiate(cardDisplayPrefab, resultContainer); CardDisplay display cardObj.GetComponentCardDisplay(); if (display ! null) { display.Setup(card); } } // 可以添加一個(gè)簡單的動畫協(xié)程讓卡牌一張張翻過來 StartCoroutine(RevealCards()); } IEnumerator RevealCards() { // 這里可以實(shí)現(xiàn)卡牌翻轉(zhuǎn)動畫 yield return new WaitForSeconds(0.5f); } }2.3 卡牌養(yǎng)成合成與升星系統(tǒng)抽到重復(fù)的卡牌不應(yīng)是無用的我們可以將其用于升星提升卡牌效果。首先為卡牌數(shù)據(jù)添加“星級”屬性。// 在CardData.cs中添加 public class CardData : ScriptableObject { // ... 原有屬性 ... public int maxStarLevel 5; // 最大星級 public int currentStarLevel 0; // 當(dāng)前星級 public int duplicatesRequiredForUpgrade 3; // 升到下一級需要的重復(fù)卡數(shù)量 // 根據(jù)星級調(diào)整效果強(qiáng)度的方法 public float GetEffectMultiplier() { return 1.0f (currentStarLevel * 0.2f); // 每星提升20%效果 } }然后創(chuàng)建卡牌管理類來處理合成邏輯。// 文件路徑Assets/Scripts/Card/CardCollectionManager.cs using System.Collections.Generic; using System.Linq; using UnityEngine; public class CardCollectionManager : MonoBehaviour { private Dictionarystring, int cardDuplicateCount new Dictionarystring, int(); // 卡牌ID - 擁有數(shù)量 // 當(dāng)獲得一張卡時(shí)調(diào)用 public void OnCardAcquired(CardData card) { string id card.cardId; if (cardDuplicateCount.ContainsKey(id)) { cardDuplicateCount[id]; } else { cardDuplicateCount[id] 1; } CheckForUpgrade(card); } void CheckForUpgrade(CardData card) { int count cardDuplicateCount[card.cardId]; int required card.duplicatesRequiredForUpgrade; int possibleUpgrades (count - 1) / required; // 計(jì)算可以升幾星減去本體 if (possibleUpgrades card.currentStarLevel card.currentStarLevel card.maxStarLevel) { // 可以升級 int starsToAdd Mathf.Min(possibleUpgrades - card.currentStarLevel, card.maxStarLevel - card.currentStarLevel); card.currentStarLevel starsToAdd; cardDuplicateCount[card.cardId] - starsToAdd * required; // 消耗重復(fù)卡 Debug.Log($卡牌 {card.cardName} 升到 {card.currentStarLevel} 星); // 更新UI播放特效等 UIManager.Instance.ShowStarUpgradeEffect(card); } } // 獲取玩家擁有的所有卡牌及其數(shù)量/星級 public DictionaryCardData, CardInfo GetCollectionInfo() { var info new DictionaryCardData, CardInfo(); var allCards GachaManager.Instance.GetPlayerCollection().Distinct(); // 去重獲取卡牌類型 foreach (var card in allCards) { cardDuplicateCount.TryGetValue(card.cardId, out int count); info.Add(card, new CardInfo { count count, starLevel card.currentStarLevel }); } return info; } } public class CardInfo { public int count; public int starLevel; }現(xiàn)在你的游戲已經(jīng)擁有了一個(gè)完整的抽卡與養(yǎng)成循環(huán)玩家消耗資源抽卡 - 獲得新卡或重復(fù)卡 - 重復(fù)卡累計(jì)用于升星 - 升星增強(qiáng)卡牌效果例如解鎖的塔更強(qiáng)大增益效果更強(qiáng)。3. 游戲進(jìn)程與資源管理整合我們需要一個(gè)GameManager來統(tǒng)籌全局狀態(tài)、資源金幣、鉆石和游戲流程波次、勝負(fù)。// 文件路徑Assets/Scripts/Managers/GameManager.cs using UnityEngine; public class GameManager : MonoBehaviour { public static GameManager Instance; // 玩家資源 public int Gold { get; private set; } public int Diamond { get; private set; } // 游戲狀態(tài) public enum GameState { Preparing, WaveInProgress, WaveCompleted, GameOver, Victory } public GameState CurrentState { get; private set; } [SerializeField] private int startingGold 500; void Awake() { if (Instance null) Instance this; else Destroy(gameObject); DontDestroyOnLoad(gameObject); // 通常GameManager跨場景存在 InitGame(); } void InitGame() { Gold startingGold; Diamond 0; CurrentState GameState.Preparing; // 初始化其他系統(tǒng)... } // 資源管理 public bool SpendGold(int amount) { if (Gold amount) { Gold - amount; UIManager.Instance.UpdateResourceUI(); return true; } else { Debug.Log(金幣不足); return false; } } public void AddGold(int amount) { Gold amount; UIManager.Instance.UpdateResourceUI(); } // 游戲流程控制例如開始下一波敵人 public void StartNextWave() { if (CurrentState ! GameState.Preparing CurrentState ! GameState.WaveCompleted) return; CurrentState GameState.WaveInProgress; // 通知WaveManager開始生成敵人 FindObjectOfTypeWaveManager()?.StartWave(); } public void OnWaveCompleted() { CurrentState GameState.WaveCompleted; AddGold(100); // 獎勵金幣 // 顯示下一波按鈕等 } public void OnBaseDestroyed() { CurrentState GameState.GameOver; // 顯示失敗UI } public void OnAllWavesCleared() { CurrentState GameState.Victory; // 顯示勝利UI } }4. 移動游戲適配與優(yōu)化要點(diǎn)我們的目標(biāo)是移動平臺因此必須考慮觸控操作和性能。4.1 觸控輸入適配Unity的EventSystem和Graphic Raycaster已經(jīng)為UI觸控提供了良好支持。對于3D場景中的建造點(diǎn)點(diǎn)擊我們使用了IPointerDownHandler。確保建造點(diǎn)物體上有Collider和EventTrigger組件或掛載了實(shí)現(xiàn)接口的腳本。對于拖拽建造等復(fù)雜操作可以考慮使用Input.touches進(jìn)行更精細(xì)的控制。4.2 性能優(yōu)化策略對象池Object Pooling大量生成的敵人、子彈必須使用對象池。// 簡易對象池示例用于子彈 public class ProjectilePool : MonoBehaviour { public GameObject projectilePrefab; public int poolSize 20; private QueueGameObject pool new QueueGameObject(); void Start() { for (int i 0; i poolSize; i) { GameObject obj Instantiate(projectilePrefab, transform); obj.SetActive(false); pool.Enqueue(obj); } } public GameObject GetProjectile() { if (pool.Count 0) { GameObject obj pool.Dequeue(); obj.SetActive(true); return obj; } else { // 池子空了動態(tài)擴(kuò)展或返回null GameObject obj Instantiate(projectilePrefab, transform); return obj; } } public void ReturnProjectile(GameObject obj) { obj.SetActive(false); pool.Enqueue(obj); } }在Projectile腳本中擊中目標(biāo)或飛出界外后調(diào)用ReturnProjectile。渲染優(yōu)化對遠(yuǎn)處的物體使用LODLevel of Detail合并靜態(tài)場景的網(wǎng)格和材質(zhì)Static Batching。代碼優(yōu)化避免在Update中做復(fù)雜的查找如Find、GetComponent。我們的TowerController使用協(xié)程每0.2秒掃描一次敵人而不是每幀掃描。將結(jié)果緩存起來。UI優(yōu)化避免過多的UI重繪。使用Canvas的Additional Shader Channels并合理設(shè)置Canvas的渲染模式。4.3 構(gòu)建設(shè)置與發(fā)布前往File - Build Settings。將你的主場景添加到Scenes In Build。在Platform中選擇Android或iOS點(diǎn)擊Switch Platform。對于Android需要安裝JDK、Android SDK和NDK。在Player Settings中設(shè)置包名com.YourCompany.YourGame、版本號、圖標(biāo)和啟動畫面。對于iOS需要在Mac電腦上使用Xcode進(jìn)行最終構(gòu)建和簽名。點(diǎn)擊Build生成APKAndroid或Xcode項(xiàng)目iOS。5. 常見問題與調(diào)試技巧在開發(fā)過程中你可能會遇到以下典型問題問題現(xiàn)象可能原因解決方案點(diǎn)擊建造點(diǎn)無反應(yīng)1. 建造點(diǎn)物體缺少Collider。2. 建造點(diǎn)被UI遮擋。3.EventSystem不存在或被禁用。1. 添加Box Collider。2. 檢查UI的Raycast Target設(shè)置或調(diào)整Canvas渲染順序。3. 確保場景中有且只有一個(gè)EventSystem。塔不攻擊敵人1. 敵人Tag不是“Enemy”。2.ScanForEnemies協(xié)程未啟動或停止。3. 攻擊范圍currentAttackRange為0或太小。4. 子彈預(yù)制體未正確設(shè)置或Projectile腳本缺失。1. 檢查敵人預(yù)制體的Tag。2. 在Start或InitializeTower中啟動協(xié)程。3. 在TowerData中設(shè)置合理的范圍并用OnDrawGizmosSelected可視化調(diào)試。4. 確保子彈預(yù)制體上有Rigidbody和Collider并掛載正確的傷害腳本。抽卡總是抽到同一張1. 卡池CardPool配置錯(cuò)誤allCards數(shù)組只包含一張卡。2. 概率配置rarityProbabilities總和不為100導(dǎo)致隨機(jī)數(shù)分布異常。1. 在Inspector中檢查CardPool資產(chǎn)確保allCards包含了所有可能抽到的卡。2. 確保各稀有度概率之和為100可以使用一個(gè)[Range]滑塊或自定義編輯器腳本來約束。游戲在手機(jī)上很卡1. 沒有使用對象池大量Instantiate/Destroy。2. 每幀進(jìn)行了昂貴的運(yùn)算如FindGameObjectsWithTag。3. 繪制調(diào)用Draw Calls過高。1. 對敵人、子彈等頻繁生成的對象實(shí)現(xiàn)對象池。2. 將查找操作頻率降低如改用協(xié)程并緩存查找結(jié)果。3. 使用幀調(diào)試器Frame Debugger和性能分析器Profiler定位瓶頸合并材質(zhì)使用精靈圖集Sprite Atlas。構(gòu)建后UI顯示錯(cuò)亂1. Canvas的Canvas Scaler設(shè)置與目標(biāo)分辨率不匹配。2. UI錨點(diǎn)Anchors設(shè)置不當(dāng)導(dǎo)致拉伸異常。1. 根據(jù)游戲類型設(shè)置Canvas Scaler如Scale With Screen Size。2. 在多種分辨率如16:9, 18:9, 19.5:9的模擬器下測試UI并正確設(shè)置錨點(diǎn)。調(diào)試技巧使用Debug.Log和Debug.DrawRay/Line在關(guān)鍵邏輯點(diǎn)輸出信息可視化攻擊范圍、射線檢測等。利用Unity的Profiler窗口分析CPU、GPU、內(nèi)存占用找到性能熱點(diǎn)。在Scene視圖使用Gizmos如我們在TowerController中做的可以非常直觀地看到塔的攻擊范圍。6. 項(xiàng)目擴(kuò)展與進(jìn)階方向至此一個(gè)融合了防御塔建造升級和抽卡養(yǎng)成的移動游戲核心框架已經(jīng)完成。你可以在此基礎(chǔ)上進(jìn)行深度擴(kuò)展更多塔類型與技能實(shí)現(xiàn)冰凍塔、毒傷塔、召喚塔等。為塔添加主動技能需要玩家手動釋放。更復(fù)雜的敵人設(shè)計(jì)具有不同抗性如魔法抗性、物理抗性的敵人鼓勵玩家搭配塔組??ㄅ蒲b備系統(tǒng)抽到的卡牌不僅可以解鎖塔還可以作為裝備給已建造的塔附加特殊屬性如攻擊附帶吸血、濺射。離線收益與任務(wù)系統(tǒng)增加每日任務(wù)、成就系統(tǒng)以及玩家離線后也能獲得少量資源的機(jī)制提升留存。數(shù)據(jù)持久化使用PlayerPrefs、JSON文件或SQLite保存玩家的卡牌收藏、資源數(shù)量和關(guān)卡進(jìn)度。引入AI為敵人添加更智能的尋路算法如A*或者為塔添加自動選擇最優(yōu)攻擊目標(biāo)最低血量、最高威脅的邏輯。網(wǎng)絡(luò)功能加入玩家排行榜、異步PVP互相防守對方的進(jìn)攻波次或者簡單的社交分享功能。這個(gè)項(xiàng)目涵蓋了Unity游戲開發(fā)中從核心玩法、數(shù)據(jù)配置、UI交互到移動端適配的完整鏈條。希望你能通過動手實(shí)踐這個(gè)項(xiàng)目不僅掌握具體的代碼實(shí)現(xiàn)更能理解如何將一個(gè)個(gè)獨(dú)立的系統(tǒng)塔防、抽卡有機(jī)地整合成一個(gè)可玩性高的完整游戲。