简体   繁体   English

如何检查单击的对象是否在C#XNA中最顶部?

[英]How can I check if the clicked object is the topmost in C# XNA?

I'm trying to reproduce the simple window interface objects in C# XNA like labels, listboxes, textboxes and panels. 我试图在C#XNA中重现简单的窗口界面对象,例如标签,列表框,文本框和面板。 All objects consequentially derive from basic abstract class (XNAInterfaceObject) that draws an object and updates it. 因此,所有对象都从基本抽象类(XNAInterfaceObject)派生,该抽象类绘制对象并对其进行更新。 In update method it interacts with a mouse and keyboard and raises various events. 在更新方法中,它与鼠标和键盘交互并引发各种事件。 The problem is when two interface objects are one over another (eg popup context menu over listbox) and both have non-null events - the code fires both events, when I just need the event of the topmost object. 问题是当两个接口对象彼此重叠时(例如,列表框上的弹出上下文菜单)并且两个接口对象都具有非null事件-当我只需要最顶层对象的事件时,代码将触发两个事件。 How can I check which object is the topmost? 如何检查哪个对象是最高的? Or make somehow the top object overlap the lower. 或以某种方式使顶部对象与下部对象重叠。 I thought about global variable which would keep the reference for the last clicked object, and other objects would check if this variable is null to proceed with their events, but I think it is a rough solution and there exists far more elegant one. 我想到了全局变量,该变量将保留最后单击的对象的引用,其他对象将检查此变量是否为null以继续其事件,但是我认为这是一个粗略的解决方案,并且存在一种更为优雅的解决方案。

Sorry for my language, I'm not a native English-speaker. 抱歉,我的语言不是英语。

I would probably break this issue down into two components: 我可能会将此问题分解为两个部分:

  1. Determining the order of interface objects. 确定接口对象的顺序。
  2. Only triggering the events on the top-most object when there's an overlap. 仅在发生重叠时才触发最顶层对象的事件。

Addressing part one is simple. 解决第一部分很简单。 Include a 'layer' field/property in the base class that specifies the depth of the object. 在基类中包括一个“层”字段/属性,用于指定对象的深度。 In most game node classes I include this regardless, as it's useful in drawing. 在大多数游戏节点类中,无论如何我都将其包括在内,因为它在绘制中很有用。 You may want a separate layering system for interface ordering if things get a bit more complex, and the downside to this approach is that you can get overlaps in which the layers are the same. 如果情况变得更复杂,您可能需要一个单独的分层系统来进行接口排序,这种方法的缺点是,在层相同的情况下可能会出现重叠。

As @Attila has suggested, you can otherwise manage a Z-Ordered list of interface elements. 正如@Attila所建议的,否则,您可以管理界面元素的Z排序列表。 In this case ordering is managed by index, and it's easy to manage but you can't also use this information for drawing without some additional processing and it won't be as quick as a simple value comparison. 在这种情况下,排序是通过索引进行管理的,并且很容易管理,但是您也不能在没有进行其他处理的情况下将此信息用于绘图,并且它不会像简单的值比较那样快。

Property 属性

public class InterfaceComponent
{
    // Class members...
    private float layer;
    public float Layer { get { return layer; } set { layer = Math.Abs(value); } }

    public bool InFrontOf(InterfaceComponent other) { return this.Layer < other.Layer; }
}

Z-Ordered List Z顺序表

public class InterfaceComponent
{
    private static List<InterfaceComponent> zOrder = new List<InterfaceComponent>();

    // Class Members....

    public InterfaceComponent()
    {
        // Construct class...
        zOrder.Add(this);
    }

    private void SetZOrder(int order)
    {
        if (order < 0 || order >= zOrder.Count)
            return;

        zOrder.Remove(this);
        zOrder.Insert(order, this);
        // There are more efficient ways, but you get the idea.
    }

