Direct2D Engine (Proof of Concept) (C++/D2D)
Spring 2011 | Project Length: 3 weeks (Coursework)
A proof-of-concept for a Direct2D game engine written in C++. The engine code was written by me, and the demo pinball game was created with a partner for our classwork.
Project Goals & Requirements
For a final project in one of my courses, we were assigned the task of making a game in C++ with a partner. Given the freedom the project’s specifications offered, I decided to create a proof-of-concept for a Direct2D framework and use it to implement the project’s game. My goals:
- Create a simple framework-style game engine (rather than a library).
- Experiment with Direct2D to see if it is really usable as a graphics API for computer games. There has been some debate online, and there are few Direct2D-for-games-related resources available, so I wanted to judge for myself how I felt about it.
- Create a sufficiently complex demo game to test the engine. (Pinball, in this case)
- Wrapped the Direct2D API in a game engine framework.
- Similar to XNA, clients of the engine implement their games by deriving from a base Game class, and defining initialization, loading/unloading of content, and update and draw loops.
- Set up DirectInput for user input.
- Set up DirectWrite for text display (FPS counter and score)
- Implemented Vector rotation with quaternions
- Set up general game objects and collision in the Pinball project.
Partner’s responsibilities: Set up the pinball table (draw the table, set up collision with barriers), implement a k-d tree for collision, create necessary game objects.
- Direct2D is definitely workable as a drawing API for games, but it’s hardly perfect. Some notes:
- Handling device-dependent resources is non-trivial. Each must be re-created when drawing fails and returns an appropriate HRESULT. The example provided in the MSDN documentation works great if everything is nested inside one class, but didn’t quite do the job once I started to encapsulate things and began moving things out of the Game class. Even though DeviceDependentResources are now wrapped in an appropriate class that requires a create() function to be defined, clients still need to handle/call recreation on their own with my current implementation.
- In most cases, Direct2D has been running fine at a steady 60fps (which seems to be its maximum); however, I was able to get the frame rate to plummet pretty easily when experimenting with it. One must be responsible about their drawing calls.
- Drawing text is mind-blowingly straightforward with DirectWrite.
- Though I did not experiment with Direct2D’s interoperability with Direct3D, I’m skeptical about the practicality of it. On the one hand, the examples on the MSDN show that it’s fairly straightforward to integrate. On the other hand, having to deal with handling device-depending resource recreation, etc. makes the logistics of integration non-trivial. It’s definitely a decision that has to be considered carefully; one should not assume Direct2D is the right choice for every project that needs to draw in 2D.
- Though drawing is incredibly simple with Direct2D, and many things are readily-available in the framework (such as Bezier curves), this doesn’t always save legwork. For example, though one can easily just use Direct2D’s implementation for Bezier curves, one must still implement their own regardless if they wish to handle collision with them. In order to keep the project’s scope in check, we just used static “invisible barrier” objects for collision with curves; it works well enough, but it’s hardly a good solution for a larger project as we had to hard-code in the barriers.
- Having to write drawing code, wrapping APIs, etc. really makes one appreciate the work put into resources like the XNA framework!
- Though writing portable code is highly desirable, sometimes one simply cannot easily avoid compiler-dependent code. For example, my assert macro uses Visual Studio’s “__pragma” keyword in its assert macro to embed #pragma directives within a macro to squelch a specific compiler warning. Additionally, “#pragma once” was used in lieu of #define include-guards. Because Direct2D will generally be programmed with Visual Studio anyway (DirectX’s libraries use Visual Studio’s format, and thus it’s non-trivial to compile with compilers such as GCC), this seemed like an acceptable exception to the usual rule.
- It’s important to keep in mind project goals. I spent a while tweaking how the ball bounced for the Pinball demo when we were close to the deadline, but realized my time would be better spent elsewhere. The engine project was really about graphics rendering and engine implementation, not perfect gameplay physics. The engine’s 2d OBBs (the OrientedRect class) detected collision fine using the separating axis theorem; whether or not the ball bounced perfectly off of them in its separate collision code was low-priority given pinball was effectively a tech demo.
Source Code (ZIP – 2.42mb) – Includes the Game Engine, Pinball, and a test project in a Visual Studio 2010 solution. Also includes the class assignment’s specifications.
I’d like to give credit to a few resources:
- “Building a Direct2D Framework,” Part 1, Part 2, Part 3. Was referenced for some ideas on how to wrap Direct2D objects, especially device-dependent resources which need to be recreated regularly. Though they’re only a few short pages with some simple ideas, the ideas within helped me get past a few points where I was stumped by how to handle Direct2D elegantly.
- Game Engine Architecture, by Jason Gregory. Provided a lot of general insight in how to develop a game engine. Used some of the code examples for timing (i.e. calculating dtSeconds in the main game loop), and used its convention for fixed-size types (e.g. F32 for 32-bit float, I64 for unsigned 64-bit integer, etc.)
- Direct2D Documentation on the MSDN. Provided many useful examples of Direct2D code which made things a lot easier to implement.