{"id":409,"date":"2020-02-25T03:34:28","date_gmt":"2020-02-25T03:34:28","guid":{"rendered":"http:\/\/www.caleb-perkins.com\/?p=409"},"modified":"2020-03-09T02:48:18","modified_gmt":"2020-03-09T02:48:18","slug":"the-card-sprint-review-leia","status":"publish","type":"post","link":"https:\/\/serket.dev\/index.php\/2020\/02\/25\/the-card-sprint-review-leia\/","title":{"rendered":"The Card:  Sprint Review &#8211; Leia"},"content":{"rendered":"<h2>Retrospective<\/h2>\n<p>Leia was a pretty off-beat sprint for me, and I didn&#8217;t think it would turn out nearly as well as it did.\u00a0 During the first half, I got so bogged-down with incorporating YarnSpinner and running into limitations with Unity&#8217;s serialization that it felt like it wasn&#8217;t making any progress at all.<\/p>\n<p>As you&#8217;ll see below this was a really fantastic step forward once I got past a lot of those roadblocks, and it&#8217;s really starting to shape up into something substantive.<\/p>\n<h2>Sprint Leia:\u00a0 02\/10\/2020 &#8211; 02\/21\/2020<\/h2>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-410\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/leia-1024x648.png\" alt=\"trello board\" width=\"1024\" height=\"648\" srcset=\"https:\/\/serket.dev\/wp-content\/uploads\/leia-1024x648.png 1024w, https:\/\/serket.dev\/wp-content\/uploads\/leia-300x190.png 300w, https:\/\/serket.dev\/wp-content\/uploads\/leia-768x486.png 768w, https:\/\/serket.dev\/wp-content\/uploads\/leia.png 1141w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p><strong>Things to stop doing:<\/strong><\/p>\n<ul>\n<li>Getting caught up in trying to reinvent the wheel<\/li>\n<\/ul>\n<p><strong>Things to keep doing:<\/strong><\/p>\n<ul>\n<li>Making use of free assets<\/li>\n<\/ul>\n<p><strong>Things to start doing:<\/strong><\/p>\n<ul>\n<li>Creating prototype assets for actual characters to use instead of the placeholders for proof of concept<\/li>\n<\/ul>\n<p>For roughly the first quarter of the sprint, I got stuck trying to integrate YarnSpinner.\u00a0 I really love their team and I think their narrative engine is great, but it&#8217;s also incredibly new and their documentation is wildly out of date.\u00a0 That said, they were extremely helpful when I jumped into the community Slack with questions and their developers helped me get past all the issues I had setting it up.<\/p>\n<p>I also wasted a lot of time running into Unity limitations.\u00a0 By default, Unity does not serialize dictionaries to the editor, which is difficult because it&#8217;s one of the most useful data structures for managing the narrative variables.\u00a0 I ended up implementing my own version with two lists and an editor script, but it still didn&#8217;t end up looking very good.<\/p>\n<p>By the time I got into working on a generic sprite component I decided to check out the asset store to see if there was anything available.\u00a0 I had been recommended <a href=\"https:\/\/assetstore.unity.com\/packages\/tools\/utilities\/odin-inspector-and-serializer-89041\">Odin<\/a> but I think it&#8217;s a little overkill for the moment (aside from also costing $55).\u00a0 Within a couple minutes I found my way to <a href=\"https:\/\/assetstore.unity.com\/packages\/tools\/utilities\/serialized-dictionary-lite-110992\">Serialized Dictionary Lite<\/a> which is exactly what I need at the moment and also completely free.\u00a0 Shoutout to Rotary Heart!<\/p>\n<h3>Progress<\/h3>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter  wp-image-414\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/intro_test.gif\" alt=\"intro animation\" width=\"297\" height=\"620\" \/><\/p>\n<p>There&#8217;s a lot to unpack from this one gif.\u00a0 On the one hand, I&#8217;m really proud of how it looks with placeholder art.\u00a0 On the other hand, it also looks very similar to gifs I&#8217;ve posted in the past.\u00a0 So what&#8217;s changed?<\/p>\n<h3>YarnSpinner<\/h3>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter  wp-image-415\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/holly_script.png\" alt=\"intro script\" width=\"408\" height=\"376\" srcset=\"https:\/\/serket.dev\/wp-content\/uploads\/holly_script.png 648w, https:\/\/serket.dev\/wp-content\/uploads\/holly_script-300x276.png 300w\" sizes=\"auto, (max-width: 408px) 100vw, 408px\" \/><\/p>\n<p>Meet YarnSpinner &#8212; the narrative engine behind such titles as <em>Night in the Woods<\/em>.\u00a0 Under the hood, it&#8217;s a pretty solid script parser with a lot of built-in features for narrative games.\u00a0 You write .Yarn files which each contain one or more <em>nodes<\/em>, which the engine manages like a finite state machine.\u00a0 It has an API for saving variables (in the form of a bool, string, or int) and event hooks for things like the beginning of a line of dialogue or the end.\u00a0 You can also call <em>Yarn Commands<\/em> directly from a script and create your own.\u00a0 This is how I&#8217;m changing the facial expression on my sprites above.<\/p>\n<p>A lot of YarnSpinner comes down to how you decide to implement certain features.\u00a0 I created a YarnStorage scriptable object to hold my Yarn variables persistently and will be using it to track state and save it between sessions.\u00a0 I also have hooks so that as YarnSpinner&#8217;s <em>DialogueRunner<\/em> passes new lines of parsed dialogue, my <em>TextController<\/em> slices the name of the speaker off from the line and places it in the little speaker UI and passes the rest of the line to the actual dialogue box.<\/p>\n<h3>Sprite Changes<\/h3>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter  wp-image-416\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/character_manager.png\" alt=\"character manager\" width=\"406\" height=\"304\" srcset=\"https:\/\/serket.dev\/wp-content\/uploads\/character_manager.png 591w, https:\/\/serket.dev\/wp-content\/uploads\/character_manager-300x224.png 300w\" sizes=\"auto, (max-width: 406px) 100vw, 406px\" \/><\/p>\n<p>With YarnSpinner set up, getting sprites to change has been a fairly simple process.\u00a0 Behind the scenes it&#8217;s using the same scriptable object architecture I mentioned in my last sprint review.\u00a0 The character sprite set is a little different from the more generic sprite set object because characters are keeping track of body and face, but both follow the same logic.<\/p>\n<p>In either case, YarnSpinner sends out a command that&#8217;s picked up by either the Background object or a specific, named character.\u00a0 Both execute a command which basically sets the Image reference to the value at the key in a dictionary based on the command parameter.<\/p>\n<h3>Artwork<\/h3>\n<p>While I&#8217;ve been getting all this set up, my artist has made the second pass at character concepts with a focus on nailing color schemes this time:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-411\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/secondpass_1-1024x579.png\" alt=\"concept_art_1\" width=\"1024\" height=\"579\" srcset=\"https:\/\/serket.dev\/wp-content\/uploads\/secondpass_1-1024x579.png 1024w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_1-300x170.png 300w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_1-768x434.png 768w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_1.png 1150w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-large wp-image-412\" src=\"http:\/\/www.caleb-perkins.com\/wp-content\/uploads\/secondpass_2-1024x577.png\" alt=\"concept_art_2\" width=\"1024\" height=\"577\" srcset=\"https:\/\/serket.dev\/wp-content\/uploads\/secondpass_2-1024x577.png 1024w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_2-300x169.png 300w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_2-768x432.png 768w, https:\/\/serket.dev\/wp-content\/uploads\/secondpass_2.png 1151w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<h2>Last Thoughts<\/h2>\n<p>I&#8217;m pretty pleased with how far I&#8217;ve come this sprint, and I&#8217;m looking forward to finishing up enough to really get a vertical slice through the intro scene.\u00a0 We&#8217;re rapidly approaching the point where enough foundation will be laid that I&#8217;ll mostly be doing a lot of scriptwriting, and I can&#8217;t wait.<\/p>\n<p>~Caleb<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Retrospective Leia was a pretty off-beat sprint for me, and I didn&#8217;t think it would turn out nearly as well as it did.\u00a0 During the first half, I got so bogged-down with incorporating YarnSpinner and running into limitations with Unity&#8217;s serialization that it felt like it wasn&#8217;t making any progress at all. As you&#8217;ll see [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":411,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[18],"tags":[41,42,20],"class_list":["post-409","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development","tag-devdiary","tag-sprint-review","tag-the-card"],"jetpack_featured_media_url":"https:\/\/serket.dev\/wp-content\/uploads\/secondpass_1.png","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/paXf4J-6B","_links":{"self":[{"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/posts\/409","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/comments?post=409"}],"version-history":[{"count":3,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/posts\/409\/revisions"}],"predecessor-version":[{"id":421,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/posts\/409\/revisions\/421"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/media\/411"}],"wp:attachment":[{"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/media?parent=409"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/categories?post=409"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/serket.dev\/index.php\/wp-json\/wp\/v2\/tags?post=409"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}