unity3d 如何探测进出飞机“导弹锁”锥的物体?

ttp71kqs  于 2022-12-30  发布在  其他
关注(0)|答案(1)|浏览(141)

正如你所猜测的那样,我正在Unity中制作一个小型的3d空战游戏。我正在努力开发的玩家能力之一(也很可能是敌人的能力)是“导弹锁定”对方飞机的能力。它应该是这样的:

  • 一架敌机进入锁定区域(玩家面前的一个小圆锥体)
  • 当敌机在区域内停留x秒后(或者十字准线“走到”目标上,等等,选择你的被动目标锁定模式),玩家获得目标锁定。玩家可以一次攻击一个或多个被锁定的敌人。
  • 如果一架敌机离开区域,目标锁定将被重置,并且必须重新捕获。

我的问题是,什么是最好的方式去做这件事?
大概,这将涉及到维护一个当前正在瞄准和已经瞄准的敌人的列表。我目前有一个函数,它返回一个给定帧中区域内所有敌人的列表,然而,比较前一帧和当前帧的结果似乎有点尴尬,因为我必须找到哪些列表元素是新的,哪些是旧的,哪些已经被删除。然后转移到他们的当前时间(?)。或者我可以增加列表中的所有项目,然后运行前面提到的函数并添加任何新的timer = 0的条目,同时删除任何已经离开区域的条目。有人能建议什么是处理这个交互的正确结构吗?
Unity提供了碰撞器事件用于碰撞进入和退出,所以我可能会附加一个圆锥体碰撞器,但是圆锥体的形状不合适(离玩家最远的一侧是平的),所以这并不理想,除非我保持圆锥体非常小,希望玩家不会注意到?
我使用了Creating a Flight Simulator in Unity3D Part 3: Weapons and AI (Vazgriz.com)的一些部分。我会理解如何有一个单一的锁定目标(然后立即选择另一个合格的目标,如果原来的离开或被摧毁)。然而,同时试图锁定x数量的对象在某种程度上似乎很复杂。

q3qa4bjr

q3qa4bjr1#

如果你已经有了敌人,我倾向于用他们来存储他们当前的“锁定状态”。如果游戏是单人游戏,那么“敌人”就不会作弊。游戏本身是自主的。如果游戏是多人游戏,我会重新考虑这种方法,但听起来不像是这样。

评价1

同样,这只是众多解决方案中的一个,但它似乎很有效。首先定义什么是可定向对象:

public enum TargetState
{
    None,
    Targeting,
    Locked
}

public interface ITargetable
{
    TargetState targetState { get; set; }
    float targettedTime { get; set; }
}

然后建立一个简单的敌人:

public class Enemy : MonoBehaviour, ITargetable
{
    [SerializeField] private Renderer _renderer;

    private TargetState _targetState = TargetState.None;
    public TargetState targetState
    {
        get => _targetState;
        set {
            if ( _targetState == value )
                return;
            _targetState = value;
            _renderer.material = Test.instance.TargetStateMaterial ( _targetState );
        }
    }
    public float targettedTime { get; set; }

    private void Start ( )
    {
        _renderer.material = Test.instance.TargetStateMaterial ( _targetState );
    }
}

最后看看一个'测试'播放器控制器:

public class Test : MonoBehaviour
{
    public static Test instance { get; private set; }

    [SerializeField] private float lockTime = 2f;
    [SerializeField] private Material normal;
    [SerializeField] private Material targeting;
    [SerializeField] private Material locked;

    [SerializeField] private float mouseSensitivity = 10f;

    private float _xAngle;
    private float _yAngle;

    private void Awake ( )
    {
        instance = this;
    }

    private void Start ( )
    {
        UnityEngine.Cursor.lockState = CursorLockMode.Locked;
    }

    public Material TargetStateMaterial ( TargetState s ) => s switch
    {
        TargetState.None => normal,
        TargetState.Targeting => targeting,
        TargetState.Locked => locked,
        _ => normal
    };

    void Update ( )
    {
        var d = Time.deltaTime * mouseSensitivity; ;
        _xAngle = Mathf.Clamp ( _xAngle += Input.GetAxisRaw ( "Mouse X" ) * d, -90f, 90f );
        _yAngle = Mathf.Clamp ( _yAngle -= Input.GetAxisRaw ( "Mouse Y" ) * d, -90f, 90f );
        transform.rotation = Quaternion.Euler ( _yAngle, _xAngle, 0 );
    }

    private void OnTriggerEnter ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable>(out var target) && target.targetState == TargetState.None )
            target.targetState = TargetState.Targeting;
    }

    private void OnTriggerStay ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable> ( out var target ) && target.targetState == TargetState.Targeting )
        {
            target.targettedTime += Time.deltaTime;
            if ( target.targettedTime >= lockTime )
                target.targetState = TargetState.Locked;
        }
    }

    private void OnTriggerExit ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable> ( out var target ) && target.targetState != TargetState.Locked )
        {
            target.targetState = TargetState.None;
            target.targettedTime = 0;
        }
    }
}

一旦你把它和这个放在一起,在检查器中抖动一些位,你会得到这样的东西:

方法2

