Unity 2D炸弹人游戏开发实战:从零实现角色控制与爆炸系统
1. 项目概述与核心价值最近在带新人入门Unity发现很多朋友对2D游戏开发既向往又有点无从下手。理论看了一堆但不动手敲代码、不实际看到角色动起来总觉得隔着一层。正好之前为了教学我花了一周时间用Unity引擎复刻了一个类似经典游戏《QQ堂》核心玩法的2D炸弹人小Demo。这个项目麻雀虽小五脏俱全它完美地串联起了Unity2D开发中几个最核心、最实用的模块角色控制、物理交互、动画状态机、简单的敌人AI以及游戏逻辑管理。对于想从零开始亲手做出一个“能玩”的游戏的初学者来说这是一个绝佳的练手项目。它不追求华丽的画面和复杂的系统而是聚焦于“实现玩法”让你在解决一个个具体问题的过程中快速建立起对Unity2D工作流和C#脚本编程的直观理解。这个Demo的核心玩法大家应该都不陌生玩家控制一个角色在网格状的地图上移动可以放置炸弹。炸弹会在几秒后爆炸爆炸的火焰会沿着上下左右四个方向蔓延摧毁可破坏的砖块并消灭碰到的敌人。玩家的目标就是利用炸弹清空所有敌人同时要小心不被自己的炸弹炸到也不能被敌人碰到。听起来简单但要把这套逻辑用代码清晰地实现出来里面涉及的知识点非常密集。接下来我就把这个项目的完整实现过程、踩过的坑以及一些能让代码更优雅的技巧毫无保留地分享出来。2. 项目整体设计与核心思路拆解2.1 为什么选择这个项目作为入门在决定做这个Demo之前我对比过很多入门项目比如打飞机、跑酷等。最终选择“炸弹人”模式主要是基于以下几个考量这些考量也构成了我们开发时的核心思路第一场景结构清晰利于理解2D坐标系与Tilemap。游戏场景是一个标准的网格地图这天然契合Unity的Tilemap系统。初学者可以通过搭建这个地图快速掌握如何创建瓦片地图Tilemap、如何设置碰撞体Tilemap Collider 2D、如何区分“地面”、“可破坏墙”和“不可破坏墙”等不同图层。这种网格化的思维对后续理解角色移动、炸弹位置计算都至关重要。第二游戏对象GameObject类型明确职责单一。整个游戏的核心对象就那么几种玩家Player、敌人Enemy、炸弹Bomb、爆炸火焰Explosion、可破坏墙Breakable Wall。每个对象需要什么组件Component脚本Script该挂载在谁身上逻辑非常清晰。这有助于新手建立“组件化”和“面向对象”的编程思想而不是把所有代码都写在一个脚本里。第三涉及的关键技术点全面且基础。这个项目几乎覆盖了2D游戏开发的所有基础环节输入与控制通过Input.GetAxis或新的Input System处理玩家键盘/手柄输入。物理与碰撞使用Rigidbody2D和Collider2D实现移动和碰撞检测理解OnTriggerEnter2D和OnCollisionEnter2D的区别。动画系统为玩家和敌人制作简单的Idle、Run动画理解Animator Controller和状态机。预制体Prefab与实例化Instantiate炸弹和爆炸火焰都是典型的预制体需要动态生成和销毁。协程Coroutine与计时炸弹的倒计时爆炸、火焰的持续与消失是学习使用yield return new WaitForSeconds的绝佳场景。简单的AI逻辑敌人的随机移动或寻路是接触游戏AI的第一步。游戏状态管理如何判断游戏胜利所有敌人被消灭或失败玩家被炸死。第四扩展性强有成就感。完成基础版本后你可以很容易地添加新元素比如不同威力的炸弹、可以穿墙的炸弹、加速道具、增加生命数的道具、多个关卡等等。每添加一个功能都是一次对已学知识的巩固和深化能持续获得正反馈。基于以上思路我们的开发路线图就非常明确了先搭建静态场景再实现玩家核心功能接着制作敌人然后处理炸弹与爆炸逻辑最后串联所有逻辑完成游戏循环。2.2 技术选型与工具准备在动手之前我们需要明确一些技术选型和准备好工具这能避免后续开发中的许多混乱。1. Unity版本与渲染管线对于纯粹的2D项目我推荐使用Unity的长期支持版LTS比如2022.3 LTS。它稳定社区资源丰富。在创建项目时务必选择“2D”核心模板。关于渲染管线对于这个入门Demo强烈建议使用默认的Built-in Render Pipeline内置渲染管线。URP通用渲染管线虽然强大但对于新手来说额外的设置如2D Renderer Data, Renderer Feature会引入不必要的复杂度。内置管线对2D Sprite的支持已经完全够用且学习资料最多。2. 输入系统旧版Input Manager vs 新的Input SystemUnity的新Input System功能强大支持跨平台输入重映射是未来的方向。但对于第一次接触Unity的初学者我建议先从经典的旧版Input Manager入手。原因很简单它的APIInput.GetKey,Input.GetAxis直观易懂能让你快速将“按下按键”和“角色移动”联系起来把注意力集中在游戏逻辑本身。等核心玩法实现后再迁移到新Input System会更容易理解其优势。在本项目中我们将使用旧版Input。3. 物理系统与图层Layer规划2D物理是项目的基石。我们需要提前规划好图层碰撞矩阵这是保证游戏逻辑正确的关键。我通常会设置以下图层Default: 默认层。Player: 玩家角色。Enemy: 敌人。Bomb: 炸弹。注意炸弹放置后需要短暂地阻止玩家再次穿过它但又不能阻挡爆炸火焰。这需要巧妙的碰撞体设置。Explosion: 爆炸火焰。Breakable: 可破坏的墙。Unbreakable: 不可破坏的墙地图边界和固定障碍。 在Edit - Project Settings - Physics 2D中我们需要仔细配置碰撞矩阵。例如Player和Enemy应该与Breakable/Unbreakable墙发生碰撞无法穿过但Explosion不应该与墙碰撞火焰需要穿过墙不这里有个关键点见后文Player和Enemy需要能触发Explosion的碰撞检测。4. 素材准备美术素材可以很简单。你完全可以用Unity自带的2D Sprite Shape或简单的色块来代表角色、炸弹和墙。为了更有趣可以在Kenney.nl等免费资源网站找一些简单的像素艺术素材。重点是确保每个Sprite的Pixels Per UnitPPU设置一致比如32或64并将素材的Texture Type设置为Sprite (2D and UI)这样在导入时才能正确分割和使用。注意关于Sprite的导入设置。如果你下载的素材是包含多个帧的精灵图集Sprite Sheet记得在Inspector窗口中将Sprite Mode从Single改为Multiple然后点击Sprite Editor进行切片Slice。切片时选择Grid By Cell Size并输入每个帧的像素尺寸如32x32这样可以快速、规整地切出所有动画帧。3. 场景搭建与核心组件配置3.1 使用Tilemap构建游戏地图地图是游戏的舞台我们用Tilemap来高效构建。创建Tilemap在Hierarchy面板右键 -2D Object - Tilemap - Rectangular。Unity会自动创建一个包含Grid、Tilemap子对象的层级。Grid是父对象管理网格的全局设置如单元格大小Tilemap是实际绘制瓦片的图层。创建瓦片资源Tile Asset在Project窗口中右键 -Create - 2D - Tiles。我们需要创建至少三种瓦片GroundTile: 用于绘制地面通常没有碰撞体。WallTile_Unbreakable: 用于绘制不可破坏的墙壁和地图边界。需要为它创建碰撞体。WallTile_Breakable: 用于绘制可破坏的砖墙。 如何为瓦片添加碰撞体在Project窗口选中你创建的WallTile资源在Inspector中找到Collider Type下拉菜单。对于墙我们选择Sprite根据精灵轮廓生成或更简单的Grid生成与网格对齐的矩形碰撞体。Grid类型对于这种方格子游戏更精确高效。绘制地图在Window菜单中打开Tile Palette瓦片调色板。将创建好的瓦片资源拖入调色板并保存。然后选择画笔工具在Scene视图中绘制。我的常规布局是最外一圈画Unbreakable墙作为边界内部随机或按某种规则填充Breakable墙留出玩家和敌人的出生空间以及通道。为Tilemap添加碰撞选中Tilemap对象在Inspector中点击Add Component添加Tilemap Collider 2D。但是这里有一个非常重要的优化点如果你直接添加Unity会为每一个有碰撞体的瓦片都生成一个碰撞体当地图很大时性能会很差。正确的做法是在添加Tilemap Collider 2D后勾选其Used By Composite选项然后继续添加一个Composite Collider 2D组件。Composite Collider 2D会自动将所有相邻的碰撞体合并成更少、更大的碰撞体形状极大提升物理性能。同时Tilemap Collider 2D的Used By Composite被勾选后其自身的Material等设置会失效物理材质需要设置在Composite Collider 2D上。实操心得图层Layer与碰撞矩阵的实战配置。光有碰撞体还不够我们必须通过图层来控制“谁和谁碰撞”。我为Tilemap对象设置了Unbreakable图层。那么在Physics 2D设置中我需要让Player和Enemy图层与Unbreakable图层碰撞勾选这样角色就无法穿墙。而Explosion图层与Unbreakable图层不碰撞取消勾选因为爆炸火焰不应该被不可破坏的墙阻挡但会被可破坏的墙阻挡直到墙被炸掉。Breakable墙我会用单独的Prefab来做而不是Tilemap方便被炸毁时删除单个对象。3.2 创建玩家角色Player玩家是一个典型的GameObject需要多个组件协同工作。创建GameObject并重命名为“Player”。添加Sprite Renderer将代表玩家的精灵Sprite拖拽到Sprite属性上。添加Rigidbody 2D这是物理核心。设置如下Body Type:Dynamic受物理引擎控制。Gravity Scale: 02D平面游戏不需要重力。Collision Detection:Discrete离散检测对于这个游戏足够如果发现高速移动穿墙可改为Continuous。Constraints: 冻结Rotation的Z轴防止角色意外旋转。添加碰撞体添加一个Box Collider 2D调整大小使其刚好贴合角色精灵。将这个GameObject的图层设置为Player。添加动画为玩家创建Animator组件并为其分配一个Animator Controller如PlayerAC。在Animation窗口中我们可以创建两个动画片段PlayerIdle站立和PlayerRun奔跑。奔跑动画可以通过在几帧之间轻微改变精灵的位置或切换不同的奔跑姿态精灵来实现。创建并编写玩家控制脚本创建一个C#脚本命名为PlayerController挂载到Player对象上。using UnityEngine; public class PlayerController : MonoBehaviour { // 移动速度 public float moveSpeed 5f; // 炸弹预制体 public GameObject bombPrefab; // 放置炸弹的冷却时间防止连续狂按 public float bombCooldown 0.5f; // 炸弹的爆炸威力火焰蔓延格数 public int bombPower 2; private Rigidbody2D rb; private Animator animator; private Vector2 movement; private float currentCooldown 0f; private bool isDead false; void Start() { rb GetComponentRigidbody2D(); animator GetComponentAnimator(); } void Update() { if (isDead) return; // 玩家死亡后不再接受输入 // 1. 处理移动输入 movement.x Input.GetAxisRaw(Horizontal); // 返回 -1, 0, 1 movement.y Input.GetAxisRaw(Vertical); // 让动画状态机知道是否在移动 if (animator ! null) { bool isMoving movement.magnitude 0.1f; animator.SetBool(IsRunning, isMoving); // 可以根据movement.x的正负来翻转角色Sprite实现面朝方向 if (isMoving Mathf.Abs(movement.x) 0.1f) { transform.localScale new Vector3(Mathf.Sign(movement.x), 1, 1); } } // 2. 处理放置炸弹输入 if (currentCooldown 0) { currentCooldown - Time.deltaTime; } if (Input.GetKeyDown(KeyCode.Space) currentCooldown 0) { PlaceBomb(); currentCooldown bombCooldown; } } void FixedUpdate() { // 在FixedUpdate中应用物理移动更平滑 if (!isDead) { rb.velocity movement.normalized * moveSpeed; } } void PlaceBomb() { // 关键计算炸弹放置的网格位置 // 假设每个网格单元大小为1我们将玩家的位置四舍五入到最近的整数坐标 Vector2 bombPos new Vector2(Mathf.Round(transform.position.x), Mathf.Round(transform.position.y)); // 检查这个位置是否已经有一颗炸弹防止重叠 // 一个简单的实现用Physics2D.OverlapCircle检查Bomb图层 Collider2D overlap Physics2D.OverlapCircle(bombPos, 0.4f, LayerMask.GetMask(Bomb)); if (overlap ! null) { // 这个位置已经有炸弹了不再放置 return; } // 实例化炸弹 GameObject bomb Instantiate(bombPrefab, bombPos, Quaternion.identity); // 将炸弹的威力传递给炸弹脚本 Bomb bombScript bomb.GetComponentBomb(); if (bombScript ! null) { bombScript.explosionPower bombPower; } } // 玩家被炸到或碰到敌人的处理 public void Die() { if (isDead) return; isDead true; Debug.Log(Player Died!); // 停止移动 rb.velocity Vector2.zero; // 播放死亡动画如果有 if (animator ! null) animator.SetTrigger(Die); // 可以在这里触发游戏结束逻辑比如调用GameManager // GameManager.Instance.GameOver(false); } }注意事项关于移动与碰撞的细节。我们使用Rigidbody2D.velocity来直接设置速度这是一种“运动学”风格的移动反应迅速。但要注意这可能会让角色在碰到墙壁时“抖动”或“卡住”。更健壮的做法是使用Rigidbody2D.MovePosition或在FixedUpdate中使用rb.velocity的同时确保碰撞体设置正确如使用Composite Collider 2D。另外Input.GetAxisRaw比GetAxis好因为它没有平滑滤波直接返回-1,0,1更适合网格对齐的移动感觉。3.3 创建炸弹与爆炸系统这是游戏逻辑最核心的部分涉及预制体、协程、物理碰撞和链式反应。1. 炸弹预制体Bomb Prefab创建一个空GameObject命名为BombPrefab。添加Sprite Renderer显示炸弹精灵。添加Circle Collider 2D。这里有一个精妙的设计我们需要炸弹在放置后能阻挡玩家和敌人但不能阻挡爆炸火焰。所以我们设置这个碰撞体的Is Trigger为false进行物理碰撞并将对象图层设为Bomb。在物理2D设置中让Player和Enemy图层与Bomb图层碰撞而Explosion图层与Bomb图层不碰撞。这样爆炸火焰就能穿过炸弹本体触发其爆炸。添加一个脚本Bomb.cs。2. 炸弹脚本逻辑炸弹的主要逻辑是被放置后开始倒计时时间到或受到其他爆炸触发时自身销毁并生成爆炸火焰。using UnityEngine; using System.Collections; public class Bomb : MonoBehaviour { public float fuseTime 3f; // 引信时间 public int explosionPower 2; // 火焰威力格子数 public GameObject explosionPrefab; // 爆炸火焰预制体 private bool hasExploded false; void Start() { // 开始倒计时协程 StartCoroutine(ExplodeAfterDelay()); } IEnumerator ExplodeAfterDelay() { yield return new WaitForSeconds(fuseTime); Explode(); } // 爆炸函数也可以被其他爆炸触发 public void Explode() { if (hasExploded) return; // 防止重复爆炸 hasExploded true; // 1. 在炸弹中心生成爆炸 CreateExplosionAtPosition(transform.position, Vector2.zero); // 中心方向 // 2. 向四个方向蔓延火焰 StartCoroutine(CreateExplosionLine(Vector2.up)); StartCoroutine(CreateExplosionLine(Vector2.down)); StartCoroutine(CreateExplosionLine(Vector2.left)); StartCoroutine(CreateExplosionLine(Vector2.right)); // 3. 移除炸弹精灵和碰撞体可选立即消失或播放动画 GetComponentSpriteRenderer().enabled false; GetComponentCollider2D().enabled false; // 4. 稍后销毁GameObject给爆炸效果留出时间 Destroy(gameObject, 0.5f); } // 在指定位置创建爆炸火焰 void CreateExplosionAtPosition(Vector2 position, Vector2 direction) { GameObject explosion Instantiate(explosionPrefab, position, Quaternion.identity); // 可以在这里传递方向信息给爆炸效果用于播放不同方向的精灵如火焰尽头 Explosion expScript explosion.GetComponentExplosion(); if (expScript ! null) { expScript.SetDirection(direction); } } // 创建一条直线上的爆炸 IEnumerator CreateExplosionLine(Vector2 direction) { for (int i 1; i explosionPower; i) { Vector2 targetPos (Vector2)transform.position direction * i; // **关键射线检测判断火焰是否能继续延伸** RaycastHit2D hit Physics2D.Raycast(transform.position, direction, i, LayerMask.GetMask(Unbreakable, Breakable, Bomb)); // 注意这里我们检测 Unbreakable, Breakable, Bomb 图层 if (hit.collider ! null) { // 碰到了东西 int hitLayer hit.collider.gameObject.layer; if (hitLayer LayerMask.NameToLayer(Unbreakable)) { // 碰到不可破坏墙停止这个方向的蔓延 break; } else if (hitLayer LayerMask.NameToLayer(Breakable)) { // 碰到可破坏墙在墙的位置生成爆炸炸掉它然后停止蔓延 CreateExplosionAtPosition(targetPos, direction); // 销毁墙假设墙的脚本有Die方法 hit.collider.GetComponentBreakableWall()?.DestroyWall(); break; } else if (hitLayer LayerMask.NameToLayer(Bomb)) { // 碰到其他炸弹触发它爆炸然后火焰继续可选或停止 CreateExplosionAtPosition(targetPos, direction); hit.collider.GetComponentBomb()?.Explode(); // 链式爆炸 // 通常链式爆炸后当前火焰也停止避免无限循环。这里我们选择停止。 break; } } else { // 没有碰到障碍物正常生成爆炸 CreateExplosionAtPosition(targetPos, direction); } // 每一格火焰生成有一点间隔视觉效果更好 yield return new WaitForSeconds(0.05f); } } // 如果爆炸火焰碰到这个炸弹提前引爆它 void OnTriggerEnter2D(Collider2D other) { if (other.gameObject.layer LayerMask.NameToLayer(Explosion)) { Explode(); } } }3. 爆炸火焰预制体与脚本创建ExplosionPrefab添加Sprite Renderer和Box Collider 2D设置为Is Trigger true图层设为Explosion。脚本Explosion.cs负责显示一段时间后消失并处理触发伤害。using UnityEngine; using System.Collections; public class Explosion : MonoBehaviour { public float aliveTime 0.5f; // 火焰存在时间 void Start() { StartCoroutine(DestroyAfterTime()); } IEnumerator DestroyAfterTime() { yield return new WaitForSeconds(aliveTime); Destroy(gameObject); } // 触发伤害 void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag(Player)) { other.GetComponentPlayerController()?.Die(); } else if (other.CompareTag(Enemy)) { other.GetComponentEnemy()?.Die(); } // 注意可破坏墙的销毁逻辑在Bomb脚本的射线检测中处理了 // 因为我们需要在火焰生成前判断是否碰到墙 } // 可选用于设置火焰精灵的方向中心、上、下、左、右、尽头 public void SetDirection(Vector2 dir) { // 这里可以根据dir来切换不同的Sprite实现更精致的爆炸效果 // 例如if(dir Vector2.zero) 显示中心爆炸图 // else if(dir Vector2.up) 显示向上延伸的火焰图 } }核心技巧爆炸蔓延的射线检测Raycast。这是整个炸弹系统的灵魂。我们不能简单地用循环在每一格生成火焰然后靠碰撞去摧毁墙。因为那样火焰会“穿墙而过”一帧视觉和逻辑都不对。正确的做法是在生成每一格的火焰之前先用射线检测该路径上是否有障碍物。射线检测的性能远优于生成物体后再碰撞检测并且逻辑清晰。我们通过LayerMask参数精确控制射线只与我们关心的图层墙、其他炸弹交互。3.4 实现敌人EnemyAI敌人的AI可以很简单比如随机移动也可以稍复杂比如朝玩家方向移动。我们从简单的随机移动开始。创建Enemy预制体类似Player包含Sprite Renderer,Rigidbody 2DDynamic, 重力0,Box Collider 2D图层设为Enemy。添加Animator。编写简单随机移动AI脚本using UnityEngine; using System.Collections; public class Enemy : MonoBehaviour { public float moveSpeed 3f; public float changeDirectionTime 2f; // 改变方向的间隔 private Rigidbody2D rb; private Animator animator; private Vector2 movement; private float directionTimer; void Start() { rb GetComponentRigidbody2D(); animator GetComponentAnimator(); directionTimer changeDirectionTime; PickRandomDirection(); } void Update() { directionTimer - Time.deltaTime; if (directionTimer 0) { PickRandomDirection(); directionTimer changeDirectionTime; } // 更新动画 if (animator ! null) { animator.SetBool(IsMoving, movement.magnitude 0.1f); if (movement.magnitude 0.1f Mathf.Abs(movement.x) 0.1f) { transform.localScale new Vector3(Mathf.Sign(movement.x), 1, 1); } } } void FixedUpdate() { rb.velocity movement * moveSpeed; } void PickRandomDirection() { // 从四个基本方向中随机选一个 int dir Random.Range(0, 4); switch (dir) { case 0: movement Vector2.up; break; case 1: movement Vector2.down; break; case 2: movement Vector2.left; break; case 3: movement Vector2.right; break; } // 也可以加入 Vector2.zero 让敌人有时停顿 } // 敌人死亡 public void Die() { Debug.Log(Enemy Died!); // 停止移动 rb.velocity Vector2.zero; // 播放死亡动画 if (animator ! null) animator.SetTrigger(Die); // 禁用碰撞体防止死后还能被触发 GetComponentCollider2D().enabled false; // 延迟销毁 Destroy(gameObject, 1f); // 通知游戏管理器敌人数量减少 // GameManager.Instance.EnemyDied(); } // 简单碰撞检测碰到玩家则玩家死亡可选 void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag(Player)) { collision.gameObject.GetComponentPlayerController()?.Die(); } } }这个AI非常基础敌人会在碰到墙时被物理引擎阻挡。一个常见的改进是在PickRandomDirection中使用射线检测预判前方是否有路如果没有则立即选择另一个方向这样能减少敌人卡在墙角的情况。4. 游戏逻辑管理与功能串联4.1 创建可破坏墙Breakable Wall可破坏墙不是Tilemap而是独立的Prefab方便被单个销毁。创建BreakableWall预制体添加Sprite Renderer和Box Collider 2DIs Trigger false图层设为Breakable。编写一个简单的脚本提供一个被摧毁的方法。using UnityEngine; public class BreakableWall : MonoBehaviour { // 被炸到时调用 public void DestroyWall() { // 可以播放一个破碎动画或粒子效果 // Destroy(gameObject); // 或者先禁用渲染和碰撞延迟销毁 GetComponentSpriteRenderer().enabled false; GetComponentCollider2D().enabled false; Destroy(gameObject, 0.5f); // 留时间给可能的音效/动画 } }在Bomb脚本的射线检测部分我们调用了hit.collider.GetComponentBreakableWall()?.DestroyWall();来触发摧毁。4.2 实现游戏管理器GameManager游戏管理器是一个单例Singleton负责全局状态如敌人计数、游戏胜负判断、关卡管理。using UnityEngine; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { public static GameManager Instance; // 单例实例 public int totalEnemies 0; private int enemiesLeft 0; private bool isGameOver false; void Awake() { // 简单的单例模式确保只有一个GameManager if (Instance null) { Instance this; DontDestroyOnLoad(gameObject); // 跨场景不销毁如果有多关卡 } else { Destroy(gameObject); } } void Start() { enemiesLeft totalEnemies; Debug.Log(Enemies Left: enemiesLeft); } // 敌人被消灭时调用 public void EnemyDied() { if (isGameOver) return; enemiesLeft--; Debug.Log(Enemies Left: enemiesLeft); if (enemiesLeft 0) { GameOver(true); // 胜利 } } // 玩家死亡时调用由PlayerController调用 public void PlayerDied() { if (isGameOver) return; GameOver(false); // 失败 } void GameOver(bool isWin) { isGameOver true; Debug.Log(isWin ? You Win! : Game Over!); // 这里可以显示游戏结束UI停止游戏时间等 // Time.timeScale 0f; // 例如3秒后重新加载场景 // Invoke(nameof(RestartLevel), 3f); } void RestartLevel() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); Time.timeScale 1f; } }在Enemy.Die()和PlayerController.Die()中需要调用GameManager.Instance.EnemyDied()和GameManager.Instance.PlayerDied()来通知游戏管理器。4.3 添加基础UI与游戏流程创建UI在Hierarchy右键 -UI - Text - TextMeshPro如果未导入TMPUnity会提示导入来创建显示敌人数量和游戏状态的文本。创建一个Canvas来容纳它们。连接UI与GameManager在GameManager中添加对UI文本的引用并在EnemyDied()和GameOver()中更新它们。设计简单的开始/结束界面可以创建两个不同的UI面板Panel一个用于开始游戏一个用于显示胜利/失败。通过脚本来控制它们的显示和隐藏。5. 常见问题、优化与扩展思路5.1 开发中常见问题与排查角色移动“滑冰”或停不下来原因在Update中直接修改transform.position或Rigidbody2D.position忽略了物理引擎。或者Rigidbody2D的Linear Drag线性阻尼太小。解决确保移动代码在FixedUpdate中使用rb.velocity或rb.MovePosition。可以适当增加Linear Drag如设为3-5来模拟摩擦力让角色更快停下。炸弹爆炸后火焰会炸到放置炸弹的玩家即使玩家已跑开原因爆炸火焰生成后立即触发了碰撞检测而玩家可能还在触发范围内。解决在Explosion脚本的OnTriggerEnter2D中可以添加一个短暂的延迟判断。或者更常见的做法是在Bomb爆炸的瞬间给玩家一个非常短暂的“无敌帧”通过设置一个bool isInvincible并在短时间内忽略爆炸伤害。敌人卡在角落或两个敌人重叠不动原因随机移动AI没有考虑障碍物仅靠物理碰撞推开有时会卡住。解决改进PickRandomDirection函数。在选择方向前用Physics2D.Raycast或Physics2D.BoxCast检测前方一定距离内是否有Unbreakable、Breakable或其他Enemy通过图层过滤。如果前方有障碍则从剩余可行方向中随机选择。爆炸链式反应导致栈溢出或性能问题原因炸弹A引爆触发炸弹B炸弹B又触发炸弹A如果它们还在彼此的爆炸范围内形成无限递归。解决在Bomb.Explode()方法开头用if (hasExploded) return;防止重复爆炸。同时在链式触发时Bomb脚本中hit.collider.GetComponentBomb()?.Explode()可以考虑让当前炸弹的火焰蔓延到此为止即break避免复杂的连锁逻辑。Tilemap碰撞体形状复杂导致性能下降原因如之前所述未使用Composite Collider 2D。解决务必为Tilemap添加Tilemap Collider 2D并勾选Used By Composite再添加Composite Collider 2D。同时在Composite Collider 2D上设置合适的Geometry Type通常用Polygons和Generation TypeSynchronous或Asynchronous。5.2 性能优化与代码整洁建议对象池Object Pooling炸弹和爆炸火焰频繁生成和销毁会产生垃圾回收GC压力。对于移动平台或大量实体时建议实现一个简单的对象池预先实例化一定数量的对象需要时激活不需要时禁用而非销毁。使用Tag和Layer进行高效检测在碰撞检测和射线检测中始终使用LayerMask来指定图层而不是使用CompareTag遍历所有对象。LayerMask的检测效率高得多。将常量参数序列化到ScriptableObject如果游戏中有很多可调参数如玩家速度、炸弹威力、敌人属性可以创建一个GameSettings的ScriptableObject资产来集中管理方便设计和平衡。使用Unity的新的Input System完成基础版本后可以尝试将输入系统迁移到新的Input System它支持手柄、键盘无缝切换并且有更优雅的输入动作配置。5.3 项目扩展方向完成这个核心Demo后你可以尝试添加以下功能来深化学习道具系统在可破坏墙被炸毁后随机生成道具如PowerUp预制体。玩家触碰后可以获得能力增加炸弹数量maxBombs、增加爆炸威力bombPower、增加移动速度moveSpeed、获得穿墙炸弹等。这需要你创建一个PowerUp脚本和对应的拾取逻辑。多关卡与场景切换设计多个不同布局的Tilemap场景。在GameManager中管理当前关卡索引胜利后加载下一关。更复杂的敌人AI实现追逐玩家的AI。可以使用Transform来获取玩家位置然后用简单的向量运算决定移动方向(player.position - enemy.position).normalized。注意要结合射线检测避免撞墙。音效与粒子效果为移动、放置炸弹、爆炸、敌人死亡等事件添加音效AudioSource和粒子系统Particle System大幅提升游戏体验。本地多人游戏这是最有挑战也最有成就感的扩展。你需要为第二个玩家创建一套独立的输入控制使用不同的按键并处理好两个玩家之间的碰撞交互他们应该是可以互相穿过的。这涉及到输入管理、角色生成和游戏逻辑的重新设计。这个“炸弹人”Demo虽然体量小但它像一颗种子几乎包含了2D游戏开发所有最基础的养分。从场景搭建到物理交互从动画控制到简单AI从预制体管理到游戏状态机每一步都是实实在在的实战经验。我建议你在实现过程中多思考“为什么这么做”多尝试修改参数看看效果甚至故意写一些“bug”然后去调试解决它。这个过程积累的经验远比单纯复制代码要宝贵得多。当你成功运行起第一个版本看着自己控制的角色放下炸弹炸开砖块消灭敌人时那种亲手创造世界的成就感正是驱动我们不断学习游戏开发的最大乐趣。