    public void SendBack() { SetZOrder(zOrder.indexOf(this) + 1); }
    public void SendToFront() { SetZOrder(0); }
    // etc...
}

Part Two There are multiple ways to approach part two. 第二部分有多种方法可以处理第二部分。 The most obvious is to run a check against all interface components for intersection and layer property, or in the case of a Z-Ordered list, all components higher up the list (approaching 0 in my example) for intersection. 最明显的是对所有接口组件的交点和图层属性进行检查,或者在Z顺序列表的情况下,对列表中位于交点上方的所有组件(在我的示例中为0)进行检查。

This can end up being pretty expensive, even if you use screens to make the list smaller. 即使使用屏幕将列表缩小,这也可能最终非常昂贵。 Instead you can manage a list of raised events and process them after you handle input. 相反,您可以管理引发事件的列表并在处理输入后对其进行处理。 For example... 例如...

public static class InterfaceEvents
{
    public static List<EventData> List = new List<EventData>();

    public static void Resolve()
    {
        while (List.Count > 0)
        {
            for (int i = List.Count - 1; i > 0; i--)
            {
                if (List[i].EventType == List[0].EventType && List[i].Sender.Intersects(List[0].Sender))
                {
                    if (List[i].Sender.Layer < List[0].Layer) // However you choose to manage it.
                    {
                        List[0] = List[i];
                        List.RemoveAt(i);
                    }
                    else
                        List.RemoveAt(i);
                }
            }
            // Toggle event from List[0]
            List.RemoveAt(0);
        }
    }
}

public struct EventData
{
    public InterfaceComponent Sender;
    public int EventType;
}

Anyway, those are my thoughts. 无论如何,这些是我的想法。 It's pretty late at night, so I hope everything's remained sensible and there are no glaring mistakes. 夜已经很晚了,所以我希望一切都保持理智,没有明显的错误。

Usually in GUI there is a list of visibility ordering (z-order) that maintains what is on top of what. 通常,在GUI中,有一个可见性顺序列表(z顺序),用于维护最重要的内容。 Using this technique (assigning az order to each of your component) you can check if there is anything more toward the top of a clicked component that also includes the clicked coordinates -- if there is, do not handle the click (som other component is on top, that will handle it); 使用此技术(为每个组件分配z顺序),您可以检查被点击组件的顶部是否还有其他内容,其中还包括被点击的坐标-如果存在,则不要处理该点击(其他组件为最重要的是,它将处理该问题); otherwise this component is the topmost one to handle the click 否则,此组件是处理点击的最高组件

A simple solution is creating a list in your Game class: 一个简单的解决方案是在Game类中创建一个列表:

List<XNAInterfaceObject> controls;

You can then use the order of the list for your problem. 然后,您可以使用列表的顺序来解决问题。 Think of the first element in your list as the control that is at the front. 将列表中的第一个元素视为位于最前面的控件。 In the GetControlAt() method of your game, you can loop through the controls from front to back: 在游戏的GetControlAt()方法中,您可以从头到尾循环浏览控件:

protected override void Update(GameTime gameTime)
{
    ...

    MouseState ms = Mouse.GetState();
    if (ms.LeftButton == ButtonState.Pressed)
    {
        XNAInterfaceObject control = GetControlAt(ms.X, ms.Y);
        if (control != null)
            control.MouseClickMethod(ms);
    }

    ...
}

private XNAInterfaceObject GetControlAt(int x, int y)
{
    for (int i = 0; i < controls.Count; i++)
    {
        if (controls[i].Rectangle.Contains(x, y)
        {
            return controls[i];
        }
    }
    return null;
}

This means that a XNAInterfaceObject should have a Rectangle property and a MouseClickMethod(). 这意味着XNAInterfaceObject应该具有Rectangle属性和MouseClickMethod()。 Keep in mind that when drawing your controls, you have to loop through the list backwards. 请记住,绘制控件时,您必须向后遍历列表。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM