You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity2017.3.1f1敌人投射物脚本无法瞄准最近目标仅返回单对象求助

现有代码核心问题排查

  • 子弹脚本逻辑错误:RandomAIProjectile的Start方法中遍历所有玩家后反复覆盖子弹飞行速度,最终子弹只会朝向数组最后一个玩家飞行,且碰撞到任意目标后立刻销毁子弹,天然只能命中单个对象
  • 敌方数据只初始化一次:RandomAIEnemy的player数组仅在Start执行时查询一次,后续新加入的玩家/AI单位永远不会被识别
  • 单玩家逻辑限制:敌方脚本的playerX仅在Start时绑定单个玩家组件,多玩家场景下碰撞后只会扣除固定单个玩家的血量
  • 目标查询逻辑限制:TryGetClosestPlayer方法仅返回距离最近的单个玩家,没有多目标筛选能力

List功能改造实现(兼容Unity 2017.3.1f1)

所有用到List的脚本头部都需要先引入泛型命名空间:

using System.Collections.Generic;

1. 敌方控制脚本改造

将固定数组改为动态List,支持多目标查询,可选定时刷新玩家列表适配玩家进出场景的需求:

public class RandomAIEnemy : MonoBehaviour // 原脚本漏了继承MonoBehaviour,必须补上才能挂载到GameObject上
{
    // 原有属性变量全部保留,仅修改玩家存储结构
    private List<GameObject> players = new List<GameObject>();
    public float playerRefreshInterval = 0.5f; // 每0.5秒刷新一次玩家列表,可根据需求调整
    private float refreshTimer;

    void Start () 
    {
        RefreshPlayerList();
        waitTime = startWaitTime;
        moveSpots.position = new Vector2 (Random.Range (minX, maxX), Random.Range (minY, maxY));
    }

    void FixedUpdate () 
    {
        // 定时刷新玩家列表
        refreshTimer += Time.fixedDeltaTime;
        if (refreshTimer >= playerRefreshInterval)
        {
            RefreshPlayerList();
            refreshTimer = 0;
        }
        EnemyS ();
        RandomMove(); // 原逻辑未调用该方法,补上才能实现随机移动
    }

    // 刷新玩家列表方法,自动过滤已销毁/失活的单位
    void RefreshPlayerList()
    {
        players.Clear();
        foreach (var p in GameObject.FindGameObjectsWithTag("Player"))
        {
            if (p != null && p.activeSelf)
            {
                players.Add(p);
            }
        }
    }

    // 新增:获取射击范围内所有玩家的方法,返回List结构的多目标集合
    private List<GameObject> GetAllPlayersInShootingRange()
    {
        List<GameObject> inRangePlayers = new List<GameObject>();
        foreach (var p in players)
        {
            if (p == null) continue;
            // 用sqrMagnitude代替Distance减少平方根计算,降低性能消耗
            float sqrDist = ((Vector2)p.transform.position - (Vector2)transform.position).sqrMagnitude;
            if (sqrDist <= shootingRange * shootingRange)
            {
                inRangePlayers.Add(p);
            }
        }
        return inRangePlayers;
    }

    // 改造射击逻辑,支持对范围内所有玩家发射子弹
    void EnemyS () 
    {
        List<GameObject> inRangePlayers = GetAllPlayersInShootingRange();
        if (inRangePlayers.Count > 0 && nextFireTime < Time.time) 
        {
            // 示例:给每个范围内的玩家都发射一颗子弹,可按需修改为随机选择N个目标的逻辑
            foreach (var target in inRangePlayers)
            {
                GameObject newBullet = Instantiate (bullet, bulletParent.transform.position, Quaternion.identity);
                // 直接给子弹传递目标,避免子弹自主查询Tag浪费性能
                newBullet.GetComponent<RandomAIProjectile>().SetTarget(target);
            }
            nextFireTime = Time.time + fireRate;
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        // 改造扣血逻辑,碰到哪个玩家就扣哪个的血量,不再绑定固定单玩家
        if (collision.gameObject.tag.Equals ("Player")) 
        {
            Instantiate(blood,transform.position, Quaternion.identity);
            var playerX = collision.GetComponent<BlackthronpodPlayerX>();
            if (playerX != null)
            {
                playerX.health --;
                Debug.Log(playerX.health);
            }
            Destroy(gameObject);
        }
        // 原有子弹碰撞逻辑保留
        if (collision.tag == "Bullet")
        {
            Game.AddToScore(1);
            Destroy(gameObject);
        }
    }
    // 其余原有方法(RandomMove、OnDrawGizmosSelected等)无需修改,直接保留即可
}

2. 子弹脚本改造

移除子弹自主查询目标的逻辑,改为接收外部传入的目标,可配置穿透次数支持单子弹多命中:

public class RandomAIProjectile : MonoBehaviour // 同样补全MonoBehaviour继承
{
    public float speed;
    Rigidbody2D bulletRB;
    public GameObject explosionEffect;
    public int penetrateCount = 1; // 子弹可穿透的目标数,1为仅命中1个,调大可实现穿甲效果
    private int hitCount = 0;

    void Awake()
    {
        bulletRB = GetComponent<Rigidbody2D> ();
        Destroy (this.gameObject, 2); // 超时销毁逻辑提前到Awake执行,避免目标为空时子弹不销毁
    }

    // 外部调用的目标设置方法
    public void SetTarget(GameObject target)
    {
        if (target == null || bulletRB == null) return;
        Vector2 moveDir = (target.transform.position - transform.position).normalized * speed;
        bulletRB.velocity = moveDir;
    }
    
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.gameObject.GetComponent<BlackthronpodDiePlayer>() != null)
        {
            Destroy (other.gameObject);
            hitCount++;
        }
        if (other.gameObject.tag == "Player") {
            Instantiate (explosionEffect, transform.position, Quaternion.identity);
            hitCount++;
        }
        if (other.CompareTag ("Bullet"))
        {
            Destroy (other.gameObject);
            hitCount++;
        }
        // 达到穿透上限再销毁子弹
        if (hitCount >= penetrateCount)
        {
            Destroy(gameObject);
        }
    }
}

性能优化补充建议

  • 玩家数量低于100的情况下,0.5秒刷新一次列表的性能消耗极低,完全可以满足Slither.io类游戏的需求
  • 如果玩家数量过大,可改为玩家生成/销毁时主动向全局管理器注册/注销的逻辑,完全避免全局查找Tag的消耗
  • 子弹可引入对象池复用,减少频繁Instantiate和Destroy带来的GC开销,适合大量生成子弹的对战场景

内容的提问来源于stack exchange,提问作者Nathan Kumwenda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 10:15:01