Expanding the World Map Editor Mode
Hello, Spellweavers!
I’m back with another update on the development of Spellweaver Saga: Retrieving The Legendary Relic. Over the past week, I’ve been focusing on enhancing the editor, particularly in creating and managing world maps. Let’s dive into new features that have been added!
Creating and Loading World Maps
One of the advancements is the ability to create and load world maps directly within the editor. Now, you can easily start a new map or load an existing one, making the process of building your game world more efficient. Once you’ve selected the map you’ll be working on, you can choose tilesets loaded with the asset file, giving you full control over the terrain and environment.
Enhanced Tile Selection with Scrollable Window
To make tile placement more intuitive, I’ve introduced a scrollable tile selection window that appears at the top of the screen. You can now easily browse through your tilesets by scrolling the mouse wheel, making it quicker to find the right tile for the job.
Introducing Tile Layers for Better Sorting
To improve the visual organization and layering of tiles, I’ve implemented tile layers with different Z coordinates. Think of it as a z-buffer for tiles—this addition enhances the sorting of tiles, ensuring that everything displays correctly in the game environment.
Auto-Saving and Map Bounds Checking
Building a robust and user-friendly editor means thinking ahead about potential issues. To prevent any mishaps, I’ve added an auto-save feature and additional checks for map bounds. This ensures that your progress is saved regularly, and any work outside the map’s limits is caught early.
Tile Flood Fill Functionality
One of the most exciting features is the tile flood fill function, which allows you to fill large areas with a specific tile easily. Here’s a peek into the code that makes it all happen:
internal void
TileFloodFill(action_stack *UndoStack, world *World, world_position StartP, ssa_tile NewTile, u32 ZLayer)
{
sswm_ground_tile *TargetGroundTiles = GetWorldMapGroundTile(World, StartP);
if(TargetGroundTiles)
{
u32 TargetChecksum = TargetGroundTiles->CheckSum[ZLayer];
if(TargetChecksum != NewTile.CheckSum)
{
editor_action FloodFillAction = {};
FloodFillAction.Type = Action_FloodFill;
FloodFillAction.FloodFill.TileCount = 0;
FloodFillAction.FloodFill.PrevTiles = (sswm_ground_tile *)Platform.AllocateMemory(MAX_FILL_TILES*sizeof(sswm_ground_tile));
tile_queue Queue = {};
Queue.Front = 0;
Queue.Rear = -1;
EnQueue(&Queue, StartP);
while(!IsEmpty(&Queue))
{
world_position P = DeQueue(&Queue);
sswm_ground_tile *GroundTile = GetWorldMapGroundTile(World, P);
if(GroundTile)
{
if(GroundTile->CheckSum[ZLayer] == TargetChecksum)
{
Copy(sizeof(sswm_ground_tile), GroundTile, FloodFillAction.FloodFill.PrevTiles + FloodFillAction.FloodFill.TileCount++);
AddGroundTile(UndoStack, World, P, NewTile, ZLayer, true);
if(TileIsValid(World, P.TileX - 1, P.TileY))
{
EnQueue(&Queue, CenteredTilePoint(World, P.TileX - 1, P.TileY));
}
if(TileIsValid(World, P.TileX + 1, P.TileY))
{
EnQueue(&Queue, CenteredTilePoint(World, P.TileX + 1, P.TileY));
}
if(TileIsValid(World, P.TileX, P.TileY - 1))
{
EnQueue(&Queue, CenteredTilePoint(World, P.TileX, P.TileY - 1));
}
if(TileIsValid(World, P.TileX, P.TileY + 1))
{
EnQueue(&Queue, CenteredTilePoint(World, P.TileX, P.TileY + 1));
}
}
}
}
PushOnActionStack(UndoStack, FloodFillAction);
}
}
}
This function not only simplifies the tile placement process but also supports undo actions, allowing for greater flexibility and creativity while building your world.
Implementing Undo and Redo Functionality
After spending some time working with these new tools, I realized how crucial it was to have the ability to undo and redo actions. To address this, I introduced an action_stack
system that can store and manage actions, allowing you to revert or reapply them as needed. Here’s a look at how this system works:
#define MAX_UNDO_ACTIONS 128
struct action_stack
{
editor_action *Actions;
s32 Start;
s32 End;
u32 Size;
};
This action_stack
structure holds an array of editor_action
objects, as well as pointers to the start and end of the stack, and a size indicator. It can store up to 128 actions for undoing and redoing.
struct editor_action
{
action_type Type;
union
{
sswm_ground_tile PrevTile;
flood_fill_action FloodFill;
};
};
The editor_action
structure includes the type of action and a union to hold the relevant data for undoing or redoing the action. This allows for flexibility in handling different types of actions, whether it’s a simple tile change or a complex flood fill.
Here are a few utility functions that manage the stack:
inline void
InitActionStack(action_stack *Stack, memory_arena *Arena)
{
Stack->Actions = PushArray(Arena, MAX_UNDO_ACTIONS, editor_action);
Stack->Start = 0;
Stack->End = 0;
Stack->Size = 0;
}
InitActionStack
initializes the stack, setting up the memory for storing actions and resetting the start, end, and size indicators.
inline b32
IsActionStackEmpty(action_stack *Stack)
{
b32 Result = (Stack->Size == 0);
return(Result);
}
IsActionStackEmpty
checks if the stack is empty by evaluating the size.
internal void
PushOnActionStack(action_stack *Stack, editor_action Action)
{
Stack->Actions[Stack->End] = Action;
Stack->End = (Stack->End + 1) % MAX_UNDO_ACTIONS;
if(Stack->Size < MAX_UNDO_ACTIONS)
{
++Stack->Size;
}
else
{
Stack->Start = (Stack->Start + 1) % MAX_UNDO_ACTIONS;
}
}
PushOnActionStack
adds a new action to the stack. If the stack is full, it overwrites the oldest action.
internal editor_action *
PopFromActionStack(action_stack *Stack)
{
editor_action *Result = 0;
if(!IsActionStackEmpty(Stack))
{
Stack->End = (Stack->End - 1 + MAX_UNDO_ACTIONS) % MAX_UNDO_ACTIONS;
--Stack->Size;
Result = Stack->Actions + Stack->End;
}
return(Result);
}
PopFromActionStack
removes the most recent action from the stack, allowing it to be undone.
inline void
ClearStack(action_stack *Stack)
{
ZeroArray(MAX_UNDO_ACTIONS, Stack->Actions);
Stack->Start = 0;
Stack->End = 0;
Stack->Size = 0;
}
Finally, ClearStack
resets the entire stack, clearing all stored actions.
What’s Next?
The journey doesn’t end here! I’m already working on implementing navigation meshes and will continue refining the editor to enhance usability and expand its capabilities. My goal is to make building the world of Spellweaver Saga as intuitive and enjoyable as possible. There is a little gallery below, you can check it out.
Happy spellweaving, BabyKaban
Enjoy Reading This Article?
Here are some more articles you might like to read next: