XNA Game Engine教程系列#4–DrawOrder

此教程较短。我本来准备把这部分内容放到下面的,但这样就太长了,所以我分成了两部分。在这一章中我们将在Component类中增加一个DrawOrder属性。这个属性将修改GameScreen绘制Component的顺序。我们需要修改Componnet,GameScreen,ComponentCollection,并创建一个叫做ComponentEnumerator的自定义枚举器,这将使我们能够根据Component的DrawOrder设置组件。

首先我们将在Component类中添加一个drawOrder属性。我们还将创建一个叫做DrawOrder的共有成员,它能够在值发生变化时调用DrawOrderChanged事件,当绘制顺序改变时其他物体可以通过“hook”收到信息。例如,ComponentCollection需要知道这一点,使其能够以更新绘制顺序并返回到绘制的componennts。以下是DrawOrder属性、DrawOrderChanged、ComponentDrawOrderChangedEventArgs和ComponentDrawOrderChangedEventHandler的代码,后两个在组件类外部,而前两个在组件类内部。

// The draw order of the component. Lower values draw first

int drawOrder = 1;

// Draw order changed event

public event ComponentDrawOrderChangedEventHandler DrawOrderChanged;

// Public draw order. If the value is changed, we fire the draw

// order change event public int DrawOrder

{

get { return drawOrder;}

set {

// Save a copy of the old value and set the new one

int last = drawOrder;

drawOrder = value;

// Fire DrawOrderChanged

if (DrawOrderChanged != null)

DrawOrderChanged(this, new ComponentDrawOrderChangedEventArgs( this, last, this.Parent.Components));

}

}

// Event arguments for draw order change on a component

public class ComponentDrawOrderChangedEventArgs : EventArgs {

// Component that was modified public Component Component;

// The old draw order public int LastDrawOrder;

// The collection that owns the component

public ComponentCollection ParentCollection;

public ComponentDrawOrderChangedEventArgs(Component Component, int LastDrawOrder, ComponentCollection ParentCollection)

{

this.Component = Component;

this.LastDrawOrder = LastDrawOrder;

this.ParentCollection = ParentCollection;

}

}

// Event handler for draw order change on a component

public delegate void ComponentDrawOrderChangedEventHandler( object sender, ComponentDrawOrderChangedEventArgs e);

现在,我们将创建ComponentEnumerator。这个类是负责告诉游戏循环按什么顺序绘制物体。举例来说,我们要绘制的顺序不一定和更新是相同的。因此,我们将创建一个自定义的枚举器并返回ComponentCollection,即:“foreach(Component component in Components.InDrawOrder)”。这个枚举器包含组件列表和ints列表。第二个列表确定返回组件的顺序,这是因为列表中的每个数字都指向组件列表的一个索引。代码如下:

using System.Collections;

using System.Collections.Generic; Generic;

namespace Innovation

{

public class ComponentEnumerator : IEnumerator

{

// The current position int position = -1;

// The collection we are enumerating ComponentCollection collection;

// The order in which to return items from 'collection'

List ordered = new List();

// The current item, which is the item who's index is

// at the current position in the order list

public object Current { get { return collection[ordered[position]]; } }

// Constructor sets the local component list and order

public ComponentEnumerator(ComponentCollection Collection, List Order)

{

this.collection = Collection;

this.opublic bool MoveNext()

{

position++;

// If we have reached the end of the list, stop

// enumerating

if (position == ordereturn false;

// Otherwise keep going

return true;

}

public IEnumerator GetEnumerator()

{

return this;

}

// Resets to the beginning of the list

public void Reset()

{

position = -1;

}

}

}

下一步,我们需要建立ComponentCollection使用此枚举器。下面是代码:

using System.Collections.Generic;

using System.Collections.ObjectModel;

namespace Innovation

{

// A custom collection for managing components in a GameScreen

public class ComponentCollection : Collection

{

// The GameScreen to manage components for GameScreen owner;

// The list containing each component's index in the

// component list, in the order we want them to draw

List inDrawOrder = new List();

// Constructor

public ComponentCollection(GameScreen Owner) { owner = Owner; }

// Override InsertItem so we can set the parent of the

// component to the owner and manage the DrawOrder

protected override void InsertItem(int index, Component item)

{

// Remove component from it's parent's list

if (item.Parent != null && item.Parent != owner)

item.Parent.Components.Remove(item);

// Set the new parent item.Parent = owner;

// Tell what to do when the item's draw order changes item.

DrawOrderChanged += new ComponentDrawOrderChangedEventHandler(ComponentDrawOrderChangeEventHandler);

base.InsertItem(index, item);

// Update its position in the draw order list UpdateDrawPosition(item);

}

// Draw order changed event handler

void ComponentDrawOrderChangeEventHandler(object sender, ComponentDrawOrderChangedEventArgs e)

{

// We simply update the component's position using the

// existing method

UpdateDrawPosition(e.Component);

}

// Updates the position of the component in the draw order list

void UpdateDrawPosition(Component Component)

{

// Save the draw order and index location in the component list

int ord = Component.DrawOrder;

int loc = Items.IndexOf(Component);

// Remove the index from the in order list

if (inDrawOrder.Contains(loc))

inDrawOrder.Remove(loc);

// Create our index variable int i = 0;

// Search through the ordered list until we find a component of

// lesser or equal draw order value

if (ord > 0)

{

while (i < inDrawOrder.Count)

// If the current item's draw order is greator or

// equal to the one we are working with...

if (Items[inDrawOrder[i]].DrawOrder >= ord)

{

// If it is greator, decrement it so it is

// above the component we are moving's draw order...

if (Items[inDrawOrder[i]].DrawOrder > ord) i--;

// And stop looping

break;

}

// Otherwise, keep going (until we reach the end of the

// list)

else i++;

}

// Insert the location of the component in the component list

// into the ordered list

inDrawOrder.Insert(i, Items.IndexOf(Component));

}

// Tells what enumerator to use when we want to loop through

// components by draw order

public ComponentEnumerator InDrawOrder { get { return new ComponentEnumerator(this, inDrawOrder); }

}

// Override RemoveItem so we can set the parent of

// the component to null (no parent)

protected override void RemoveItem(int index)

{

Items[index].Parent = null;

// Unhook the draw order change event

Items[index].DrawOrderChanged -= ComponentDrawOrderChangeEventHandler;

// Remove the component from the collection

base.RemoveItem(index);

// Rebuild inDrawOrder

inDrawOrder.Clear();

foreach (Component component in Items)

UpdateDrawPosition(component);

}

}

}

最后,我们将改变GameScreen以使用这个新枚举器,在Draw方法中将这行代码:

foreach (Component component in Components)

变为

foreach (Component component in Components.InDrawOrder)

现在,我们可以进行测试了。如果在建立了actor后,才在LoadContent方法中创建一个新的ClearScreen并运行游戏,我们应该看到一个红色的屏幕。

ClearScreen clr = new ClearScreen();

红屏

这是因为ClearScreen在actor之后绘制,它会覆盖前面绘制的东西。但是,如果我们告诉actor在ClearScreen之后绘制,那么只有背景是红色的,因为actor绘制在其上。

ClearScreen clr = new ClearScreen();

actor.DrawOrder = 2;

actor与红色背景

下次我们将在引擎中添加一个物理系统!


发布时间:2008/12/26 上午8:04:54  阅读次数:6551

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号