0

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

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

4

3 回答 3

1

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

  1. 确定接口对象的顺序。
  2. 仅在重叠时触发最顶层对象上的事件。

解决第一部分很简单。在指定对象深度的基类中包含一个“层”字段/属性。在大多数游戏节点类中,我无论如何都会包含它,因为它在绘图中很有用。如果事情变得有点复杂,您可能需要一个单独的分层系统来进行接口排序,而这种方法的缺点是您可能会获得层相同的重叠。

正如@Attila 所建议的那样,您可以管理界面元素的 Z 顺序列表。在这种情况下,排序是由索引管理的,它很容易管理,但是如果不进行一些额外的处理,您也不能使用此信息进行绘图,并且它不会像简单的值比较那样快。

财产

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-有序列表

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 顺序列表的情况下,检查列表中较高的所有组件(在我的示例中接近 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;
}

无论如何,这些是我的想法。现在已经很晚了,所以我希望一切都保持理智,没有明显的错误。

于 2012-06-20T14:31:36.850 回答
0

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

于 2012-06-20T14:01:56.447 回答
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()。请记住,在绘制控件时,您必须向后循环列表。

于 2012-06-20T15:57:38.443 回答