Sorry in advance because a lot of this is going to seem out of context.
I’m posting this so everyone’s on the same page about what’s been suggested so far.
The subfolders thing isn’t as much of an issue if you’re willing to try to pack multiple files into a single file.
Remember that directories are actually files themselves.
They’re basically files that contain a list of files.
You could have .map files that contain a mixture of dialogue and tile data, or even jam everything into one giant file, which is what a number of triple-A games do.
To be honest though, rather than insisting on a fast SD system, I’d suggest looking into how you partition your world.
Knowing how big you can realistically make a map and how you can divide your map into chunks is useful.
For example, a 256x256 map is 65536, which would be 64KB at 1 byte per tile, so that’s obviously far beyond what you can fit in memory at once.
64x64 is 4096, which would be 4KB, which is a more realistic size.
Before you go diving in to working with code, make sure you’ve got some designs available, especially if you’re going to have lots of people working with you.
Making some prototypes/mockups to test how viable something is would be a good idea, but you’ll struggle to be producive if you’re trying to figure out how the game’s going to work at the same time as you’re programming it.
Instead of having a 2D array, I’d recommend having a flat array for the map.
(If you need help with that, I’ve done that before 1.)
Then you can make your map file essentially be a flat array of chunks.
Then your code mostly becomes something like this:
class Chunk
{
public:
static constexpr std::size_t Width = 32;
static constexpr std::size_t Height = 32;
static constexpr std::size_t Size = Width * Height;
private:
std::uint8_t buffer[Size];
static std::size_t flattenIndex(uint16_t x, uint16_t y)
{
// Compiler optimises the multiply to be a shift when Width is a power of two
return x + (y * Width);
}
public:
std::uint8_t * getBuffer(void)
{
return this->buffer;
}
std::uint8_t get(uint16_t x, uint16_t y) const
{
return this->buffer[flattenIndex(x, y)];
}
};
class Map
{
public:
static constexpr std::size_t Width = 256;
static constexpr std::size_t Height = 256;
static constexpr std::size_t Size = Width * Height;
static constexpr std::size_t WidthInChunks = Width / Chunk::Width;
static constexpr std::size_t HeightInChunks = Height / Chunk::Height;
static constexpr std::size_t SizeInChunks = WidthInChunks * HeightInChunks;
private:
// File management info
public:
void loadChunk(Chunk & chunk, uint16_t chunkX, uint16_t chunkY)
{
const std::size_t chunkOffset = chunkX + (chunkY * WidthInChunks);
fileSeekAbsolute(chunkOffset);
fileReadBytes(chunk.getBuffer(), Chunk::Size);
}
};
Note in particular that you only have to seek once to load a chunk.
If seeking is the bottleneck then that will be a huge improvement (hopefully).
This diagram may help (if you can’t read it, I can redo it in paint):
I’ve spent a lot of time researching RPG map structure.
Essentially you’re looking at the same sort of relationships repeated several times at different levels.
(It’s like a sort of dimensionality. Almost like 4D, but not quite. Very meta though.)
Thinking about the structure of the system is key to optimising it.
32x32 = 1024 bytes, and if you need 4-9 in memory at once, that’s 4-9KB.
Never be afraid to shift work onto the map creation tool.
The more preprocessing you do, the less work the game itself has to do.
If you need to have variable sized maps then you’ll have to change the map format to accomodate that, perhaps by putting the sizes in the header of the file.
Unfortunately there’s no way out of designing file formats when making an RPG, you’re going to have to have to have structured files at some point.
(Map width and height will no longer be constexpr and the code I posted would need a lot of modification.)
If you haven’t looked at it yet, have a look at some tutorials of how to use Pokemon Advance Map, it’s a mapping tool for hacking Pokemon ROMs.
I’m not suggesting to use it for this game, but seeing how the tool works will give you a better idea of how Pokemon’s maps work and how they handle things like scripted tiles and warp points.
It would depend on what the constraints of your story are and what you actually need to happen.
Pokemon uses a bytecode system similar to Mother3.
There’s a way to printing text (either a code for ‘start text’ and a code for ‘end text’ or simply treating each text character as a ‘print char’ command that prints a specific character), a way for giving the player items, a way for giving the player Pokemon etc.
(Skyrim uses a similar system, but it’s more focused on dialogue trees and treats text and code completely separately.)
Edit: Aha, here’s a list of Pokemon commands:
http://sphericalice.com/romhacking/documents/script/
Making the environment change based on the story requires some sort of ‘flag’ system. In Pokemon’s case, every event is a bit somewhere in EEPROM and story events toggle those bits.
In the case of Skyrim they have a full quest system where the quest stage is essentially a numerical value and there’s a byte for every quest somewhere in the save data. Typically quest stage 10 is the first stage and quest stage 200 is the last stage (numbers chosen for the same reason BASIC used to number lines in multiples of 10 - to provide room to insert new lines).
I would advise maybe allowing your scripting language to manipulate event tiles and having a ‘map startup’ script that you use to modify the map before the player walks onto it.
Either that or you have to specify in the file format which event flag must be set/clear for an event to be present on the map.
In case anyone can’t tell, I’ve done a lot of research into PRGs :P