{"id":383,"date":"2014-01-22T10:18:49","date_gmt":"2014-01-22T18:18:49","guid":{"rendered":"https:\/\/robotinvader.com\/blog\/?p=383"},"modified":"2014-01-22T10:38:48","modified_gmt":"2014-01-22T18:38:48","slug":"checkpoints-in-unity","status":"publish","type":"post","link":"https:\/\/robotinvader.com\/blog\/?p=383","title":{"rendered":"Checkpoints in Unity"},"content":{"rendered":"<p>One of the new features in Wind-up Knight 2 is checkpoints. \u00a0They work the way you \u00a0expect: if you die after passing a checkpoint you will restart at the checkpoint location with the game world restored to its previous state. \u00a0This sort of system is easy to implement if you plan for it from the beginning of development, but it can be tricky to retrofit to an existing game engine because entities tend to change their state subtly as the game is played. \u00a0What animation frame was the character on when the checkpoint was hit? \u00a0What was his velocity? \u00a0His collision contacts? \u00a0If you didn&#8217;t plan on saving this sort of state when you wrote your entity behaviors, adding it in later could represent a pretty major code change.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-384\" alt=\"logo\" src=\"https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/logo-1024x512.png\" width=\"640\" height=\"320\" srcset=\"https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/logo-1024x512.png 1024w, https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/logo-300x150.png 300w, https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/logo.png 2048w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/p>\n<p>We didn&#8217;t have checkpoints in the original Wind-up Knight, and when we decided to add them for the sequel I realized we were looking at a potentially massive change to the codebase. \u00a0Our requirements for checkpoints were as follows:<\/p>\n<ul>\n<li><strong>Flexible<\/strong>. \u00a0Need to be able to serialize all the entities in levels we already built.<\/li>\n<li><strong>Instant<\/strong>. \u00a0No hitching when saving the checkpoint, and restarting after a death should be instantaneous.<\/li>\n<li><strong>Repeatable<\/strong>. \u00a0We only store one checkpoint at a time, but a level can have any number of checkpoints in them.<\/li>\n<li><strong>Efficient<\/strong>. \u00a0We can&#8217;t use too much runtime memory.<\/li>\n<\/ul>\n<p>At first, my main concern was Flexibility. \u00a0We have a lot of entities that change their state in a lot of different ways. \u00a0Coins that are collected. \u00a0Enemies that animate, attack, and turn around. \u00a0Rocks that fall only once. \u00a0Gates that open and close. \u00a0I wanted to make a system that would deal with all of those things without having to make code modifications to each.<\/p>\n<p>My first attempt was to use C# reflection. \u00a0I figured that if I walked the object tree and recorded all of the public fields and properties, I could restore those values on checkpoint reset and be 90% done. \u00a0Turns out that this isn&#8217;t so hard to do in C#, and I was able to knock out a prototype in a couple of hours. \u00a0But immediately there were problems: the number of fields to serialize was so large that there was a visible framerate hitch, and properties on many Unity objects have side-effects when set (e.g. rigidbody asserts if you try to write to velocity while isKinematic is true), making deserialization difficult. \u00a0I also noticed that most of the data getting serialized was not really information needed for a checkpoint restore. \u00a0Most fields never change.<\/p>\n<p>This lead me to try the opposite extreme. \u00a0I would serialize only specific objects, and only a few parameters of those objects, and anything that wasn&#8217;t sufficiently covered would require custom code. \u00a0For this second attempt I decided only to record the following information:<\/p>\n<ul>\n<li>Object transform<\/li>\n<li>Whether or not the object was destroyed since the last checkpoint<\/li>\n<\/ul>\n<p>That&#8217;s it. \u00a0Any other state would have to be dealt with on a per-script basis. \u00a0I figured that this method would get me about half way there and then I&#8217;d spend a lot of time modifying entity code to deal with state storage and loads. \u00a0Sort of a lame solution, I thought, but one that probably would meet all of the requirements above.<\/p>\n<p>I implemented this in three parts:<\/p>\n<ol>\n<li>CheckpointRegistry, a singleton that maintains a record of all objects that are tracked for checkpointing.<\/li>\n<li>CheckpointAware, a script that marks an object (and all of its children) for tracking, and<\/li>\n<li>CheckpointBehavior, a child of MonoBehaviour that adds two virtual methods for dealing with checkpoint saves and loads.<\/li>\n<\/ol>\n<p>CheckpointRegistry contains a HashSet of objects that have been marked for tracking. \u00a0When a new object is registered, all of its children are automatically registered as well, and top-level objects are called when save or restore events occur. \u00a0The Registry also provides methods for destroying objects; CheckpointRegistry.Destroy() makes an object inactive and adds it to another set, to be reactivated on the next checkpoint restore or actually deleted on the next checkpoint save.<\/p>\n<p>The real work occurs in CheckpointAware. \u00a0This script, which is dropped on any object that should save its state when a checkpoint is hit, records the transforms of itself and all of its children. \u00a0CheckpointAware adds its object to the CheckpointRegistry when it is allocated, and waits to be told to save or restore its state. \u00a0When a call from the Registry comes in, CheckpointAware reads or writes all of the transforms in its subgraph and calls the appropriate method on any CheckpointBehaviors therein.<\/p>\n<div id=\"attachment_385\" style=\"width: 616px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-22-at-10.02.02-AM.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-385\" class=\" wp-image-385     \" alt=\"Bask in the warmth of the checkpoint's light.\" src=\"https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-22-at-10.02.02-AM.png\" width=\"606\" height=\"386\" srcset=\"https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-22-at-10.02.02-AM.png 800w, https:\/\/robotinvader.com\/blog\/wp-content\/uploads\/2014\/01\/Screen-Shot-2014-01-22-at-10.02.02-AM-300x190.png 300w\" sizes=\"auto, (max-width: 606px) 100vw, 606px\" \/><\/a><p id=\"caption-attachment-385\" class=\"wp-caption-text\">Bask in the warmth of the checkpoint&#8217;s light.<\/p><\/div>\n<p>To sum up the algorithm: CheckpointAware stores position, scale, and rotation of subsets of the hierarchy. \u00a0CheckpointRegistry stores references to all of the objects in all of those subsets, and manages object destruction from within those subsets. \u00a0A checkpoint is saved by calling CheckpointRegistry.SetCheckpoint(), which calls a similar method on each CheckpointAware instance. \u00a0CheckpointAware records the transform of itself and its children to a simple struct and calls CheckpointBehavior.SaveState() on any children with CheckpointBehavior-derived components. \u00a0Restoring the checkpoint is the same, but reversed: CheckpointAware walks its list of stored transforms and writes the cached values back into them, and then calls CheckpointBehavior.RestoreState() on any children that need it. \u00a0Whew.<\/p>\n<p>In any given Wind-up Knight level there are thousands of objects that need to be serialized for checkpoints. Fortunately this method is very fast; there&#8217;s no visible hitch on saving or restoring the state, even on fairly low-end devices. \u00a0It&#8217;s also pretty efficient, as only the minimum amount of data required to restore a checkpoint is saved. \u00a0So there&#8217;s two of our four requirements right off the bat. \u00a0It&#8217;s also easy to manage multiple checkpoints with this system, so we can mark off Repeatable as well.<\/p>\n<p>Which leaves us with one last requirement: Flexibility. \u00a0The weakness of this approach is that it&#8217;s only storing positional and lifetime information; nothing about entity state is recorded by default. \u00a0I thought it was only going to get me half way. \u00a0But once I had it up and running, I found that it is almost a complete solution. \u00a0For Wind-up Knight, storing position and lifetime alone proved to be about 90% of the final solution.<\/p>\n<p>I did end up having to go through a bit of entity code to save and restore internal variables. \u00a0But even that turned out to be simple; most objects just need to record an int or a bool, and can cache them right in the script instance. \u00a0I spent a day converting existing scripts to CheckpointBehaviors and adding SaveState() and RestoreState() implementations, usually only three or four lines of code each, and then I was done.<\/p>\n<p>Retrofitting a mass of existing code to perform new tricks isn&#8217;t fun (even when it&#8217;s simple and easy), but the payoff was worth it. \u00a0Now dying in Wind-up Knight 2 (which happens a lot) hardly slows you down; you&#8217;re sent back a bit and get to try again without missing a beat. \u00a0It&#8217;s a huge improvement in the general flow of the game compared to its predecessor (which required a full scene reload on every death&#8211;ugh). \u00a0The architecture of this checkpoint system occupies a nice middle ground between general purpose serialization and script-specific logic, and I&#8217;m glad (and a little surprised) that the design worked out so well.<\/p>\n<p>Oh, and if you&#8217;re one of the hardcore group of folks that wants to play the game without checkpoints, never fear: you can turn them off.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the new features in Wind-up Knight 2 is checkpoints. \u00a0They work the way you \u00a0expect: if you die after passing a checkpoint you will restart at the checkpoint location with the game world restored to its previous state. &hellip; <a href=\"https:\/\/robotinvader.com\/blog\/?p=383\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,8],"tags":[],"class_list":["post-383","post","type-post","status-publish","format-standard","hentry","category-game-engineering","category-wind-up-knight"],"_links":{"self":[{"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/383","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=383"}],"version-history":[{"count":6,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/383\/revisions"}],"predecessor-version":[{"id":392,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/383\/revisions\/392"}],"wp:attachment":[{"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=383"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=383"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/robotinvader.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=383"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}