Hi everybody,
Im a newbie and I started working on a City Builder where replacing parts of buildings is a key feature.
Im looking for help to try understand what are the best practices/structures for allowing players to swap building parts like a roof (replacing childprefabs) and having a saving/loading method that can save/load the latest state of the buildings on a grid.
Any simple example or reference scripts/videos would be really helpful!
Thank you for your help!
The bottom line of something like this is that the model-view approach is key. Ergo, you need to be able to represent a building (and your entire city) as purely data (no visuals). Notably, this data canât have any direct references to Unity objects if you want to use this for save data as they canât be written to disk (so you need IDâs to look up the assets, or indirect references).
You then use this pure data to build the visual representation.
Since your city/buildings are already purely data, this can be written to disk, and used to restore the state at a later point in time.
Itâs a principle that underlies a lot of games from RTS games, city builder/simulations games, and other games like Minecraft.
There are probably resources for this out there though I donât know any off the top of my head. In any case your data structure will depend on the project specifics. If this does seem complicated, it will be due to not being particularly beginner stuff. A city builder is definitely a tall ask for a beginner.
3 Likes
Just to provide an example. Assuming your building can be composed of tens of different prefabs for each floor, from cellar to roof. Then what you need to save is âwhich prefab is used for each floorâ?
You could save references to each prefab asset, like its path, and thatâll work - but itâll break your playerâs savegames if you ever change your project structure and thus the prefab paths change. Also, a path is a string with possibly hundreds of bytes of data saved per floor.
A better solution is to make one or several ScriptableObject that has nothing but a List in it. To this you assign the building prefabs. Then you only need to store the prefabâs index in this list for each store. Thatâs just four bytes now - or even two or just one byte depending on the number of possible prefabs (65k vs 255) and savegame file size optimization requirements.
Reordering this list will still break player savegames but itâs more controllable and is less likely to happen by accident.
For both those reasons above is why I use a GUID/UUID type piece of data - similar to how Unity produces a GUID for all our assets - in order to save references to assets. Even so far as having an âindirect referenceâ system which contains the implementation for looking up said asset on an as-need basis. Can even assign these indirect references via the inspector.
More work than simple index or path saving, but doesnât break when renaming or reordering your asset structure.
Absolutely!
Itâs one of those tradeoffs. GUID of course will increase the size per instance by tenfold but often enough, itâs more important to have the savegame work 100% reliably.
1 Like
To give you more context I managed to get into a place where replacing childprefab works and I manage to save it and load it properly. The problem I got is that after I load I cannot find a way of telling the save file about the childprefab replacement.
Any suggestions about a simple structure that records changes for child prefabs in a grid based game that make PlaceObject, SavePlacedObject and LoadPlacedObject methods coherent?
The basis of my data structure are two ScriptableObjects: one for preset objects and one for prefabs (which hold the parts for customization).
Attached the best logic I got so far and included all the building data components Im recording for context
Thank you all!
ObjectPlacer.cs (10.5 KB)
Modifications (particularly visual ones) need to be saved additively as additional pieces of data, which can can be used to modify the visual representation initially produced by the backing data.
A super-rough spit ball of what I mean:
public class Building // non monobehaviour
{
[SerializeField]
private BuilingID _prefabID;
[System.NonSerialized]
private BuildingComponent _component;
[SerializeReference]
private List<IBuildingModification> _modifications = new();
public void InitializeVisuals(CityGrid grid)
{
_component = // look up prefab, work out position in world, instantiate, etc etc
foreach (var modification in _modifications)
{
ApplyModification(modification);
}
}
public void AddModification(IBuildingModification modification)
{
_modifications.Add(modification);
ApplyModification(modification);
}
private void ApplyModification(IBuildingModification modification)
{
modification.ApplyModification(_component);
}
}
public interface IBuildingModification
{
void ModifyBuilding(BuildingComponent building);
}
public sealed class BuildingModificationColor : IBuildingModification
{
[SerializeField]
private Color _color;
public void ModifyBuilding(BuildingComponent building)
{
// apply colour to building
}
}
Not meant to be functioning code, just meant to showcase what I mean.
In any case it doesnât change the advice given. Like we mentioned itâs about having a separate data model to your visuals. Namely, your data comes first, from which you build the visual representation. Starting with the view (in this context, game objects in a scene) and converting that into data isnât going to be manageable moving forwards.
It definitely wonât boil down into one script as well. This stuff can cover dozens and dozens of scripts/classes.
1 Like