news 2026/10/19 8:13:15

Unity 2D炮台追踪旋转与子弹对象池实现详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity 2D炮台追踪旋转与子弹对象池实现详解

如果让我列一个“Unity 2D 新手最容易卡壳的小功能”排行榜,炮台追踪主角并发射子弹绝对能排上前三。你既要处理炮口方向的计算,又要管子弹的生成和回收,还得考虑射程、遮挡、冷却节奏,任何一个环节没想清楚,画面就会变得很离谱:炮管要么转错方向,要么子弹从屁股后面飞出来,要么隔着墙疯狂开火。

这篇文章就把我之前做一款 2D 塔防 Demo 时积累的经验完整整理一遍,从炮台转向的原理、场景层级设计,到对象池、射线遮挡检测,再到实测中踩过的坑,一次性讲透。适合刚接触 Unity 2D 的初学者,也适合已经能写基础逻辑但总在细节上翻车的人参考。

1. 炮台转头的核心逻辑:追踪旋转背后的角度计算

1.1 为什么很多人的炮台一转头就“倒立”

在 2D 里要让炮台转向玩家,最常见的错误做法是直接用Transform.LookAt。这个 API 默认让物体的 Z 轴朝前,在 2D 世界中旋转轴实际上是 Z 轴,出来的效果经常是炮台直接“躺平”或者以很奇怪的角度立起来。还有一种常见做法是让炮管的transform.up指向玩家,这能不能用?能用,但前提是 Sprite 原始的“炮口朝向”和up方向一致,而且炮台的层级结构得配合好,否则底座也会跟着转,画面立刻变诡异。

写代码的人往往就在这里开始怀疑人生。

其实一个稳定可靠的做法很简单:先算出“从炮台指向玩家”的方向向量,再用Mathf.Atan2得到这个向量与 X 轴正方向的弧度夹角,转成角度后赋给transform.rotation的 Z 轴。这个流程看起来不难,但实际做起来有至少三个容易翻车的点。

第一处:很多人会把Atan2的参数顺序写反。C# 的Mathf.Atan2签名是(float y, float x),第一个参数是 y,第二个才是 x。别问我为什么这么设计,反正无数人在这里吃过亏。如果你写反了,得到的角度会偏 90 度或者出现负角度。

第二处更隐蔽:Unity 2D 默认的 Sprite 在制作时通常都是“炮口朝上”的,也就是图片的正方向对着 Y 轴。而Atan2算出来的 0 度对应的是 X 轴正方向。你要是直接把角度赋给rotation,炮台会默认朝右,和美术资源差 90 度。最直接的解法是在计算出的角度上减 90 度,或者让美术出图时把素材默认朝右。正常情况下我会在代码里留一个可配置的角度偏移字段,这样以后换素材、换角色朝向,都不用改代码,只调一个 Inspector 参数。

第三处:旋转轴选错。2D 物体的旋转全部围绕 Z 轴,如果你不小心写成Euler(xxx, xxx, 0),炮台就会像陀螺一样往 3D 方向转,看起来很滑稽。

1.2 Atan2 才是 2D 旋转的基石

下面是一份可以直接用的炮管追踪脚本,我先贴代码,再拆开解释每一行的作用。

using UnityEngine; public class TurretAim : MonoBehaviour { [Header("目标")] public Transform player; [Header("转向参数")] public float rotateSpeed = 240f; [Header("角度偏移:素材朝上时填-90,朝右时填0")] public float angleOffset = -90f; private void Update() { if (player == null) return; Vector2 direction = player.position - transform.position; float targetAngle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg + angleOffset; Quaternion targetRotation = Quaternion.Euler(0f, 0f, targetAngle); transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotateSpeed * Time.deltaTime); } }

逐行说明:

  • Vector2 direction = player.position - transform.position;这一步算出从炮台指向玩家的方向向量。2D 情况下只需要 X 和 Y。
  • Mathf.Atan2(direction.y, direction.x)返回的是弧度,范围在-π到π之间。参数顺序是 y 在前,x 在后。
  • Mathf.Rad2Deg把弧度转成角度,方便让Quaternion.Euler使用。
  • angleOffset用来修正素材默认朝向。比如素材炮口朝上,原始角度基准是 X 轴正方向,所以要减 90 度。
  • Quaternion.Slerp做球面插值,让炮管平滑地转向目标,而不是瞬间“啪”一下转过去。

