SummerJammers Update #2: Components All the Way Down
By PeezMachine 10 Comments
(If you're not sure what subclasses and interfaces are, I've included some helpful definitions at the end of the post)
Welcome back! I've had two weeks to work toward some milestones, so let's start by seeing how that went.
- straight toss in player-determined direction
Incomplete. Disc implementation was pushed back to the next deadline.
- player "dash"
Done!
- basic player/disc hit detection
Incomplete.
- simple "incidence = reflection" disc physics
Designed but not tested since I haven't written any objects that implement the CanDeflectDiscs interface, such as walls.
Best Bug: By accidentally asking for my SceneManager service instead of the SpriteBatch service, I managed to create a fun little Draw loop that went: SceneManager --> Scene --> Player --> SceneManager --> ...
It would be easy to look at the lineup there and say "one and a half out of four is pretty weak," but it's actually been a remarkably productive two weeks. To understand just how much progress has been made, we need to talk a bit about XNA's "component" architecture. There are two component classes defined in XNA, GameComponent and DrawableGameComponent. A GameComponent is essentially just an object that can be "enabled" when it needs to interact with the game logic and "disabled" otherwise (the DrawableGameComponent inherits those capabilities but adds members to determine if it should be drawn or not). The components contain event handlers that get called when an object is enabled/disabled or shown/hidden, but but there's actually not much else to them. The actual Update and Draw methods are left blank, since it's obviously up to each particular component to determine how it interacts with the game and draws itself to the screen. There's nothing saying you have to use XNA's components, but they get the job done and having the event handlers all written is pretty nice, so I've been using them in lieu of writing my own component classes... kind of.
You see, there's a complication. XNA also contains a GameComponentCollection class which can group components to manage relative update and draw orders. So, for example, if I want to ensure that the players get drawn after the court (so that you can actually see them), I could throw the court and both players into a collection with the appropriate DrawOrder values and they will be sorted automatically. This is some pretty sweet functionality, but there's a huge catch. The type of things that are allowed to be stored in these collections are any objects that implement the IGameComponent interface, namely GameComponent, DrawableGameComponent, or any subclasses I write. The collection itself doesn't implement this interface, which means you can't have a GameComponentCollection full of GameComponentCollections. This is too bad because it locks out a really cool recursive implementation where a single Draw or Update call gets sent to a top-level object like a Scene and it trickles down, drawing chunks of objects in the correct order (and each individual object in correct order relative to its chunkmates).
So I toyed with a couple of solutions. My first idea involved subclassing GameComponentCollection to make a new class called SuperComponentCollection. I would make this component implement the IGameComponent interface, thus allowing it to store other collections as elements and give me the recursive functionality I wanted. This was less than ideal for a number of reasons, so I instead decided to write a subclass of the SortedList class that would essentially store and sort chunks of components. It was at this point that I remembered something very important: just because a method is called Draw doesn't mean it has to directly put something on the screen. This lead me to a ridiculously simple solution: just allow each component to keep a list of sub-components. That way, when a component gets a Draw or Update call, it can just pass the call down the line to its children, who will pass it to their children until it finally gets to objects that literally appear on the screen (a compact class I call ImageComponent). So I subclassed the GameComponent and DrawableGameComponent classes into SuperComponent and SuperDrawableComponent and wrote (my first ever!) custom Comparer class that told each object how to sort its children by update and draw order. It works beautifully, with a single top-level Draw call trickling down.

This is big. When I first started with XNA a few weeks ago, it took me a matter of minutes to get something drawn to screen and to move it around with an X360 controller. But that was all hard-coded hack work. Now I'm pretty much in the same place - drawing some dummy art and moving it around with an X360 controller - but it's all going through the proper channels. What's happening on the screen isn't a tech demo, it's the game's actual logic running. In a similar vein, I also built a lot of the IO infrastructure that decouples input commands from the particular device, so while I'm using an X360 controller right now, any device that has a C# library can be supported.
I also feel like I'm "over the hump," so to speak. There's that moment with most computer-science related problems where you can finally make sense of what's happening each step of the way, and I feel like I've reached that point when it comes to the update and draw flow, which is the game in many ways. So it's been a lot of foundation-building lately, but I've got enough pieces in place that I should be able to start putting together a complete practice match without too much fuss. I've got the Stage, Disc, and Player classes in a pretty good place and have started doing some work on mapping on-court position (which is resolution-invariant) to absolute draw position (which is resolution-dependent). So let's go ahead and set the next goal. It'll be modest for a two-week goal, largely because I'm out of town (and away from my desktop) for over a week for Thanksgiving (which is also why there are no screenshots). That means I'll largely be writing code and pseudocode in Notepad until I can get home and add it properly, so I'm largely going to work on things that are self-contained or brand new.
By 5:00 PM PST on Monday, December 9, 2013
- Fully implement Wall, Disc, and Goal classes
- Build a complete test stage with goals, wall, and bleachers
- Fully implement methods to convert on-court position to draw position
- Implement Draw transforms and logic for 1920x1080 and 1280x800 resolutions (more will be added later, I just want to have two different resolutions for testing purposes while I build that framework)
In other news, the community overwhelmingly approved the name SummerJammers, so thanks to everyone who submitted names and voted. Assuming I hit the next milestone, I'd say the call for assets and extra development will go out by the end of the year. In the meantime, can y'all do me a favor and tell me what your native resolution is? I'd like to natively support any widely used resolutions, and I'd like to know what they are before I start doing too much UI work.
Today's Vocabulary:
Subclass. A class which inherits all of the properties and abilities of another class but can add more specific unique characteristics. Poodle is a subclass of Dog- a Poodle can do all the normal Dog things like Bark and Eat, but it can also Strut, which not all Dogs can do. You can subclass a subclass (like Canadian Attack Poodle could subclass Poodle) but you can generally not inherit from two different classes at the same time (like Poodle and Retriever).
Interface: A sort of contract that states that any class which implements the interface is guaranteed to have certain properties and abilities. For example, the IDrawable interface could stipulate that any implementing class must have a DrawOrder value and a Draw() method. However, the exact implementation of methods is left to the implementing class, so you could have a class whose Draw() method does absolutely nothing. In return for implementing an interface, a class can be used any time that interface is asked for, so if a certain bit of code requires an IDrawable object, I can give it anything that implements that interface regardless of what type of object it is. Classes can implement any number of interfaces, in stark contrast to the limit on class inheritances.
10 Comments