This is the engineering behind FactoIsle — the systems I had to invent so a solo Unity factory game would actually run. Chunks, a compact save, pipes, multiblocks, and a quest graph.

Chunks

The playable world is 512×512 Unity units. Terrain continues past that edge, but anything outside is underwater and empty. I could not load the whole map at once — more than 100,000 entities.

I split it into 32×32 chunks that load and unload with the player. A ticket system I wrote keeps selected chunks resident, so machines still tick off-screen and the player’s current chunk is never dropped.

Chunk grid around the player

Saving

On first load, a chunk fills itself from its biome. I split entities into logic and data. The data containers are binary-serializable and hold whatever that entity needs.

When a chunk unloads, each entity writes its container with an id and type. Chunks save independently, so the whole world fits in one file under 10MB.

NBData tag = Data.Get<NBData>(); // Named Binary Data
if (tag.Has("itemInMouth"))
{
    int logType = tag.GetInt("itemInMouth");
    Entity entity = Library.Entity(logType);
    if (entity != null && entity is Log log)
    {
      _itemInMouth = log;
    }
}

Pipes

Automation is the point of the game, so I built pipes as placeable blocks. After a placement, the world rebuilds tube systems from connectivity and machine ports.

Item and fluid pipes between machines

Pipes have their own storage and tick, and they save through the same data-container path as entities.

Multiblocks

I did not want machines as prefabs you drop. You place a pattern of blocks; the checker I wrote accepts every rotation and runs on place and destroy.

Sawmill multiblock pattern

public override void AssembleMultiblockPattern(MultiblockPattern pattern)
{
    pattern.AddBlock(new Vector3Int(0, 0, 0), Library.Entity<BlockPlank>());
    pattern.AddBlock(new Vector3Int(1, 0, 0), Library.Entity<BlockPlank>());
    pattern.AddBlock(new Vector3Int(2, 0, 0), Library.Entity<BlockPlank>());

    pattern.AddBlock(new Vector3Int(0, 0, 1), Library.Entity<BlockPlank>());
    pattern.AddBlock(new Vector3Int(1, 0, 1), Library.Entity<BlockPlank>());
    pattern.AddBlock(new Vector3Int(2, 0, 1), Library.Entity<BlockPlank>());

    pattern.AddBlock(new Vector3Int(1, 1, 1), Library.Entity<BlockMachineAdvanced>());
    pattern.AddBlock(new Vector3Int(1, 1, 0), Library.Entity<BlockMachineBasic>());
}

Quests

Quests are the progression layer I used to unlock items, liquids, and machines.

Quest log

Each quest is a class on a shared base. Requirements, unlocks, and dependencies are declared in code. The story system loads them by reflection; custom state goes into the same entity data containers.

public class ChopTreeQ : Quest
{
    public override Sprite GetSprite()
    {
        return Library.Entity<OakTree>().GetSprite();
    }

    protected override void OnInit(Action<Requirement> add)
    {
        add(new GatherRequirement(Library.Entity<OakTree>(), 2));
        add(new PickupRequirement(Library.Entity<LogOak>(), 10));
    }

    public override List<Quest> UnlockedBy(Story story)
    {
        return new List<Quest>() { story.GetQuest<PickupStoneQ>() };
    }

    public override List<Entity> UnlockedEntities()
    {
        return new List<Entity>
        {
            Library.Entity<Plank>(),
        };
    }

    public override Chapter GetChapter(Story story)
    {
        return story.GetChapter<TutorialC>();
    }
}

The game is in alpha on itch.io.

Part of FactoIsle