我正在尝试在C#XNA中重现简单的窗口界面对象,如标签,列表框,文本框和面板。所有对象都是从绘制对象并更新它的基本抽象类(XNAInterfaceObject)派生的。在更新方法中,它与鼠标和键盘交互并引发各种事件。 问题是当两个接口对象是一个在另一个上时(例如,在列表框上的弹出上下文菜单)并且两者都具有非空事件 - 当我只需要最顶层对象的事件时,代码将触发两个事件。如何检查哪个对象是最顶层的?或者以某种方式使顶部物体与下部物体重叠。我想到了全局变量,它将保留最后一次单击对象的引用,而其他对象将检查此变量是否为null以继续其事件,但我认为这是一个粗略的解决方案,并且存在更优雅的一个。
对不起我的语言,我不是母语为英语的人。
答案 0 :(得分:1)
我可能会将这个问题分解为两个部分:
解决第一部分很简单。在基类中包含一个“图层”字段/属性,用于指定对象的深度。在大多数游戏节点类中,无论如何我都包含它,因为它在绘图中很有用。如果事情变得有点复杂,你可能需要一个单独的分层系统来进行界面排序,这种方法的缺点是你可以得到层次相同的重叠。
正如@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()。请记住,在绘制控件时,您必须向后遍历列表。