October 18, 2008

XNA Game Engine Tutorial Series #4 – DrawOrder

This tutorial is going to be a quick one. I originally had this as part of the next one, but it is getting to be too long for one post so I’m splitting it into two parts. In this chapter we are going to be adding a draw order property to the Component class. This number will modify the order in which the GameScreen will draw its components. We will need to modify Component, GameScreen, ComponentCollection, and create a custom enumerator called ComponentEnumerator that will allow us to loop over the components by their draw order.

The first thing we are going to do is add an int property called drawOrder to the Component class. We will also create a public version of this called DrawOrder that fires an event called DrawOrderChanged when the value changes, which other objects can ‘hook’ to be notified when the draw order changes. For example, ComponentCollection needs to know about this so it can update the order it returns componennts in while drawing. Here is the code for the DrawOrder property and DrawOrderChanged, and the ComponentDrawOrderChangedEventArgs and ComponentDrawOrderChangedEventHandler. The latter two go outside Component somewhere, while the first two go inside Component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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));
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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);

Now we are going to make the ComponentEnumerator. This class is in charge of telling a loop what order to loop through objects in. For example, we want to draw in an order that is not neccessarily to the same as we would update. So, we will make a custom enumerator and have ComponentCollection return when drawing, ie: ‘foreach(Component component in Components.InDrawOrder)’. This enumerator takes a list of components, and a list of ints. The second list determines what order to return components in, as each number in the list refers to an index in the component list. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System.Collections;
using System.Collections.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<int> ordered = new List<int>();
 
        // 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<int> Order)
        {
            this.collection = Collection;
            this.ordered = Order;
        }
 
        // Moves to the next item in the list
        public bool MoveNext()
        {
            position++;
 
            // If we have reached the end of the list, stop
            // enumerating
            if (position == ordered.Count)
                return false;
 
            // Otherwise keep going
            return true;
        }
 
        public IEnumerator GetEnumerator()
        {
            return this;
        }
 
        // Resets to the beginning of the list
        public void Reset()
        {
            position = -1;
        }
    }
}

Next we need to set up ComponentCollection to use this enumerator. There are a number of changes so here is the entire code with all the changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
using System.Collections.Generic;
using System.Collections.ObjectModel;
 
namespace Innovation
{
    // A custom collection for managing components in a GameScreen
    public class ComponentCollection : Collection<Component>
    {
        // 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<int> inDrawOrder = new List<int>();
 
        // 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);
        }
    }
}

Finally, we will change GameScreen to use the new enumerator. Change this line in the draw method:

1
foreach (Component component in Components)

to this:

1
foreach (Component component in Components.InDrawOrder)

Now, we can test this out. If we create a new ClearScreen in the LoadContent method after we create our actor and run the game, we should see a red screen.

1
ClearScreen clr = new ClearScreen();
Red Screen

Red Screen

This is because the ClearScreen is drawing after the actor, overwriting anything it drew. But, if we tell the actor to draw after the ClearScreen, only the background will be red because the actor is drawing over it.

1
2
ClearScreen clr = new ClearScreen();
actor.DrawOrder = 2;
Actor with red background

Actor with red background

That’s all for this one, next up we’ll be adding physics to our engine!

Download the Code for this Chapter

Back to Game Engine Tutorial Series


kick it at GameDevKicks.com


10 Responses to “XNA Game Engine Tutorial Series #4 – DrawOrder”

  1.   floAr Says:
      October 29, 2008 at 12:50 pm

    Hey Sean. I just would like to thank u for this great tutorials .. They are incedible usefull and understandable ;)
    Can´t wait for the next one.
    floAr

  2.   Chip Says:
      October 30, 2008 at 11:34 pm

    I’ve been looking for stuff like this! Keep them coming!!!

  3.   Mike Says:
      November 4, 2008 at 3:52 pm

    Excellent set of tutorials, Sean! Your documentation is thorough and very comprehensible. I look forward to your next one.

  4.   Liort Says:
      November 13, 2008 at 4:40 am

    Hi! nice tutorial

    I wanted to ask-

    why is the need for an event to fire in case of a DrawOrder change?

    In this case, cant i simply update the ComponentCollection of the specific GameScreen to keep it in order? Why other components need to know about this.. ?

    Lior

  5.   monkeyboy Says:
      December 1, 2008 at 4:59 am

    Hey Sean,

    Again, another homerun with your tutorials.

    I do have another question. I had to make the following change in order to reproduce the test above (ClearScreen drawing over Actor). Let me know your thoughts. With the code above the InDrawOrder list would always inserted the newest component into position zero.

    if (Items[inDrawOrder[i]].DrawOrder > ord)
    {
    i–;
    break;
    }
    else
    i++;

  6.   Eibach Says:
      December 13, 2008 at 4:36 pm

    I had to do the same as monkeyboy =/

  7.   Brian Says:
      January 10, 2009 at 7:36 am

    > why is the need for an event to fire in case of a DrawOrder change?

    There’s not, really. You could just as easily create a Comparison delegate and sort the temporary lists by the draw order.

  8.   Chris Harshman Says:
      February 9, 2009 at 5:15 pm

    I am not expert but in both my typed out code, and your project file you cannot get the result you describe.

    It is something to do with the way the drawOrder is used.

    I am not sure on the full cause yet, but I am looking into, without a hack fix, which can be done but is not pretty.

  9.   James Says:
      February 9, 2009 at 7:35 pm

    Chris,

    The problem you’re having is in line 71 of the ComponentCollection class above. Change the >= to just > and it should work for you.

    This happens because unless you specify a draw order for each component they are all defaulted to 1. Since on line 71 it was greater than OR equal-to as monkeyboy stated it was not passing the if check on line 75 if they were equal, and breaking out immediately to the insert which would put it just before all components with the same DrawOrder.

    I also changed line 75 to if(i > 0) because of problems with expicitly setting the DrawOrder of a Component to a non default value. Occasionally index ‘i’ would end up as a -1 which will crash on the Insert. This just makes sure that ‘i’ doesn’t go below 0 which is the very front of the Collection anyway.

    Thanks again Sean for some great ideas.

  10.   Yanko Says:
      April 15, 2009 at 2:39 pm

    it does not display the box just red back ground? why?

comment Leave a Reply