【问题标题】:Unity3D How can I select multiple objects in 3D with a drag and select / lasso select?Unity3D 如何通过拖动和选择/套索选择在 3D 中选择多个对象?
【发布时间】:2020-11-18 02:59:51
【问题描述】:

我正在努力寻找一个好的教程或信息,让我能够以用户友好的方式选择多个 3D 对象。

到目前为止,我找到的最好的教程是这个:https://sharpcoderblog.com/blog/unity-3d-rts-style-unit-selection。本教程通过使用可选对象的 transform.position 并检查它是否在用户的选择范围内来工作。

我希望让用户能够选择一个单位,即使它只是部分在用户的选择范围内,例如大多数 RTS 游戏(2D 和 3D)。

一种可能性是使用相机的剪裁距离和用户的选择创建一个临时网格,然后检查碰撞,但我找不到任何使用这种方法的教程,也不知道这是否是最好的方法主题。

【问题讨论】:

标签: unity3d 3d collision-detection multipleselection


【解决方案1】:

如果我理解正确你想

  • 不知何故开始选择
  • 收集在收集过程中被“击中”的每个对象
  • 以某种方式结束集合

你不能简单地使用光线投射吗?我现在假设是简单的鼠标输入,但您基本上可以将其移植到您拥有的任何输入上。

// Just a little helper class for an event in the Inspector you can add listeners to
[SerializeField]
public class SelectionEvent : UnityEvent<HashSet<GameObject>> { }

public class SelectionController : MonoBehaviour
{
    // Adjust via the Inspector and select layers that shall be selectable
    [SerializeField] private LayerMask includeLayers;

    // Add your custom callbacks here either via code or the Inspector
    public SelectionEvent OnSelectionChanged;
 
    // Collects the current selection   
    private HashSet<GameObject> selection = new HashSet<GameObject>();
    // Stores the current Coroutine controlling he selection process
    private Coroutine selectionRoutine;

    // If possible already reference via the Inspector
    [SerializeField] private Camera _mainCamera;

    // Otherwise get it once on runtime
    private void Awake ()
    {
        if(!_mainCamera) _mainCamera = Camera.main;
    }

    // Depending on how exactly you want to start and stop the selection     
    private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            StartSelection();
        }
        if(Input.GetMouseButtonUp(0))
        {
            EndSelection();
        }
    }
    
    public void StartSelection()
    {
        // if there is already a selection running you don't wanr to start another one
        if(selectionRoutine != null) return;
    
        selectionRoutine = StartCoroutine (SelectionRoutine ());
    }
    
    public void EndSelection()
    {
        // If there is no selection running then you can't end one
        if(selectionRoutine == null) return;
    
        StopCoroutine (selectionRoutine);
        selectionRoutine = null;
    
        // Inform all listeners about the new selection
        OnSelectionChanged.Invoke(new HashSet<GameObject>(selection);
    }
    
    private IEnumerator SelectionRoutine()
    {
        // Start with an empty selection
        selection.Clear();
        
        // This is ok in a Coroutine as long as you yield somewhere within it
        while(true)
        {
            // Get the ray shooting forward from the camera at the mouse position
            // for other inputs simply replace this according to your needs
            var ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
         
            // Check if you hit any object   
            if(Physics.Raycast(ray, out var hit, layerMask = includeLayers ))
            {
                // If so Add it once to your selection
                if(!selection.Contains(hit.gameObject)) selection.Add(hit.gameObject);
            }
    
            // IMPORTANT: Tells Unity to "pause" here, render this frame
            // and continue from here in the next frame
            // (without this your app would freeze in an endless loop!)
            yield return null;
        }
    }
}

当然,在这个例子中你可以直接在Update 中进行,但我想以一种你可以根据需要轻松交换输入法的方式提供它;)

在 UX 方面,您可能还想在每次向选择中添加新对象时调用第二个事件,如 OnSelectionPreviewUpdate 或类似的东西,以便能够例如可视化选择结果。


我可能理解错了,听起来你宁愿把所有东西都放在一个绘制的形状内。

这稍微复杂一些,但这是我的想法:

  • 拥有一个默认禁用且不执行任何操作的虚拟选择刚体对象
  • 上面甚至没有渲染器,而是一个网格过滤器和网格碰撞器
  • 在“绘制”时根据输入创建网格
  • 然后使用Rigidbody.SweepTestAll 来检查你是否被它击中

在智能手机上输入,但我希望思路清晰

【讨论】:

  • 感谢您的回复,我很难理解它如何允许我在给定区域内选择多个对象(套索选择/拖动和选择)。我的理解是,这将允许我在鼠标悬停在其上的条件下添加任何内容,但如果我将鼠标从一个角拖到另一个角,则不允许选择屏幕上的所有对象
  • @MatthieuRaynauddeFitte 是的,抱歉刚刚理解了你的问题^^ 起初我以为你想要一个悬停选择。现在我没时间了;)我简要地添加了我的想法作为编辑,但现在我还不能实现它..正如电话里所说的那样:)
  • 没问题,感谢您抽出宝贵的时间 :) 我确实对 raycast 的研究不够,我会检查是否在 raycast 文档中找不到更多内容
【解决方案2】:

我想我会尝试创建一个 PolygonCollider2D,因为它与创建网格相比非常简单。您可以通过为其提供 2D 点(例如指针/鼠标的位置)来设置其路径(轮廓)。使用SetPath 方法。然后,您可以使用其中一种方法来检查空间中的另一个点是否与该对撞机形状重叠。

虽然 PolygonCollider2D 与 2D 组件交互,但您仍然可以使用其Collider2D.OverlapPoint 方法在将 3D 对象转换为 2D 空间后检查其位置/边界。

您还可以使用它的CreateMesh 方法创建一个网格,用于在屏幕上绘制您的选择区域。

您可以阅读有关 PolygonCollider2D 的更多信息here

希望它有意义并希望它有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 2015-08-07
    • 2014-12-23
    • 2021-02-01
    • 2014-02-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多