I’m using SFML for pretty much everything in my game engine. I started using it to help me learn C++ and never left. It’s pretty amazing; cross-platform, hardware accelerated 2d and one of the best APIs I’ve ever used. It’s a genuine pleasure to work with when it comes to expressiveness and simplicity.
I’ve recently been working on adding rich-text support to my engine, and while SFML does come with text rendering built in, it’s very simplistic. A text instance can only have one color and combination of styles in total, so if want to italicize a single word in a sentence you’re out of luck; you’ll have to split the text up into multiple chunks and position them independently. I’ve written a class that handles this all automatically, along with a simple markup parser to manage formatting. You can get the source here, and discuss it over at the SFML forums on the topic I made for it.
I’ve done some parser work before (notably for Slang), so the markup interpreter was fairly straightforward. All formatting is done with single-character delimiters (*bold*, ~italic~, and _underlined_), and the color tags can contain a hex value or color name. I’m sure it can use some optimizing, but so far it’s quite fast and works exactly as well as the vanilla Text class.
If you haven’t used SFML before, I highly recommend checking it out! The fact that I was able to put all of this together in just one day is a testament to the power and ease of use that it provides.
My graph system is working nicely with multiple resolutions, but I realized while working on it that I’ve essentially locked myself into having every scene the same size. While not necessarily a problem, from a design standpoint it really isn’t conducive to the type of levels I want to implement. Of course, allowing scenes to exist that are larger than the size of the screen introduces several new problems, namely:
That doesn’t seem too hard, right?
Easy stuff first: The camera is going to need to center itself between all points I tell it to focus on. All I have to do is get the average value of all positions. Fortunately this actually happened exactly as I thought it would.
Now on to zooming. Essentially, I check distance between the two focus point furthest from each other. If it’s over a certain length, zoom out. If it’s under, zoom in. Stop zooming in once the view is a certain width, so it doesn’t zoom in to eternity.
Seems simple enough, right? That’s what happens when I write up a blog post after the fact. I really should start writing these while I’m in the thick of things…
Planning posts are all well and good, but what happens when you end up going in an entirely different direction?
I ended up creating a new class, MN::Window, that inherits from sf::RenderWindow but adds some useful members to determine resolution and pixel scale ratio.
Now, when I’m constructing the pathing graph, each cell is WindowWidth/20 by WindowHeight/15. I still have a 20×15 grid and an array of Vector2fs, but they’re static to save on memory. I can also access grid locations from anywhere in the program, so all objects in the world will use graph indices for positional values from now on.
Previously I had been developing Meander to run in a window at 800×600. Besides the issue with hard-coded window dimensions, it also created the problem that resizing it larger (to, say, 1920×1080) stretched the images out from a 4:3 ratio to 16:9. And no, they’re not the same. To fix this, I started targeting a 16:9 resolution by default. But what happens when I want the display to be smaller? I added a few members to MN::Window that help me solve this problem.
To display correctly on a lower resolution, all the sprites on the screen need to be scaled by a factor of WindowDimensions/OriginalDimensions. For example, a transition from 1920×1080 to 1366×768 works out to this:
1366.0f / 1920.0f == 0.7
768 / 1080 == 0.7
I’m no good at math. I may seem like it sometimes, but it’s all an act, I assure you. I don’t know if the scale ratio for any resolution of 16:9 will result in x === y. But just to be sure I’m going to leave both equations in.
This tells us that each sprite needs to display on the screen at 0.7 of its actual height and width. My sprite manager can take care of that each time the resolution changes.
Whew! Got that checked off the list. I made myself promise I wouldn’t play any video games until I’d finished. Time for a rest. :D
Goal number the last on my roadmap reads: “Decoupling the navigation mesh from specific window sizes”. What on earth does that mean? I’m glad you asked.
Thus far I’ve been programming and debugging my program using a fixed-size window at 800 by 600 pixels. Unfortunately, when I wrote my Graph implementation I was all about magic numbers and not so much about planning for the future. Consider the following code snippet:
std::vector< sf::Vector2f > Graph::BuildGrid()
std::vector< sf::Vector2f > Nodes;
for (float height = 20; height < 600; height += 40)
for (float width = 20; width < 800; width += 40)
sf::Vector2f Node = sf::Vector2f(width, height);
At some point I got it into my head to store exact locations in the graph. Now, there’s not anything necessarily wrong with this…as long as
Obviously this is unacceptable, and really should have been fixed by now. I need a plan!
Currently the Graph class is made up of:
Looking at this I realized that I really don’t need Nodes or States to be in there. After all, my pathfinder’s solver function only takes two numbers as arguments; the grid indices for the start and end of the path. At each step of the path, the pathfinder accesses the Node list with that step’s index and returns the position. I can easily strip this out and replace it with a simple function to convert a graph index to a Vector2. I’ll need a mesh to determine where on the screen the mouse clicks to signal the start of a pathfinding call, but I can most likely construct this on the fly.
No more delays. Time to get this done.
Everybody loves screenshots, right? Well, I do at least, and this is my blog. So here’s a screenshot of the GUI for my game engine. I’ll have a more technical/in-depth post after I get a video done, but I wanted to put this up right away.
Ladies and gentlemen, allow me to introduce… “Gooey”!
More later. :)