如何检查点击的对象是否是C#XNA中的最顶层?

时间:2012-06-20 13:53:04

标签: c# events xna

我正在尝试在C#XNA中重现简单的窗口界面对象,如标签,列表框,文本框和面板。所有对象都是从绘制对象并更新它的基本抽象类(XNAInterfaceObject)派生的。在更新方法中,它与鼠标和键盘交互并引发各种事件。 问题是当两个接口对象是一个在另一个上时(例如,在列表框上的弹出上下文菜单)并且两者都具有非空事件 - 当我只需要最顶层对象的事件时,代码将触发两个事件。如何检查哪个对象是最顶层的?或者以某种方式使顶部物体与下部物体重叠。我想到了全局变量,它将保留最后一次单击对象的引用,而其他对象将检查此变量是否为null以继续其事件,但我认为这是一个粗略的解决方案,并且存在更优雅的一个。

对不起我的语言,我不是母语为英语的人。

3 个答案:

答案 0 :(得分:1)

我可能会将这个问题分解为两个部分:

  1. 确定接口对象的顺序。
  2. 仅在重叠时触发最顶层对象上的事件。
  3. 解决第一部分很简单。在基类中包含一个“图层”字段/属性,用于指定对象的深度。在大多数游戏节点类中,无论如何我都包含它,因为它在绘图中很有用。如果事情变得有点复杂,你可能需要一个单独的分层系统来进行界面排序,这种方法的缺点是你可以得到层次相同的重叠。

    正如@Attila建议的那样,您可以管理Z-Ordered界面元素列表。在这种情况下,订单由索引管理,并且它易于管理,但您不能在没有一些额外处理的情况下使用此信息进行绘制,并且它不会像简单的值比较那样快。

    <强>属性

    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

    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...
    }
    

    第二部分 有多种方法可以接近第二部分。最明显的是对交叉点和图层属性的所有接口组件进行检查,或者在Z-Ordered列表的情况下,所有组件都在列表的上方(在我的示例中接近0)用于交集。

    即使您使用屏幕缩小列表,这也可能会非常昂贵。相反,您可以管理引发事件的列表,并在处理输入后处理它们。例如......

    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;
    }
    

    无论如何,这些都是我的想法。这是非常深夜,所以我希望一切都是明智的,没有明显的错误。

答案 1 :(得分:0)

通常在GUI中有一个可见性排序列表(z-order),用于维护最重要的内容。使用这种技术(为你的每个组件分配az顺序),你可以检查点击组件的顶部是否还有更多的东西,包括点击的坐标 - 如果有的话,不要处理点击(som其他组件是在顶部,将处理它);否则此组件是处理点击的最顶层组件

答案 2 :(得分:0)

一个简单的解决方案是在Game类中创建一个列表:

List<XNAInterfaceObject> controls;

然后,您可以使用列表的顺序来解决您的问题。将列表中的第一个元素视为前面的控件。在游戏的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;
}

这意味着XNAInterfaceObject应该具有Rectangle属性和MouseClickMethod()。请记住,在绘制控件时,您必须向后遍历列表。