这里有一个新手经常问的问题:为什么用Slerp而不是Lerp?Quaternion.Lerp对角度插值在大部分情况下看着也正常,但某些边界场景下它会走一条比较奇怪的路径。Slerp是正规的球面插值,旋转过程中角度变化更均匀,不会出现突然“绕远路”的现象。虽然单帧差别不大,但持续追踪一个运动目标时,Slerp的手感明显更跟手。

再说rotateSpeed * Time.deltaTime这个公式。它不是一个严格的“每秒转多少度”的写法,而是阻尼逼近。你设 240f 代表每次插值的系数,实际感受是“一秒内完成大部分转动”。如果你希望炮台有一个明确的角速度上限,比如“每秒最多转 60 度”,那更合适的写法是:

float step = rotateSpeed * Time.deltaTime; transform.rotation = Quaternion.RotateTowards(transform.rotation, targetRotation, step);

RotateTowards是严格按角度步长逼近目标的,适合做那种“炮塔转动不够快,追不上快速移动的玩家”的效果。我在 Demo 里后来就是换成RotateTowards,因为可以精确控制炮台的跟进速度,做难度梯度时很有用。

1.3 角度卷绕:为什么不会转一整圈

用四元数做旋转还有一个隐藏福利:它天然处理了角度跨 0 度线的问题。

如果你在玩家从炮台正上方转到正下方时手动做欧拉角插值,会遇到-170度和170度之间到底走 20 度还是 340 度的问题。很多新手在这里写出一堆反人类的角度判断逻辑,最后发现还是没搞定。

四元数的Slerp或RotateTowards会自动沿最短路径转。比如当前角度是 350 度,目标角度是 10 度,它不会顺时针绕 340 度,而是逆时针补 20 度。这个特性让炮台追踪非常省心,基本上你只管算目标旋转,剩下的交给 Unity 的旋转插值。

所以说,炮台转向这件事,核心就是把“方向向量”转成“角度值”,然后用四元数插值平滑过渡。方向对了,后面子弹的发射方向也就顺理成章地对了。

2. 炮台场景搭建:层级、锚点与炮口位置的细节

2.1 炮台预制体的层级与锚点设计

很多人拿到炮台素材后,会把底座和炮管做成一张图,或者全部塞进一个空物体里。这两种做法在第一版都能跑通,但到了你需要“底座固定、炮管旋转”的时候就会卡住。合格的做法是把炮台拆成两层:

Turret(根节点,挂逻辑脚本和碰撞体) ├── Base(底座 Sprite,不参与旋转) └── Barrel(炮管 Sprite,旋转的主体) └── MuzzlePoint(空物体,炮口开火位置)

这样底座永远保持不动,炮管独立旋转,炮口开火点跟随炮管转。后续要加炮口闪光、子弹出膛效果、升级换炮管素材,都会非常方便。

有一个特别容易忽略的细节:炮管 Sprite 的 Pivot 必须设置在炮管根部,也就是炮管围绕旋转的那个点。如果 Pivot 在图片中心,旋转起来炮管就像陀螺一样绕自己中心转,怎么看怎么不对。在 Unity 的 Sprite 导入设置里,把 Pivot 改成Bottom或者自定义到根部位置即可。

我见过不少人在这里栽跟头,因为美术给的是整张炮塔大图,很多时候 Pivot 默认在中心,导致一旋转就怪。解决办法也简单:要么让美术重新切图,要么你手动把 Sprite 的 Pivot 调到底部。

2.2 炮口开火点 MuzzlePoint:子弹从这里出来

子弹生成的代码如果写的是Instantiate(bulletPrefab, transform.position, transform.rotation),结果就是子弹从炮台中心甚至底座位置飞出来,而不是从炮管口。这种情况很常见,因为transform.position是当前脚本挂载物体的坐标,通常指整个炮台的根节点。

正确做法是在炮管头部放一个空物体 MuzzlePoint,把它放在炮管尖端。子弹生成时,直接用muzzlePoint.position和muzzlePoint.rotation。这样不管炮管怎么转,子弹出膛的位置永远是炮口本身。

MuzzlePoint 的localPosition要按炮管实际长度来设。比如炮管长度 1.2,炮管根部 Pivot 在 (0, 0, 0),那 MuzzlePoint 的本地坐标就是(0, 1.2, 0)。如果炮管素材默认朝上,它在 Y 轴方向;如果素材朝右,就是(1.2, 0, 0)。这个细节和前面讲的angleOffset必须一致,否则会出现“炮口朝上,但子弹往右飞”的奇葩场面。

2.3 做预制体时最容易踩的“前朝向”问题

还有一个非常常见的坑:美术给的素材炮口朝右,你按“炮口朝上”的逻辑写了 -90 度偏移,结果运行时炮台指天。

核心原因是:Sprite 的原始朝向,可能跟你想的不一样。判断方法很简单,运行游戏时暂停,选中炮管,看看 Sprite 当前朝向:

  • 炮口朝右:angleOffset设 0
  • 炮口朝上:angleOffset设 -90
  • 炮口朝左:angleOffset设 180 或 -180

最省心的做法不是每次换素材都改代码,而是把这个偏移做成公开字段,在 Inspector 里直接调。我自己的习惯是统一让美术把素材朝上出图,代码里默认angleOffset = -90f,以后换素材只替换 Sprite 就行,逻辑不动。

3. 子弹的生产管理:对象池不是可选项

3.1 高频发射时 Instantiate 和 Destroy 的开销

新手阶段,最简单的开枪逻辑是:

GameObject bullet = Instantiate(bulletPrefab, muzzlePoint.position, muzzlePoint.rotation); Destroy(bullet, 2f);

单发、低频场景下问题不大。但塔防场景里五座炮台,每座每秒发射两发子弹,一秒就是十个 GameObject 在反复创建和销毁。每次Instantiate都会在主线程上产生内存分配,Destroy后还需要等待 GC 回收,表现就是阶段性掉帧,手机上尤为明显。

对象池的核心思想是:提前创建一批子弹隐藏在场景里,开火时取一个“活的”出来,用完了再“还”回去,而不是销毁。对频繁生成和回收的对象,效果立竿见影。而且对象池的实现并不复杂,别觉得这个功能多高级,花十分钟写一次能省后面无数性能问题。

3.2 一个够用的对象池实现

下面是一个用队列实现的最小对象池,复制即可用。

using System.Collections.Generic; using UnityEngine; public class BulletPool : MonoBehaviour { [Header("预制体")] public GameObject bulletPrefab; [Header("预加载数量")] public int preloadCount = 30; [Header("父节点")] public Transform poolParent; private Queue<GameObject> pool = new Queue<GameObject>(); private void Awake() { if (poolParent == null) poolParent = transform; for (int i = 0; i < preloadCount; i++) { GameObject go = CreateBullet(); go.SetActive(false); pool.Enqueue(go); } } private GameObject CreateBullet() { GameObject go = Instantiate(bulletPrefab, poolParent); Bullet bullet = go.GetComponent<Bullet>(); bullet.SetPool(this); return go; } public GameObject Get() { if (pool.Count == 0) { pool.Enqueue(CreateBullet()); } GameObject obj = pool.Dequeue(); obj.SetActive(true); return obj; } public void Return(GameObject obj) { obj.SetActive(false); pool.Enqueue(obj); } }

有几个关键点必须注意:

第一个,CreateBullet里面要调bullet.SetPool(this)。因为子弹回收时需要知道自己属于哪个池子,如果场景里有多个炮台多个池子,这个引用必须在实例化时绑定,否则回收时找错池子会出大问题。

第二个,Get时如果队列空了,不要报错,而是现造一个新的。这样即使预制加载数量不够,极端高并发下也能正常撑住,不会因为对象不够而丢子弹。

第三个,Return时一定先SetActive(false),再Enqueue。这样下一次取出来时是一个隐藏对象,调用方不会看到一闪而过的“未激活子弹”。

3.3 子弹的自动回收:生命周期与碰撞销毁

子弹本身要有一个回收逻辑,常见做法是碰撞到目标或墙体就回收,或者飞出一定时间后超时回收。

public class Bullet : MonoBehaviour { [SerializeField] private float lifeTime = 2f; private BulletPool pool; private float elapsed; public void SetPool(BulletPool p) { pool = p; } private void OnEnable() { elapsed = 0f; } private void Update() { elapsed += Time.deltaTime; if (elapsed >= lifeTime) { pool.Return(gameObject); } } private void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag("Player")) { // 这里处理扣血逻辑 pool.Return(gameObject); } } }

这里必须提醒一句:OnEnable里重置elapsed非常关键。如果你第一次发射时子弹活了 2 秒后被回收,第二次发射时不重置计数器,子弹刚出来就立刻满足elapsed >= lifeTime,直接回收。这种“子弹刚冒出枪口就消失”的问题,源于状态没重置,排查起来很费时间。

另外,如果子弹移动逻辑是用Transform.Translate且速度很快,碰撞检测可能出现穿透。这个我放到后面讲,因为它还涉及物理检测的选型和射线遮挡检测。

4. 什么时候开火:射程与遮挡判定

4.1 无差别开火太傻了:为什么要有射程

炮台如果写死“见人就开枪”,不管主角在哪一步,画面会非常难控制。玩家还没靠近,子弹就满屏飞;玩家躲在远处看戏,炮台还在乱开火,既浪费性能又让关卡显得无脑。

要加射程限制,最先想到的是算距离。距离小于射程才允许开火:

float distance = Vector2.Distance(muzzlePoint.position, player.position); if (distance < range) Fire();

但射程只是第一步。如果玩家和炮台之间隔了一堵墙,从炮台中心到玩家的距离在射程范围内,炮台照样隔墙开火,这就很滑稽。所以还得加遮挡判定,也就是“能不能看到玩家”。看不到就不开枪,这才是符合直觉的炮台行为。

4.2 用 Raycast 处理“墙壁挡子弹”

在 2D 里做遮挡检测的利器是Physics2D.Raycast。从炮口位置发射一条射线,方向指向玩家,长度等于当前距离。如果命中的是“障碍物层”,说明玩家被挡住,不开火。

层配置建议:

层名用途
PlayerLayer玩家角色、可被子弹命中的单位
BulletLayer所有子弹
ObstacleLayer墙体、柱子、障碍物

注意不是把层分好就够了,还要在Physics2D的碰撞矩阵里设置好哪些层互相碰撞。比如子弹和玩家要碰撞,子弹和墙体要碰撞,但子弹和子弹可以互不干扰。这样一方面减少多余的物理计算,另一方面也避免射线误判。

下面是完整的判断函数:

private bool CanSeePlayer() { if (player == null) return false; Vector2 from = muzzlePoint.position; Vector2 to = player.position; Vector2 dir = to - from; float distance = dir.magnitude; if (distance > range) return false; // 发射长度稍加余量,避免浮点误差导致边缘抖动 RaycastHit2D hit = Physics2D.Raycast( from, dir.normalized, distance + 0.1f, obstacleMask ); if (hit.collider != null) return false; return true; }

三个细节值得单独说说。

射线起点用muzzlePoint而不是transform.position,这一点非常有价值。如果你的炮管很长,玩家站在炮管旁边,从炮台中心出发的射线可能因为穿模检测不到墙体,而子弹明明是从炮管口射出的,就会造成“看似能打,实际子弹被墙挡住”的错觉。我也是被这个问题恶心过一次之后,才彻底统一从 MuzzlePoint 发射线。

射线长度要比实际距离多 0.1 的余量。因为如果玩家恰好站在墙边,射线距离刚好等于墙的厚度的上限,浮点误差可能导致这一帧能命中、下一帧不能命中,表现就是开火时灵时不灵。加一点余量能显著降低这种边缘抖动。

还有一个关于LayerMask的常见误操作:很多人会把obstacleMask传成~0或者~1,结果射线连子弹、玩家、自己全都能检测到,导致炮台永远“看不见”玩家。这种事很容易发生,特别是从网上复制代码时。在 Inspector 里一定要看清楚 mask 只选择了 ObstacleLayer 这一层。

4.3 开火节奏:冷却时间的两种实现

开火不是每帧判断能打就打,否则炮台会以每秒几十发的高频射击。需要加冷却时间。

第一种写法,用 Update 里的计时器:

private float nextFireTime; private void Update() { if (CanSeePlayer() && Time.time >= nextFireTime) { Fire(); nextFireTime = Time.time + fireCooldown; } }

直观、清晰,适合单炮台。缺点是每帧都要调一次CanSeePlayer,而且CanSeePlayer里有射线检测。对于单个炮台还好,如果场景里同时有几十个炮台,每帧几十条射线,性能还是有点压力的。

第二种写法,用协程:

private void Start() { StartCoroutine(FireLoop()); } private IEnumerator FireLoop() { while (true) { float cd = 0.1f; if (CanSeePlayer()) { Fire(); cd = fireCooldown; } yield return new WaitForSeconds(cd); } }

这样炮台每隔一小段时间做一次判定。没有玩家就 0.1 秒后再看一眼,有玩家就开火并等待完整的冷却时间。好处是后续加炮口闪光、音效、升级逻辑都方便在同一个协程里集中处理。

这里要提醒一个坑:如果用了协程开火,注意不要让子弹的对象池回收逻辑把协程搞挂。协程在炮台脚本上,子弹回收入池不影响它,所以没问题。我看到有人把协程写在子弹脚本上,一旦子弹被回收,协程也跟着停止,下一发子弹出来就不会执行任何逻辑了。这个问题很难一眼看出来,只能通过打 Log 排查。

我自己推荐第二种方式:用协程集中管理开火逻辑,因为它的扩展性足够好。后面想改成“三连射”“扇形散射”“蓄力攻击”,都是在 FireLoop 里做文章,不用把逻辑塞进 Update 里。

5. 实测中容易翻车的细节与排查思路

5.1 转向反向或偏移 90 度

症状是炮台对着反方向,或者固定偏移一个角度。排查顺序如下:

  1. 检查Mathf.Atan2的参数顺序是不是(y, x)。
  2. 用Debug.DrawLine画出炮口到玩家的方向线,确认向量方向是否正确。
  3. 用Debug.Log打印targetAngle,对比玩家实际方位。比如玩家在炮台上方,理论角度应该是 90 度左右。如果输出 0 度或 180 度,说明素材默认朝向与角度基准不匹配,调angleOffset。
  4. 确认旋转轴是 Z 轴。2D 物体旋转全部围绕 Z 轴,如果你不小心设成Euler(angle, 0, 0),炮台就会往 3D 方向转,看起来像发病了。

5.2 子弹总从炮管旁边冒出来

原因基本只有两个:用了transform.position而不是muzzlePoint.position,或者 MuzzlePoint 没放在炮口位置,只是随便挂了个空节点。

排查时在脚本里加一个 Gizmos 绘制,方便直接在编辑器里看到开火点:

private void OnDrawGizmosSelected() { if (muzzlePoint != null) { Gizmos.color = Color.red; Gizmos.DrawWireSphere(muzzlePoint.position, 0.15f); } }

然后选中炮台,在场景视图里旋转几个方向,确认红圈始终在炮口尖端。如果不在,就调 MuzzlePoint 的局部位置。

5.3 子弹速度太快导致穿透

子弹移动方式一般是Transform.Translate或者Rigidbody2D.velocity。当速度很快时,物理引擎的碰撞检测可能在两帧之间跳过目标碰撞体,子弹直接穿墙而过。

如果是用Rigidbody2D移动,可以先把Collision Detection改成Continuous或Continuous Speculative。缺点是连续碰撞检测的性能开销更大。如果速度超过 10 单位每帧,即使 Continuous 也可能失效,这时更可靠的方式是把子弹运动改成“每帧向前发一条射线,检测前方碰撞”。

我自己的建议:如果只是做炮台 Demo,把子弹速度控制在一个合理范围内,比如10~15个单位每秒,开 Continuous 就够用。追求更高速度时,直接用射线检测做子弹前防更稳。

5.4 Update 和 FixedUpdate 的选择

炮台转向和开火逻辑我倾向于放 Update,因为它是表现层的东西,需要跟随渲染帧率保持平滑。如果放到 FixedUpdate(默认 0.02 秒一次,也就是 50Hz),玩家在高刷新率显示器上能看到炮台的转向明显一顿一顿的,观感很差。

射线检测理论上放 FixedUpdate 也可以,因为物理系统本身固定步长。但你如果让转向在 Update 跑,射击判定在 FixedUpdate 跑,会存在微小的不同步。现象是:炮管还没完全转到位,子弹就射出去了。这个误差一般很小,能接受;但如果你追求精确,可以把射击判定也放 Update,只是每帧的频次要自己控制。

说到底,炮台这种东西没有绝对的标准答案,核心是别让表现层和判定层脱节太多,不然调试的时候你会被自己绕晕。

5.5 常见症状排查表

症状可能原因排查重点
炮台反向旋转Atan2 参数写反,或素材朝左打印 targetAngle,对比玩家方位
炮台到处乱转旋转轴设错检查 Euler 是否为 (0, 0, angle)
子弹出膛位置不对没用 muzzlePoint,或层级错误用 Gizmos 画开火点确认
子弹不回收pool 引用未设置,或 Return 未触发确认 SetPool 被调用
隔墙打人Raycast 未加 obstacleMask检查 LayerMask 与 Layer 配置
开火后不再开火协程被意外停止,或 nextFireTime 未更新打 Log 查看开火循环是否仍然存活
子弹偶尔穿透碰撞检测模式过低,或速度过快改 Continuous,或改为射线检测

这张表是我自己做项目时的 CheckList,每次测试时一看症状,直接对号入座,能省不少时间。

最后再分享一个我自己养成的小习惯:开发这类功能时,一定要把调试视图开起来,把射线和 MuzzlePoint 都用 Gizmos 画出来。眼睛看到的东西永远比 Log 更直观,调起来也快得多。调试五分钟,写作两小时,这种亏我吃过太多次了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 8:13:10

AMD/Nvidia显卡GOP更新工具V1.9.6.5:解决UEFI启动黑屏与兼容性问题

简介&#xff1a;这是一款面向显卡玩家与DIY爱好者的UEFI GOP更新工具&#xff0c;用于为AMD、NVIDIA显卡新增或升级UEFI GOP模块&#xff0c;解决老卡在新平台无法正常UEFI启动、开机黑屏或分辨率异常等问题。资源包共39个文件&#xff0c;约6.86MB&#xff0c;包含18个efirom…

作者头像 李华
网站建设 2026/10/19 8:13:09

C#数据采集卡开发实战:从DLL互操作到连续采样缓冲设计

去年做一套多通道振动监测的上位机&#xff0c;客户提的需求很简单&#xff1a;一块数据采集卡&#xff0c;C#程序读数据&#xff0c;屏幕上画波形&#xff0c;数据能存盘。听起来两个小时就能交货&#xff0c;实际做下来&#xff0c;光是DLL互操作和连续采样的缓冲设计就占掉了…

作者头像 李华
网站建设 2026/10/19 8:11:46

diagram-design:构建人机共读的系统逻辑快照

1. 项目概述&#xff1a;这不是画图&#xff0c;是构建可执行的系统逻辑骨架“diagram-design”这个词组乍一看像某个设计软件里的菜单项&#xff0c;但在我过去十年带过的几十个跨领域项目里&#xff0c;它从来不是指用鼠标拖拽几个方块再连几条线那么简单。真正让我反复被客户…

作者头像 李华
网站建设 2026/10/19 8:10:13

网络攻击行为背后的核心算法:从搜索思维到攻防实战

安全基础系列写到这里&#xff0c;终于到了很多人又爱又怕的部分——网络攻击行为背后的主要算法。这里先说明一下&#xff1a;算法不是指密码学里的AES、RSA这类加解密算法&#xff0c;而是攻击者在侦察、突破、传播、隐蔽这条完整行为链路上实际使用的计算方法与策略。通俗点…

作者头像 李华
网站建设 2026/10/19 8:10:11

无人机红外目标检测:基于YOLOv11的行人车辆识别系统实战解析

无人机挂上红外相机之后&#xff0c;很多事就变得不一样了。白天清清楚楚的可见光画面&#xff0c;到了夜间、大雾、树丛遮挡条件下&#xff0c;基本就是一片糊。前阵子我帮某无人机团队做了一个基于深度学习YOLOv11的红外检测系统&#xff0c;专门检测行人和车辆&#xff0c;整…

作者头像 李华