使用不同的方法,这个修改的代码可以识别“攻击者”和“目标对象”。每个“攻击者”可以记录当前被锁定或锁定的所有“目标对象”。并且每个“目标对象”知道以它为目标的“攻击者”。代码类似于上面的代码,但是这个代码集更准确地适合OP中概述的要求:
“我试图开发的玩家能力之一(很可能是敌人的能力)是“导弹锁定”对方飞机的能力。”
下面是一个使用Dictionary集合的修改后的解决方案:

public enum TargetState
{
    None,
    Targeting,
    Locked
}

public interface IAttacker
{
    // Nothing required just yet, but this would undoubtedly change.
}

public interface ITargetable
{
    void Targetted ( IAttacker attacker );
    void Locked ( IAttacker attacker );
    void Clear(IAttacker attacker);
}

现在是一个敌人的实现示例。需要强调的是,如果游戏是在战场上,那么根据攻击者的最后一个目标状态分配材料可能是不合适的(在代码注解中注明)。但适合即时需求。

public class Enemy : MonoBehaviour, ITargetable
{
    private Renderer _renderer;
    private readonly Dictionary <IAttacker, TargetState> _attackers = new();

    private void Start ( )
    {
        _renderer = GetComponent<Renderer>();
        _renderer.material = Test.instance.TargetStateMaterial ( TargetState.None );
    }

    public void Clear ( IAttacker attacker )
    {
        _attackers.Remove ( attacker );
        // The following isn't appropriate in a 3rd person scenario. Will do for now.
        _renderer.material = Test.instance.TargetStateMaterial ( TargetState.None );
    }

    public void Locked ( IAttacker attacker )
    {
        _attackers [ attacker ] = TargetState.Locked;
        // The following isn't appropriate in a 3rd person scenario. Will do for now.
        _renderer.material = Test.instance.TargetStateMaterial(TargetState.Locked );
    }

    public void Targetted ( IAttacker attacker )
    {
        _attackers [ attacker ] = TargetState.Targeting;
        // The following isn't appropriate in a 3rd person scenario. Will do for now.
        _renderer.material = Test.instance.TargetStateMaterial ( TargetState.Targeting );
    }
}

和攻击者的测试实现。

public class Test : MonoBehaviour, IAttacker
{
    public static Test instance { get; private set; }

    [SerializeField] private float lockTime = 2f;
    [SerializeField] private Material normal;
    [SerializeField] private Material targeting;
    [SerializeField] private Material locked;

    [SerializeField] private float mouseSensitivity = 10f;

    private float _xAngle;
    private float _yAngle;

    private Dictionary <int, ( float targetTime, TargetState state)> _targetted = new();

    private void Awake ( )
    {
        instance = this;
    }

    private void Start ( )
    {
        UnityEngine.Cursor.lockState = CursorLockMode.Locked;
    }

    public Material TargetStateMaterial ( TargetState s ) => s switch
    {
        TargetState.None => normal,
        TargetState.Targeting => targeting,
        TargetState.Locked => locked,
        _ => normal
    };

    void Update ( )
    {
        var d = Time.deltaTime * mouseSensitivity; ;
        _xAngle = Mathf.Clamp ( _xAngle += Input.GetAxisRaw ( "Mouse X" ) * d, -90f, 90f );
        _yAngle = Mathf.Clamp ( _yAngle -= Input.GetAxisRaw ( "Mouse Y" ) * d, -90f, 90f );
        transform.rotation = Quaternion.Euler ( _yAngle, _xAngle, 0 );
    }

    private void OnTriggerEnter ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable> ( out var target ) )
        {
            if ( _targetted.ContainsKey ( other.gameObject.GetInstanceID ( ) ) ) return;
            _targetted.Add ( other.gameObject.GetInstanceID ( ), (0, TargetState.Targeting) );
            target.Targetted ( this );
        }
    }

    private void OnTriggerStay ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable> ( out var target ) )
        {
            var id = other.gameObject.GetInstanceID ( );
            if ( !_targetted.TryGetValue ( id, out var targetInfo ) )
            {
                targetInfo = (0, TargetState.Targeting);
                _targetted.Add ( other.gameObject.GetInstanceID ( ), targetInfo ); // Check to see if OnTriggerEnter was never called.
            }
            targetInfo.targetTime = targetInfo.targetTime + Time.deltaTime;
            if ( targetInfo.targetTime > lockTime )
                targetInfo.state = TargetState.Locked;
            _targetted [ id ] = targetInfo;

            Debug.Log ( $"OnTriggerStay ({targetInfo.targetTime}, {targetInfo.state})" );

            // A late check to see whether to inform the other ITargetable that they've been locked.
            if ( targetInfo.state == TargetState.Locked )
                target.Locked ( this );
        }
    }

    private void OnTriggerExit ( Collider other )
    {
        if ( other.gameObject.TryGetComponent<ITargetable> ( out var target ) )
        {
            var id = other.gameObject.GetInstanceID ( );
            if ( !_targetted.TryGetValue ( id, out var targetInfo ) )
                return; // Check to see if OnTriggerEnter was never called.
            if ( targetInfo.state == TargetState.Locked )
                return;
            _targetted.Remove ( id );

            // Inform the other ITargetable that they're not being targetted by this IAttacker anymore.
            target.Clear ( this );
        }
    }
}

应该注意的是,“攻击者”也可以是一个“目标”,只需将两个接口添加到类声明中即可。
一个快速测试显示它的行为与上面的GIF完全相同。

相关问题