尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity塔防抽卡游戏开发实战:从零构建移动端策略与收集玩法

Unity塔防抽卡游戏开发实战:从零构建移动端策略与收集玩法 在实际游戏开发中防御塔和抽卡机制是两种非常流行且能有效提升游戏可玩性的设计元素。防御塔游戏考验玩家的策略布局和资源管理能力而抽卡机制则通过随机性和收集要素极大地增强了游戏的长期吸引力和商业潜力。将两者结合可以构建一个既有策略深度又充满收集乐趣的移动端游戏项目。本文旨在从零开始带领你使用 Unity 3D 引擎结合内置渲染管线打造一个包含防御塔建造、升级、战斗以及抽卡收集角色/塔楼等核心玩法的完整移动游戏原型。无论你是刚接触 Unity 的初学者还是希望系统学习特定机制整合的开发者都能通过本文获得从项目搭建、核心逻辑实现到移动端适配的完整实践路径。我们将从最基础的 Unity 工程创建开始逐步实现敌人寻路、塔防攻击、抽卡 UI 与逻辑、数据持久化等模块最终形成一个可在手机上运行测试的完整项目闭环。1. 理解项目核心机制与架构设计在动手写代码之前理清防御塔和抽卡两大核心机制如何协同工作是避免后续开发混乱的关键。一个典型的结合了这两种机制的游戏流程可能是玩家通过关卡战斗获得游戏货币使用货币进行抽卡获得新的防御塔或塔的升级部件然后用这些新单位去挑战更难的关卡。1.1 防御塔机制的核心组件防御塔机制通常包含以下几个相互关联的模块敌人系统 (Enemy System)负责生成敌人并让敌人沿着预设路径移动。每个敌人拥有生命值、移动速度、奖励金币等属性。路径系统 (Path System)定义关卡中敌人的移动路线通常由一系列路径点 (Waypoints) 组成。防御塔系统 (Tower System)这是核心。一个塔需要具备攻击范围 (Attack Range)一个圆形碰撞体或通过距离计算来检测敌人。目标选择策略 (Targeting Strategy)如何从范围内的多个敌人中选择攻击目标如最近、最强、最先进入等。攻击行为 (Attack Behavior)发射子弹、造成伤害、应用减速等效果。升级逻辑 (Upgrade Logic)消耗资源提升攻击力、射程、攻击速度等属性。关卡与波次管理 (Wave Management)控制敌人生成的节奏、波次数量和强度。1.2 抽卡机制的核心组件抽卡机制则更侧重于 UI、数据和随机性卡池数据 (Card Pool Data)定义所有可抽取的“卡牌”对象每个卡牌对应一种防御塔或升级道具包含其名称、图标、稀有度、属性、获取权重等。抽卡逻辑 (Gacha Logic)实现随机算法。最简单的可能是根据权重随机更复杂的会包含“保底”机制。资源管理 (Resource Management)管理用于抽卡的货币如钻石、金币以及抽卡结果的存储。用户界面 (UI)抽卡按钮、动画、结果展示面板、卡牌仓库界面等。1.3 项目架构设计思路为了保持代码清晰我们采用模块化的设计。整个项目可以划分为几个相对独立的“领域”通过定义良好的接口如事件、单例管理器进行通信。游戏管理器 (GameManager)作为单例管理游戏全局状态如当前关卡、玩家金币、生命值、游戏暂停/结束逻辑。敌人管理器 (EnemyManager)负责根据波次数据生成和回收敌人。塔管理器 (TowerManager)管理所有已建造的塔可能处理塔的建造、升级、出售。抽卡管理器 (GachaManager)作为单例管理卡池数据执行抽卡逻辑并将结果通知其他系统如为玩家解锁新塔。UI 管理器 (UIManager)控制所有游戏界面的显示与隐藏更新金币、生命值等显示。数据持久化 (Data Persistence)使用PlayerPrefs或JSON文件保存玩家的金币数量、已解锁的卡牌、关卡进度等。这种设计使得抽卡系统和塔防战斗系统既能独立开发和测试又能通过GameManager或事件系统进行数据交换例如抽卡获得新塔后塔的建造菜单中应出现该塔的选项。2. 环境准备与 Unity 项目初始化工欲善其事必先利其器。确保你的开发环境配置正确是项目顺利推进的第一步。2.1 软件与工具准备Unity Hub Unity Editor从 Unity 官网下载并安装 Unity Hub。通过 Hub 安装一个长期支持 (LTS) 版本例如2022.3.x。在安装时确保勾选“Android Build Support”如果你要发布到安卓或“iOS Build Support”针对苹果设备。对于本教程使用内置渲染管线 (Built-in Render Pipeline) 即可它足够满足我们的需求且更简单。代码编辑器推荐使用Visual StudioWindows或Visual Studio for Mac并确保在 Unity Hub 的安装设置中关联好。Rider 也是一个强大的选择。目标平台 SDKAndroid需要安装 Java JDK 和 Android SDK NDK。Unity Hub 通常可以帮你一键安装。iOS需要在 macOS 上安装 Xcode。2.2 创建新项目与初始设置打开 Unity Hub点击“新建项目”。选择“3D (Core)”模板。将项目命名为“TowerDefenseGacha”并选择一个合适的存储位置。点击“创建项目”。等待 Unity 初始化完毕。项目创建后首先进行一些基础设置设置目标平台点击菜单栏File - Build Settings。选择Android或iOS然后点击Switch Platform。这个过程可能需要一些时间。创建文件夹结构在 Project 窗口的 Assets 文件夹下创建以下文件夹来组织资源这是一个良好的实践_Scripts(脚本)Prefabs(预制体)Scenes(场景)Materials(材质)Textures(贴图)UI(用户界面素材)Audio(音效)保存初始场景将默认的SampleScene重命名为MainGame并拖入Assets/Scenes文件夹。3. 构建基础塔防游戏框架我们先实现一个最基础的塔防玩法这是整个项目的战斗核心。3.1 创建敌人与路径系统设计路径在场景中创建一个空物体命名为Path。为其创建几个子空物体命名为Waypoint_1,Waypoint_2... 这些子物体的位置就是敌人的移动路径点。你可以将它们排列成一条曲折的路线。创建敌人预制体在场景中创建一个3D Object - Capsule命名为Enemy。缩放至合适大小如(0.8, 0.8, 0.8)。为其添加一个材质比如设为红色以便区分。为其添加一个Rigidbody组件并勾选Is Kinematic我们通过脚本控制移动而非物理模拟。创建一个 C# 脚本Enemy.cs拖到敌人上。然后将这个敌人从 Hierarchy 拖到Assets/Prefabs文件夹创建为预制体。最后删除场景中的实例。编写敌人移动脚本(Enemy.cs)using UnityEngine; using System.Collections.Generic; public class Enemy : MonoBehaviour { public float startHealth 100f; public float moveSpeed 5f; public int goldReward 10; private float currentHealth; private int waypointIndex 0; private ListTransform waypoints; void Start() { currentHealth startHealth; // 假设路径点的父物体叫“Path” GameObject pathObject GameObject.Find(Path); if (pathObject ! null) { waypoints new ListTransform(); foreach (Transform child in pathObject.transform) { waypoints.Add(child); } } // 初始位置设为第一个路径点 if (waypoints.Count 0) transform.position waypoints[0].position; } void Update() { Move(); } void Move() { if (waypoints null || waypointIndex waypoints.Count) { ReachDestination(); return; } Vector3 targetPos waypoints[waypointIndex].position; Vector3 dir (targetPos - transform.position).normalized; transform.Translate(dir * moveSpeed * Time.deltaTime, Space.World); // 让敌人面朝移动方向 if (dir ! Vector3.zero) transform.rotation Quaternion.LookRotation(dir); if (Vector3.Distance(transform.position, targetPos) 0.1f) { waypointIndex; } } public void TakeDamage(float damage) { currentHealth - damage; if (currentHealth 0) { Die(); } } void Die() { // 通知游戏管理器增加金币 GameManager.Instance.AddGold(goldReward); Destroy(gameObject); } void ReachDestination() { // 敌人到达终点扣减玩家生命值 GameManager.Instance.TakeLifeDamage(1); Destroy(gameObject); } }创建敌人生成器创建一个空物体EnemySpawner挂载脚本EnemySpawner.cs。这个脚本将根据波次数据在路径起点第一个路径点生成敌人。using UnityEngine; using System.Collections; public class EnemySpawner : MonoBehaviour { public GameObject enemyPrefab; public float timeBetweenWaves 5f; public int[] enemiesPerWave; // 每波敌人的数量在Inspector中设置 private int currentWave 0; private bool isSpawning false; void Start() { StartCoroutine(StartNextWave()); } IEnumerator StartNextWave() { yield return new WaitForSeconds(timeBetweenWaves); if (currentWave enemiesPerWave.Length) { StartCoroutine(SpawnWave(enemiesPerWave[currentWave])); currentWave; } else { Debug.Log(所有波次结束); // 可以触发关卡胜利逻辑 } } IEnumerator SpawnWave(int enemyCount) { isSpawning true; for (int i 0; i enemyCount; i) { SpawnEnemy(); yield return new WaitForSeconds(1f); // 每个敌人间隔1秒 } isSpawning false; // 等待本波所有敌人被消灭或到达终点后开始下一波 StartCoroutine(StartNextWave()); } void SpawnEnemy() { if (enemyPrefab ! null) { Instantiate(enemyPrefab, transform.position, Quaternion.identity); } } }3.2 创建防御塔系统创建塔预制体创建一个3D Object - Cylinder作为塔身命名为BasicTower。为其创建一个子物体如一个 Cube 或 Sphere作为炮塔旋转部分命名为TurretHead。在TurretHead下再创建一个子物体如一个细长的 Cube作为炮管命名为Barrel用于指示攻击方向。为塔身添加一个材质如蓝色。为塔添加一个Sphere Collider将其Is Trigger设为true并调整半径作为攻击范围。取消勾选Rigidbody如果需要的话。将整个BasicTower拖成预制体。编写基础塔脚本(Tower.cs)using UnityEngine; using System.Collections.Generic; public class Tower : MonoBehaviour { public float attackRange 5f; public float attackRate 1f; // 每秒攻击次数 public float attackDamage 20f; public GameObject bulletPrefab; public Transform firePoint; // 子弹发射点可以拖拽Barrel的末端 private float attackCountdown 0f; private Transform target; private ListGameObject enemiesInRange new ListGameObject(); void Update() { UpdateTarget(); if (target ! null) { // 让炮塔旋转部分朝向目标 Vector3 dir target.position - transform.position; Quaternion lookRotation Quaternion.LookRotation(dir); Vector3 rotation lookRotation.eulerAngles; // 假设TurretHead是旋转部分 Transform turretHead transform.Find(TurretHead); if (turretHead ! null) turretHead.rotation Quaternion.Euler(0f, rotation.y, 0f); if (attackCountdown 0f) { Attack(); attackCountdown 1f / attackRate; } attackCountdown - Time.deltaTime; } } void UpdateTarget() { // 简单的目标选择选择列表中的第一个敌人最先进入范围 if (enemiesInRange.Count 0) { // 可以在这里实现更复杂的逻辑如选择最近的敌人 target enemiesInRange[0].transform; } else { target null; } } void Attack() { if (bulletPrefab ! null firePoint ! null) { GameObject bulletGO Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); Bullet bullet bulletGO.GetComponentBullet(); if (bullet ! null) { bullet.Seek(target); bullet.damage attackDamage; } } } void OnTriggerEnter(Collider other) { if (other.CompareTag(Enemy)) { enemiesInRange.Add(other.gameObject); } } void OnTriggerExit(Collider other) { if (other.CompareTag(Enemy)) { enemiesInRange.Remove(other.gameObject); } } // 在Unity编辑器中可视化攻击范围 void OnDrawGizmosSelected() { Gizmos.color Color.red; Gizmos.DrawWireSphere(transform.position, attackRange); } }创建子弹脚本与预制体创建一个 Sphere 作为子弹添加刚体 (Rigidbody) 并勾选Is Kinematic。创建脚本Bullet.cs并挂载。将其拖成预制体BulletPrefab然后在 Tower 的 Inspector 中赋值。using UnityEngine; public class Bullet : MonoBehaviour { public float speed 30f; public float explosionRadius 0f; // 用于未来实现范围伤害 public float damage 20f; private Transform target; public void Seek(Transform _target) { target _target; } void Update() { if (target null) { Destroy(gameObject); return; } Vector3 dir target.position - transform.position; float distanceThisFrame speed * Time.deltaTime; // 如果这一帧会击中目标 if (dir.magnitude distanceThisFrame) { HitTarget(); return; } transform.Translate(dir.normalized * distanceThisFrame, Space.World); transform.LookAt(target); } void HitTarget() { // 简单伤害逻辑 Enemy enemy target.GetComponentEnemy(); if (enemy ! null) { enemy.TakeDamage(damage); } Destroy(gameObject); } }3.3 实现游戏管理器与基础 UI创建游戏管理器(GameManager.cs)这是一个单例管理游戏状态。using UnityEngine; using UnityEngine.UI; public class GameManager : MonoBehaviour { public static GameManager Instance; public int playerGold 100; public int playerLives 20; public Text goldText; public Text livesText; void Awake() { if (Instance null) Instance this; else if (Instance ! this) Destroy(gameObject); } void Start() { UpdateUI(); } public void AddGold(int amount) { playerGold amount; UpdateUI(); } public bool SpendGold(int amount) { if (playerGold amount) { playerGold - amount; UpdateUI(); return true; } else { Debug.Log(金币不足); return false; } } public void TakeLifeDamage(int damage) { playerLives - damage; UpdateUI(); if (playerLives 0) { GameOver(); } } void UpdateUI() { if (goldText ! null) goldText.text 金币: playerGold; if (livesText ! null) livesText.text 生命: playerLives; } void GameOver() { Debug.Log(游戏结束); Time.timeScale 0f; // 暂停游戏 // 可以在这里显示游戏结束UI } }创建基础 UI在 Hierarchy 中右键 -UI - Canvas。将 Canvas 的Render Mode设为Screen Space - Overlay。在 Canvas 下创建两个UI - Text分别命名为GoldText和LivesText。调整它们的位置如屏幕左上角。将这两个 Text 组件分别拖拽到GameManager脚本的对应字段中。至此一个最基础的塔防框架已经完成。你可以运行游戏看到敌人生成并沿着路径移动塔会自动攻击进入范围的敌人敌人死亡会奖励金币到达终点会扣减生命值。4. 实现抽卡机制与系统集成现在我们将抽卡系统集成到塔防框架中。假设抽卡可以获得新的防御塔类型。4.1 设计卡牌数据与卡池创建卡牌数据类在_Scripts下创建文件夹Data新建一个 C# 脚本CardData.cs。这是一个纯粹的 数据容器 不继承MonoBehaviour。[System.Serializable] // 使其可序列化方便在Inspector中编辑和保存 public class CardData { public string cardId; // 卡牌唯一标识 public string cardName; // 卡牌名称 public string description; // 描述 public Sprite icon; // UI图标 public TowerType towerType; // 关联的塔类型枚举 public int rarity; // 稀有度1普通2稀有3史诗... public int weight; // 抽卡权重权重越高越容易抽到 } // 定义塔类型枚举 public enum TowerType { Basic, Cannon, Slow, Splash // 可以继续扩展 }创建卡池管理器(CardPoolManager.cs)这是一个单例负责加载和管理所有卡牌数据。using UnityEngine; using System.Collections.Generic; public class CardPoolManager : MonoBehaviour { public static CardPoolManager Instance; public ListCardData allCards new ListCardData(); // 在Inspector中手动配置或从JSON加载 void Awake() { if (Instance null) Instance this; else if (Instance ! this) Destroy(gameObject); DontDestroyOnLoad(gameObject); // 跨场景不销毁 } // 根据权重随机抽取一张卡 public CardData DrawRandomCard() { int totalWeight 0; foreach (CardData card in allCards) { totalWeight card.weight; } int randomPoint Random.Range(0, totalWeight); int currentWeight 0; foreach (CardData card in allCards) { currentWeight card.weight; if (randomPoint currentWeight) { return card; } } // 理论上不会走到这里 return null; } // 十连抽 public ListCardData DrawTenCards() { ListCardData results new ListCardData(); for (int i 0; i 10; i) { results.Add(DrawRandomCard()); } return results; } }配置卡池在场景中创建一个空物体CardPoolManager挂载上述脚本。在 Inspector 中展开All Cards列表点击“”号添加条目并为每个条目填写卡牌信息需要提前准备好图标 Sprite。4.2 创建抽卡 UI 与逻辑创建抽卡 UI在 Canvas 下创建一个Panel命名为GachaPanel作为抽卡的主界面。初始状态设为Active为false。在GachaPanel下创建一个Button作为单抽按钮 (DrawOneButton)。一个Button作为十连抽按钮 (DrawTenButton)。一个Text显示抽卡消耗 (CostText)。一个Image作为结果显示区域 (ResultDisplay)。一个Button作为关闭按钮 (CloseButton)。编写抽卡 UI 控制器(GachaUIController.cs)using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; public class GachaUIController : MonoBehaviour { public GameObject gachaPanel; // 抽卡面板 public Button drawOneButton; public Button drawTenButton; public Text costText; public Image resultDisplayImage; // 用于显示抽到卡牌的图标 public Text resultNameText; // 显示抽到卡牌的名字 public int drawOneCost 100; public int drawTenCost 900; // 通常有折扣 void Start() { drawOneButton.onClick.AddListener(OnDrawOneClicked); drawTenButton.onClick.AddListener(OnDrawTenClicked); UpdateCostDisplay(); // 默认隐藏面板 if (gachaPanel ! null) gachaPanel.SetActive(false); } void UpdateCostDisplay() { costText.text $单抽: {drawOneCost}金币\n十连: {drawTenCost}金币; } // 打开抽卡界面可以由主UI的某个按钮触发 public void OpenGachaPanel() { if (gachaPanel ! null) gachaPanel.SetActive(true); } public void CloseGachaPanel() { if (gachaPanel ! null) gachaPanel.SetActive(false); } void OnDrawOneClicked() { if (GameManager.Instance.SpendGold(drawOneCost)) { CardData drawnCard CardPoolManager.Instance.DrawRandomCard(); ShowResult(drawnCard); // 重要解锁这张卡对应的塔 UnlockTower(drawnCard); } } void OnDrawTenClicked() { if (GameManager.Instance.SpendGold(drawTenCost)) { ListCardData drawnCards CardPoolManager.Instance.DrawTenCards(); // 简单处理只显示最后一张实际项目需要更华丽的展示 if (drawnCards.Count 0) { ShowResult(drawnCards[drawnCards.Count - 1]); foreach (var card in drawnCards) { UnlockTower(card); } } } } void ShowResult(CardData card) { if (card ! null) { resultDisplayImage.sprite card.icon; resultNameText.text card.cardName; // 可以在这里播放特效或动画 Debug.Log($抽到了: {card.cardName} (稀有度: {card.rarity})); } } void UnlockTower(CardData card) { // 这里需要将解锁的塔类型信息传递给“塔建造UI”或“塔管理器” // 例如可以触发一个事件或者直接调用建造UI的解锁方法 TowerBuildManager.Instance.UnlockTowerType(card.towerType); Debug.Log($已解锁塔类型: {card.towerType}); } }4.3 集成抽卡与塔防解锁新塔抽卡的目的是解锁新的塔。我们需要一个中心来管理玩家已解锁的塔类型并更新建造菜单。创建塔建造管理器(TowerBuildManager.cs)using UnityEngine; using System.Collections.Generic; public class TowerBuildManager : MonoBehaviour { public static TowerBuildManager Instance; // 塔类型与预制体的映射 public DictionaryTowerType, GameObject towerPrefabDict new DictionaryTowerType, GameObject(); // 玩家已解锁的塔类型列表 public ListTowerType unlockedTowerTypes new ListTowerType(); // 在Inspector中预先关联好预制体 public GameObject basicTowerPrefab; public GameObject cannonTowerPrefab; // 等等 void Awake() { if (Instance null) Instance this; else if (Instance ! this) Destroy(gameObject); // 初始化字典 towerPrefabDict[TowerType.Basic] basicTowerPrefab; towerPrefabDict[TowerType.Cannon] cannonTowerPrefab; // ... 初始化其他类型 // 初始解锁基础塔 unlockedTowerTypes.Add(TowerType.Basic); } // 由GachaUIController调用解锁新塔 public void UnlockTowerType(TowerType type) { if (!unlockedTowerTypes.Contains(type)) { unlockedTowerTypes.Add(type); // 通知UI更新例如在建造按钮栏显示新塔的按钮 UIManager.Instance.UpdateTowerBuildButtons(); } } // 获取当前选中的塔类型由UI按钮设置 private TowerType selectedTowerType TowerType.Basic; public void SelectTowerType(TowerType type) { if (unlockedTowerTypes.Contains(type)) { selectedTowerType type; } } // 在指定位置建造选中的塔 public void BuildTowerAt(Vector3 position) { if (towerPrefabDict.ContainsKey(selectedTowerType)) { GameObject towerToBuild towerPrefabDict[selectedTowerType]; // 这里应该检查金币是否足够位置是否合法等 Instantiate(towerToBuild, position, Quaternion.identity); } } }更新 UI 管理器(UIManager.cs)创建一个简单的 UI 管理器负责根据unlockedTowerTypes动态生成或激活建造按钮。现在游戏流程已经串联起来玩家通过战斗获得金币 - 打开抽卡界面消耗金币抽卡 - 抽到新塔类型并解锁 - 在塔建造菜单中可以使用新类型的塔进行建造。5. 移动端适配、数据持久化与常见问题5.1 移动端输入与 UI 适配在移动设备上我们需要将 PC 端的鼠标点击操作替换为触摸操作。塔的建造修改塔建造逻辑通过射线检测触摸点来确定建造位置。// 在 TowerBuildManager 或一个专门的 InputHandler 中 void Update() { if (Input.touchCount 0 Input.GetTouch(0).phase TouchPhase.Began) { // 检查是否点击了UI元素如果是则忽略 if (EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId)) return; Ray ray Camera.main.ScreenPointToRay(Input.GetTouch(0).position); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { // 假设地面有一个“Ground”层 if (hit.collider.gameObject.layer LayerMask.NameToLayer(Ground)) { BuildTowerAt(hit.point); } } } // PC端备用方便测试 if (Input.GetMouseButtonDown(0)) { if (EventSystem.current.IsPointerOverGameObject()) return; Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { if (hit.collider.gameObject.layer LayerMask.NameToLayer(Ground)) { BuildTowerAt(hit.point); } } } }UI 缩放在 Canvas Scaler 组件上将UI Scale Mode设置为Scale With Screen Size并设定一个参考分辨率如 1920x1080。确保按钮和文字在不同屏幕尺寸下清晰可点。5.2 数据持久化保存游戏进度我们需要保存玩家的金币、已解锁的塔和关卡进度。这里使用简单的PlayerPrefs。using UnityEngine; using System.Collections.Generic; public class SaveSystem : MonoBehaviour { private const string GOLD_KEY PlayerGold; private const string UNLOCKED_TOWERS_KEY UnlockedTowers; // 存储为逗号分隔的字符串如 Basic,Cannon public void SaveGame() { PlayerPrefs.SetInt(GOLD_KEY, GameManager.Instance.playerGold); // 保存已解锁的塔 string unlockedTowersString string.Join(,, TowerBuildManager.Instance.unlockedTowerTypes); PlayerPrefs.SetString(UNLOCKED_TOWERS_KEY, unlockedTowersString); PlayerPrefs.Save(); Debug.Log(游戏已保存); } public void LoadGame() { if (PlayerPrefs.HasKey(GOLD_KEY)) { GameManager.Instance.playerGold PlayerPrefs.GetInt(GOLD_KEY); GameManager.Instance.UpdateUI(); } if (PlayerPrefs.HasKey(UNLOCKED_TOWERS_KEY)) { string unlockedTowersString PlayerPrefs.GetString(UNLOCKED_TOWERS_KEY); string[] towerNames unlockedTowersString.Split(,); TowerBuildManager.Instance.unlockedTowerTypes.Clear(); foreach (string name in towerNames) { if (System.Enum.TryParse(name, out TowerType type)) { TowerBuildManager.Instance.unlockedTowerTypes.Add(type); } } UIManager.Instance.UpdateTowerBuildButtons(); } Debug.Log(游戏已加载); } // 可以在游戏退出、切换场景或特定时机调用SaveGame void OnApplicationQuit() { SaveGame(); } }注意PlayerPrefs适合存储简单数据。对于更复杂的结构如卡牌收集进度、多个存档建议使用JSON或XML序列化到文件中。5.3 常见问题与排查在开发过程中你可能会遇到以下典型问题问题现象可能原因检查与解决思路敌人不移动或移动方向错误1.Enemy.cs脚本中waypoints列表为空或未正确获取。2. 路径点Path物体的名称或层级不对。3. 敌人预制体上的Enemy脚本未启用。1. 在Enemy.Start()中打印waypoints.Count检查。2. 确认场景中存在名为Path的 GameObject且其子物体是路径点。3. 检查预制体及其场景实例。塔不攻击敌人1. 敌人没有Tag设为 “Enemy”。2. 塔的Sphere Collider的Is Trigger未勾选。3. 子弹预制体未正确赋值给Tower脚本。4.firePoint未赋值。1. 检查敌人预制体的 Tag。2. 检查塔的碰撞体设置。3. 在 Inspector 中确认bulletPrefab和firePoint字段已拖拽赋值。4. 使用Debug.DrawRay在Update中绘制攻击射线辅助调试。抽卡 UI 按钮点击无反应1. UI 按钮的OnClick()事件未绑定方法。2.GachaUIController脚本未挂载或未激活。3. 金币不足但SpendGold的失败提示未显示。1. 在 Inspector 中检查按钮的事件列表。2. 确认脚本所在 GameObject 是激活的。3. 在SpendGold方法中添加更明显的反馈如 UI 飘字。建造塔时金币被扣除但塔未出现1.BuildTowerAt方法中的位置无效如碰撞检测失败。2.towerPrefabDict中对应TowerType的预制体为null。3. 实例化位置被其他物体遮挡。1. 在BuildTowerAt中打印position和selectedTowerType进行调试。2. 检查TowerBuildManager的 Inspector确保所有预制体已赋值。3. 确保建造位置地面有正确的 Layer 并被射线检测到。游戏打包到手机后运行崩溃或黑屏1. 脚本存在平台相关代码如System.IO路径问题。2. 资源未正确包含在构建中。3. 移动端输入处理有问题。1. 使用Application.persistentDataPath替代硬编码路径。2. 在Build Settings - Player Settings中检查图标、分辨率等设置。3. 在 Unity Editor 中使用 Remote 真机调试功能连接手机查看日志。5.4 性能优化与最佳实践建议对象池敌人和子弹频繁创建和销毁会产生垃圾回收压力。务必实现简单的对象池来复用它们。事件系统使用 C# 事件或 UnityEvent 来解耦模块。例如敌人死亡时发布一个事件GameManager和成就系统监听该事件而不是直接调用GameManager.Instance.AddGold()。配置数据外置将敌人属性、波次信息、塔属性、卡牌数据等存储在ScriptableObject或 JSON/XML 文件中便于策划调整而无需修改代码。状态管理明确游戏状态如菜单、战斗中、暂停、抽卡中并防止状态冲突如在抽卡动画中不能建造塔。音频与粒子为攻击、抽卡、UI 点击等添加音效和粒子效果能极大提升游戏体验。通过以上步骤你已经完成了一个融合防御塔与抽卡机制的 Unity 3D 移动游戏核心原型。这个项目涵盖了从基础框架搭建、核心玩法实现、系统集成到移动端适配的完整流程。你可以在此基础上继续扩展例如加入更多类型的塔和敌人、设计更丰富的卡牌和保底机制、制作关卡选择界面、添加音效和粒子特效、接入 SDK 实现广告与内购等逐步将其打磨成一个完整的可发布游戏。
返回列表