
最近在独立游戏开发圈一个名为“Lost Marbles”的塔防3D武器大战项目引起了不小的讨论。很多开发者尤其是刚接触Unity 3D塔防游戏制作的朋友在尝试复刻或理解其核心玩法时常常卡在几个关键环节如何高效管理多种武器系统3D场景下的敌人寻路与塔的攻击逻辑如何协同以及如何让游戏既有策略深度又不失爽快的战斗体验。本文将以“Lost Marbles/塔防3D武器大战”这类游戏为蓝本为你拆解一套从零到一的完整开发实战流程。我们将聚焦于Unity引擎涵盖3D场景搭建、可升级的武器塔系统、智能敌人AI、关卡管理与经济系统等核心模块。无论你是想学习3D塔防游戏的基础架构还是希望为自己的项目寻找一个可复用的框架这篇文章都能提供清晰的代码示例和工程化的实现思路。让我们从最基础的项目搭建开始一步步构建属于你自己的“武器大战”。1. 核心概念与游戏设计拆解在开始写代码之前我们需要明确我们要构建的是一个什么样的游戏。所谓“3D武器大战”塔防其核心玩法通常包含以下几个要素防御塔武器玩家放置的静态或有限旋转的攻击单位。每种武器拥有独特的属性如攻击范围、伤害值、攻击速度、子弹特效和特殊能力如减速、溅射、穿透。敌人波次沿着预定路径向终点移动的单位。它们拥有生命值、移动速度、护甲等属性被武器击败后玩家获得资源。路径Path在3D场景中预先设计好的敌人移动路线通常由一系列路径点Waypoints组成。资源与经济系统玩家通过击败敌人获得金币或积分用于建造新武器或升级现有武器。关卡与波次管理游戏按关卡进行每个关卡包含多波敌人波次间有间隔时间供玩家调整防御。与2D塔防相比3D塔防在实现上需要额外考虑3D空间计算攻击范围是一个球体Sphere而非圆形射线检测Raycast和碰撞检测Collider都发生在三维空间。视觉表现需要处理3D模型、粒子特效、光照和摄像机角度给玩家更沉浸的体验。寻路算法虽然核心仍是沿着路径点移动但在3D地形起伏或存在简单障碍时可能需要更复杂的处理。理解了这些我们的开发目标就清晰了在Unity中创建一个3D场景实现敌人沿路径移动允许玩家在指定位置放置武器武器自动搜索并攻击范围内的敌人。2. 开发环境与项目初始化工欲善其事必先利其器。以下是本次实战所需的开发环境游戏引擎Unity 2021.3 LTS 或更高版本。LTS版本稳定性好适合项目开发。编程语言C#。我们将使用Visual Studio 2019/2022或Rider作为代码编辑器。核心Unity模块确保安装了“Universal RP”通用渲染管线用于更好的图形效果或保持内置渲染管线也可。项目初始化步骤打开Unity Hub点击“New Project”。选择“3D Core”模板如果使用URP可选择“3D (URP)”模板为项目命名例如“3D_TowerDefense_Demo”选择保存位置后创建。项目创建后我们先规划一下文件夹结构保持工程整洁Assets/ ├── _Scripts │ ├── Managers │ ├── Tower │ ├── Enemy │ └── UI ├── _Prefabs │ ├── Towers │ ├── Enemies │ └── Effects ├── _Scenes ├── _Materials ├── _Textures └── _Audio在Project窗口右键创建这些文件夹。3. 构建游戏场景与路径系统游戏世界是战斗发生的地方我们首先搭建一个简单的测试场景。创建基础地形在Hierarchy窗口右键 - 3D Object - Plane命名为“Ground”。调整其Scale例如(10,1,10)作为一个大的地面。设置路径点路径是敌人行进的轨道。我们使用空物体Empty GameObject作为路径点。在Hierarchy中创建一个空物体命名为“Path”。在“Path”下创建多个空物体分别命名为“Waypoint_0”, “Waypoint_1”, “Waypoint_2”…。将这些路径点摆放在地面Ground上方形成一条你希望的敌人行进路线。例如从场景一侧蜿蜒至另一侧。确保每个路径点的Y轴位置大致相同或根据你的地形设计起伏。创建路径脚本我们需要一个脚本管理这些路径点并提供给敌人查询。// 文件路径Assets/_Scripts/Managers/WaypointPath.cs using System.Collections.Generic; using UnityEngine; public class WaypointPath : MonoBehaviour { // 在Inspector面板中按顺序拖入路径点 public ListTransform waypoints new ListTransform(); // 绘制Gizmos在Scene视图中可视化路径 void OnDrawGizmos() { if (waypoints null || waypoints.Count 2) return; Gizmos.color Color.red; for (int i 0; i waypoints.Count - 1; i) { if (waypoints[i] ! null waypoints[i 1] ! null) { Gizmos.DrawLine(waypoints[i].position, waypoints[i 1].position); Gizmos.DrawSphere(waypoints[i].position, 0.2f); } } // 绘制最后一个路径点的球体 if (waypoints[waypoints.Count - 1] ! null) { Gizmos.DrawSphere(waypoints[waypoints.Count - 1].position, 0.2f); } } // 获取路径点数量 public int GetPathLength() { return waypoints.Count; } // 根据索引获取路径点位置 public Vector3 GetWaypointPosition(int index) { if (index 0 index waypoints.Count waypoints[index] ! null) { return waypoints[index].position; } return Vector3.zero; } }将WaypointPath脚本挂载到之前创建的“Path”物体上。然后在Inspector面板中将“Waypoint_0”、“Waypoint_1”等按顺序拖拽到waypoints列表中。现在你在Scene视图中可以看到一条红色的连线清晰地标出了敌人的行进路径。4. 实现敌人Enemy系统敌人是游戏中的攻击目标它们需要沿着路径移动并拥有生命、速度等属性。创建敌人预制体在Hierarchy中右键 - 3D Object - Capsule命名为“Enemy_Base”。可以为其添加一个简单的材质改变颜色如红色。将其拖入Assets/_Prefabs/Enemies文件夹生成预制体。然后删除场景中的实例。编写敌人移动脚本// 文件路径Assets/_Scripts/Enemy/EnemyController.cs using UnityEngine; public class EnemyController : MonoBehaviour { public float moveSpeed 3.0f; public int maxHealth 100; private int currentHealth; private WaypointPath path; // 对路径的引用 private int currentWaypointIndex 0; private bool isActive false; void Start() { currentHealth maxHealth; // 动态查找场景中的路径更好的方式是通过GameManager注入 path FindObjectOfTypeWaypointPath(); if (path ! null path.GetPathLength() 0) { isActive true; transform.position path.GetWaypointPosition(0); // 出生在第一个路径点 } } void Update() { if (!isActive || path null) return; // 获取当前目标路径点位置 Vector3 targetPosition path.GetWaypointPosition(currentWaypointIndex); // 计算移动方向 Vector3 moveDirection (targetPosition - transform.position).normalized; // 移动 transform.position moveDirection * moveSpeed * Time.deltaTime; // 让敌人面朝移动方向可选 if (moveDirection ! Vector3.zero) { transform.rotation Quaternion.LookRotation(moveDirection); } // 检查是否到达当前路径点 if (Vector3.Distance(transform.position, targetPosition) 0.1f) { currentWaypointIndex; // 如果到达最后一个路径点敌人“逃脱”需要扣减玩家生命值并销毁自身 if (currentWaypointIndex path.GetPathLength()) { OnReachedEnd(); return; } } } // 受到伤害的方法 public void TakeDamage(int damage) { currentHealth - damage; if (currentHealth 0) { Die(); } } private void Die() { // 触发死亡特效、音效奖励金币等 Debug.Log(gameObject.name 被击败); // 通知游戏管理器敌人死亡增加金币 GameManager.Instance?.AddGold(10); Destroy(gameObject); } private void OnReachedEnd() { Debug.Log(gameObject.name 到达终点); // 通知游戏管理器玩家生命值减少 GameManager.Instance?.TakePlayerDamage(1); Destroy(gameObject); } }创建敌人生成器Spawner 敌人不是一次性出现的而是按波次生成。我们需要一个生成管理器。// 文件路径Assets/_Scripts/Managers/EnemySpawner.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class EnemySpawner : MonoBehaviour { public GameObject enemyPrefab; // 敌人预制体 public Transform spawnPoint; // 生成点可以设置为第一个路径点 public float timeBetweenWaves 5f; // 波次间隔 public float timeBetweenSpawns 0.5f; // 同一波内敌人生成间隔 public int enemiesPerWave 10; // 每波敌人数量 private int currentWave 0; private bool isSpawning false; void Start() { StartCoroutine(StartNextWave()); } IEnumerator StartNextWave() { yield return new WaitForSeconds(timeBetweenWaves); currentWave; Debug.Log(第 currentWave 波开始); StartCoroutine(SpawnWave()); } IEnumerator SpawnWave() { isSpawning true; for (int i 0; i enemiesPerWave; i) { SpawnEnemy(); yield return new WaitForSeconds(timeBetweenSpawns); } isSpawning false; // 等待所有敌人都被消灭或到达终点后开始下一波这里简化处理直接等待一段时间 // 更完善的逻辑是监听场上敌人数量 yield return new WaitForSeconds(5f); // 给一个缓冲时间 StartCoroutine(StartNextWave()); } void SpawnEnemy() { if (enemyPrefab ! null spawnPoint ! null) { Instantiate(enemyPrefab, spawnPoint.position, spawnPoint.rotation); } } }在场景中创建一个空物体“EnemySpawner”挂载此脚本。将之前创建的敌人预制体拖入enemyPrefab将“Waypoint_0”拖入spawnPoint。运行游戏你应该能看到敌人胶囊体沿着红色路径一波一波地向终点移动。5. 实现武器塔Tower系统武器塔是玩家互动的核心。我们需要实现塔的建造、攻击逻辑和升级。创建塔基建造点在场景地面Ground上创建一些Cube缩放成扁平状如Scale为 (1, 0.1, 1)命名为“BuildSite_1”、“BuildSite_2”等。这些就是可以建塔的位置。为它们添加一个简单的材质如灰色以示区别。创建塔预制体与脚本创建一个Cylinder或导入一个塔的3D模型命名为“Tower_Base”。为其添加一个子物体空物体命名为“FirePoint”放在塔顶用于作为子弹生成的起点。创建塔的核心脚本// 文件路径Assets/_Scripts/Tower/TowerController.cs using System.Collections; using UnityEngine; public class TowerController : MonoBehaviour { [Header(攻击属性)] public float attackRange 5f; // 攻击范围 public float attackRate 1f; // 每秒攻击次数 public int attackDamage 20; public GameObject bulletPrefab; // 子弹预制体 public Transform firePoint; // 子弹发射点 [Header(目标锁定)] private Transform currentTarget; private float attackCooldown 0f; [Header(升级)] public int upgradeCost 100; public int upgradeDamageIncrease 10; public float upgradeRangeIncrease 0.5f; void Update() { // 攻击冷却 if (attackCooldown 0) { attackCooldown - Time.deltaTime; } // 寻找目标 if (currentTarget null) { FindTarget(); } else { // 检查目标是否还在范围内 float distanceToTarget Vector3.Distance(transform.position, currentTarget.position); if (distanceToTarget attackRange || !currentTarget.gameObject.activeInHierarchy) { currentTarget null; } else { // 朝向目标仅Y轴旋转 Vector3 lookDir currentTarget.position - transform.position; lookDir.y 0; // 保持塔身不倾斜 if (lookDir ! Vector3.zero) { Quaternion targetRotation Quaternion.LookRotation(lookDir); transform.rotation Quaternion.Slerp(transform.rotation, targetRotation, Time.deltaTime * 5f); } // 攻击 if (attackCooldown 0) { Attack(); attackCooldown 1f / attackRate; } } } } void FindTarget() { // 查找所有带有“Enemy”标签的物体 GameObject[] enemies GameObject.FindGameObjectsWithTag(Enemy); Transform closestEnemy null; float closestDistance Mathf.Infinity; foreach (GameObject enemy in enemies) { float distance Vector3.Distance(transform.position, enemy.transform.position); if (distance attackRange distance closestDistance) { closestDistance distance; closestEnemy enemy.transform; } } currentTarget closestEnemy; } void Attack() { if (bulletPrefab ! null firePoint ! null currentTarget ! null) { GameObject bulletGO Instantiate(bulletPrefab, firePoint.position, firePoint.rotation); Bullet bullet bulletGO.GetComponentBullet(); if (bullet ! null) { bullet.Seek(currentTarget, attackDamage); } } } // 在Scene视图中绘制攻击范围 void OnDrawGizmosSelected() { Gizmos.color Color.yellow; Gizmos.DrawWireSphere(transform.position, attackRange); } // 升级方法 public void Upgrade() { // 这里需要检查玩家金币是否足够由UI或GameManager调用 attackDamage upgradeDamageIncrease; attackRange upgradeRangeIncrease; Debug.Log(塔已升级伤害 attackDamage 范围 attackRange); } }将脚本挂载到“Tower_Base”上把“FirePoint”子物体拖拽到脚本的firePoint字段。别忘了给敌人预制体Enemy_Base打上“Enemy”标签。实现子弹Bullet逻辑创建一个Sphere作为子弹预制体缩小比例如Scale 0.2。为其添加刚体Rigidbody并取消使用重力添加碰撞器Collider。创建子弹脚本// 文件路径Assets/_Scripts/Tower/Bullet.cs using UnityEngine; public class Bullet : MonoBehaviour { public float speed 10f; public GameObject impactEffect; private Transform target; private int damage; public void Seek(Transform _target, int _damage) { target _target; damage _damage; } 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() { // 播放命中特效 if (impactEffect ! null) { Instantiate(impactEffect, transform.position, transform.rotation); } // 对敌人造成伤害 EnemyController enemy target.GetComponentEnemyController(); if (enemy ! null) { enemy.TakeDamage(damage); } Destroy(gameObject); } }将Bullet脚本挂载到子弹预制体上并将子弹预制体保存到_Prefabs/Effects文件夹。最后将这个子弹预制体拖拽到塔控制器TowerController的bulletPrefab字段。实现建造系统 玩家需要点击塔基来建造塔。这通常通过射线检测Raycast实现。// 文件路径Assets/_Scripts/Managers/BuildManager.cs using UnityEngine; public class BuildManager : MonoBehaviour { public static BuildManager Instance; // 单例模式方便访问 public GameObject towerPrefab; // 要建造的塔的预制体 private GameObject towerToBuild; // 当前选中的待建塔 void Awake() { if (Instance ! null) { Debug.LogError(多个BuildManager实例); return; } Instance this; } void Start() { // 初始化例如从UI按钮设置默认塔 towerToBuild towerPrefab; } // 由UI按钮调用设置要建造的塔类型 public void SetTowerToBuild(GameObject tower) { towerToBuild tower; } // 获取当前可建造的塔 public GameObject GetTowerToBuild() { return towerToBuild; } }然后我们需要一个脚本来处理塔基的点击事件// 文件路径Assets/_Scripts/Tower/BuildSite.cs using UnityEngine; public class BuildSite : MonoBehaviour { public Vector3 positionOffset new Vector3(0, 0.5f, 0); // 塔放置的位置偏移 private GameObject currentTower; // 当前已建造的塔 void OnMouseDown() { // 如果这个位置已经有塔了可以设计为升级或出售这里先简单处理 if (currentTower ! null) { Debug.Log(此处已有塔); return; } GameObject towerToBuild BuildManager.Instance.GetTowerToBuild(); if (towerToBuild null) return; // 这里应该检查玩家金币是否足够调用GameManager // if (GameManager.Instance.Gold towerToBuild.GetComponentTowerController().buildCost) return; // 扣除金币 // GameManager.Instance.SpendGold(cost); // 建造塔 GameObject tower Instantiate(towerToBuild, transform.position positionOffset, transform.rotation); currentTower tower; Debug.Log(塔已建造在 gameObject.name); } }将BuildSite脚本挂载到场景中每一个“BuildSite”物体上。现在运行游戏。当敌人波次开始后你可以点击场景中的灰色塔基塔就会被建造出来并自动攻击进入范围的敌人。6. 实现游戏管理、UI与经济系统一个完整的游戏需要状态管理和玩家交互界面。创建游戏管理器GameManager// 文件路径Assets/_Scripts/Managers/GameManager.cs using UnityEngine; using UnityEngine.UI; public class GameManager : MonoBehaviour { public static GameManager Instance; [Header(玩家状态)] public int playerHealth 20; public int playerGold 200; [Header(UI引用)] public Text healthText; public Text goldText; public GameObject gameOverPanel; void Awake() { if (Instance ! null Instance ! this) { Destroy(gameObject); } else { Instance this; } } 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 TakePlayerDamage(int damage) { playerHealth - damage; UpdateUI(); if (playerHealth 0) { GameOver(); } } void UpdateUI() { if (healthText ! null) healthText.text 生命: playerHealth; if (goldText ! null) goldText.text 金币: playerGold; } void GameOver() { Debug.Log(游戏结束); Time.timeScale 0; // 暂停游戏 if (gameOverPanel ! null) gameOverPanel.SetActive(true); } }搭建简单UI在Hierarchy右键 - UI - Canvas。在Canvas下创建两个TextUGUI - Text分别命名为“HealthText”和“GoldText”放在屏幕左上角。调整字体大小和颜色。创建一个Panel作为游戏结束界面命名为“GameOverPanel”里面可以放“Game Over”文字和一个“Restart”按钮。初始状态设置为不激活取消勾选Inspector顶部的复选框。将Canvas下的“HealthText”和“GoldText”拖拽到GameManager脚本的对应字段。将“GameOverPanel”拖拽到gameOverPanel字段。完善建造与升级的经济判断 修改BuildSite.cs和TowerController.cs中的建造和升级方法加入金币检查。在TowerController中增加一个buildCost变量。在BuildSite.OnMouseDown()中调用GameManager.Instance.SpendGold()来判断和消费。在TowerController.Upgrade()中同样先检查金币。7. 常见问题与调试技巧在实现上述系统时你可能会遇到一些典型问题问题现象可能原因排查与解决思路敌人不移动或移动方向错误1.WaypointPath脚本中的路径点列表顺序错误或为空。2. 敌人EnemyController脚本未正确获取到WaypointPath实例。3. 路径点位置在模型内部导致敌人卡住。1. 检查Path物体上waypoints列表的顺序和数量在Scene视图用Gizmos查看连线。2. 在敌人Start方法中打印path变量确认是否为null。考虑使用更稳健的引用方式如Singleton或依赖注入。3. 确保路径点在地面之上且路径是连贯的。塔不攻击敌人1. 敌人没有正确设置“Enemy”标签。2. 塔的attackRange设置过小。3.FindTarget方法逻辑错误或currentTarget未被正确赋值。4. 子弹预制体未设置或Bullet脚本缺失。1. 确认敌人预制体及其场景实例的Tag是否为“Enemy”。2. 在Scene视图中选中塔查看黄色线框范围是否覆盖敌人。3. 在TowerController.Update()中添加Debug.Log打印currentTarget和攻击逻辑。4. 检查塔的bulletPrefab字段和子弹上的Bullet脚本。点击塔基没有反应1.BuildSite脚本未挂载或生效。2. 塔基物体没有Collider如Box Collider。3. 存在其他UI元素阻挡了射线检测。1. 确认BuildSite脚本已挂载。2. 为每个塔基物体添加一个Box Collider组件。3. 检查Canvas的Graphic Raycaster或Event System设置确保没有阻挡点击。游戏运行卡顿随着敌人增多越来越慢1. 每帧使用GameObject.FindGameObjectsWithTag查找所有敌人性能开销大。2. 子弹和敌人销毁后未正确管理产生内存泄漏虽然Unity会处理但大量Instantiate/Destroy仍有开销。3. 没有使用对象池。1. 将敌人列表在EnemySpawner或GameManager中集中管理塔从该列表查询避免每帧全局查找。2. 为子弹和敌人实现简单的对象池Object Pooling复用游戏对象而非频繁创建销毁。性能优化小贴士对象池对于频繁生成和销毁的对象子弹、敌人使用对象池是标准做法。创建一个池管理器预先实例化一定数量的对象需要时激活不需要时禁用并放回池中。避免每帧FindFind、FindObjectOfType、FindGameObjectsWithTag等方法非常耗时应仅在初始化时调用一次然后将引用缓存起来。使用Layer进行碰撞检测为子弹和敌人设置不同的物理层Layer并使用Physics.OverlapSphere或指定Layer的射线检测比遍历所有游戏对象高效得多。8. 扩展思路与最佳实践一个基础的“武器大战”框架已经完成。要让游戏更具可玩性和工程化可以考虑以下扩展方向武器多样性创建不同的塔预制体如速射机枪、远程狙击炮、范围溅射炮塔、减速辅助塔。使用继承或ScriptableObject来管理不同塔的数据伤害、射速、范围、造价、升级链。在TowerController基类上派生出MachineGunTower、SniperTower等子类重写Attack等方法。敌人多样性创建不同的敌人预制体高速低血、慢速高甲、飞行单位、BOSS。使用同样的ScriptableObject来配置敌人属性。为敌人添加不同的移动模式如遇到减速效果会降低速度。完善的关卡系统将波次信息敌人类型、数量、生成间隔用JSON或ScriptableObject配置。创建LevelManager来读取关卡配置控制敌人生成器并在所有波次完成后进入下一关或显示胜利界面。升级与技能树为每种塔设计多级升级每级提升属性并可能改变外观。实现全局技能如临时全局攻击力提升、金币增益等。输入与UI优化实现拖拽建造而非点击建造。当鼠标悬停在塔基上时高亮显示。建造前显示攻击范围的半透明预览。创建详细的塔信息面板显示当前属性、升级预览和造价。代码结构优化使用事件C# Event Action解耦系统。例如敌人死亡时触发一个OnEnemyDied事件GameManager和UIManager监听此事件来更新金币和分数而不是在EnemyController中直接调用GameManager.Instance。将游戏配置如初始金币、生命值、塔属性分离到独立的GameConfig脚本或ScriptableObject中便于平衡和调整。从点击一个灰色方块建造第一座塔到看着它自动锁定并消灭成群结队的敌人这个过程的实现涵盖了3D游戏开发中寻路、AI、物理、UI和资源管理等多个核心模块。虽然这是一个简化版本但它提供了一个坚实且可扩展的起点。你可以在此基础上为武器添加酷炫的粒子特效和音效设计更复杂的地图和敌人类型甚至加入本地多人合作元素。游戏开发是一个不断迭代和打磨的过程最重要的是动手实践在遇到问题时善用Unity官方文档、社区论坛和调试工具一步步将你的“武器大战”构想变为现实。