Been far too long since I put a post here. So let's catch up. I've been spending more time consuming entertainment lately, which is a large part of the reason I haven't been keeping this very up to date. I might post a review of Watch_Dogs, but the other thing I've really been playing is Metal Gear Solid, and there's already been so many wonderful things written about it that I really can't add much more. (In point of fact, the Super Bunny Hop retrospectives on the MGS series really drove me to decide to finally play the games that would run on systems that I own. I'm actually really excited to play MGS3, because there's so many really cool systems in it, and it's very menu driven, but still very fluid to play; important things to draw inspiration from as I explore my own game mechanics in the coming months. Of course, being the ridiculous person I am, I am trying to play the games in order, and I'm still on MGS1 (and I'm hoping to get to play the ported MG1 and MG2 off of the MGS3 disc, but I was going to start with MGS anyway). Apart from the painfully drawn out sophistry (which I know I won't escape in a Metal Gear game), the game is mostly fun. I just wish I wasn't so damn bad at it. The game really really shines as a stealth game, but the forced combat sections (goddamn comm tower stairs, I'm looking directly at you) are not as much fun, and I am super duper terrible at. I also really sucked at the first sniper battle, which should have been easy, but there's no good way to quick pan the sniper rifle, which I'm used to in most games. Very frustrating. However, the torture section, which I ended up having to do a couple times because I forgot to call Otacon after the first round, which is historically considered very hard, I found to be incredibly easy. I guess I can mash a single button with the best of them. I haven't finished the game yet, but I'm in what is probably the third act (getting the key cards entered in).
Anyhow, you're all probably more interested in my progress with Threatened Destiny. As usual, I've had more frustrating difficulty with the frameworks that I'm using than with any of my actual code. I ended up using a boost::multi_array to represent my StageMap in memory. It's got some generally convenient growth functions, and after some tinkering, getting the boost::multi_array to grow and shift the way I wanted it to wasn't so bad. Then I ran into another fundamental problem with the frameworks. If you grow the in-memory representation of the StageMap, logically, you'd want to grow the graphical representation too, right? It wouldn't be much good to increase the storage area for the StageMap if the level designer can't actually drop tiles into that area. I'd thought that this would be fairly easy: since my drawing area is based on a wxScrolledCanvas, I should just need to adjust the SFML::RenderWindow size and the wxScrolledCanvas virtual size, and I'd be good to go. However, for reasons I cannot fathom, this doesn't work correctly. As I think I've mentioned before, wxScrolledCanvas objects have two sizes: a ClientSize, which represents the actual size that can be displayed on the screen, and a VirtualSize, which represents the entirety of the available drawing area, which is commonly larger than could possibly be displayed at one time on an ordinary monitor. So if you're going to change the drawable area (as opposed to the viewed area), you adjust the wx::VirtualSize. This works just fine. The problem is that the sfml::RenderWindow size never reports a size greater than the wx::ClientSize (which should be the size of the sfml::View, not the RenderWindow). This is in spite of the fact that you actually can draw over the entire wx::VirtualSize. I'd been doing my math for drawing the isogrid based on the sfml::RenderWindow size, because I thought it'd be most reasonable to base the drawing math on the actual drawing area, but since the RenderWindow never reports the correct size after growing the StageMap area, my math was always incorrect. In the end, I just ignore the sfml::RenderWindow's claimed size and base all my calculations on wx::VirtualSize, so everything actually works.
I'm now into saving and loading, which is the next step toward being able to actually get content from the editor into the game engine. I really wanted to use gzipped YAML files for my data files, which turned out to be a major factor for me rolling my own save/load-serialization/deserialization routines instead of using boost::serialization. I appreciate boost::serialization's goals, and I really kind of like it, but it's strength is definitely when you can have symmetric serialization (that is to say, you're using primitive member variables, so that serializing your object and deserializing your object are pretty much the same operation, just with the direction of the data flow reversed). Unfortunately, since I'm dealing with image data in SFML objects, the serialization and deserialization stages are asymmetric (getting image data out of memory is a different process than putting image data into memory). I use std::unique_ptr's throughout my code, and boost::serialization can't handle them out of the box, I'd have to either switch to std::shared_ptr's (which are not semantically what I want) or write the approriate adapter to allow boost::serialization to handle std::unique_ptrs. I found some information online for doing that, so it wouldn't be incredibly difficult, but it's still non-trivial. I also would have needed to write my own handler to utilize gzipped YAML files, which looked like more effort than I wanted to put into my first pass implementation. Fortunately, building the YAML objects was very easy, and I'd developed a gzipped YAML handler before I even started on any of this real work, so producing basic save/load stuff for tileset metadata (and the tiles they contain) was not terribly difficult. But getting the tile atlas image data in and out did its best to make up for any apparent ease the metadata offered. SFML gives you the ability to get a pointer to the memory location that an SFML::Image is stored in. I thought that all I'd need to do was iterate over this pointer, base64 encode the resulting data, and shove that into my YAML file, and that part of the process actually worked perfectly well, albeit inefficiently in terms of storage space. It took me a couple days of struggling with the data loading stage before I finally realized that SFML doesn't have any capacity to allow you to accept that data however. SFML::Images have a loadFromMemory() function, which I naievely assumed would read in the same data that the SFML::Image stored in memory. Oh, assumptions. If only I'd listened to Samuel L. Jackson.
Turns out, SFML::loadFromMemory() expects to receive a well formatted image in a standard image format, just like SFML::loadFromFile() only pointing at a memory location instead of a filesystem location. That's very frustrating, because it means that the data I'd saved out was completely useless. The only way to make the save/load work would be to save the data into an intermediate form; storing the data into either a completely different image format in memory and writing that out, so that I can load a complete image later (which requires me to include a new library to support that image format) or using SFML::Image's built-in ability to write the image to a "physical" file, and then loading from that file. I didn't want to have files other than the TileSet file required for TileSets to load, but even using an intermediate file, that's solvable -- I can read the file in, base64 encode it, and put the resulting string into the YAML file. Then I base64 decode the string in the YAML file into a chunk of memory, and hand that memory pointer to SFML to load. That's the solution I eventually settled on, because it's ever so slightly less odious than needing to bring in another image library and trying to figure out how to get a good copy of that data into memory to base64 encode, but I'm still not very happy about needing to create a temporary file and then delete it. I've made my peace with the method because it actually works, but I see it as kind of an awkward hack. My base64 library supports reading filestreams easier than memoryspace anyway, so that was at least one convenient aspect of this approach.
The next step is to allow the creation of new StageMaps, and save and load those StageMaps. Once the editable StageMap can be saved, I'll be ready to build the functionality to "export" StageMaps, which will pack a minimal TileSet inside the StageMap and allow the entire StageMap to be contained within a single file.