Overview

7 Separating data and logic

This chapter explains that separating data from logic is a practical way to make game code simpler, easier to debug, and easier to extend. The author emphasizes keeping code minimal and avoiding unnecessary patterns or abstractions so that the flow of data is obvious. By organizing game state into a central data structure and handling behavior in separate logic classes, the project becomes cleaner and less prone to hidden complexity as features are added.

A major example is saving and loading game state. Because all core gameplay data is already collected in one place, the game can be saved by writing that data directly to a file and loaded by reading it back in the same order. The chapter argues that binary storage is preferable to JSON or XML because it is smaller, faster, and avoids conversion overhead. It also shows how versioning save files by filename helps preserve backward compatibility without cluttering loading code with many branches. Loading functions are kept separate by source and version so bugs are easier to isolate, and duplicated code is encouraged when it helps keep features independent and predictable.

The same design approach is then applied to asset loading. Instead of depending on broad engine systems indiscriminately, the chapter recommends choosing the simplest tool that fits the problem, such as assigning prefabs through the Unity editor for assets that are only needed at startup. A singleton asset manager provides clear entry points for retrieving specific assets, making it obvious where each asset comes from and reducing the risk of broken references. The chapter closes by extending the idea to different asset sources like local resources, bundles, and live-updated content, stressing that each source should remain separate so the code stays understandable and testing stays manageable.

The code that loads our game data should be implemented using static functions in a separate static class from GameData. This reduces code complexity by making it easier to follow what happens when game data is saved and loaded.
When saving in binary, our GameData is written out as a continuous array of bytes.
If we are loading fewer enemies than allocated, we just read the enemy data into the allocated array.
If we are loading more enemies than allocated, we read in all the enemies, but only copy as many enemies as were allocated.
If we forgot to write a variable, for example numEnemies, to the save file, and then try to read it, we’ll get an out-of-stream error. You can fix this by comparing which variables are written and which are read, then identifying the missing one.
Saving and loading code should be separated by source, but any common code can be moved to a shared static function.
Having the AssetManager be a singleton makes it easier for different objects to access it.
We should separate our asset managers by source, just like we did with the saving and loading of our game data.
The flow in which we create our balance data binary file.
Asset bundles make it easy to manage our memory. We can load the common bundle first and keep it in memory at all times. Then we can load each zone’s bundle separately.

Summary

  • Storing all our game data in a single class makes it trivial to save and load our game.
  • Storing our data in JSON or XML format requires an additional step to convert the data from binary to text.
  • Storing our data in JSON or XML requires significantly more space than storing it in binary format.
  • To save space, variables stored in JSON or XML require being renamed to single or double letters, making the code harder to read.
  • Our data is already in binary, so storing it in binary avoids unnecessary conversion and gives us direct control over how we write and read the data.
  • The functions that save and load our game data should be separate from the game data class.
  • The function that loads our game data should not allocate memory; instead, we should pass it a preallocated game data object so we can call it multiple times.
  • Backward compatibility significantly increases code complexity and should be implemented as late as possible.
  • When the save format changes, storing the version number in the filename lets each loading function handle a single known binary layout, rather than adding more branches to a single loading function.
  • When hunting for bugs, don't underestimate the value of the debugger.
  • Separate code that saves and loads game data from different locations into distinct files to make debugging easier.
  • When deciding how to load assets, always consider the problem you are trying to solve.
  • Assigning assets through the Unity editor is a good option if you have very few assets that rarely change.
  • The resource folder is a good choice if you have a small number of files that change frequently.
  • Asset bundles are a good option if you have lots of assets and want to separate them by lifetime usage.
  • Avoid live-updating content unless absolutely necessary.
  • Live-updating content is tricky to test and can lead to unexpected bugs in production.
  • Don’t use black-box features just because someone says so. Always solve the problem at hand.

FAQ

Why does separating data and logic reduce code complexity?It reduces the amount of code we need to understand, maintain, and debug. When data is centralized and logic is kept separate, it becomes easier to see what each part of the program does and easier to extend later.
Why should GameData contain only the data needed to save and load the game?If a value is not needed to restore the game state, it probably does not belong in GameData. Keeping only essential gameplay data makes save/load simpler and avoids storing unnecessary information.
Why is binary preferred over JSON or XML for saving GameData?Binary avoids format conversion, produces smaller files, and gives direct control over how data is stored. JSON and XML add overhead, increase file size, and require converting data back and forth between text and binary.
What are the main advantages of using BinaryWriter and BinaryReader?They let us write and read primitive values directly in binary form. That makes saving/loading faster, simpler, and less error-prone than converting data to and from text formats.
Why does the book recommend using a separate static GameDataIO class?A separate static class keeps save/load logic out of GameData itself. This makes it easier to follow, debug, and change the loading or saving process without mixing it with the data container.
Why should LoadLocal take an existing GameData object instead of allocating a new one?Passing in an existing object avoids repeated allocations, reduces garbage collection pressure, and helps prevent memory fragmentation. It also keeps memory management easier to track in one place.
How does versioning save files help with backward compatibility?By storing the version in the filename, old and new formats can coexist. If the save format changes, older loading functions can still be kept around to support existing player saves.
Why is it better to separate save/load code by source, such as local files versus server data?Separating by source makes bugs easier to trace because each path is distinct. It also prevents one loading path from becoming a large, confusing function with lots of branches.
Why does the chapter recommend assigning assets through the Unity editor for simple cases?For assets that are used immediately at startup, editor assignment is the simplest solution. It avoids unnecessary complexity from resources, bundles, or addressables when those systems are not needed.
Why is an AssetManager singleton useful in this chapter?It gives the game one clear place to access prefabs and UI assets from anywhere. Each asset also gets its own dedicated retrieval method, which makes the source of data easier to understand and debug.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • High Performance Unity Game Development ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • High Performance Unity Game Development ebook for free