Unlike the in depth investigation and learning required to understand premultiplied alpha, as discussed in my previous post, today's topic is simple and satisfying.
Collections of collections
I've structured the graphical elements of my game into a number of logical layers (not display layers). I won't go into all of them, but to give you an idea, here are some of the key parts, from the most fundamental to the most complex:
- The most basic layer is a
Texture
, which is an abstract class that represents a picture that is loaded from a file. - Above that is an
OpenGLES20Texture
, which is an implementation ofTexture
specifically for OpenGL ES 2.0. - Above that is a
Graphic
, which includes information about theTexture
, plus information about its orientation, size, and color. - The next layer up is
Widget
, which is an abstract class that has a 2D position, an abstractAnimate
method, and an abstractRender
method. - One layer above that is
Symbol
, which implementsWidget
and provides a number of concrete features, including references to one or moreGraphic
instances. I use this class for all of the interactive controls in the game, like the checkmark to start a game, the question mark icon for starting the tutorial, etc. - Another layer above
Widget
are all of the game UI elements, likeShip
, which is the white ball that moves around and bounces off of walls. This also implementsWidget
and contains the logic to make the ship do what it is supposed to. I have similar classes for the other game UI elements likeWall
,Diagonal
, etc.
Given the above structure, this means that all graphical elements that I need to render inherit from Widget. In the various game screens I keep track of these in collections that inherit from the following:
public interface IWidgetEnumeration { /// <summary>Returns the <see cref="Widget"/>s in the collection.</summary> IEnumerable<Widget> Widgets { get; } }
For example, the game screen parent class collects all of these multiple collections via the following:
protected sealed override IEnumerable<IWidgetEnumeration> WidgetsToRender { get { foreach(IWidgetEnumeration collection in LowerWidgetsToRender) { yield return collection; } yield return WallsHorizontal; yield return WallsVertical; yield return Goals; yield return Diagonals; yield return Poles; yield return _diagonalMarkers; yield return Ships; yield return Collisions; foreach(IWidgetEnumeration collection in UpperWidgetsToRender) { yield return collection; } yield return _pauseSymbols; } }
What's a little interesting here is that this accessor is an IEnumerable<IWidgetEnumeration>
, or an enumeration of collections. This allows subclasses to override LowerWidgetsToRender
and UpperWidgetsToRender
to add additional widgets as necessary. What's been slightly annoying to me for a while, and what finally gets to the point of this blog entry, is that there have been a number of instances when I needed to only add a single new graphical element in a sub-class. But, since I need to return an IWidgetEnumeration
I kept needing to create a collection to contain that single graphical element. This made the override of LowerWidgetsToRender
look something like this:
protected override IEnumerable<IWidgetEnumeration> LowerWidgetsToRender { get { yield return _lowerWidgets; } }
Where _lowerWidgets
is an IWidgetEnumeration
that contains just one Widget
. I couldn't just return the Widget
directly, because I must return an IWidgetEnumeration
. This seems inefficient to create this collection just to contain one element. But wait, IWidgetEnumeration
is an interface. What's to stop me from implementing that directly on Widget
so I can just return that directly? Well, that's exactly what I did. I made Widget
implement IWidgetEnumeration
and added the following simple bit of code to it.
public IEnumerable<Widget> Widgets { get { yield return this; } }
This allowed me to change the above LowerWidgetsToRender
accessor into the following, where _tutorialCounter
is the single Widget
that was inside the previous collection.
protected override IEnumerable<IWidgetEnumeration> LowerWidgetsToRender { get { yield return _tutorialCounter; } }
I don't think this refactor improves the performance of the code in any measurable way, but it does make things a bit more straight forward and easier to understand. It's obviously not particularly clever or exciting, but I was happy when I thought of the solution and do feel the code is better because of it.
Next: A Tutorial; Step-by-step.
No comments:
Post a Comment