Converting a 2D game to modern OpenGL; lots of architecture questions before I start.
Hi folks,
I've got a 2D RPG project that I've been working on for the past few years. When I first began this project, I thought it'd be a great way to grow my understanding of OpenGL. Problem is, the course I took in school still taught the fixed pipeline and never got into the topic of shaders. This was in 2008. I've since learned that there has been a better way for years.
I know that the core profile is something that I will have to adopt (judging by my velocity, I think immediate mode will be completely phased out before I finish my game ;). My next steps with this game (the battle system) will involve a lot of graphical work (flashy magic spells and the like) and I want to make sure I'm not accumulating technical debt by continuing on the path of the fixed pipeline. That means it's time to update my project. The question is how?
The current design
My game shows a tile-based map with an overhead look (slightly tilted to show height). The result is similar to many old RPGs, such as in this picture of the game Suikoden. This means that the camera is not directly overhead. There are also potentially multiple background and foreground layers, with tall objects obscuring objects behind them.
The engine uses a standard game loop, with a step function and a draw function. In the initialization of the game, I set up an orthographic projection that maps pixel coordinates to screen coordinates. From that point onward, I don't alter the projection matrix.
During the draw function, I do the following:
- Snap to my camera, which does translation and creates a scissor region to keep the player character in the center of the screen.
- Sort all the actors by y-location, top-to-bottom. This will be used to make sure that actors are drawn in back-to-front order.
- For each row:
- Draw the background tiles for the row.
- Draw the sprites at that row.
- Draw the foreground tiles/objects for the row.
The source code for this function is available here.
Each sprite has a draw function, which calls its respective Spritesheet object to draw it. The Spritesheet object binds the Spritesheet texture, figures out the right u,v coordinates, and draws a quad at the specified sprite coordinates using glTexCoord and glVertex calls. This code is available here.
The tile layers have a similar construct, in which they call a Tileset object that binds a texture and draws a quad. This is code that I'd like to refactor since it's more or less the same as the Spritesheet draw code.
In summary, every object binds its texture and then draws a quad using immediate mode instructions. It works, but it's not good given the alternatives. I'd like to redesign it to use the programmable pipeline.
Redesigning my project for modern OpenGL
This is where I get a little stuck. There are lots of tutorials about how to use modern OpenGL, but they are teaching how to use OpenGL, not how to make a complex game with OpenGL. Most of these tutorials tend to focus only on the OpenGL parts and don't set up an object-oriented architecture or entity-component system to show how it'd be used "in the field". The main method creates, compiles and links some shaders, and makes some VBOs, and then there is a while loop, and if you hit ESC, we break out of the while loop, clean up and exit. This is perfect for teaching someone how to use OpenGL without confusing them or blowing up the tutorials to epic length. I'm fine with it, and I learned a lot, and I wouldn't change them. More resources on how to integrate OpenGL into a game's architecture would still be helpful.
I've come up with a bunch of questions around this, to make sure I start down the right path.
-
First off, is there a known guide for doing the kind of conversion I'm about to do? I haven't found anything after a lot of searching, but if someone's written about it then I'm sure it would answer most of my questions.
-
Since the engine just loads the maps and sprites requested by the authors of the game scripts, I can't compile a giant texture atlas ahead of time. It's possible that in the middle of gameplay, the tileset might change, or a new NPC is added with a spritesheet that hasn't yet been loaded in. I think this means that many texture switches are unavoidable. Given that, I can't just bind once and then set up a single stream of indices. How should my draw loop operate, then? How do I ensure that everything is drawn in the right order if it's not just a single VBO?
-
How many VBOs do I need anyway? Right now, everything is a square or rectangle, so if each object has to be drawn with a separate glDrawArrays call, do I even need more than one VBO?
-
Who is responsible for applying translation, scaling and rotation to the vertices in the VBO? What should the game code on the CPU do, and what calculations should I offload to the shaders? I could have each entity create its complete matrix and texture indices, then send that matrix and my single quad VBO to the vertex shader, but that doesn't seem efficient.
-
Who is responsible for drawing? It was nice to have each entity be responsible for drawing itself, but it seems like now I need to have some kind of Visitor outside of the entities collecting drawing information from them. Is that correct?
-
What OpenGL extensions should I be able to reliably assume are present nowadays? Should I even be concerned about machines that don't have VBOs available, for example?
-
Is there a standard go-to library for wrapping OpenGL resources in an RAII way? What do people think about OGLplus, for example? While I don't mind writing my own RAII Shader object, I feel like it's probably been done dozens of times already and doesn't teach me anything new.
TL;DR - I have a project that I'd like to "upgrade" to modern OpenGL. Various tutorials are available for the modern OpenGL API, but I don't know how to best re-architect my engine to use it.
Posted Jul 6, 2014, 10:52 PM UTC
Coulple of quick comments:
The standard render function does
There are 2 ways os setting the orthographic camera:
or
It doesn't really matter which coordinates you pick, just as long as you are consistent.
You could use multiple texture atlases, or dynamic textures. You could use the z coordinate of the texture coordinates to specify which texture to use. This article has an example of how to use OpenGL Texture Arrays. Note that Texture Arrays require OpenGL 3.x+.
You could use one VBO just dumping all your objects in there but that is an optimization.
The Book: OpenGL Insights shows the modern shader approach to starting OpenGL. As long as you target OpenGL 2.0 (or higher) you'll be good.
Older laptops won't support OpenGL 2.1+ -- there was a big cry last year when Minecraft dropped support for pre OpenGL 2.1. Depending on the size of your community you may get a) no, or b) some backlash. It sounds like for you this isn't / won't be an issue -- just something to keep in mind when you get bug reports when people complain they can't run your game.
Hope this helps.
Thanks for responding!
I've seen GLEW used in plenty of tutorials, and having tried to do it myself, I think could do far better by using the tried-and-tested method everyone else uses instead. :)
That's good to know. I didn't think about putting each texture into a texture array instead; that's a good way to take on the single VBO stream optimization if I need it later. :)
This is something that I'm wrestling with now. If I continue with what I have, I can focus on gameplay and I can still create all the visual effects that I want to have (albeit in a less efficient way). Pretty much any hardware that supports the compatibility profile will be able to run my game if I continue with it.
On the other hand, I'm concerned about devices that don't support the compatibility profile. I can't think of any that exist now, but my game stops working once these deprecated functions are simply dropped in the future. Do you think that this is a valid concern any time soon?
As another minor con to sticking with what I have, I lose a learning opportunity if I continue this way -- I want to ship this game but I've also become a better programmer through making this game and it's a good chance to apply what I learned in modern OpenGL tutorials.