<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Particular Reality DevLog]]></title><description><![CDATA[DevLog for the "Particular Reality" VR game]]></description><link>https://particularreality.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!i8iE!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png</url><title>Particular Reality DevLog</title><link>https://particularreality.substack.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 16 Jul 2026 18:15:52 GMT</lastBuildDate><atom:link href="https://particularreality.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Binary Charm Ltd]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[particularreality@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[particularreality@substack.com]]></itunes:email><itunes:name><![CDATA[Binary Charm]]></itunes:name></itunes:owner><itunes:author><![CDATA[Binary Charm]]></itunes:author><googleplay:owner><![CDATA[particularreality@substack.com]]></googleplay:owner><googleplay:email><![CDATA[particularreality@substack.com]]></googleplay:email><googleplay:author><![CDATA[Binary Charm]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Two years of Particular Reality]]></title><description><![CDATA[Well, not really two years]]></description><link>https://particularreality.substack.com/p/two-years-of-particular-reality</link><guid isPermaLink="false">https://particularreality.substack.com/p/two-years-of-particular-reality</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Tue, 05 Aug 2025 17:00:22 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/bd76fc25-62b5-4b13-9fa1-baaf9079ce82_768x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Exactly two years ago, I started working on this project.</p><p>As some of you might have noticed from the lack of new articles in the last months, I've been into a bit of a rough patch. Nothing dramatic, but a series of other work and personal commitments lined up and managed to keep me away from <em>Particular Reality</em>.</p><p>Unfortunately, by its nature of <em>no compromises</em> passion project (you can read more about this in my article "How to pick a game project"), it can happen that I need to put it on hold.</p><p>But what was supposed to be a short break of a couple weeks became a six months limbo.</p><p>Here's the graphic summary of <em>year one</em> and <em>year two</em>, in a format similar to the one I used one year ago.</p><p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FjnP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FjnP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 424w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 848w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 1272w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FjnP!,w_2400,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png" width="1200" height="479.6703296703297" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:582,&quot;width&quot;:1456,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:89297,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://particularreality.substack.com/i/170070056?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:&quot;center&quot;,&quot;offset&quot;:false}" class="sizing-large" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FjnP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 424w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 848w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 1272w, https://substackcdn.com/image/fetch/$s_!FjnP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff78ccae5-1728-44c0-9bb8-a4a6177dec61_1920x768.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Basically, in a whole year I managed to only do 11 weeks of part time work on the project (probably around 250 hours total). It really wasn't a good year, I guess. Fingers crossed for the upcoming one.</p><p>But what went wrong? I'm going to try and discuss the core problem...</p><p></p><h3>The high cost of context-switching</h3><p>I'm not talking about processes executed by the CPU, but it's actually quite the same: being forced to stop working on a project for a while makes you forget many details (the "context"), so going back to it can feel a daunting task, because you must first "rediscover" them. </p><p>Keeping the code well organized can definitely make the process easier and faster, and the DevLog itself is helpful too: I can read the last couple of posts, and remember what was I working on.</p><p>But when you are experimenting with features, and doing "exploratory" programming, you can't expect to always have perfectly organized code. So, if you are forced to stop working at the wrong time, resuming can be a bit more challenging than it should be.</p><p>I went into a downward spiral where I initially didn't go back to the project because I didn't have sufficient time to "get back into it", but that implied staying away from it for longer time. Which, in turn, made that process of "getting back to it" progressively need more time and effort. Because of other work deadlines, I could never manage to get back on track. I tried a couple of times, but <em>things happened</em> and I had to stop again. Basically, six months went by in the blink of an eye.</p><p></p><h3>So what's the plan?</h3><p>After six months of not opening the project at all, I admit that I don't feel like resuming work right now, even if I have a little more free time in the upcoming three weeks. My current priority is completing and archiving some other tasks I've been neglecting for too long, so that finally, after that, I will hopefully be able to focus on <em>Particular Reality</em> without other interruptions for a long time. The idea of resuming work for a bit, and then having to take another break to take care of something I have on my backlog, is unsustainable.</p><p>So, even if I prefer to not set a specific "resume" date yet, I still wanted to publish this quick "birthday" post to explain what's (not) going on.</p><p>The project is not dead and I'm still thinking about it and occasionally writing down some notes that will come useful.</p><p>Zero doubts about wanting to keep working on it and publish the final result.</p><p>I just need to clear the road ahead first.</p><p>Until next time!</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #34]]></title><description><![CDATA[Guiding through portals]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-34</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-34</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 25 Jan 2025 17:02:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b56baa9b-ccc1-4aba-a928-898812a7da2a_840x840.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2025-01-20 - Menu zone logic</h2><p>I'm going to proceed like I did in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-33">week 33</a> for the <em>limbo</em> zone, but this time dealing with the <em>menu</em> zone.</p><p>Let's see what I designed in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-25">week 25</a>, and in particular on 2024-08-27:</p><blockquote><p>...after entering from the limbo, [the player] will be drawn to the zone where they can open a portal to the adjacent platform. When in position, the floating particles will morph to the gesture needed to open the portal, prompting the player to do that same gesture.</p><p>When the "skeleton avatar" hands match the "particles hands", they will activate the portal to the adjacent platform.</p><p>...</p><p>the player should take a step forward while sticking to the same gesture. We should teach this by having the "particles hand" go forward into the portal, pushing the player to follow in the same way.</p></blockquote><p>Then, this sequence needs to be repeated twice, one to go to the <em>new game</em> platform and another to actually enter the gameplay session crossing a <em>bridge portal</em>.</p><p>I also did this sketch showing the three steps:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VwfY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VwfY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 424w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 848w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 1272w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VwfY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png" width="728" height="1330.3553299492385" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:2160,&quot;width&quot;:1182,&quot;resizeWidth&quot;:728,&quot;bytes&quot;:2152224,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VwfY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 424w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 848w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 1272w, https://substackcdn.com/image/fetch/$s_!VwfY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5455810-2bb6-4a9d-93d9-9413c68379fa_1182x2160.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let's draft the code for this zone.</p><div><hr></div><p>Basically, I need to set the same type of hint, but on three different spots.</p><p>At least for now, it's totally fine to hardcode these spots.</p><p>In the menu zone, I had labelled only the <em>start</em>, <em>new_game</em> and <em>continue_game</em> platforms.</p><p>I added a <em>center</em> label to the... well, central platform, so that I can easily look it up from the zone logic.</p><p>Skipping the lookup of the zone and platforms, this should basically be it:</p><pre><code>            RTLocation playerLoc = m_rPlayerStateManager.getCurrLocation();
            Hex.EDir? portalOpeningSlice = null;
            if (playerLoc.m_platformId == startPlatform.m_id) {
                portalOpeningSlice = Hex.EDir.S;                
            } else if (playerLoc.m_platformId == centralPlatform.m_id) {
                portalOpeningSlice = Hex.EDir.SE;
            } else if (playerLoc.m_platformId == newGamePlatform.m_id) {
                portalOpeningSlice = Hex.EDir.S;
            }
            if (portalOpeningSlice.HasValue) {
                rSmartParticlesSM.setPortalHint(
                    new RTSpot(playerLoc, portalOpeningSlice.Value)
                );
            }</code></pre><p>So, depending on the current platform the player is standing on, I set a "portal hint" on that same platform, on the correct hexagonal slice to guide them from the <em>start</em> platform to the bridge portal connecting the <em>new game</em> platform and the first gameplay zone.</p><div><hr></div><p>Let's pause for a minute.</p><p>Last week I guided the player through the <em>mirror portal</em> which shows up when starting the game.</p><p>Now, I need to guide them through the "normal" portals, which require a gesture to be activated, meaning that I must finally make use of the <em>gesture hint</em> I implemented in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-32">week 32</a>.</p><p>This means that I need a different behaviour for these hints, and rethinking about it, my naming of the hint mode (`<code>EHintMode.portal</code>`), last week, was wrong: that was the behaviour needed specifically for the <em>mirror</em> portal.</p><p>I'm going to refactor `<code>EHintMode.portal</code>` to `<code>EHintMode.mirrorPortal</code>`, updating all the references in last week code accordingly. Then, I'm going to re-introduce a "brand new" `<code>EHintMode.portal</code>` to use for the new behaviour.</p><div><hr></div><p>I did the changes I mentioned, and some other minor fixes. Tomorrow I'll proceed with the logic of the new <em>hint mode</em>.</p><div><hr></div><h2>2025-01-21 - Portal opening hints</h2><p>As the "normal" portals don't open automatically like the <em>mirror portal</em>, we need a slightly more complex FSM.</p><p>Let's go back to the predicates I used last week:</p><ul><li><p>`<code>C</code>` : player "captivated", meaning that the hint sphere is in their FOV</p></li><li><p>`<code>P</code>`: player "positioned", meaning they're in the correct spot to open the portal</p></li><li><p>`<code>L</code>`: player "looking" towards the portal (more precisely: properly rotated to open it)</p></li><li><p>`<code>T</code>`: timeout, some specified time has passed</p></li></ul><p>I'm adding a new one:</p><ul><li><p>`G`: opening gesture recognized</p></li></ul><p>Now, to keeps thing manageable I'll try an approach where the hints are given in sequence, and if you fail to do a step after a certain timeout, the flow is sent back to the beginning. Unless one doesn't invalidate the previous predicates (for example: by moving from the spot), the flow should naturally go the "current" step (with no unwanted delays). </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xrAy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xrAy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 424w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 848w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 1272w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xrAy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png" width="434" height="731" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:731,&quot;width&quot;:434,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:36995,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xrAy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 424w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 848w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 1272w, https://substackcdn.com/image/fetch/$s_!xrAy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1f71d942-0294-4a15-b58c-64ad3142104c_434x731.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>I implemented the new FSM and started testing it.</p><p>The core logic works pretty well, but there's a lot of work to be done before I can consider this part "complete".</p><p>There are still the glitches I briefly showed in the video at the end of the previous articles, and new ones related to transitioning to the `<code>show_anim</code>` state (that we didn't use with the <em>mirror portal</em>).</p><p>I'm going to try and fix everything in the remaining days of the week.</p><div><hr></div><h2>2025-01-22 - Hint particles appearance and disappearance</h2><p>So, the two most unpleasant things in the current state of the hints are</p><ul><li><p>the "looking towards portal" check is not well tuned, meaning that it stops guiding the player gaze too soon</p></li><li><p>the hint visuals (both "hint sphere" and "hint clip") appear/disappear abruptly</p></li></ul><p>Let's face the appearance/disappearance problem first.</p><div><hr></div><p>I'm going to add, at the state management level, two 0-1 values, `<code>hintParticlesVisibility01</code>` and `<code>hintClipVisibility01</code>`. I'm going to animate such values towards 0 or 1 depending on if these elements must be visible or not in a certain state.</p><p>Then, in the elements "presentation" scripts, I'm going to use these values to tune the placeholder VFX. I'm going to reuse the `<code>VfxBhv</code>` script I already used to handle the enemies and other elements, which easily allows me to change size, particles emission rate and colors. Good enough for now. </p><div><hr></div><p>Ok, I did the `<code>VfxBhv</code>` refactoring and added a bit of logic to fade the visibility values, in the main <em>smart particles</em> state manager.</p><p>It doesn't look great, but it's far better than an instant visibility change.</p><p>Not worth a video, but I'll capture one tomorrow after fixing something else too.</p><div><hr></div><h2>2025-01-23 - Improving the gaze-guide</h2><p>Today I'm going to work on improving the code used by the hint system to check if the player is properly rotated towards a portal.</p><p>The current check was built upon the one used to see if it was possible to open a portal. Why that's not good enough? </p><p>Well, up to now it's been possible to open portals backwards, or looking quite "sideways", as long as you are on the right half of the platform. If that's a good idea or not, is a topic for another day.</p><p>Right now, the point is that such check doesn't work well with the "gaze guide" hint: I want to check that the player is "precisely" looking towards the destination platform, before considering the suggestion by the hint system successful (and transitioning to the following state).</p><div><hr></div><p>I implemented a simple check using the dot product between the direction towards the destination platform, and the player look direction projected on the ground.</p><p>When the dot product is close to one (currently, greater than `<code>0.95f</code>`), it's considered good.</p><p>It's not perfect, but I'd say the system starts to properly "guide" me to open the portal!</p><div id="youtube2-ZHImM9-Uk48" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ZHImM9-Uk48&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ZHImM9-Uk48?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The `<code>guide_look</code>` fade out is pretty ugly, but at least is there. </p><p>Instead, the `<code>guide_anim</code>` clip showing the hand gesture still appears/disappears abruptly.</p><div><hr></div><h2>2025-01-24 - Fading the gesture hint</h2><p>Today I have very little time available, but I should manage to do at least a little thing: remove the instant appearance/disappearance of the arms giving the hint about how to perform the portal opening gesture.</p><div><hr></div><p>Ok, it was pretty quick: I just added a parameter to the "particles body" VFX to set the spawn rate of the particles, and hooked it up to the `<code>hintClipVisibility01</code>` I had already added a couple of days ago.</p><p>This is a test of the complete sequence, opening and crossing the three portals of the menu zone.</p><div id="youtube2-X4V8KhHw5kE" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;X4V8KhHw5kE&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/X4V8KhHw5kE?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Next week I'm going to be super busy, so I'll probably be back with a new post in two weeks. Or three. But hopefully two. Stay tuned!</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #33]]></title><description><![CDATA[Guiding out of the limbo]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-33</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-33</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 11 Jan 2025 17:30:47 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/24eccb3d-9e66-48f1-b233-c4f62d2a9322_908x908.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I spent the last two development weeks by building elements that now, finally, I can use to implement the "tutorial" sequence I described in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-25">week 25</a>. Here we go!</p><div><hr></div><h2>2025-01-06 - Limbo zone logic</h2><p>Let's start from the beginning, the limbo platform where you need to enter <em>Particular Reality</em> and embody the avatar. I wrote (and sketched):</p><blockquote><p>I need to push the player roughly near one edge of the hexagonal platform, and facing the opposite edge. This way, the portal to cross, which is going to "split" the platform in two halves when it appears, will open in front of the player, well visible.</p><p>Having some kind of arrow stamped on the platform would be effective, but too crude for my taste. What I'm thinking is having a dynamic hint system, based on floating particles, that goes into the player field of view and moves trying to push him into a certain spot.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aBjF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aBjF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 424w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 848w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aBjF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png" width="1456" height="364" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:364,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2876916,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aBjF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 424w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 848w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!aBjF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fef8c87-f22f-456b-a138-dbf52cb6d554_4096x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div></blockquote><div><hr></div><p>From a high level perspective, the logic of the limbo zone should be:</p><ul><li><p>the zone is initialized with the mirror portal closed</p></li><li><p>if the mirror portal is closed</p><ul><li><p>If the player is not on the bottom half of the platform, facing towards the top half, bring them in that status with the hint system</p></li><li><p>otherwise, open the mirror portal</p></li></ul></li><li><p>if the mirror portal is opened</p><ul><li><p>check the proximity of the player with its mirrored double, and if crosses a certain threshold, cross the mirror portal and embody the avatar (this part is already implemented, even if needs to be refactored)</p></li><li><p>optional: if a certain time has passed and the player is not approaching the portal, maybe use the hint system to encourage them to do that</p></li></ul></li></ul><p>According to my experience, the best way to think about gameplay logic is by having states activated by arbitrarily complex predicates.</p><p>I'm going to do a bit of refactoring, making it easier to check the predicates needed without duplicating the logic I already have to check if the player is in the correct position to open a portal. </p><div><hr></div><p>I did the refactoring I wanted, or at least part of it. </p><p>I also wrote the "tentative" code for the limbo zone logic.<br>Without thinking too much about where this is currently placed, here's the snippet:</p><pre><code>            if (!isPlayerReadyToOpenPortalTowards(Hex.EDir.N)) { 
                RTSpot mirrorPortalOpeningSpot = new RTSpot(
                    WorldStateManager.getStartupLoc(),
                    Hex.EDir.S
                );
                rSmartParticlesSM.setPortalHint(mirrorPortalOpeningSpot);                
            } else if (rPortalSM.getState() == Portal.EState.opened) {
                const float fCROSSPORTAL_HINT_WAIT_SECS = 5f;
                float fPortalBeenOpenedSecs = 
                    rPortalSM.getCurrStateElapsedTime();

                if (m_fTimeAtCurrLoc &gt; fCROSSPORTAL_HINT_WAIT_SECS) {
                    RTSpot portalDestinationSpot = new RTSpot(
                        WorldStateManager.getMenuStartLoc(),
                        Hex.EDir.N
                    );
                    rSmartParticlesSM.setGotoSpotHint(portalDestinationSpot);
                }
                mirrorPortalHandling();
            }</code></pre><p>The idea is that, at this level, I simply set what kind of hint I want depending on the situation. <br>Then, in the `<code>SmartParticlesStateManager</code>`, one layer below, I will appropriately use the features we implemented in the last weeks to guide the player.</p><p>I'm talking, of course, of the implementation of the `<code>setPortalHint()</code>` and `<code>setGotoSpotHint()</code>` methods, which are currently empty.</p><p></p><h2>2025-01-07 - Improving visual debugging</h2><p>I think that, before proceeding, it makes sense to revisit and improve the debug visualization features related to the player movement/target platform selection.</p><p>That way, I'll be able to easily understand if the conditions that should activate a certain hint are true at a specific time.</p><div><hr></div><p>I haven't much time to write today, so I'm going to keep it brief.</p><p>I'm refactoring the old `<code>DebugPlayerBhv</code>` and a couple of other scripts so that they follow the pattern of the `<code>DebugSmartParticlesBhv</code>` I recently discussed. This avoid some code duplication and improves the organization of the scene, removing unnecessary interdependencies.</p><p>Things are gradually getting a bit tidier, and that's great, even if it's taking a bit more than expected. Can't escape Hofstadter's law.</p><div><hr></div><p>I added a couple of timers indicating how long one has been on a certain platform, and on a specific hexagonal slice on that platform.</p><p>I also drafted a small panel following the player on the ground, showing the timer values and other information that might get useful when testing the hint system.</p><p>It's not worth a video, but here's an image.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kK44!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kK44!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!kK44!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!kK44!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!kK44!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kK44!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e95d5127-5b8d-4580-b23c-823b777788c3_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:264617,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kK44!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!kK44!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!kK44!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!kK44!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe95d5127-5b8d-4580-b23c-823b777788c3_600x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><div><hr></div><h2>2025-01-08 - Restructuring hint state management</h2><p>I realized that to quickly test the hint visualization, in the last weeks, I cheated a bit and calculated the <em>world</em> transform for the hint sphere accessing directly the player tracking data from the Unity scene. That doesn't sit right. I must do the work and have the state management layer not dependant on the presentation layer. This calls for some more preliminary refactoring which won't carry visible results.</p><div><hr></div><p>Ok, I was able to <em>mostly</em> fix what I wanted, but I had to do it in a hacky way.</p><p>To fix it properly, I will need to do some more in-depth restructuring.</p><p>Long story short, in the state management layer, at the moment, I only deal with zone-relative coordinates: while in the presentation layer, when two zones are loaded, I can do calculations involving platforms and elements from both zones, I can't really do that in the state management layer.</p><p>If I want to be able to have logic involving an adjacent zone, that you are maybe peeking into from an opened bridge portal, I will need to fix that.</p><p>For now, I did the barely minimum I needed to be able to have the `<code>guide_move</code>` state working when the destination spot is not only on the current zone (which was the "easy" case) but also on the single platform of the adjacent zone that is on the other end of the current bridge portal, if any.</p><p>As the "mirror" portal is a special case of bridge portal, connecting the <em>limbo zone</em> to the <em>menu zone</em>, this should allow me to implement the hint telling the player to move into the portal, if they don't figure that out after a while. I will just need to set the target spot for the `<code>guide_move</code>` state to the "other end" ot the portal.</p><div><hr></div><h2>2024-01-09 - Hint modes design</h2><p>In the zone logic code written Monday, I called the `<code>setPortalHint()</code>` and `<code>setGotoSpotHint()</code>` stubs: it's time to fill them in, defining how the low level states of the <em>smart particles</em> subsystem (that we already implemented) are meant to be used to give players hints in specific contexts.</p><p>I'm going to introduce the notion of <em>hint mode</em> to describe the "high level" hints I want to have readily available to the zone logic code. Each hint mode will have its custom path in the <em>smart particles</em> FSM. Maybe, I will then factor this out to properly nested state machines, if it's worth it.</p><pre><code>        public enum EHintMode {
            none,
            portal,
            gotoSpot
        }</code></pre><p>So, let's define a simple (hopefully) FSM for each of these two modes we need for the <em>limbo zone</em>, and assume that calling `<code>setPortalHint()</code>` and `<code>setGotoSpotHing()</code>` activates each one of them.</p><div><hr></div><p>Let's start from `EHintMode.portal`.</p><p>To be able to open a portal one must be</p><ul><li><p>in the correct spot</p></li><li><p>facing the correct direction</p></li></ul><p>So the <em>hint particles</em> should</p><ul><li><p>catch the player attention (`<code>captivate</code>`)</p></li><li><p>if they are not at the right spot, send them there  (`<code>guide_move</code>`)</p></li><li><p>if they are at the right spot, but are not facing the correct direction, make them look that way (`<code>guide_look</code>`)</p></li></ul><p>The system must be robust, meaning that if after some time the player fails to follow the hint, it should go back to `<code>captivate</code>` their attention and try again.</p><p>To make the diagram readable, let's assign some one letter identifiers to the predicates we are going to need:</p><ul><li><p>`<code>C</code>` : player "captivated", meaning that the hint sphere is in their FOV</p></li><li><p>`<code>P</code>`: player "positioned", meaning they're in the correct spot to open the portal</p></li><li><p>`<code>L</code>`: player "looking" towards the portal (more precisely: properly rotated to open it)</p></li><li><p>`<code>T</code>`: timeout, some specified time has passed</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hpuC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hpuC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 424w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 848w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 1272w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hpuC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png" width="744" height="729" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:729,&quot;width&quot;:744,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59185,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hpuC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 424w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 848w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 1272w, https://substackcdn.com/image/fetch/$s_!hpuC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a760f8-4d05-48b2-af17-76d8e0f9c6bc_744x729.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I added a `<code>done</code>` state just to have the diagram flowing better. And we're going to add some extra state like that to take care of the appearance/disappearance of the hint particles anyway.</p><div><hr></div><p>Let's move to the `<code>EHintMode.gotoSpot</code>` FSM, which should be a bit simpler.</p><p>To send the player to a specific spot, we shouldn't need `<code>guide_look</code>`: we just catch their attention with `<code>captivate</code>` and then use `<code>guide_move</code>`. If the player fails to reach the destination spot in a certain time, and they're not looking at the hint sphere (meaning that the player attention is not <em>captivated</em>), we go back to `<code>captivate</code>`, repeating the loop until the player reaches the spot.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H8LX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H8LX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 424w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 848w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 1272w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H8LX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png" width="744" height="729" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:729,&quot;width&quot;:744,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:52129,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!H8LX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 424w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 848w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 1272w, https://substackcdn.com/image/fetch/$s_!H8LX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d08f74f-5dac-4807-b6bd-71adca36432a_744x729.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tomorrow I'll try to implement and test all this.</p><div><hr></div><h2>2024-01-10 - Hint modes implementation</h2><p>As first step, I'm going to implement some utility functions to evaluate the predicates I described yesterday, which will probably be the most delicate part.</p><p>Then, I will map the logic defined by the diagrams to the code, which should be straightforward.</p><div><hr></div><p>Ok, my time is over for now, but let's say that I implemented what I designed yesterday, and it <em>somewhat</em> works.</p><p>There's plenty of details to take care of, and polishing to be made, but for this week, this is it:</p><div id="youtube2-koR9jbIll5k" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;koR9jbIll5k&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/koR9jbIll5k?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The worst thing is how the hint abruptly disappears as soon as I'm in the correct direction to open the portal. Then, it sometimes intersects platforms, and it's not clear where does it come from when, after opening the portal, it suggests go cross it by guiding to the target platform.</p><p>Well, you gotta start somewhere!<br></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #32]]></title><description><![CDATA[Smart particles hints]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-32</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-32</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 04 Jan 2025 17:00:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/2948ec8c-47e3-4d9d-935b-2c2380e84f82_840x840.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This week, I should complete the basic implementation of the <em>smart particles</em> design, taking care of the last missing state:</p><ul><li><p>`<code>show_anim</code>`: the particles should morph to a detached body (or maybe just hands?) which playbacks a captured animation, inviting the player to emulate it</p><ul><li><p>input: where to perform the animation, and what animation to perform</p></li></ul></li></ul><p>Let's go!</p><div><hr></div><h2>2024-12-30 - Revisiting motion clip management</h2><p>So, what's the motion clip capture and playback situation?</p><p>If you want the full story, you need to read:</p><ul><li><p><a href="https://particularreality.substack.com/p/deep-dive-into-body-tracking">Deep dive into body tracking - Tales from the Meta SDK</a></p></li><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-16">AP Week 16: Motion capture and playback</a></p></li><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-17">AP Week 17: Motion clips management</a></p></li><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-18">AP Week 18: Additional clip management and debugging</a></p></li><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-19">AP Week 19: Clip playback on skinned meshes</a></p></li></ul><p>Otherwise, here's a quick summary. </p><p>I can, using the in-VR "clip management" UI I did:</p><ul><li><p>capture body tracking data and save it to a motion clip</p></li><li><p>play a saved clip, optionally changing its playback speed and looping behaviour</p></li><li><p>define a selection (setting begin/end frames) on a clip and, if needed, extract it to a new clip</p></li></ul><p>The idea is saving clips for the gestures I want to teach, giving them identifiers, and playing them back with a suitable skinned mesh character which is going to be part of the <em>smart particles</em> subsystem.</p><div><hr></div><p>Until now, I just saved clips and played them back at the same position they had been captured, and using a specific test character (currently, the magenta particles avatar seen in week 19).</p><p>Now, I need to improve the clip playback system so that I can</p><ul><li><p>play clips on different (compatible) targets</p></li><li><p>play clips at different positions/rotations and not only where they were captured</p></li></ul><div><hr></div><p>I revisited the code I wrote months ago and did a bit of refactoring. Now, the `<code>UpperBodyClipPlayerBhv</code>` doesn't directly apply the clip data to the test character: it applies such data to any linked node having a script inheriting from a new abstract class I defined, `<code>AUpperBodyClipPlayerTargetBhv</code>`.</p><p>The logic which was embedded in `<code>UpperBodyClipPlayerBhv</code>` got factored out to a `<code>UpperBodyClipTestPlayerTargetBhv</code>`, but now I will be able to reuse `<code>UpperBodyClipPlayerBhv</code>` to drive other setups. </p><p>Just a bit of ground work for today!</p><div><hr></div><h2>2024-12-31 - Capturing and loading hint clips</h2><p>To get started, I'm going to capture a couple of motion clips showing how to open the portal and walk into it.</p><p>I'm going to define an enumeration identifying this set of motion clips.</p><pre><code>public enum EHintClipId {
    openPortal,
    stepIntoPortal
}</code></pre><p>Then, I'm going to capture two clips (placeholders of course, without caring that much about capturing them nicely) for these hints.</p><div><hr></div><p>I did a single capture of opening and entering a portal, and then extracted the two clips from there:</p><div id="youtube2-xJXNCq8hYMQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;xJXNCq8hYMQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/xJXNCq8hYMQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Currently, the motion clips are stored as simple files on mass storage. That means that I would need to "manually" copy such files on the Quest file system if I want to to test a standalone build. Embedding the data in the build would be preferable.</p><p>Actually, I already solved the same problem when dealing with the level descriptions: I don't remember if I discussed the details on the DevLog, but I added a secondary data path which allows to load them from the <em>Resources</em> data and not only from external files.</p><p>I'm going to extend such simple system to also work with motion clips.</p><div><hr></div><p>Done! From a quick test, the loading seems working. Tomorrow I'll setup a skinned character to be used by the hint system and play the clips on that, </p><div><hr></div><h2>2025-01-01 - New year, new skinned character</h2><p>In Italy they say that&nbsp;what you do on the first day of the year, you&#8217;ll do all year around.</p><p>Guess what, it doesn't really work, but still, let's pretend it does and do a little work today, hoping that I'll manage to keep developing the game without too many forced breaks during the whole new year.</p><div><hr></div><p>So, I duplicated the character setup I'm currently targeting when doing playback from the clip management UI. I changed the particles color to the same yellowish tint I configured for the placeholder VFX.</p><p>I also created another `<code>UpperBodyClipPlayerBhv</code>` and linked it to the new character setup.</p><p>Then, I added another subsystem to the <em>smart particles</em> one, naming it <em>hint clips</em>, which drives this <em>hint clip player</em> targeting the <em>hint character</em>. It's not <strong>that</strong> confusing.</p><div><hr></div><p>I tested the new setup, and it works like expected.</p><p>Before working on the placement of the clip, I need to deal with the problem that I don't want the hint character to feature the full upper body, but just the arms and hands.</p><p>You might remember that to hide the legs I did a quite ugly hack: I forced the scale of the legs bones to a very small number in `LateUpdate()`. That wasn't perfect, because there's still two shiny points at the base of the legs that show up, bud I was happy enough of the solution, considering it's just temporary.</p><p>This time, unfortunately, I can't use the same trick, because this is the bones hierarchy:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5WOh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5WOh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 424w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 848w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 1272w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5WOh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png" width="285" height="160" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:160,&quot;width&quot;:285,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:8222,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5WOh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 424w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 848w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 1272w, https://substackcdn.com/image/fetch/$s_!5WOh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F243c68aa-5845-45ca-9772-98091abe8bc9_285x160.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>I can scale down the legs and the head (scaling down the `<code>Neck</code>` node), but I can't scale down the chest without also affecting the arms, under the shoulders. </p><p>So, what can I do?</p><div><hr></div><p>I went out of my comfort zone and tried using <em>Blender</em> to actually make an alternate version of the character FBX where I stripped the meshes of the body parts that I didn't want (keeping the skeleton untouched).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TLJV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TLJV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 424w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 848w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 1272w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TLJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png" width="957" height="759" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:759,&quot;width&quot;:957,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:195763,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TLJV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 424w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 848w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 1272w, https://substackcdn.com/image/fetch/$s_!TLJV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe39dbc24-a46d-473d-a5c7-6172d7461ede_957x759.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I managed to have an "arms and hands only" version of the character, and it rendered properly, but the arms and hands didn't animate well. What was wrong? I know very little about modelling and rigging, so I really don't know. But to understand if it was because of the things I had removed, I tried just importing and re-exporting the character FBX, with no modification, and that didn't work either. Is some obscure export setting configured the wrong way? A rabbit hole not worth getting into.</p><p>Time for a plan B...</p><div><hr></div><p>Opening the model in Blender wasn't totally useless: it made me remember that the character mesh actually features three submeshes. I should have known, because I remembered dealing with three different materials attached, and AFAIK you can't have different materials without different submeshes.</p><p>To see the submeshes in the inspector, you need to select the mesh in the project tree, inside the imported FBX tree. The entry with the little cyan grid icon. In this case, I got this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kPix!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kPix!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!kPix!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!kPix!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!kPix!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kPix!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png" width="600" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64135,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kPix!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 424w, https://substackcdn.com/image/fetch/$s_!kPix!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 848w, https://substackcdn.com/image/fetch/$s_!kPix!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 1272w, https://substackcdn.com/image/fetch/$s_!kPix!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb291e58b-36d9-416e-b684-1694ced9ad1e_600x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ha! The arms are indeed a specific submesh, the blueish #1.</p><p>Let's try something...</p><div><hr></div><p>Well, it worked. Here's a clip.</p><div id="youtube2-WVj_HPCnT2g" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;WVj_HPCnT2g&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/WVj_HPCnT2g?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Tomorrow I'll clean up a bit the ugly hack I did, and explain what I did.</p><div><hr></div><h2>2024-01-02 - Removing submeshes</h2><p>So, what did I do to only render the arms and hands of the character skinned mesh?</p><p>After realizing that what I wanted to keep was in a separated submesh, I tried to generate at runtime a new `<code>Mesh</code>` object, cloning the original, except for the submeshes definition, where I only kept the one I wanted.</p><p>A submesh is made of a sequence of triangles, and each triangle is defined by the indices of the three vertices that form that triangle (taken from the `<code>vertices</code>` array).</p><p>When you create a new `<code>Mesh</code>` object via code, you can specify the number of submeshes, and then the Unity `<code>Mesh</code>` API lets you get/set this triangles definition for each submesh via these methods:</p><ul><li><p>`<code>int[]&nbsp;GetTriangles(int&nbsp;submesh)</code>`</p></li><li><p>`<code>void&nbsp;SetTriangles(int[]&nbsp;triangles, int&nbsp;submesh)</code>`</p></li></ul><p>So, my hack leaves all the "base" mesh data unaltered (changing it but keeping it consistent might be complicated), without removing the vertices, normals etc, but changes the submeshes definition so that Unity only uses a subset of the data when rendering.</p><p>Not optimal in terms of memory use, but perfect for my prototyping purposes, as there are no visible artefacts.</p><p>And there's other good news: I can use the same approach for the other characters, where I hid the legs by scaling their bones hierarchy to `<code>0.001</code>` in `<code>LateUpdate()</code>`. Less useless work done, and no visual artefacts (the two shiny dots at the base of the invisible legs I have right now).</p><p>Refactoring and clean-up time!</p><div><hr></div><p>This is the method I added to my character presentation script:</p><pre><code>private static Mesh PartialClone(
        Mesh rMesh, 
        int[] rSubmeshesToKeep) {
    Mesh rNewMesh = new Mesh {
        subMeshCount = rSubmeshesToKeep.Length
    };
    rNewMesh.vertices = (Vector3[])rMesh.vertices.Clone();
    rNewMesh.normals = (Vector3[])rMesh.normals.Clone();
    rNewMesh.uv = (Vector2[])rMesh.uv.Clone();
    rNewMesh.colors = (Color[])rMesh.colors.Clone();
    rNewMesh.tangents = (Vector4[])rMesh.tangents.Clone();
    rNewMesh.boneWeights = (BoneWeight[])rMesh.boneWeights.Clone();
    rNewMesh.bindposes = (Matrix4x4[])rMesh.bindposes.Clone();

    int iNewMeshSubmeshesIdx = 0;
    foreach (int iSubmeshToKeepIdx in rSubmeshesToKeep) {
        int[] rTriangles =
            (int[]) rMesh.GetTriangles(iSubmeshToKeepIdx).Clone();
        rNewMesh.SetTriangles(rTriangles, iNewMeshSubmeshesIdx);
        ++iNewMeshSubmeshesIdx;
    }

    rNewMesh.RecalculateBounds();
    rNewMesh.RecalculateNormals();
    return rNewMesh;
}</code></pre><p>I didn't check if I could avoid cloning the mesh data (I probably could), but let's stay on the safe side, considering it's just a temporary fix for a temporary character mesh.</p><p>Then, I exposed a `<code>m_submeshesSetup</code>` setting to the inspector, to configure what kind of visualization I want for each character instance:</p><pre><code>public enum ESubmeshesSetup {
    FullBody,
    UpperBody,
    ArmsOnly
}

[SerializeField] private ESubmeshesSetup m_submeshesSetup;</code></pre><p>Finally, in `Awake()`, I called the `PartialClone` method indicating what submeshes to keep depending on the configured value.</p><pre><code>const int iBODY_SUBMESH_IDX = 0;
const int iARMS_SUBMESH_IDX = 1;
const int iLEGS_SUBMESH_IDX = 2;

protected virtual void Awake() {
    if (m_submeshSetup == ESubmeshSetup.UpperBody) {
        m_rSkinnedMeshRenderer.sharedMesh = PartialClone(
            m_rSkinnedMeshRenderer.sharedMesh,
            new int[] { iBODY_SUBMESH_IDX, iARMS_SUBMESH_IDX }
        );
    } else if (m_submeshSetup == ESubmeshSetup.ArmsOnly) {
        m_rSkinnedMeshRenderer.sharedMesh = PartialClone(
            m_rSkinnedMeshRenderer.sharedMesh,
            new int[] { iARMS_SUBMESH_IDX }
        );
    } // else do nothing and keep the full body mesh
}</code></pre><p>Sometimes, you solve a basic problem in a stupid-simple way, and then are presented with a more complex version of the same problem which pushes you to find a less na&#239;ve solution. If feels nice when that new solution also provides a better solution for the original, basic problem. That's what happened this time, with the removal of the legs scaling hack and its artefacts.</p><div><hr></div><h2>2024-01-03 - Placing hint clips</h2><p>So, we have a way to load, identify and play on a special "arms only" character some motion clips.</p><p>It's time to properly place them in the game world.</p><p>Honestly, I'm not sure if I want to show them </p><ul><li><p>into "ideal" positions, like relative to a spot (as we defined it in week 31, so a platform location and a platform slice identifier)</p></li><li><p>according to the current player position and orientation, so that they can basically "match" the hint clip with their hands</p></li></ul><p>There's another problem to take into account: players have a different stature.</p><p>Ideally, I would want to "transpose" a captured animation to the hint character but using the bones size derived from the live tracking data, so that the animation adapts to the actual player stature.  This feels a bit overkill, especially considering the current stage of development, but I wrote down a note about looking into it at some point in my "polishing / improvements" notes.</p><p>Instead, to keep it simple, I will just scale the hint character a bit according to the detected player height and - as I'm doing the captures - mine (`<code>1.75m</code>`).</p><p>For example, if I detect that the player height is `<code>1.90m</code>`, I will set the hint character scaling to `<code>1.90 / 1.75 = 1.085</code>`, if it's `<code>1.60m</code>` the scale will be instead `<code>1.60 / 1.75 = 0.914</code>`.</p><p>This is a common approach in XR development, and if you're surprised that a uniform scaling can work pretty well, consider that the width of the arm span of a person is roughly the same as their height, as shown by the square in the <em>Vitruvian Man</em>.</p><p>Coding time!</p><div><hr></div><p>I proceeded without significant problems. For now, I decided to use the <em>spot</em> identification system to place the <em>hints character</em>. I'll probably add other options along the way.</p><p>I wrote a bit of code to take care of the character scaling depending on the player height, and tested that it affects the character correctly. That said, the feature is incomplete, because I don't have a calibration sequence yet, so I don't have the correct value for the player height. I tried calculating a kind of "continuously adjusting average height", but I didn't like the result, so I scrapped it. Instead, I wrote a nice `<code>// TODO</code>` into the method implementation to calculate the scale, and had it return `<code>1.0f</code>`.</p><p>Here's a short clip where I test the placement of the hint animation via inspector:</p><div id="youtube2-oDE-dacNiG0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;oDE-dacNiG0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/oDE-dacNiG0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>And week 32 is done too. I'm now able to show <em>hint clips</em> with adequate in-game placement.</p><p>I should finally have all the pieces I need to implement the tutorial sequence I described in <a href="https://particularreality.substack.com/i/148312213/teaching-locomotion">week 25</a>... so, don't miss the next article! </p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #31]]></title><description><![CDATA[Smart particles hints]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-31</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-31</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 28 Dec 2024 17:01:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9defaa9f-b04f-452b-a173-a55d0d6dc372_792x792.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At the end of the previous article, I anticipated I would be busy and would skip a week. Things happened, and I skipped five weeks instead. Annoying, but it is what it is.</p><p>Let's see if I manage to do a bit of extra work in these XMAS holidays, and make up for the lost time.</p><div><hr></div><h2>2024-12-23 - Hint particles setup</h2><p>So, what was I doing before this forced break?</p><p>I started working on the <em>smart particles</em>, doing a bit of design and then introducing a subset of them called <em>probe particles</em> which I linked to the portal opening/closing mechanic.</p><p>That last part wasn't completely done, but before going back at it I prefer to draft the other <em>smart particles</em> behaviours.</p><p>Let's copy the  definitions of a subset of <em>smart particles</em> states from the design done during <a href="https://particularreality.substack.com/p/advanced-prototyping-week-29">week 29</a>:</p><ul><li><p>`<code>captivate</code>`: the particles should catch the player's eye. A basic way to do it is move towards the centre of the player's field of view, at a certain distance, and idle around there. </p><ul><li><p>input: the player state data</p></li></ul></li><li><p>`<code>guide_move</code>`: the particles should guide the player towards a certain spot. </p><ul><li><p>input: the player state data and the destination spot  (a location and a hexagonal slice identifier should suffice)</p></li></ul></li><li><p>`<code>guide_look</code>`: the particles should invite the player to look towards a certain point</p><ul><li><p>input: the player state data and the destination point</p></li></ul></li></ul><p>These states sound quite interconnected, because they deal with particles that catch the player attention and guiding them. </p><p>So, I'm going to introduce another subset of <em>smart particles</em>, called <em>hint</em> particles, that will take care of these states, similarly to how the <em>probe</em> particles dealt with the portal interactions.</p><div><hr></div><p>To guide the player towards a specific spot, or to have them looking towards a certain point, I need to get their attention first. That's the idea behind the `<code>captivate</code>` state. </p><p>But thinking about it, I'm not sure I need an explicit state for that. Maybe, I could implement it as part of the `<code>guide_move</code>` and `<code>guide_look</code>` states, with a nested state machine which makes sure that, before guiding the movement or the game of the player, the hint particles have the attention of the player.</p><p>Anyway, I'm going to keep the state separated for now, which also facilitates testing and keeps things simple.</p><div><hr></div><p>Let's say that, one way or the other, the <em>hint</em> particles have a "core", which is a sphere that acts as placeholder volume for the particles. We can assume that, if such sphere is in the player field of view, they're seeing the particles too.</p><p>I'm going to put a sphere in the scene, and try to steer it in front of the player. I also want to add a check to see if the sphere is actually in the camera frustum at a certain instant.</p><div><hr></div><p>Done! Here's the result, while testing in-editor:</p><div id="youtube2-F8Uz2OSDfU0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;F8Uz2OSDfU0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/F8Uz2OSDfU0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Notice that when the sphere is totally visible, is green, when it's partially visible, it's yellow, and when it's completely outside the frustum, is red.</p><p>Here's how I implemented a simple check using the camera frustum planes and the sphere center and radius:</p><pre><code>public enum EFrustumCheckRes {
    totallyInside,
    totallyOutside,
    partiallyInside
}

public static EFrustumCheckRes SphereFrustumCheck(
        Camera rCamera, 
        Vector3 vSphereCenter, 
        float fSphereRadius) {

    bool bIsTotallyInside = true;

    Plane[] planes = GeometryUtility.CalculateFrustumPlanes(rCamera);
    foreach (Plane plane in planes) {
        float fDist = plane.GetDistanceToPoint(vSphereCenter);

        if (fDist &lt; -fSphereRadius) {                    
            return EFrustumCheckRes.totallyOutside;
        }

        if (fDist &lt; fSphereRadius) {
            bIsTotallyInside = false;
        }
    }

    return bIsTotallyInside ? 
        EFrustumCheckRes.totallyInside : 
        EFrustumCheckRes.partiallyInside;
}</code></pre><p>The floating behaviour steers the sphere towards a point at two meters from the player, along their gaze vector. Notice that the sphere goes up and down too, following the "look" direction.</p><p>Basic but effective. The only problem is that, looking down, I can send the sphere below the platforms, which doesn't make sense.</p><p>I'll fix it later projecting to the ground plane when under a minimum height.</p><div><hr></div><h2>2024-12-24 - Guiding towards a specific spot</h2><p>Today, I'm going to take care of the `<code>guide_move</code>` state:</p><ul><li><p>`<code>guide_move</code>`: the particles should guide the player towards a certain spot. </p></li></ul><p>A long time ago, I defined `<code>RTLocation</code>`, a pair of identifiers (zone id and platform id).</p><p>Then, I have the `<code>Hex.EDir</code>` enumeration, which identifies each of the six slices of an hexagon:</p><pre><code>    public struct Hex : System.IEquatable&lt;Hex&gt; {
    // ...
        public enum EDir {
            NE,
            N,
            NW,
            SW,
            S,
            SE,
        };
    // ...
    }</code></pre><p>I think that, in terms of "guiding the player somewhere", I won't need to be more specific than "that slice of that platform". Additionally, I remember passing as parameter an `<code>Hex.EDir</code>` together with a `<code>RTLocation</code>` a few times already.</p><p>This tells me that it's a good idea to introduce a new simple type to indicate a specific platform slice. I could add an optional `<code>Hex.EDir</code>` to `<code>RTLocation</code>`, but that could get confusing.</p><p>So, let's define `<code>RTSpot</code>` (not much imagination with naming, today) pairing an `<code>RTLocation</code>` and a `<code>Hex.EDir</code>`.</p><div><hr></div><p>Quick and easy. For testing/debugging purposes, another thing I often wanted is having the platform slices labelled, so that I can quickly check that things are correctly positioned.</p><p>Let's take care of that too.</p><div><hr></div><p>Platform labelling done! While I was at it, I not only labelled the edges but the platforms themselves, using the string identifiers I set in the level definitions. This way, I will be able to easily recognize the "meaningful" platforms while testing.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jh23!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jh23!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 424w, https://substackcdn.com/image/fetch/$s_!jh23!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 848w, https://substackcdn.com/image/fetch/$s_!jh23!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 1272w, https://substackcdn.com/image/fetch/$s_!jh23!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jh23!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png" width="667" height="667" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:667,&quot;width&quot;:667,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:221426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jh23!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 424w, https://substackcdn.com/image/fetch/$s_!jh23!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 848w, https://substackcdn.com/image/fetch/$s_!jh23!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 1272w, https://substackcdn.com/image/fetch/$s_!jh23!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F67dc267f-8e58-45e0-8b31-1eb1510c43f5_667x667.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now it's time to add the logic to send the <em>hint sphere</em> towards a specific spot. Should be easy enough.</p><div><hr></div><p>I added the zone and platform numeric identifiers to the platform label, so I can easily know what values I need to insert in the testing script if I want to send the sphere to a specific spot.</p><div id="youtube2-ZWvL5z0L6ts" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ZWvL5z0L6ts&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ZWvL5z0L6ts?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>This will need a bit of tuning, but the essence is there! </p><div><hr></div><h2>2024-12-25 - Guiding the player gaze</h2><p>Hey it's Christmas! Maybe I shouldn't work... but come on, just a little!</p><p>So, while to guide the player towards a specific spot I just sent the <em>hint sphere</em> there, just above platform height, I imagine guiding their gaze will be a little more complicated.</p><p>While in the `<code>captivate</code>` state the <em>hint sphere</em> goes to the middle of the player field of view, in this case I should try to send it to the edge of that field of view, suggesting the player to look that way.</p><p>You are probably familiar with this kind of visual hint if you have played any 3D space sim: you get some tiny arrows at the sides of the screen, pointing at enemy ships outside the field of view. If you rotate towards one of these arrows, you end up getting the enemy ship it was pointing at in front of you. </p><p>My case is slightly different, because I don't have to position UI elements at the edge of the screen, but the <em>hint sphere</em> in 3D space. Still, the core idea is the same.</p><div><hr></div><p>I'm not sure if I'll ever need to have the player look up or down, that will be influenced by how much I will go crazy with the level design.</p><p>To limit the scope, I'm considering planar levels, so the <em>hint sphere</em> should basically push the player to rotate left or right (whichever is faster).</p><p>For example, let's assume the player head is vertical, and they're looking towards North, but the current hint is to look towards South-West: the <em>hint sphere</em> should go to the left edge of the player field of view until they're rotated to look towards SW (and the sphere would be at the center of the FOV).</p><p>I can imagine different ways to implement this - let's try the simplest one.</p><div><hr></div><p>I tried implementing the straightforward approach I had in mind, but it doesn't work well. Bug or fundamentally wrong idea? I'll find out tomorrow...</p><div><hr></div><h2>2024-12-26 - Fixing the gaze hint</h2><p>If you've been reading the DevLog for a while, you already know what I'm going to do now: add a bit of debug visualization to understand what's wrong.</p><div><hr></div><p>I added a couple of extra spheres for debugging purposes: one to indicate the point we want the player to look at (the hint target), and another to indicate the position where we want the <em>hint sphere</em> to go, at the edge of the field of view.</p><p>I did an even simpler version of yesterday's code, which only considers the horizontal angle between the player forward direction and the target direction, because I felt that the error was in the calculation of the vertical rotation.</p><p>The resulting code worked pretty well, as can be seen in the following video:</p><div id="youtube2-oKlh8xauRZQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;oKlh8xauRZQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/oKlh8xauRZQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The blue sphere is the target point, and the sepia sphere is the hint point, which the usual <em>hint sphere</em> continuously tries to reach. Notice that the sepia sphere sticks to one side of the frustum when the target sphere is outside of the field of view, and lies on the invisible line between player and blue sphere instead.</p><p>So how does this work? </p><ul><li><p>I get the forward vector of the player and the head position</p></li><li><p>I calculate the target direction, which is the vector from the player head to the target point, normalized</p></li><li><p>I calculate the signed angle between these two direction vectors, which tells me the "yaw" needed to rotate the head towards the target</p></li><li><p>I clamp this needed rotation angle to half the horizontal field of view</p></li><li><p>I rotate the forward vector horizontally by the clamped rotation angle</p></li><li><p>Finally, I calculate the point along the "rotated forward" at the desired distance from the head</p></li></ul><p>Here's the relevant code snippet:</p><pre><code>        public static Vector3 getGuideLookPos(Data rData) {

            Vector3 vHeadPos = BodyInputManagerBhv.i().getHeadPose().position;
            Vector3 vFwd = BodyInputManagerBhv.i().getPlayerFwd();
            Vector3 vUp = Vector3.up;

            Vector3 vTargetDir = (rData.m_lookAtPoint - vHeadPos).normalized;

            float fFwdToTargetAngle = 
                Vector3.SignedAngle(vFwd, vTargetDir, vUp); 

            float fHalfHFOV = CamerasManagerBhv.i().getHorizFOV() * 0.5f;             
            float fLookToTargetClampedAngle =
                Mathf.Clamp(fFwdToTargetAngle, -fHalfHFOV, fHalfHFOV);
                        
            Quaternion qHorizRot =
                Quaternion.AngleAxis(fLookToTargetClampedAngle, vUp);            
            Vector3 vHintDir = qHorizRot * vFwd;                        
            Vector3 vHintPos = vHeadPos + vHintDir * Params.fLOOKAT_DIST;
            
            return vHintPos;
        }</code></pre><p>There are probably better solutions to this kind of problem, the one I implemented is essentially a 2D solution, but considering the initial assumption of planar levels, it should work pretty well.</p><p>Maybe I'll refine it or change it in the future!</p><div><hr></div><h2>2024-12-27 - Hint VFX placeholder</h2><p>I'm tempted to draft the last remaining state of the <em>smart particles</em>, the `<code>show_anim</code>` state which is supposed to teach the player a gesture by displaying an animation to perform.</p><p>But it's been quite some time since I implemented the core features I'm going to need (it's a good moment to go back and read the posts for weeks <a href="https://particularreality.substack.com/p/advanced-prototyping-week-16">16</a>, <a href="https://particularreality.substack.com/p/advanced-prototyping-week-17">17</a>, <a href="https://particularreality.substack.com/p/advanced-prototyping-week-18">18</a> and <a href="https://particularreality.substack.com/p/advanced-prototyping-week-19">19</a>), and I feel like it's going to take more than one day to revisit all that stuff and make good use of it.</p><p>So, considering it's Friday, I'm going to postpone that to next week. So, what I can do instead?</p><div><hr></div><p>It feels a good moment to do a bit of clean-up and refactoring of the work done in the past days.</p><p>I'm going to add a placeholder VFX for the <em>hint particles</em>, and relegate the spheres I've been showing so far to the debug mode.</p><div><hr></div><p>Ok, that went pretty well.</p><p>I put a placeholder VFX (basically the one I did for the <em>fireball</em> a long time ago, but set to yellow) in place of the <em>hint sphere</em> I've been using so far.</p><p>The visualization that was in place until now was relegated to the debug mode, so if I sense that something is off with the VFX placement, I can easily look behind the scenes.</p><div id="youtube2-eekVIixIUE4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;eekVIixIUE4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/eekVIixIUE4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>While I was at it, I drafted a generic script for the "debug only" visualization of a subsystem, factoring out some logic that until now I had repeated multiple times in other debug behaviours.</p><p>I will progressively move to this new system other debug components. By isolating these elements, I will be able to easily strip them out from a build. </p><p>In the Unity scene, I have a separated subtree for the debug nodes. The <em>smart particles</em> debug node has a reference to the "non-debug" manager of that subsystem, which uses as its data source. Only when the debug mode is active, it takes care of providing an alternate visualization (in this case, placing the three spheres we've used yesterday).</p><p>Week 31: done!</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #30]]></title><description><![CDATA[Particles and portals]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-30</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-30</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sun, 17 Nov 2024 15:02:06 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6e89732c-2b02-406c-a5da-e00177a73092_810x810.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As you may have noticed, I had to take a break from the project.</p><p>Unity 6 was officially released, and that prompted me to update <a href="https://t.co/3nKMrpeSvj)">my assets</a> on the store to make them compatible with it, something I had been procrastinating for a while. Additionally, I had to switch to full-time contract work for a while to meet a deadline.</p><p>Well, time to get back on track!</p><div><hr></div><h2>2024-11-11 - Particular portals</h2><p>Last time, while I was wrapping up the post, I mentioned that I had a new idea about a possible  relationship between <em>smart particles</em> and portals.</p><p>So, here it is.</p><p>Let's say that it's the particles that give powers to the basic player skeleton you embody at the beginning. Then it makes sense that it's the particles which open the portal, flowing from the body to the portal, leaving a "bare" skeleton. If you close the portal, they flow back to the body. If you cross the portal, they stick back to you while you cross it. </p><p>I think it makes sense to make the "smart particles" a central element of the game, something which connects the player to the alternate reality of the game world.</p><p>Before doing that, I'm going to do a little design.</p><div><hr></div><p>Let's revisit the portal FSM:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DtpA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DtpA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 424w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 848w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 1272w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DtpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png" width="790" height="400" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:400,&quot;width&quot;:790,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40753,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DtpA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 424w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 848w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 1272w, https://substackcdn.com/image/fetch/$s_!DtpA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb1646b05-4688-4fc9-a9de-df0ddfc7b1e3_790x400.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I think that we can mirror these states with parallel states of our <em>smart particles</em> FSM. The only additional complexity should be that they should also let the player understand what platform is the current target, acting like a selector. And there might no be a valid target, depending on the level layout and on the player position on the current platform.</p><p>I'm going to call these particles acting as a selector <em>probe particles</em>, and I think it makes sense to have them work independently</p><p>I might be wrong, but I feel like the <em>probe particles</em> should be in one of three states:</p><ul><li><p>`<code>invisible</code>`: for the states where the player is not embodying particles at all</p></li><li><p>`<code>idle</code>`: for the cases where when there's no valid target platform - they should "stay back" near the player, in their field of view, ready to jump to select a nearby platform</p></li><li><p>`<code>follow_portal</code>`: for the cases when there's a valid target platform -they should float there, and act accordingly to the portal state</p><ul><li><p>`<code>closed</code>` -&gt; idle, ready to open the portal</p></li><li><p>`<code>opening</code>` -&gt; opening the portal and bringing it to the player</p></li><li><p>`<code>closing</code>` -&gt; closing the portal/sending it back</p></li><li><p>`<code>opened</code>` -&gt; idle, on the opened portal </p></li></ul></li></ul><p>By deferring to the portal state, I should be able to avoid replicating the same logic: basically, the `<code>follow_portal</code>` state tells the probe particles to "do what the portal does", which makes sense as they must be synchronized.</p><p>And the impact of the `<code>follow_portal</code>` state is not limited to the <em>probe particles</em>, because according to the idea I described at the beginning of the post I'm imagining that, when opening a portal, the particles that normally follow the skeleton flow to open the portal, and stick back to the player body when they cross the portal.</p><p>Let's recap:</p><ul><li><p>I need some <em>probe particles</em>, still managed by the <em>smart particles</em> subsystem, but that basically act as target platform selector</p></li><li><p>when a target platform is selected, I can probably drive the whole subsystem on the basis of the portal state. </p><ul><li><p>`<code>closed</code>`: probe particles idle, highlighting the portal</p></li><li><p>`<code>opening</code>`: probe particles "open" the portal together with the body particles which are flowing there</p></li><li><p>`<code>closing</code>`: the body particles flow back, the probe particles go back to idle</p></li><li><p>`<code>opened</code>`: all the particles are keeping the portal active. If the player crosses the portal, the body particles stick back to the player, and the probe particles go back to probing for the next destination</p></li></ul></li></ul><p>I'm definitely getting some details wrong, and that's why, as usual, tomorrow I'm going to start prototyping and figure out the details along the way.</p><div><hr></div><h2>2024-11-12 - Probe particles</h2><p>I'm going to use a few floating balls as placeholders for the <em>probe particles</em> that I want to procedurally animate.</p><p>Time to write some code!</p><div><hr></div><p>Ok, I got to something which is "good enough" for now.</p><p>I instantiated six spheres and wrote a bit of code which animates them using a simple mix of attraction/repulsion forces, with some bounds, randomness and sin waves oscillation thrown in.</p><p>I can specify a target point which attracts them, which will allow me to direct the particles towards a specific platform.</p><p>This is me manually moving the target point with the mouse in the Unity scene view, after a bit of tuning of the parameters involved.</p><div id="youtube2-Zkhi6sySag0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Zkhi6sySag0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Zkhi6sySag0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h2>2024-10-13 - Probing platforms</h2><p>Now that I have a bunch of floating spheres I can send around, I need to send them at the right place.</p><p>As first step, depending on if there's a valid target platform or not,  I'm going to set as attractor point for the probe particles</p><ul><li><p>the center of the target platform</p></li><li><p>the point at the vertical center of the player field of view, at a certain distance</p></li></ul><p>As height, I might go for something like 1.5 mt, or maybe the head height.</p><p>I can infer state from the player and portal state management, so, at least for now, I'm going to work directly at the presentation layer, and drive the probe particles from the `<code>SmartParticlesBhv</code>` script.</p><div><hr></div><div id="youtube2-mb2XiaW8IT0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;mb2XiaW8IT0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/mb2XiaW8IT0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Well, it was quick, but there's definitely a lot more tuning to do.</p><p>Trying with the headset, the motion feel too wide and chaotic.</p><p>The little time I had available for today ran out, so I'm going to get back at it in the future.</p><div><hr></div><h2>2024-10-14 - Portal preopening and postclosing</h2><p>I've been doing some thinking, and to have things flowing the way I'm imagining them, I think I need to introduce a small intermediate step in the portal opening.</p><p>Now, when the player performs the gesture, the portal instantly shows up and comes forward. I want everything pretty fast, to facilitate frenetic action gameplay, but I also want everything to be "readable" and flowing seamlessly. When the portal didn't move, but directly appeared on the player platform, just doing the portal pop-up animation was maybe fine.  Now that it opens on the target platform, and then comes forward on the current platform, well,  it's not great.</p><p>So, I'm going to introduce a separate state where the portal "opens" on the target platform, and only after that phase it will come forward to the player.</p><p>This small delay at the opening should offer an opportunity to make use of the <em>probe particles</em> (which will in someway create the "border" of the portal), and will also filter out some brief but unwanted portal activations, if the gesture gets detected for an instant but it wasn't maybe intentional. Now, you will have to keep doing it for a short while before the portal starts coming towards you.</p><p>Let's get going!</p><div><hr></div><p>I feel a bit lazy today, so instead of renaming the old `<code>opening</code>` state to `<code>comingForward</code>`, and introducing a new `<code>opening</code>`, I just added a `<code>preopening</code>` state and plugged it between `<code>closed</code>` and `<code>opening</code>` . I'll regret it at some point, of course.</p><p>I also added a `<code>postclosing</code>` state, to keep things symmetrical.</p><p>If you remember, I made sure that opening and closing the portal were reversible actions, so that you can abort or resume opening or closing a portal without ugly jumps in the flow. I did the same with `<code>preopening</code>` and `<code>postclosing</code>`, using a new "level" value going between 0-1, which goes up when `<code>preopening</code>` and down when `<code>postclosing</code>`. </p><p>I will skip the diagram of the revised FSM, as it's not that interesting and I'd like to focus on implementing the changes</p><div><hr></div><p>I was going to test my changes, and I realized that I have never implemented a `<code>TestPortalBhv</code>` script as I'm doing lately. I guess it's a good moment as any to take care of that.</p><div><hr></div><p>Ok, I added the new state, prepared the testing script, and also changed a bit the procedural animation. Not worth a video, but I'll try to make one tomorrow!</p><div><hr></div><h2>2024-10-15 - Binding probes and portals</h2><p>I'm going to start binding the renewed portal logic (featuring `<code>preopening</code>` and `<code>postclosing</code>`states) to the <em>probe particles</em> behaviour.</p><p>A couple of days ago, I tested "sending" the particles to the current destination platform. Now, I want to do something with them when I activate the portal.</p><p>For example, let's move the probe particles to the six vertices of the portal hexagon, and have the edge gradually fade in. The idea should be that the probe particles, sent forwards to the target platform, "cut" the space and bring it towards the player.</p><div><hr></div><p>In the following video, there's a significant slow down factor modulating the portal animations. I usually use this trick to make sure the animation logic is sound and flows without jumps, which might be hard at full speed. When everything is convincing and stable at slow speed, I remove such factor.</p><div id="youtube2-47I3yfnUh-c" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;47I3yfnUh-c&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/47I3yfnUh-c?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Ok, it's not great, because the motion of the <em>probes</em> towards the vertices is too unpredictable. I need to have the spheres reach the vertices in a fixed amount of time. I made the portal edge additive and fading (until now, it was solid), a tiny improvement to avoid it appearing and disappearing instantly.</p><p>There's still a lot left undone, but I laid out the foundations: I added the needed transition states to the portal logic (doing a bit of refactoring on the go), and linked the probe particles state management to the portal, so that I can properly animate them.</p><p>The next steps are obvious. Unfortunately, I already know that I won't able to work on the project next week. Fingers crossed for the week after that!</p><div><hr></div><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #29]]></title><description><![CDATA[Smart particles basics]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-29</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-29</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 26 Oct 2024 16:01:28 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9b2de470-689d-455c-9e93-ed7e6f8723e6_790x790.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-10-21 - Smart particles design</h2><p>The <em>smart particles</em> should be one of the two subsystems related to the player avatar (the other is the "skeleton" management discussed in the <a href="https://particularreality.substack.com/p/advanced-prototyping-week-27">week 27</a> and <a href="https://particularreality.substack.com/p/advanced-prototyping-week-28">week 28</a> articles).</p><p>They should act as both an embodiment of the player when in active gameplay, and as a hint system teaching him gestures and other gameplay actions.</p><p>At least, this is what I'm imagining at the moment: the idea is to start somewhere and extend the usage as the gameplay needs change.</p><p>So where do we start? Let's extract from the design done in week 25 a list of things that the <em>smart particles</em> should be able to do:</p><ul><li><p>catch the attention of the player by going into their field of view</p></li><li><p>guide the player towards a specific spot</p></li><li><p>show/suggest a gesture to perform</p></li><li><p>attach to the skeleton avatar and "follow" it</p></li><li><p>detach from the skeleton avatar</p></li></ul><p>Ideally, the <em>smart particles</em> should transition smoothly from one activity to another, morphing between shapes and effects as needed.</p><p>But that's for later, when I'm going to work on presentation and VFX. As usual, instead, I'm going to focus on the logic first, and just implement a quick placeholder visualization.</p><p>I'm still prototyping, after all.</p><div><hr></div><p>I'm going to define some state identifiers, with a brief description of the logic that the state should implement and the input it would need. </p><ul><li><p>`<code>captivate</code>`: the particles should catch the player's eye. A basic way to do it is move towards the centre of the player's field of view, at a certain distance, and idle around there.</p><ul><li><p>input: the player state data</p></li></ul></li><li><p>`<code>guide_move</code>`: the particles should guide the player towards a certain spot. </p><ul><li><p>input: the player state data and the destination spot  (a location and a hexagonal slice identifier should suffice)</p></li></ul></li><li><p>`<code>guide_look</code>`: the particles should invite the player to look towards a certain point</p><ul><li><p>input: the player state data and the destination point</p></li></ul></li><li><p>`<code>show_anim</code>`: the particles should morph to a detached body (or maybe just hands?) which playbacks a captured animation, inviting the player to emulate it</p><ul><li><p>input: where to perform the animation, and what animation to perform</p></li></ul></li><li><p>`<code>skeleton_follow</code>`:  the particles follow the skeleton avatar, which should be the "normal" state during active gameplay</p><ul><li><p>input: the player body data needed to match/follow it</p></li></ul></li><li><p>`<code>skeleton_idle</code>`: the particles don't follow the skeleton avatar, and stay idle wherever they were when they entered the state</p><ul><li><p>input: probably none</p></li></ul></li><li><p>`<code>invisible</code>`: the particles are not shown (startup state)</p></li></ul><p>From what I described, this subsystem sounds rather complex compared to other things I've implemented so far.</p><p>When something is complex, one should try to further divide into manageable subtasks, which can be reasoned about (and maybe implemented) independently. And in fact, each of these states might end up having its own nested state machine.</p><p>One step at a time, I'll get there.</p><div><hr></div><h2>2024-10-22 - Adding a new subsystem</h2><p>As discussed, the <em>smart particles</em> are an important and quite complex element, so I'm going to introduce a new subsystem to take care of them.</p><p>This means, as usual, defining their state management and presentation code. The first step is drafting these elements with empty stubs that I will fill along the way.</p><div><hr></div><p>At what "level" should the smart particles operate and, consequently, where should I put their state? Application or game?</p><p>Like the avatar state, I'm pretty confident they should go in the application state, as I need them working properly outside of actual gameplay, in the limbo/menu zones too.</p><p>I'm not sure yet if I'm going to need some kind of "gameplay only" data override to handle them in-game, but I'll figure that out in the future.</p><div><hr></div><p>Ok then, I defined</p><ul><li><p>`<code>SmartParticles</code>` and `<code>SmartParticlesManager</code>`  (state management)</p></li><li><p>`<code>SmartParticlesBhv</code>` (presentation)</p></li><li><p>`<code>TestSmartParticlesBhv</code>` (testing)</p></li></ul><p>...and I wired things up so that the `<code>SmartParticlesManager</code>` is nested inside of `<code>AppStateManager</code>`.</p><p>This initial setup should be ok: I checked that the game runs and that all data is initialized properly.</p><p>The new subsystem currently does nothing, but also doing nothing is something, in the sense that it's not crashing the game.</p><div><hr></div><h2>2024-10-23 - Energy bars refactoring</h2><p>Yesterday I discussed how the <em>smart particles</em> data had a bigger scope that the gameplay data, because they also need to run outside of gameplay.</p><p>But there's a little thing that bothers me, and it's the energy bars, for which the opposite is true. They are now always visible, while they should be a "gameplay only" feature.</p><p>Let's look again at our table about player management:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wHzH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wHzH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wHzH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png" width="712" height="224" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:224,&quot;width&quot;:712,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15780,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wHzH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!wHzH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F143d9cf1-4779-4031-a1db-786ebff8786e_712x224.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>My initial idea was that health and mana should be represented through the particles following the skeleton.</p><p>I'm not sure exactly how that is going to happen, in terms of visualization, but in terms of structure and update logic, moving the energy bars management to the <em>smart particles</em> code I introduced yesterday sounds like a small step in the right direction. Or, at least, in a less wrong direction.</p><p>So, as a first step in implementing the <em>smart particles</em> subsystem, I'm going to move to `<code>SmartParticlesBhv</code>` (taking care of presentation) the references to the energy bar nodes and their management.</p><p>I will make it so they are only visible in the `<code>skeleton_follow</code>` and `<code>skeleton_idle</code>` states, where they should behave like the particles: following the avatar, or standing still.</p><p>To be able to test this, I will also implement a restricted version of the  `<code>SmartParticlesManager</code>` FSM, limited to these states and inputs:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8SMj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8SMj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 424w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 848w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 1272w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8SMj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png" width="454" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21016,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8SMj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 424w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 848w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 1272w, https://substackcdn.com/image/fetch/$s_!8SMj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5063282-037e-4ee8-8eca-65ae3810e1f2_454x431.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Of course, to be able to test it, I will also add the `<code>startFollowingSkel</code>` and `<code>stopFollowingSkel</code>` input signals as buttons of the `<code>TestSmartParticlesBhv</code>` script.</p><div><hr></div><p>Ok, I did what planned. The only problem is that there's a mismatch between the hand pose that I'm using to position the energy bars and the upper body skeleton. I think it's the usual mismatch that was introduced at some point updating the Movement SDK, and that I already discussed a few times. </p><div id="youtube2-9tc3zNdZjoI" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;9tc3zNdZjoI&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/9tc3zNdZjoI?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>I have two options: I change the positioning of the energy bar to use the (slightly wrong) bone pose as taken from the skeleton, or I try to fix the mismatch once and for all.</p><p>Well, today I won't do either: it's getting late.</p><div><hr></div><h2>2024-10-24 - Attached and detached particles</h2><p>The most obvious step to do today is adding back the "particles avatar" I recently disabled, which in my vision is also part of the <em>smart particles</em> system, and specifically of the two states that I decided to use as a starting point:  `<code>skeleton_follow</code>` and `<code>skeleton_idle</code>`.</p><p>What I need to do should be pretty simple: give control of that "particles avatar" element to the script orchestrating the <em>smart particles</em> presentation (`<code>SmartParticlesBhv</code>`) and</p><ul><li><p>setting it hidden in the `<code>invisible</code>` state</p></li><li><p>update it so that it follows the player in the `<code>skeleton_follow</code>` state</p></li><li><p>keeping it visible, but not updating it with body tracking data, when in the `<code>skeleton_idle</code>` state</p></li></ul><div><hr></div><p>Well, that was quick as expected, for once! And not interesting enough to capture a video, but here's an image.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b6MR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b6MR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 424w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 848w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 1272w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b6MR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png" width="855" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7d011f6-b533-4822-88e8-6c1c13875719_855x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:855,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:384657,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!b6MR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 424w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 848w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 1272w, https://substackcdn.com/image/fetch/$s_!b6MR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d011f6-b533-4822-88e8-6c1c13875719_855x640.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Obviously, the particles intersect with the skeleton mesh, and the result is an ugly mess. </p><p>But that doesn't bother me right now - it's still just a placeholder visualization.</p><p>Instead, there are two more significant things I want to work on.</p><div><hr></div><p>For starters, I currently have more than one `<code>OVRBody</code>` script in the scene, because the quick thing to do, starting from the initial sample, was fetching the body tracking data that way.</p><p>But I did significant work (weeks <a href="https://particularreality.substack.com/p/advanced-prototyping-week-16">16</a>-<a href="https://particularreality.substack.com/p/advanced-prototyping-week-17">17</a>-<a href="https://particularreality.substack.com/p/advanced-prototyping-week-18">18</a>-<a href="https://particularreality.substack.com/p/advanced-prototyping-week-19">19</a> and the extra article <a href="https://particularreality.substack.com/p/deep-dive-into-body-tracking">"Deep dive into body tracking"</a>) to be able to drive a skinned character mesh from body tracking data saved in a custom motion clip, and I took care of that adding an indirection layer between sourcing the body tracking data and animating the skinned mesh.</p><p>Then, I only used that data path for playing saved motion clip, but I could do the same with live data. Why bother?</p><p>Well, as I'm starting to have multiple skinned meshed driven by the same tracking data, I can easily avoid some useless data duplication and processing. But a possible improvement in performances is not the primary factor: it's more about decoupling subsystems and keeping the data flow tidy.</p><p>It makes sense to start separating and keeping minimal the parts that take input data, and that use a specific API to do so. Today such data comes from the `<code>OVRBody</code>` script,  tomorrow it might be something provided by Pico, Sony, Apple, Valve...</p><p>I know that I'm just prototyping, but I guess today I feel positive about the distant future.</p><div><hr></div><p>Done! Now I have a single instance of the body tracking data, and I use that to control two separate skinned character meshes, the "skeleton" and the "particles".</p><p>And I have an idea for a third one, but we'll discuss that tomorrow.</p><div><hr></div><p>What's the second think I wanted to fix?</p><p>There's a problem with the "particles" character that I've been ignoring so far: sometimes, when I move/rotate my head while testing, the VFX particles disappear for no apparent reason. When that happens, moving back causes them to become visible again. </p><p>Let's see if I manage to remove this annoying glitch.</p><div><hr></div><p>Ok, after a brief search, I found about a culling parameter that one can set in the VFX graph to avoid processing "out of bounds" particles.</p><p>There were some fixed bounds set, centered on the origin of the world. So, when I moved outside of those bounds, the particles disappeared.</p><p>To try a fix, I changed the center of the bounds so that it follows the main camera.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0tae!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0tae!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 424w, https://substackcdn.com/image/fetch/$s_!0tae!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 848w, https://substackcdn.com/image/fetch/$s_!0tae!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 1272w, https://substackcdn.com/image/fetch/$s_!0tae!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0tae!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png" width="985" height="520" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:520,&quot;width&quot;:985,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84776,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0tae!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 424w, https://substackcdn.com/image/fetch/$s_!0tae!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 848w, https://substackcdn.com/image/fetch/$s_!0tae!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 1272w, https://substackcdn.com/image/fetch/$s_!0tae!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ea404dc-9030-4b0e-a7a4-332b74472625_985x520.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Well, it works. It's not great because I should pass in as parameter the position to use as center, and not make it dependent on the camera. But it's super late and I'm happy enough of having found the cause of the problem - a better fix won't take much time.</p><div><hr></div><h2>2024-10-25 - Yet another character instance</h2><p>While implementing the body tracking data "routing" to multiple avatars ("skeleton" and "particles"), yesterday, I wondered if that could also be useful for other things.</p><p>Well, there was an obvious one: when the player is using a portal, I can have another avatar instance placed on the "destination" platform, so that the part of player avatar which is going through the portal can be properly seen.</p><p>Let's explain it a little better, starting from the beginning.</p><div><hr></div><p>Right now, this is what happens when you enter a portal: parts of your avatar go behind the portal mesh which is showing the texture rendered by the "portal camera" as discussed in the past:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rll8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rll8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 424w, https://substackcdn.com/image/fetch/$s_!rll8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 848w, https://substackcdn.com/image/fetch/$s_!rll8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!rll8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rll8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png" width="1080" height="1080" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e753407c-9145-4d40-9de1-869bd737222c_1080x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1080,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:571470,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rll8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 424w, https://substackcdn.com/image/fetch/$s_!rll8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 848w, https://substackcdn.com/image/fetch/$s_!rll8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!rll8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe753407c-9145-4d40-9de1-869bd737222c_1080x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I didn't mention this until now, but honestly it's something that has been bothering me a little since I first implemented the most basic form of portal, during the PoC prototyping days.</p><p>Now, if I put a clone of the player avatar positioned on the destination platform, at the same platform-relative position, I should be able to see the parts which are now hidden by the portal. More precisely, I'm going to see the copy of those parts, coming from the clone.</p><p>Let's give it a try.</p><p></p><div><hr></div><p>Ok, getting the basics working was relatively easy. I just had to create the copy, animate it with the same body tracking data, like I did yesterday, and apply the proper offset. Let's see where I'm at:</p><div id="youtube2-xYsdlTnAehU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;xYsdlTnAehU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/xYsdlTnAehU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The video should show "the trick" pretty well. To test more effectively, I disabled the "particles avatar" and configured the "cloned" character as <em>activated</em> (meaning that they have the brighter material), which makes it obvious what mesh you're actually seeing when throwing your hands into the portal.</p><p>While I'm sure one could fake this in terms of rendering, doing some work on the shaders involved, there are advantages to the na&#239;ve approach I'm using. For example, is easy to have a slightly different mesh for the "other side" of the portal, or to make it interactive.</p><p>This needs some thinking in terms of gameplay, but I'm not far from being able to punch an enemy through a portal, for example.</p><p>Anyway, I'm not done yet. I need to</p><ul><li><p>make the cloned character visible only when it's behind the portal</p></li><li><p>set the cloned character layer so that it works properly with the zone crossing, when opening a bridge portal</p></li><li><p>set the cloned character to the same state of the original (activated or not)</p></li><li><p>re-enable the particles avatar too</p></li></ul><div><hr></div><p>All done! Here's me putting my hands into two portals, a "normal" and a "bridge" one. </p><div id="youtube2-iAITD8ZOECE" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;iAITD8ZOECE&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/iAITD8ZOECE?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Of course, the difference is a bit less obvious now that I'm not using the "activated" material for the avatar on the other side of the portal. Still, the particles are only on the primary avatar, and the health/mana bar too. That's why the moment one gets teleported, the particles appear back (instantly) on the arms - that's when the cloned avatar disappear, and you're only seeing the primary one.</p><div><hr></div><p>While testing, I remembered that didn't bother fixing the health/mana bar that I poorly attached to the player on Wednesday.</p><p>I also realized that I have an opportunity to link the <em>smart particles</em> to the portal management in a more sensible way (in terms of design and game world consistency).</p><p>But... I'm going to save this discussion for next time! </p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #28]]></title><description><![CDATA[Improving portals (again)]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-28</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-28</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 05 Oct 2024 17:00:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/6bfd2e83-6ba1-42df-a466-3ba6fdbf92f3_1080x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-09-30 - Minor clean-up day</h2><p>In the conclusion of my previous post, I wrote that I felt the need to do a bit of clean-up before proceeding with the <em>smart particles</em>.</p><p>So for today I'm just going to do some minor restructuring of the recent code, and of the Unity scene.</p><div><hr></div><p>I did many minor improvements here and there, nothing worth discussing at length.</p><p>I also removed some old scripts which weren't useful anymore, and removed a few test nodes from the Unity scene.</p><p>I feel like it's always worth doing a bit of refactoring after getting something working, resisting the temptation to just move forward. The project stays more manageable and you exploit the fact that the reasoning behind it it's still fresh: you take the opportunity to make the code easier to understand for when "future you" is going to go back to make some changes, having forgotten lots of details in the meantime.</p><p>Is everything perfect after one afternoon of tidying up? Not by far. There's still a lot to do, especially about the portal management, but you gotta start somewhere.</p><div><hr></div><h2>2024-10-01 - Portal subsystem refactoring</h2><p>Yesterday I decided to skip refactor and clean-up for things related to the portal system. I noticed things that weren't in the right place, but the changes needed didn't feel "minor", so they called for a dedicated day. Which is today.</p><div><hr></div><p>While working with the enemies and the avatar, I have structured the code into a nice set of elements which deal with different responsibilities of an entity.</p><p>I have the concepts of <em>state provider</em> and <em>state presenter</em>, and around that I also built the test behaviours which let me test the visualization via inspector. Additionally, I have separated the "debug visualization" operations.</p><p>For example, in the case of an <em>enemy</em>, I have</p><ul><li><p>In the <em>state management</em> layer</p><ul><li><p>`<code>EnemyStateManager</code>` and the related data types (`<code>Enemy.Data</code>`, `<code>Enemy.EState</code>`...)</p></li></ul></li><li><p>In the <em>presentation</em> layer</p><ul><li><p>`<code>EnemyBhv</code>` handling the presentation of an enemy, using other sub-components as needed</p></li><li><p>`<code>DebugEnemyBhv</code>`  handling the presentation of an enemy in debug mode (e.g. showing the health bars)</p></li><li><p>`<code>TestEnemyBhv</code>` allowing the test of the presentation via inspector</p></li></ul></li></ul><p>I did the portal subsystem before getting to this architectural organization, so it's a bit messier than this. Refactoring time! </p><div><hr></div><p>I moved some transient data from the `<code>PlayerStateManager</code>` to the `<code>PortalStateManager</code>`, which was long overdue.</p><p>After making `<code>PortalStateManager</code>` self-sufficient in terms of the information needed to drive the portal presentation behaviour, I updated `<code>PortalBhv</code>` so that is a  <em>state presenter</em> for the portal entity. Along the way, I removed a `<code>PortalManagerBhv</code>` which had no reason to exist anymore (I didn't write it, but yesterday, doing a similar process, I got rid of `<code>PlayerManagerBhv</code>`).</p><p>Abstractions help in keeping things manageable when they make sense, but sometimes one introduces some unnecessary ones, that provide no real semantic value. So, good riddance!</p><p>Tomorrow I'll continue the refactoring work dealing specifically with the portal surface rendering.</p><div><hr></div><h2>2024-10-02 - From PortalRenderer to CameraManager</h2><p>Something that has always bothered me a little is that in the `<code>PortalRendererBhv</code>` I'm doing things which are not only related to portals. What am I doing that doesn't fit? Well, I'm changing the cameras settings, both in terms of culling layers and of rendering background.</p><p>I did it there because `<code>PortalRendererBhv</code>` was where I took care of positioning the "portal camera", so it was quick to do there other "more global" things too. Quick, but dirty.</p><p>Today I'd like to revisit `<code>PortalBhv</code>` and `<code>PortalRendererBhv</code>` and shuffle things a bit.</p><div><hr></div><p>Ok, it wasn't easy, but I did a step in the right direction.</p><p>Unfortunately I had forgotten many details about this part of the code (which probably is one of the most complicated so far), so it took me a while to figure out what to do.</p><p>Until now, `<code>PortalRendererBhv</code>` was operated by `<code>PortalBhv</code>`, which had a direct reference to it.</p><p>`<code>PortalBhv</code>` passed the portal surface `<code>MeshRenderer</code>` to `<code>PortalRendererBhv</code>`, which took care of updating the material (setting the render textures and changing the proximity level parameter, for example).</p><p>Then, `<code>PortalRendererBhv</code>` also took care of managing the cameras, and not only the "portal cameras" that update the render textures, but also the main cameras (and the skyboxes).</p><p>In today's refactoring, I changed `<code>PortalRendererBhv</code>` to `<code>CamerasManagerBhv</code>`, now working as an independent subsystem. The part which worked on the portal surface `<code>MeshRenderer</code>`, updating its material, got moved into the main `<code>PortalBhv</code>`, where it belongs, as it's working on a child component of the portal node managed by `<code>PortalBhv</code>`.</p><p>`<code>PortalBhv</code>`, during its initialization, queries `<code>CamerasManagerBhv</code>` to get references to the render textures that it must assign to its surface `<code>Material</code>`. </p><p>There's a definite architectural improvement, because `<code>CamerasManagerBhv</code>` doesn't directly modify the portal, but just the cameras. `<code>PortalBhv</code>` now handles the `<code>Material</code>` and remains in charge of configuring the transforms which indicate entrance and exit of the portal, which `<code>CamerasManagerBhv</code>` uses to update the portal cameras poses (and, in turn, the render textures which get displayed on the portal surface).</p><p>Part of me wants to generalize  all this so that the "portal cameras" are instantiated and moved directly by `<code>PortalBhv</code>`, but that would be worth it only if many portals could be opened at the same time, each with its cameras. I'm designing the game avoiding that possibility (which would definitely kill the framerate), so I'm going to contain myself and don't do it. At least for now!</p><p>Talking about moving portal cameras, I think I know what I'm going to do tomorrow...</p><div><hr></div><h2>2024-10-03 - Portal camera movement</h2><p>Last week, on <a href="https://particularreality.substack.com/i/149504341/mirrored-character-placement">2024-09-25</a>, I wrote:</p><blockquote><p>You might notice a problem, which I already spotted a long time ago but haven't faced yet. The "content" shown in the "distant" bridge portal is wrong. It doesn't feel like the portal is coming forward, but like it is a strange window into another space, which feels right only when completely opened. The effect is even worse in the headset, because of stereoscopy: you see the "close" mirrored avatar, but confined in the "far" hexagon, and your brain and your eyes complain.</p></blockquote><p>Considering that yesterday I "refreshed" my understanding of the portal camera management, it should be a good moment to fix this. </p><div><hr></div><p>Ok, I had to take pen and paper for a minute, but I did what I wanted.</p><p>The difference is subtle for the normal portals, but quite meaningful for the "bridge" portals which stay opened at a distance.</p><p>Let's try and see what I changed with a diagram, showing "before and after" the fix...</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gKnD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gKnD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 424w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 848w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gKnD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png" width="1280" height="1088" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1088,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67058,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gKnD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 424w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 848w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!gKnD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd92d6f-c5f0-4def-8b87-621b4816733d_1280x1088.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The yellow camera is attached to the player. The other is the "portal camera", which draws to the render texture which is then displayed on the portal surface. The portal starts appearing from the destination platform, and while scaling up to full size, it comes forward to the player platform. In the case of <em>bridge portals</em>, there's no scaling: the "distant" portal is always visible at full scale, and only comes forward.</p><p>The positioning of the portal camera is calculated on the basis of the player camera and of the current portal. I'm simplifying a bit, because the real setup involves more cameras, to take care of stereoscopy: right eye and left eye see two different textures rendered on the portal surface.</p><p>Anyway, in the upper part of the diagram (before the fix), the portal camera was always in the "destination" position, even when the portal was distant, which caused the problem described at the beginning, because you saw something that was supposed to be far as if it was "near" (cropped in the portal area).</p><p>Now, instead, I animated the portal camera so that, at the beginning of the animation, it matches the position of the player camera, and at the end, it's in the correct position to show the viewpoint as if the player were on the destination platform. Basically, the camera moves together with the portal, but in the opposite direction.</p><p>How does the result look? Let's see it with the portal opening and closing speed slowed down by a factor of ten.</p><div id="youtube2-NoIX5TeHZYw" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;NoIX5TeHZYw&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/NoIX5TeHZYw?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>And here's a short in-editor capture that shows what happens behind the scenes:</p><div id="youtube2-dYHQMjiFLfo" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;dYHQMjiFLfo&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/dYHQMjiFLfo?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>I added a red cube to make it easier to understand. In the bottom right corner, there's the portal camera render preview. In the scene view, you can see how the camera node moves together with the player, and then animates to the destination. When the player crosses the portal and gets teleported, the player camera matches what was shown by the portal camera and the magic trick is complete.</p><div><hr></div><p>I like the resulting effect, but a little problem showed up: with this new camera behaviour, when there's a bridge portal that can be crossed both sides, the "cover" for the other sides is shown in the portal. Something that I had not anticipated, but that is completely correct considering how they are currently implemented and positioned. Here's a short clip showing the problem:</p><div id="youtube2-1TH7GmEAS5A" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;1TH7GmEAS5A&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/1TH7GmEAS5A?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>It doesn't show in the clips, but the new effect doesn't really work well with the portal scaling of the "non-bridge" portals. So I decided to disable it, at least for now. I'm going to do a different "appearance" animation. Between this and the need to fix the "covers", I guess I'll be pretty busy tomorrow.</p><div><hr></div><h2>2024-10-04 - Refactoring portal covers</h2><p>Ok then, time to refactor the <em>portal covers</em> and fix the issue shown yesterday.</p><p>As first step, I'm going to rename a few things currently named "bridge portal" to "portal cover", because that's what it currently is: the bridge portal themselves are handled like all the other portals, with a couple variations handled in place for "bridge" and "mirror" portals.</p><p>So the red/green elements that show where a bridge portal is, and disappear when they're ready to open, are basically "covers" in front of the actual portal.</p><div><hr></div><p>Well, that was a lot of renaming, but at least now the concept of <em>bridge portal</em> (a portal connecting two zones of the game world) and that of <em>portal cover</em> (an element visualizing if a bridge portal is available and the target zone is loaded) are well separated.</p><p>And now, the real work begins...</p><div><hr></div><p>I started by aligning the "portal cover" with the other entities I recently worked on, so I restructured `<code>PortalCoverBhv</code>` as a <em>state presenter</em> and added a `<code>TestPortalCover</code>` behaviour that allows me to test via inspector the different states and the animations of the component.</p><p>This was also a good way to refresh my memory about how this thing worked and how it interacted with the gameplay simulation logic.</p><p>It was also a good opportunity to change the obnoxious red/green colors that I had used as test color to indicate the locked/unlocked state. Still temporary colors, but maybe they won't ruin all my screenshots and videos anymore.  </p><div><hr></div><p>About the actual modification, I think the easiest fix is setting the portal cover to the same state that I set when the portal is opened (so, when the cover becomes invisible). In theory, I should only need to set this state on the `<code>PortalCoverManager</code>` that handles "the other side" of the bridge portal... let's try!</p><div><hr></div><p>Well, it's not pretty, but did the job:</p><pre><code>            if (portalDstLoc.HasValue 
                    &amp;&amp; portalDstLoc.Value.m_zoneId != playerLoc.m_zoneId) {

                PortalCoverStateManager rOtherSidePortalCover =
                    getWorldStateManager()
                    .getPlatformStateManager(portalDstLoc.Value)
                    .getPortalCoverStateManager();                
                if (rOtherSidePortalCover != null) {
                    rOtherSidePortalCover.setLocked(false);
                    rOtherSidePortalCover.setLoadingState(true, true);
                }

            }</code></pre><p>So basically "if there's currently a portal bringing to a platform in a zone which is not the player zone, try to fetch the `<code>PortalCoverStateManager</code>` of the destination platform. If you find one, set it in a way that makes the cover disappear".</p><p>The `<code>setLoadingState()</code>` call is awful. I don't remember why I did it that way and not using two enumerations and not two booleans, like I usually do to keep things more readable. Well, that's a change for another day...</p><p>End of week video capture!</p><div id="youtube2-lir4LLuioD4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;lir4LLuioD4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/lir4LLuioD4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Ok then, thanks for reading yet another entry of the <em>Particular Reality</em> DevLog.</p><p>I managed to post each week for three consecutive weeks, but now I'm afraid I'm going to take a break for a week or two, and take care of a few things on my backlog.</p><p>As soon as I can, I'll be back at work, and I think I'm going to finally start development of the <em>smart particles</em> subsystem mentioned in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-25">week 25</a>.</p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #27]]></title><description><![CDATA[Leaving the limbo]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-27</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-27</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 28 Sep 2024 17:00:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b2e52504-deda-42df-ac63-0b99596a725d_916x916.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-09-23 - Measuring mirror proximity</h2><p>Let's continue from where we stopped. I need to</p><ul><li><p>detect if the player is touching/approaching the "mirrored" skeleton</p></li><li><p>implement the special "mirror portal" logic</p></li></ul><p>I would also like to do some kind of VFX on the portal surface, to make it react to the player's body, but that's not a priority at this stage.</p><p>I think the most natural thing to do is reaching into the mirror with the hands, but somebody could try with the head too. I mean, I guess they could also use the elbows.</p><p>I'd like to have an indication of the minimum current distance between body and mirror, so that I can modulate some effect to ease into the "threshold" point where the transition happens.</p><p>Basically, the problem reduces to calculating the distance between a point (a body part) and a plane (the mirror). Once I can do that, doing it for multiple elements and considering the minimum is trivial.</p><div><hr></div><p>I did a quick test considering head and hands, and showing the distance values in the debug panel.</p><div id="youtube2-RsRYA2VtVCo" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;RsRYA2VtVCo&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/RsRYA2VtVCo?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>The Unity API offers a `<code>Plane</code>` class with a `<code>GetDistanceToPoint</code>` method, so I just had to initialize a `<code>Plane</code>` with the mirror position/normal, and fetch the head/hands position from the body input data.</p><p>For example, that's the code for the head distance:</p><pre><code>Plane p = new Plane(m_vMirrorNormal, m_rMirrorTr.position);
Vector3 vHeadPos = BodyInputManagerBhv.i().getHeadPose().position;
float fHeadDistance = p.GetDistanceToPoint(vHeadPos);</code></pre><p>Of course, I could do better, considering the actual meshes geometries, but that sounds like something for a future polishing phase, and after having the final 3D model so that I can fine-tune the threshold distances.</p><div><hr></div><h2>2024-09-24 - Mirror proximity indicator</h2><p>I'm going to bring the test done yesterday to a state which I can actually use in the game logic.</p><p>I'm going to define two values which I will be able to easily tune:</p><ul><li><p>`<code>farMirrorActivation</code>`</p></li><li><p>`<code>nearMirrorActivation</code>`</p></li></ul><p>I will compare the minimum distance of those measured from body to mirror with this range, and derive a 0-1 value from there.</p><p>I will then use this value to modulate a "pre-transition" effect, and finally, when the value reaches 1, I will activate the "mirror portal" and teleport the player to the <em>menuZone</em>.</p><div><hr></div><p>Calculating the 0-1 value was trivial, and I also did some restructuring so that the processing is done in the "gameplay logic" part of the code.</p><p>Yesterday, for testing purpose, I cheated a bit and did it accessing the body tracking directly and not from the "state management" layer.</p><p>That took some time, as I also ran into a bug and into something I had only partially implemented and had to be completed before usage. So, that's it for today!</p><div><hr></div><h2>2024-09-25 - Mirrored character placement</h2><p>I have a mirror proximity indicator which I can use to activate the teleport to the menu zone, and I have the "mirrored" character.</p><p>But I don't have the "mirror portal" yet.</p><p>To get started, I'd like to use the bridge portal system I already have implemented, and just change some details about how the portal opens or activates the teleportation of the player.</p><p>If I manually open the bridge portal to the limbo zone, of course, the mirrored character becomes covered by the portal: to focus on the "mirroring" I temporarily placed it on the limbo platform, together with the player avatar.</p><p>It's finally time to move it to the destination platform, in the menu zone, so that it can show up only inside the portal.</p><div><hr></div><p>Ok, I added a bit of logic to fetch the menu platform placement and used it to adjust the position of the mirrored character.</p><p>I also did the layer setup that I usually do to properly cull objects depending on the camera which is drawing them (the "normal" camera, or the camera for the portal render texture).</p><p>I also visualized the "proximity" index by setting the screen fade opacity, just for testing.</p><div id="youtube2-wIpOp9GmGcs" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;wIpOp9GmGcs&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/wIpOp9GmGcs?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>You might notice a problem, which I already spotted a long time ago but haven't faced yet. </p><p>The "content" shown in the "distant" bridge portal is wrong. It doesn't feel like the portal is coming forward, but like it is a strange window into another space, which feels right only when completely opened. The effect is even worse in the headset, because of stereoscopy: you see the "close" mirrored avatar, but confined in the "far" hexagon, and your brain and your eyes complain.</p><p>In my next iteration on the portal rendering, I will get it fixed. I'm tempted to try and do it now, but I don't want to wander off the path.</p><p>...and the Meta Connect keynote is about to start, so that's it for today!</p><div><hr></div><h2>2024-09-26 - Proximity indicator VFX</h2><p>Well, the Meta Connect news about the Orion AR glasses prototype were pretty cool. On the VR/gaming side, nothing spectacular, but some good little things. And I think the Quest 3S has potential to further expand the VR user-base, which of course is a good thing for the medium. And more potential players for _Particular Reality_, yay! </p><p>So, let's get back at work, or it will never ship!</p><div><hr></div><p>We have the mirrored character properly positioned, we have an indicator of the player proximity to the "mirror portal", and we have an avatar ready to change visualization mode.</p><p>Two things are still missing:</p><ul><li><p>some kind of effect to bind to the proximity indicator: yesterday I used the opacity of the white quad for quick testing, but that's reserved for the mode transition when going from limbo to menu (and changing avatar)</p></li><li><p>some custom logic for the special "mirror" portal behaviour: it must open without requiring the opening gesture, and the teleport should happen depending on the proximity indicator and not the portal crossing</p></li></ul><p>Let's proceed in order.</p><p>So, what kind of effect do I want to activate depending on the proximity indicator?<br>I think that it should be something on the portal itself. The player should understand that by reaching into it something might happen.</p><div><hr></div><p>I don't feel very creative in terms of VFX today, so I went for something super-simple that I will definitely replace at some point.</p><p>I passed in a `<code>float</code>` value to the shader that draws the portal contents, and used the value to fade between greyscale and normal color. </p><div id="youtube2-bitrpYie6eQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;bitrpYie6eQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/bitrpYie6eQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Simple enough to take the chance and throw in a fragment shader snippet:</p><pre><code>float lum = dot(col.rgb, float3(0.2126, 0.7152, 0.0722));
float3 gray = float3(lum, lum, lum);
float3 c = lerp(gray, col.rgb, _ActivationLevel);
col.rgb = c;</code></pre><p>I calculate the <em>luminance</em> of the color, and use it to define an appropriate gray value.</p><p>Then, I linearly interpolate between the "normal" color and the grey depending on the `<code>_ActivationLevel</code>` passed from outside.</p><div><hr></div><p>If you're wondering about how the luminance calculation works, and what does the dot product have to do with it, the answer is simple enough to include here. </p><p>The luminance is the "perceived brightness" of a color, and can be calculated by modulating the values of its RGB channels for some constants.</p><p>For a "linear" RGB color `<code>c</code>`, the luminance `<code>lum</code>` can be calculated like this:</p><pre><code>lum = (0.2126 * c.r) + (0.7152 * c.g) + (0.0722 * c.b)</code></pre><p>If you remember the dot product formula, you already understood why that works.</p><p>If you don't, well, for two 3D vectors `<code>a</code>` and `<code>b</code>`:</p><pre><code>dot(a, b) == (a.x * b.x) + (a.y * b.y) + (a.z * b.z)</code></pre><p>So, it's exactly the computation we need for the luminance, with `<code>a = (0.2126, 0.7152, 0.0722)</code>` and `<code>b = c.rgb</code>`.</p><div><hr></div><h2>2024-09-27 - Mirror portal activation</h2><p><br>It's finally time to face the core challenge, which is implementing custom logic for the "mirror portal".</p><div><hr></div><p>I already have a (bridge) portal configured to bring the player to the <em>menuZone</em>. </p><p>Right now, all the portals work like this:</p><ul><li><p>when the player is in a suitable position of the platform and performs the "opening" gesture, they appear/open</p></li><li><p>when they are crossed, the player is teleported to the destination location</p></li></ul><p>So, the player positioning check, and the teleportation to the destination, also apply to the special "mirror" portal.</p><p>Instead, I need to provide alternatives for:</p><ul><li><p>opening the portal (it should open automatically)</p></li><li><p>activating the player teleportation (it should activate when the proximity indicator reaches value 1)</p></li></ul><div><hr></div><p>As first step, I'm going to be shameless and hardcode the special logic behind a couple of conditionals. I have a very simple way to check if the portal available on the current platform is the "mirror" portal, which is just checking if the source or target zone of the portal is the <em>limboZone</em>.</p><div><hr></div><p>I added the logic and it worked but... not everything went smoothly as expected! When I get to the destination platform, I find myself in a state which is detected as crossing back from the menu to the limbo with the "normal" bridge portal logic. I didn't think about it, but it makes sense if I consider how the normal "portal crossing" detection works.</p><p>Considering that I'm changing the way one exits the menu, I can remove that bridge portal from the <em>menuZone</em> definition and just avoid the problem.</p><div><hr></div><p>Cool, it works! Time to test a Quest build in standalone mode and see if there are any problems that don't show up testing in-editor via Meta Link.</p><div><hr></div><p>Guess what? It didn't work well! I think it's something related to the calibration offset... let's try to figure it out.</p><div><hr></div><p>Ok, after a bit of debugging I managed to get it working consistently. There were a couple of things about how the reflection was calculated, and a world/local position I got wrong but worked anyway if there was no calibration offset, which explains what happened.</p><p>Let's see a capture on device - I disabled the passthrough for the capture, because the room is a mess, but that works too.</p><div id="youtube2-aNv2vyvqMNU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;aNv2vyvqMNU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/aNv2vyvqMNU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>And another week is gone! I'm quite happy about the progress, but the code quality isn't great because I rushed things a bit. I wanted to see it done before the week ended, and I did it, but I took some shortcuts.</p><p>Next week I'm going to do a bit of refactoring and clean-up, before proceeding with the <em>smart particles</em>.</p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #26]]></title><description><![CDATA[Revisiting the player avatar]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-26</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-26</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 21 Sep 2024 17:00:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/8ebf007d-6817-4e00-a14e-9696d9789037_1560x1560.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-09-16 - Avatar modes</h2><p>In <a href="https://particularreality.substack.com/p/advanced-prototyping-week-25">week 25</a>, I did a lot of thinking and wrote down many ideas. Now it's time to work on making those ideas part of the game.</p><p>Before starting to actually write code, let's prepare a list of the things I need to do, at a surface level. </p><p>So, I need to:</p><ul><li><p>improve significantly the player avatar management</p></li><li><p>make the special "mirror portal" between limbo and menu zone</p></li><li><p>make the "white flash" transition between limbo and menu zone</p></li><li><p>implement the dynamic "hint system" with particles pushing the player towards a specific spot, and showing what gestures to do</p></li><li><p>implement the navigation between contexts and the application flow shown by the FSM diagram at the end of the article</p></li></ul><p>That's a lot of work, but as long as I manage to reasonably divide it into tasks to be faced one at a time, and put together at the end, it should be fine.</p><p>I'm going to start from the avatar.</p><div><hr></div><p>I think that I reached a good synthesis of what I need to do about the avatar in a table I showed last week:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rKhB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rKhB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png" width="712" height="224" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:224,&quot;width&quot;:712,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15780,&quot;alt&quot;:&quot;table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image.&quot;,&quot;title&quot;:&quot;table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image.&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image." title="table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image." srcset="https://substackcdn.com/image/fetch/$s_!rKhB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Basically, the  `<code>Context</code>` and `<code>World</code>` tell when a certain avatar/particles setup operates and that will come into play later.</p><p>Now, we have to work on the `<code>Avatar</code>` and `<code>Particles</code>` so that they can be in the different required configurations, each with its behaviour.</p><p>Even if they will ultimately work together, we should be able to work on the two subsystems separately.</p><p>I feel like we need a name to distinguish "these" particles from the others. Let's go with "smart particles", as they will actively do things and not just be a cosmetic or passive element of the game.</p><p>So, two separate subsystems, that will work together, because normally the <em>smart particles</em> will follow the avatar.</p><p>Let's start from the avatar management, that I already have implemented in a basic form, and that I will now refactor. The table indicates three possible avatar modes, so let's define an identifier for each mode, to keep things short and sweet:</p><ul><li><p>Semi-transparent hands: `<code>limboHands</code>`</p></li><li><p>Skeleton (inactive): `<code>inactiveSkeleton</code>`</p></li><li><p>Skeleton (active): `<code>activeSkeleton</code>`</p></li></ul><p>According to what I defined in week 25, the player can't go directly from gameplay to limbo, but must always go through the <em>menu</em> zone. This means that we don't need to define a direct transition between `<code>limboHands</code>` and `<code>activeSkeleton</code>`: the avatar must always go through `<code>inactiveSkeleton</code>`, like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o-29!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o-29!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 424w, https://substackcdn.com/image/fetch/$s_!o-29!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 848w, https://substackcdn.com/image/fetch/$s_!o-29!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 1272w, https://substackcdn.com/image/fetch/$s_!o-29!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o-29!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png" width="428" height="365" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:365,&quot;width&quot;:428,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17703,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o-29!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 424w, https://substackcdn.com/image/fetch/$s_!o-29!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 848w, https://substackcdn.com/image/fetch/$s_!o-29!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 1272w, https://substackcdn.com/image/fetch/$s_!o-29!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdfeb8cea-5983-49ef-9252-4aa4b7d39d01_428x365.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The kind of handling I need to implement to take care of these state changes is easier than most things we've been dealing in the past weeks.</p><p>Why? I don't need to think about state persistence, possibility to rewind gameplay etc.</p><p>I'm basically dealing with a state element which is not part of the gameplay state, but of the application state. Something which is intrinsically transient and won't be stored in a savegame.</p><p>This doesn't mean that I will be <em>totally</em> sloppy, and I'm still going to define a simple FSM which supports non-instant transitions. That will come useful when I need to orchestrate the state change of different elements when implementing the global application flow.</p><div><hr></div><p>Let's complete the day with a really NOT interesting FSM diagram which makes the transition states explicit and asymmetric, so that by reading the FSM state is immediately obvious not only if a transition is happening, but also which way it's going.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HzLg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HzLg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 424w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 848w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 1272w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HzLg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png" width="800" height="657" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:657,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:43129,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HzLg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 424w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 848w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 1272w, https://substackcdn.com/image/fetch/$s_!HzLg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2708c0af-7044-4327-b6e0-7e877ba047be_800x657.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This won't force me to be fancy in the presentation layer, but if I decide to be, the logic will be there already.</p><p></p><h2>2024-09-17 - Avatar FSM implementation</h2><p>Time to do some actual implementation work.</p><div><hr></div><p>As first step, I renamed what I've been calling `<code>PlayerAvatar</code>` until today to `<code>PlayerBody</code>`, as it was pretty much a description of the head/hands and bones of the player body, as used not only to drive the avatar but in the motion clip capture and playback.</p><p>So it was not very appropriate anyway, and I can now use that same name in the right context: the avatar management.</p><div><hr></div><p>I went and implemented the FSM I designed yesterday. As I'm doing recently, I also implemented a `<code>TestPlayerAvatarBhv</code>` to test the state changes via inspector.</p><p>There's a bit of boilerplate in the code, which bothers me a little, but I feel like the system is becoming quite robust. Everything worked correctly on the first try.</p><p>While implementing the FSM, I noticed that there was quite some repetition: basically, the states are of two kinds, the three "core" which represent the normal visualization of a certain mode, and the "transition" states which will be presented with some kind of animation between the modes.</p><p>The execution exits the "core" states when the right input signal comes, while the "transition" states automatically send to another state after a specific delay.</p><p>We're talking about very quick animations, and considering that they should be activated when navigating through contexts as previously explained, they shouldn't need to be interruptible or reversible, which keeps things simple.</p><p>Let's revisit the diagram - I recently learned how to apply colors to states in <em>Mermaid</em> diagrams, and hey, I'm going to make use of that.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mIdr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mIdr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 424w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 848w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 1272w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mIdr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png" width="800" height="657" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:657,&quot;width&quot;:800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:48020,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mIdr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 424w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 848w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 1272w, https://substackcdn.com/image/fetch/$s_!mIdr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79a6f542-9a30-4864-b9a4-2aafcfdf6513_800x657.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I labelled the transitions so that they show the input signal needed to change state (on the arrows coming out of the <em>cyan</em> states, the "core" ones), or the constant representing the duration of the transition (on the arrows coming out of the <em>magenta</em> states).</p><p>In a resource constrained environment, I would implement this FSM logic differently, in a more compact way that just uses two `<code>currCoreState</code>`and `<code>nextCoreState</code>` variables, and a flag and a timestamp to handle the transition logic.</p><p>Instead, I'm sticking to the approach used in the other FSM implementation shown so far.</p><p>This doesn't mean that I can't avoid repetition in the code: I just need to define a couple of helper methods to generate the two kinds of state function I need, but with different parameters:</p><pre><code>private static Func&lt;IPlayerAvatarFSMView, EState?&gt; getBaseStateFunc(
    Dictionary&lt;EInput, EState&gt; rTransitionsMap
) {
    return (IPlayerAvatarFSMView rExec) =&gt; {
        var rData = rExec.getExecData().getData();
        if (rExec.isInputAvailable()) {
            FSMInput&lt;EInput&gt; input = rExec.fetchInput();
            EState eState;
            if (rTransitionsMap.TryGetValue(input.m_id, out eState)) {
                return eState;
            } 
        }
        return null;
    };
}

private static Func&lt;IPlayerAvatarFSMView, EState?&gt; getAnimStateFunc(
        float fDurationSecs,
        EState eDstState
) {
    return (IPlayerAvatarFSMView rExec) =&gt; {
        var rData = rExec.getExecData().getData();
        float fProgress =
            rExec.getExecData().getCurrStateElapsedTime() /
            fDurationSecs;
        rData.m_fAnimProgress01 = Mathf.Clamp01(fProgress);
        if (rData.m_fAnimProgress01 == 1f) {
            return eDstState;
        }
        return null;
    };
}</code></pre><p>Then, I can simply define the state functions for the whole FSM in a very compact manner. </p><p>It would be cool (and not that hard) to automatically generate this definition from the <em>Mermaid</em> diagram text... but not this time.</p><pre><code>private static 
    Dictionary&lt;EState, Func&lt;IPlayerAvatarFSMView, EState?&gt;&gt; rStates =
    new Dictionary&lt;EState, Func&lt;IPlayerAvatarFSMView, EState?&gt;&gt;() {
        {
            EState.showing_limboHands,
            getBaseStateFunc(
                new Dictionary&lt;EInput, EState&gt;() {
                    {
                        EInput.setSkeleton, 
                        EState.limboHands_to_inactiveSkeleton 
                    }
                }
            )
        },
        {
            EState.limboHands_to_inactiveSkeleton,
            getAnimStateFunc(
                fSETSKELETON_DURATION_SECS, 
                EState.showing_inactiveSkeleton
            )
        },
        {
            EState.showing_inactiveSkeleton,
            getBaseStateFunc(
                new Dictionary&lt;EInput, EState&gt;() {
                    { 
                        EInput.activateSkeleton, 
                        EState.activating_skeleton 
                    },
                    { 
                        EInput.setLimboHands, 
                        EState.inactiveSkeleton_to_limboHands 
                    }
                }
            )
        },               
        {
            EState.activating_skeleton,
            getAnimStateFunc(
                fACTIVATESKELETON_DURATION_SECS, 
                EState.showing_activeSkeleton
            )
        },
        {
            EState.showing_activeSkeleton,
            getBaseStateFunc(
                new Dictionary&lt;EInput, EState&gt;() {
                    { 
                        EInput.deactivateSkeleton,
                        EState.deactivating_skeleton 
                    }
                }
            )
        },
        {
            EState.deactivating_skeleton,
            getAnimStateFunc(
                fDEACTIVATESKELETON_DURATION_SECS,
                EState.showing_inactiveSkeleton
            )
        },
        {
            EState.inactiveSkeleton_to_limboHands,
            getAnimStateFunc(
                fSETLIMBOHANDS_DURATION_SECS,
                EState.showing_limboHands
            )
        }
    };</code></pre><div><hr></div><h2>2024-09-18 - Avatar presentation layer</h2><p>Now that I have the avatar mode logic in place, and a way to activate it through the inspector, it's time to work on the presentation layer.</p><p>For the "limbo hands" I'm just going to use the default hands offered by the SDK.</p><p>For the "skeleton", I'm going to use the robot character from the Meta samples I started with, changing something on the material to show the activated/non activated modes.</p><div><hr></div><p>I did a few changes to the scene and added two simple scripts controlling the two elements which come into play:  `<code>LimboHandsBhv</code>` and `<code>SkeletonAvatarBhv</code>`, each connected to the relevant nodes.</p><p>Then, I implemented the "state presenter" behaviour for the player avatar, which reads from its state manager and configures the nodes which take care of the presentation.</p><p>The visibility change of the hands and of the skeleton is instant for now, even if I pass in a 0-1 value to indicate the progress when in a transition state. In the case of the skeleton activation/deactivation, I use such value for a simple color interpolation.</p><p>At some point, when I'm going to have the actual, custom 3D model for the player avatar, I will do something fancier. </p><p>Everything is quite simple and tidy: </p><ul><li><p>`<code>LimboHandsBhv</code>` controls the visibility of the semi transparent hands</p></li><li><p>`<code>SkeletonAvatarBhv</code>` controls the visibility and "activation" of the skeleton body</p></li><li><p>`<code>PlayerAvatarBhv</code>` reads the avatar state data and uses it to properly configure `<code>LimboHandsBhv</code>` and `<code>SkeletonAvatarBhv</code>`</p></li></ul><p>Here's a short clip of me checking that everything works properly using the `<code>TestPlayerAvatarBhv</code>`.</p><div id="youtube2-JFz3Q9NXgZY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;JFz3Q9NXgZY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/JFz3Q9NXgZY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Notice the small jump in the hand position when switching between hands and skeleton. You might remember about the mismatch that got introduced when updating the `<code>com.meta.movement</code>` package to version `<code>5.0.1</code>`. I checked on GitHub and the current release is `<code>6.0.0</code>`. I wonder if they fixed it? It might be worth checking, hoping there are no big breaking changes (major version bump!).</p><p>Anyway, it's going to take a while to have the avatar mode switching normally working in-game as specified by the design: we need to build some other pieces of the puzzle first.</p><div><hr></div><h2>2024-09-19 - Mirrored body</h2><p>Let's focus on one small part of the design work done in week 25:</p><blockquote><p>With pretty much nothing else to do, the player will have to walk into the portal, or at least reach into it with their hands. When he "touches" the reflected avatar, in a white flash, I will teleport the player to the "menu" zone,  and they will find themselves embodying the avatar which was "on the other side" one moment before that.</p></blockquote><p>So, how do I implement this? Let's see what do I have, and what's missing.</p><p>I have</p><ul><li><p>a working portal system which allows me to "see" into the destination zone</p></li><li><p>a way to drive characters from body tracking data (live, or saved)</p></li></ul><p>I don't have</p><ul><li><p>logic to detect the conditions to activate this special teleport  (the player is not walking into a portal but is just "touching" it)</p></li><li><p>the "white flash"</p></li><li><p>a way to "mirror" the body tracking data</p></li></ul><p>I can place a copy of the skeleton avatar on the destination platform (that gets shown in the portal on the limbo platform), but if I don't flip the avatar properly, the illusion won't work.</p><p>Luckily, I remember the mirroring being part of one of the samples. Time to revisit them and see if they can save me some work.</p><div><hr></div><p>I checked the sample I remembered about, which is in the `<code>MovementRetargeting</code>` scene of the `<code>com.meta.movement</code>`package.</p><p>The implementation involves:</p><ul><li><p>a `<code>MirrorTransformation</code>` script, which lets you define mirror normal and plane offset, and links to a  `<code>_transformToMirror</code>` which acts as parent transform of the "mirrored" objects</p></li><li><p>a `<code>LateMirrorerdObject</code>` script to be attached to a "mirrored" object and linked to the "original" one, which lets you define a list of `<code>MirroredTransformPair</code>`, where each pair links an "original transform" (of the object to mirror) to a "mirrored transform" (of the mirrored object), and copies the original transform to the mirrored transform</p></li></ul><p>In the case of the player character, in the scene there's another instance of the character model used for the player, under the node set as `<code>_transformToMirror</code>`, and on its `<code>Skeleton</code>` node there's the `<code>LateMirroredObject</code>` which links it to the player character model.</p><p>I should be able to replicate this setup pretty easily. The only problem is that in the `<code>limboHands</code>` state I wouldn't have an "original" body to feed to `<code>LateMirroredObject</code>`, because the skeleton avatar is not actively tracking in that state. Maybe I can change it so that is active but not rendered? Let's try.</p><div><hr></div><p>Ok, I got it working!</p><p>I did what I anticipated, with the only detail that "active but not rendered" didn't mean turning off the `<code>SkinnedMeshRenderer</code>`, which needs to be active to properly update the bones. What I had to do, instead, was keeping it active but drawing it with the "null shader" shown on <a href="https://particularreality.substack.com/i/141759666/fixing-some-avatar-details">2024-02-14</a>.</p><p>I didn't have to touch anything except the implementation of a `<code>setShown()</code>` method I had defined in `<code>SkeletonAvatarBhv</code>`.</p><div><hr></div><p>There's still a few things to get done before considering this "mirroring" feature done, like placing the "mirror" transform relatively to the limbo platform (for now I just placed it at zero, which works only if there's no play area offset manually).</p><p>I'll get back to it soon.</p><div><hr></div><h2>2024-09-20 - White flash transition</h2><p>Let's proceed in building the elements we need. Today I'm going to tackle the effect to be used when transitioning from limbo hands (in the limbo zone) to the skeleton avatar (in the menu zone).</p><p>I'll follow my usual implementation principle, which is doing things in a basic way but with a sensible interface, and maybe replace them in the future with something better.</p><p>I want to be able to tune the transition timing and have a "safe" interval where I know that the player can't see anything, allowing me to change things around them without breaking the immersion.</p><p>The most basic thing that comes to mind is a full screen white flash that I can control via code setting the transparency level.</p><p>So, at the end, I'm going to do something like:</p><ul><li><p>when ready, fade to fully opaque white in 0.2 sec</p></li><li><p>when fully opaque, set the avatar mode / teleport to the menu zone etc</p></li><li><p>finally, fade to fully transparent in 0.1 sec</p></li></ul><div><hr></div><p>So, how to implement the white flash?</p><p>The most crude way, which I've used sometimes in the past adapting a classic non-VR approach, is sticking a quad drawn with a shader supporting transparency in front of the camera, and set its color.</p><p>After a brief search, I noticed that there's an `<code>OVRScreenFade</code>` script offered by the SDK that basically does that, creating the geometry via code, which is nice.</p><p>Let's try to use it...</p><div><hr></div><p>I want control over the transparency level and timing of the fade, so I didn't use the `<code>FadeIn</code>` / `<code>FadeOut</code>` methods offered by `<code>OVRScreenFade.cs</code>`. </p><p>Actually, I made a super-thin wrapper over it and I'm going to use that whenever I need the functionality. </p><pre><code>using UnityEngine;
using BinaryCharm.Behaviours;

namespace BinaryCharm.ParticularReality.Presentation {
    public class ScreenFadeManagerBhv : ManagerBhv&lt;ScreenFadeManagerBhv&gt; {

        private void Awake() {
            registerInstance(this);
        }

        public void setOpacityLevel(float f01) {
            OVRScreenFade.instance.SetExplicitFade(f01);
        }

        public void setFadeColor(Color c) {
            OVRScreenFade.instance.fadeColor = c;
        }
    }
}</code></pre><div><hr></div><p>In the `<code>PlayerAvatarBhv</code>` script, which "orchestrates" the other components related to the avatar presentation, I called  `<code>setOpacityLevel</code>` remapping the progress level of the transition states  `<code>limboHands_to_inactiveSkeleton</code>` and `<code>inactiveSkeleton_to_limboHands</code>`.</p><p>It sounds like a good moment to throw in a cool little trick: to remap a 0-1 value to a value which instead goes up and down (so that we can have maximum opacity in the middle, and have a fade out/fade in) I did:</p><pre><code>// fAnim01 is the progress level of the transition
fFadeOpacity = 1f - Mathf.Abs(2f * fAnim01 - 1f);
ScreenFadeManagerBhv.i().setOpacityLevel(fFadeOpacity);</code></pre><p>An equivalent formulation, using a conditional, would have been:</p><pre><code>if (fAnim01 &lt; 0.5f) {
    fFadeOpacity = fAnim01 * 2f;
} else {
    fFadeOpacity = (1f - fAnim01) * 2f;
}</code></pre><p>...that could be written in a more compact way using the ternary operator:</p><pre><code>fFadeOpacity = (fAnim01 &lt; 0.5f ? fAnim01 : 1f - fAnim01) * 2f;</code></pre><p>Using the absolute value is a bit more opaque, but feels cleaner and avoids branching.</p><p>Not that would matter here, but it's the kind of thing which is nice to know and can be much more important in the context of shader code. Still, when I do this kind of thing, I try to leave as a comment the equivalent, explicit formulation.</p><div><hr></div><p>Another week of (part-time) work on <em>Particular Reality</em> is done. Let's complete this DevLog entry with a short clip:</p><div id="youtube2-hSEP4_dYYRk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;hSEP4_dYYRk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/hSEP4_dYYRk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>At some point I will probably add an easing function to the value, but even in the current state, I feel it's adequate.</p><p>Next week I'll continue the work, taking care of the "touching mirror detection" and, hopefully, of the "mirror portal" teleportation, so that we can see it all come together.</p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #25]]></title><description><![CDATA[Designing the next steps]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-25</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-25</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 31 Aug 2024 17:00:59 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e76675c2-b623-441c-8f1a-add4856f5a72_1144x1144.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-08-26 - Through the looking glass</h2><p>I have a busy week, but I'm not going to stop completely!</p><p>I feel it's the right week to do a little <em>design</em>.</p><div><hr></div><p>In <a href="https://particularreality.substack.com/p/advanced-prototyping-week-24">week 24</a>, I implemented enemy attacks which can lower the player health. What happens when such health goes to zero? It's time to revisit and evolve the global application flow..</p><p>How can I stay faithful to my "total immersion" design principle, and still have a familiar "game" flow with save/load, "runs", pause, and maybe rewind?</p><p>I've been thinking about these things for quite some time, and it's time to write about them and flesh out the details.</p><div><hr></div><p>Remember <a href="https://particularreality.substack.com/p/advanced-prototyping-week-07">week 7</a>? I defined two special zones, "limbo" and "menu".</p><p>The "limbo" zone should be the zone which acts as a passage from the outside world (or the VR headset "home" environment) to the game world, while the "menu" should let the player start a new game or resume playing a previous game.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ioOP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ioOP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 424w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 848w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 1272w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ioOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png" width="610" height="688" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:688,&quot;width&quot;:610,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120309,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ioOP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 424w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 848w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 1272w, https://substackcdn.com/image/fetch/$s_!ioOP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F54dda20c-bc3a-44e1-b320-9004d1048b0d_610x688.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In these two "special" zones, it should be acceptable to have some kind of UI elements or messages, but I'd like to keep them minimal anyway.</p><p>I need to put myself into the shoes of somebody that has never played or seen the game, and think about what to show them when they start the game.</p><div><hr></div><p>Loading the "limbo" should be almost instantaneous, and the hexagonal platform should appear in mixed reality. The *Binary Charm* and _Particular Reality_ logos could also float somewhere. This is where I might need to show any disclaimer/warnings about potential epilepsy triggering etc. </p><p>Additionally, I will need to make sure that the platform is inside the "guardian" area, and let the user adjust it if they want. Any other configurable options, if any, will go here too.</p><p>In the _limbo_, there should basically be no avatar, but only the default semi-transparent hands, just to make the player aware that the hand tracking is working.</p><p>Then, I need to push the player roughly near one edge of the hexagonal platform, and facing the opposite edge. This way, the portal to cross, which is going to "split" the platform in two halves when it appears, will open in front of the player, well visible.</p><p>Having some kind of arrow stamped on the platform would be effective, but too crude for my taste. What I'm thinking is having a dynamic hint system, based on floating particles, that goes into the player field of view and moves trying to push him into a certain spot.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kSc7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kSc7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 424w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 848w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kSc7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png" width="1456" height="364" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:364,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2876916,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kSc7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 424w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 848w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!kSc7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec4f6e23-0697-4007-a354-5414009fcb96_4096x1024.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>When the player reaches the required position, a portal splitting the hexagonal platform in two halves will appear. It's going to be like a mirror showing our reflection from another reality: a <em>Particular Reality</em>. </p><div><hr></div><p>Our reflection should feature what I'm going to call a "skeleton avatar", a minimalistic, stylized upper body that will always track the player body movements after they enter the game world.</p><p>With pretty much nothing else to do, the player will have to walk into the portal, or at least reach into it with their hands. When he "touches" the reflected avatar, in a white flash, I will teleport the player to the "menu" zone,  and they will find themselves embodying the avatar which was "on the other side" one moment before that.</p><p>Obviously, this should resonate with the player, thanks to <em>Alice in Wonderland</em> (and, consequently, <em>Matrix</em>), without forgetting the classic moment from <em>Prince of Persia</em>.</p><p>Then what? </p><h2>2024-08-27 - Teaching locomotion</h2><p>Ok, yesterday I got to the point where the player goes from the limbo to the menu zone by crossing the portal/mirror, and starts embodying the "skeleton avatar".</p><p>In the menu zone, the player should be able to start playing a new game, or resume a previously saved game. Entering this "playing" mode should be made obvious by having a second level of embodiment, where the "skeleton avatar" binds itself to some kind of "flowing energy" (rendered via particle effects).</p><p>But first, the player should be taught at least one fundamental thing: how to move between platforms.</p><div><hr></div><p>For the second time (forced action repetition is the basic way to teach the rules of the game world), the player, standing on the second half of the first platform of the "menu zone", after entering from the limbo, will be drawn to the zone where they can open a portal to the adjacent platform.</p><p>When in position, the floating particles will morph to the gesture needed to open the portal, prompting the player to do that same gesture.</p><p>When the "skeleton avatar" hands match the "particles hands", they will activate the portal to the adjacent platform.</p><p>As we know from one year of DevLog, the player should take a step forward while sticking to the same gesture.</p><p>We should teach this by having the "particles hand" go forward into the portal, pushing the player to follow in the same way.</p><p>After reaching the central platform, the player will need to reiterate the action, but changing direction. So we're slightly making things more complicated, trying to teach "to move to an adjacent platform, you must be in correct half of the current platform, facing towards the destination". The floating particles will suggest the actions to be done, as they already did on the previous platform. </p><p>At this point, the player will be on the "new game" platform, which will feature a "bridge portal" which brings to the start of the actual game.</p><p>Once again, the floating particles will still suggest what to do. When the player will cross this portal, marking the beginning of a playthrough, the particles which have "guided" the player until now will stick to them, somewhat "activating" the skeleton and marking the start of the gameplay session.</p><p>Considering the menu map (without the "continue game" platform), here's a visual representation of the three portal crossings we are going to guide the player through, starting from the bottom and reaching the "gameplay" on the top. The red arrows are movement on a platform (the "step back"/direction change), while the azure crayon shows the "jumps" done crossing an opened portal.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DN3u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DN3u!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 424w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 848w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 1272w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DN3u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png" width="1182" height="2160" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2160,&quot;width&quot;:1182,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2152224,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DN3u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 424w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 848w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 1272w, https://substackcdn.com/image/fetch/$s_!DN3u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c786db8-3c8e-478b-beb5-82805bfa1840_1182x2160.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>2024-08-28 - Navigating through contexts</h2><p>Let's recap the flow we have described until now:</p><ul><li><p>the game starts in the mixed reality "limbo", the player doesn't embody an avatar</p></li><li><p>the player moves to "menu" and embodies the "skeleton avatar", particles float around and give hints</p></li><li><p>the player enters the "gameplay" stage, and the "skeleton avatar" activates, with particles sticking to it</p></li></ul><p>In the "gameplay" stage, there's a sub context to handle, the one where the game is paused (and maybe gets rewind).</p><p>Let's split "gameplay" into "active gameplay" and "paused gameplay", and recap how I'm planning to make each context easy to tell from the others:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rKhB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rKhB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png" width="712" height="224" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:224,&quot;width&quot;:712,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15780,&quot;alt&quot;:&quot;table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image.&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image." title="table showing a recap of the characteristics of each context. Unfortunately substack doesn't support tables, and I had to use an image." srcset="https://substackcdn.com/image/fetch/$s_!rKhB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 424w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 848w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1272w, https://substackcdn.com/image/fetch/$s_!rKhB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F105af6dc-c3e1-4899-8260-c7df3fbd72ab_712x224.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Basically, when the game gets paused, the particles should "detach" from the skeleton, standing still even if the player (still driving the "skeleton avatar") moves.</p><p>I'd like to have a single "pop context" interaction, that the player can perform to "go up" one level in the stack, like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2UOU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2UOU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 424w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 848w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 1272w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2UOU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png" width="454" height="454" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d33fd89a-6113-4539-a97a-411d3319122c_454x454.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:454,&quot;width&quot;:454,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:16404,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2UOU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 424w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 848w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 1272w, https://substackcdn.com/image/fetch/$s_!2UOU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd33fd89a-6113-4539-a97a-411d3319122c_454x454.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It's like when pressing "esc" pauses the game, and then pressing it again brings you to the main menu. There, another "esc" keypress quits the game.</p><p>As there's no standard "esc" interaction for VR, and I'm trying to avoid relying on controllers and "system" gestures, the only problem is finding the right gesture/movement, teaching it to the players, and make them aware that the possibility to perform the interaction is available.</p><p>Not relying on the system gesture (which currently, on Quest, is a pinch held for a while) doesn't mean that we don't have to handle it, of course. Actually, by being an intrinsically "immersion breaking" interaction, it might provide me a good opportunity to show a panel teaching how to do an `exitContext()` interaction, if I can't think of anything better.</p><p>Anyway, I feel like it's time for one of my FSM diagrams. Let's try to describe the global application flow.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WJgC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WJgC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 424w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 848w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 1272w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WJgC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png" width="1200" height="1468" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1468,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:136247,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WJgC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 424w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 848w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 1272w, https://substackcdn.com/image/fetch/$s_!WJgC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d53a2e5-28bc-417a-a6d3-95748d780446_1200x1468.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A few comments on the diagram, before calling it a day:</p><ul><li><p>The `<code>in_limbo_shutdown</code>` is not really necessary, but I'd like to have it, for symmetry: I'm going to implement a quick animation where you exit the <em>Particular Reality</em> and are briefly sent to the mixed reality you've already seen when launching the game. If you're in a hurry, you can always quit the game straight from the system menu. I'm not sure yet about giving or not the option to go back to the "limbo" without quitting. For now, I'm going to keep it simple and assume that "exiting" from the menu zone implies wanting to quit the game.</p></li><li><p>Walking outside the hexagonal platform is another way to put pause the game, and solves problems like "what happens if the player has a large room and walks to an adjacent platform?". </p></li><li><p>I haven't considered the "setup" logic yet. I briefly mentioned yesterday, and it's part of the global application flow, so I should formalize that too</p></li></ul><p>Well, I'm going to continue tomorrow...</p><h2>2024-08-29 - Unfinished business</h2><p>While reviewing yesterday's work before proceeding, I spotted a flaw.</p><p>I wrote that while in the "active gameplay" context, a second way to pause would be to simply walk outside the platform, which would also prevent breaking the game world logic, which forces you to change platform via portals.</p><p>But what about walking outside when in the other contexts?</p><ul><li><p>limbo: it's fine, you're basically walking into your room in mixed reality, but to proceed to the game you must step onto the platform</p></li><li><p>paused gameplay: it's fine too, you might walk around but to resume playing you are forced to go back to the platform</p></li><li><p>menu: problems! problems! eeek!</p></li></ul><p>If I want to keep some level of consistency, I think that "exiting context" in the menu zone (including stepping out of the platform) must definitely bring back to the limbo, which then is not just for starting and quitting the game, but can potentially be entered and abandoned again at any moment.</p><p>When stepping outside the platform while in the "menu" context, the "skeleton avatar" must be detached, the environment should go back to mixed reality, and the player should go back to have the semi-transparent tracked hands.</p><p>When going back inside the platform, the "mirror portal" will open again, allowing the player to re-embody the "skeleton avatar" and go back to the same platform they were before switching to the limbo.</p><p>If I'm not overlooking anything else, this slightly more complex flow should make sense, and it's now possible to quit the game from the startup scene too, because for additional consistency I would say that at this point walking outside the platform, when in the limbo, should be the action that quits the game.</p><p>Let's update  yesterday's diagram, and also take the opportunity to add the "setup" part.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PEKr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PEKr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 424w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 848w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 1272w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PEKr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png" width="1200" height="1864" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1864,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:176563,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PEKr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 424w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 848w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 1272w, https://substackcdn.com/image/fetch/$s_!PEKr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b936d1-9f52-41d2-a61e-7da5c9eb0673_1200x1864.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Ok, looks like a definite improvement!</p><p>I got fancy and assigned different colors to the states, depending on the context they're in, according to this mapping:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!602Q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!602Q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 424w, https://substackcdn.com/image/fetch/$s_!602Q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 848w, https://substackcdn.com/image/fetch/$s_!602Q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 1272w, https://substackcdn.com/image/fetch/$s_!602Q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!602Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png" width="232" height="163" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/42969775-109a-4072-afcd-63860724fcd2_232x163.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:163,&quot;width&quot;:232,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4658,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!602Q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 424w, https://substackcdn.com/image/fetch/$s_!602Q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 848w, https://substackcdn.com/image/fetch/$s_!602Q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 1272w, https://substackcdn.com/image/fetch/$s_!602Q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F42969775-109a-4072-afcd-63860724fcd2_232x163.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>I added  a preliminary check for the validity of the configuration, and a new `<code>in_limbo_config</code>` state to take care of fixing it when needed. Then, I added the possibility to go back to the limbo from the menu. Now, `<code>exitContext()</code>` should be able to work consistently, always mapped to the same interaction, which should be "do a gesture/movement OR step outside the platform".</p><p>Of course, to activate the signal by stepping outside the platform, the player will need to step back in first to activate it again (or at each frame outside the platform, we would pop a context, which would quit the game in a handful of frames).</p><p></p><h2>2024-08-30 - Back and forth</h2><p>I'd like to spend some time thinking about what should hopefully be the last "cloudy" parts about the high level application flow.</p><p>I mentioned the "continue game" platform a few times, but never stopped and described how I imagine it working.</p><p>And in the diagram, there are two ways to go back to the `in_menu` state:</p><ul><li><p>from `<code>paused</code>`, voluntarily performing an `<code>exitContext()</code>`</p></li><li><p>from `<code>playing</code>`, in case of <em>game over</em></p></li></ul><p>Let's discuss them separately.</p><div><hr></div><p>Going to the menu zone from the `<code>paused</code>` state is the easy one. I know how I like this kind of auto-saving system: when I go back to playing, I want to be able to resume the game seamlessly, exactly from the state it was when I had to stop playing.</p><p>There's all kinds of things that can force someone to interrupt a gameplay session (battery running out, unexpected call, dinner ready...), and I hate when a game doesn't allow to easily save and then restore later. Forcing the player to leave the application running, paused, is not acceptable to me.</p><p>So, I'm just going to autosave whenever one pauses the game. Done that, it doesn't matter what happens next: maybe they quit regularly passing through the limbo, or more abruptly terminating the process from the system menu. Maybe they leave the game running, even if paused, in the headset until the battery runs out.</p><p>Let's define some further details. When "exiting the context" of the paused game, we defined that the players find themselves in the <em>menu</em> zone, but where exactly?</p><p>I'd like to put them on the "continue game" platform, and I'd like that the "bridge portal" that one can open from that platform is nothing else that a way to step back into the "paused game" that they have just left.</p><p>Actually, I'd like the bridge portal to act like a 3D thumbnail of the saved game. When you open it by stepping on the right zone of the "continue game" platform, you can glimpse into the portal and you might recognize the detached particles of your avatar as you have left it when pausing the game. Crossing the portal brings you back to the same "paused" game you had left, and you can reattach your "skeleton avatar" to the particles and restore the execution.</p><p>It doesn't matter if you had just paused, in the same execution of the application, of if it's the game you were playing two weeks earlier, before going on vacation.</p><p>What happens if you have a saved game, and consequently the possibility to continue but instead you decide to go back to the "new game" portal to start from scratch?</p><p>That's a whole other can of worms, let's leave it closed for now, and assume that we handle a single slot:  "new game" overwrites it.</p><div><hr></div><p>What about the "game over" case, when the player's health goes to zero?</p><p>I feel it's early to take a final decision about this.</p><p>There's many possibilities. I'm going to write some that I might consider.</p><ul><li><p>A checkpoint system. When you lose all your health, you get sent back to the latest checkpoint, with full health. The latest checkpoint might be the current zone entrance.</p></li><li><p>Forced rewind: you never really "lose", but are forced to rewind to a previous state where you have some health. Might be problematic (limits on the rewind duration), and I'm not even sure rewinding will exist</p></li><li><p>Game over and that's it. You need to restart the "run" from the beginning, maybe keeping some element of progression from the previous attempts</p></li></ul><p>I really don't know yet. But even about this, like for the save/load, I need to at least take a temporary decision which makes sense for now.</p><p>I think I'm going to go with the simples thing, which is the "do nothing" option. I will send the player back to the "new game" platform of the menu zone. They just lost, and can start a new game.</p><p></p><div><hr></div><p>Let's update the diagram to include the newly discussed logic:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Ptw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Ptw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 424w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 848w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 1272w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Ptw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png" width="1200" height="1284" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1284,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135768,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Ptw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 424w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 848w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 1272w, https://substackcdn.com/image/fetch/$s_!8Ptw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc94807f9-18b5-4a5f-bc0b-227360fc8bfb_1200x1284.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The non-coloured states indicate behind the scenes actions that are significant to make the logic work the way I described earlier.</p><p>I had to remove the "macro-state" gameplay, because there's no common flow going in nor out.</p><ul><li><p>if we are entering from the "new game" portal, we set up a new game and go into the `<code>playing</code>` state</p></li><li><p>if we are entering from the "continue game" portal, we set up the loaded game but go into the `paused` state, not straight into `<code>playing</code>`</p></li><li><p>if we are exiting because of the "game over", we are brought back on the "new game" platform</p></li><li><p>if we are exiting voluntarily with an `<code>exitContext()</code>` from `<code>paused</code>`, we are brought back on the "continue game" platform</p></li></ul><p>Additionally, I highlighted the `<code>autosave</code>` logic called when pausing the game.</p><p>Ok, that's about it! There are going to be details I have overlooked, but I think I have drafted quite a lot of stuff, which is going to keep me busy for many weeks of implementation.</p><p>I can't wait to get started, but well, I will have to, actually, because next week, and maybe the week after that, I will be busy with other tasks. Hopefully, once I get rid of those, I will manage to work for a few weeks without interruptions.</p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #24]]></title><description><![CDATA[Enemy attacks and player health]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-24</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-24</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 17 Aug 2024 17:30:05 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/64fd747d-5dc2-4ac1-a38e-be1750c6c7d2_905x905.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-08-12 - VFX custom simulation space</h2><p>I enjoyed preparing the special post and clip montage last week, <a href="https://particularreality.substack.com/p/particular-reality-year-one">looking back at one year of development</a>, but now it's time to get back at work.</p><p>Since <a href="https://particularreality.substack.com/p/advanced-prototyping-week-20">week 20</a>, I've been reintroducing the enemies into the game, following the same principle where I swap out a crude, throwaway implementation done for the PoC, for a new, robust and well engineered implementation.</p><p>At the end of <a href="https://particularreality.substack.com/p/advanced-prototyping-week-23">week 23</a>, I was faced with a problem: I do an "invisible" move of the game world elements when entering a bridge platform, so that the current zone is always roughly at the world origin. But this movement impacts the calculation implicitly done in the visual effects, causing a glitch in the particles update. At least, that's my assumption, but I'm reasonably sure that's the problem.</p><p>Let's see what options do I have, and pick one.</p><ul><li><p>ignore the issue: it's a visualization thing, so I could live with it and go back at it later, when working on the presentation</p></li><li><p>change the gameplay to hide the issue: make the enemies appear only after the player enters the zone, so they would be hidden during the zone repositioning</p></li><li><p>remove the zone repositioning: I did it thinking about the classic problem of float precision degrading with distance from zero, and to ease debugging in the editor. But it's not a "real" fix - ultimately, I'd like to use fixed point arithmetic and have deterministic calculations for both time and space</p></li><li><p>handle the problem in the VFX: by passing as parameter the needed information (the velocity vector, or maybe the base transform of the zone) it should be possible to adjust the calculation so that it doesn't glitch</p></li></ul><p>I think I'm going to try the last option. But why?</p><p>I don't want to ignore the issue, because this is not a glitch that would disappear just using prettier models and better visual effects down the road. I don't want to take gameplay decisions because of technical problems, so option two is also off the table. Option three would be great, but it would be a lot of work to implement fixed point arithmetic. And it would be wasted work if I end up ditching Unity (as I hope to do) at a later stage of development, or if they add it themselves (I read somewhere that it might get introduced to support deterministic physics in DOTS, or something like that).</p><p>Option four, instead, has these advantages:</p><ul><li><p>gaining more experience with the VFX graph would be useful anyway</p></li><li><p>this kind of manual adjustment of the position could help me to also understand how to control the time delta of the simulation, something I wanted to do last week (I ended up "just pausing" the VFX).</p></li></ul><p>Ok, enough writing and thinking, let's try to get it done.</p><div><hr></div><p>Before trying to do it from scratch, I googled a bit and found out I'm not the only one looking to do this kind of thing. Actually, there's an example on the Unity forums that should show how to work around this limitation of the VFX graph. The "old" system, Shuriken, offered not only "local" or "world" simulation space, but also a third option, "custom",  that let you specify a transform to use as basis for the particle system. Which is basically what I need to do with the "zone parent" node, which is the one I apply the offset to when entering a zone.</p><div><hr></div><p>I tried the example and it should be doing what I want, but it's not straightforward to apply it to my enemy VFX, and it's getting late. I'm going to try it first thing tomorrow.</p><p></p><p></p><h2>2024-08-13 - Playing (and failing) with the VFX graph</h2><p>Let's play a bit with the VFX graph and see if I manage to apply the "custom simulation space" fix I found yesterday.</p><div><hr></div><p>Unfortunately, it looks like it's "one of those days" where things just don't want to work.</p><p>I tried implementing the workaround in my enemy VFX, but it's not working properly.</p><p>After an afternoon of fiddling with nodes (which is something I hate), I'm not sure if I simply have some errors in my setup, or if what I'm trying to do is intrinsically wrong.</p><div><hr></div><p>I think I'm almost there, but I'm at a point where to get it working with the workaround I have implemented, I should change the scene hierarchy so that the nodes with the `<code>VisualEffect</code>` component attached aren't descendants of the <em>zone</em> nodes, and I hate the idea.</p><p>While I managed to decouple the transform that should affect the particle simulation (the local movement of an enemy inside a zone) from the transform that should not affect it (the transform of the zone, that I adjust when crossing a portal from one zone to another), the current node hierarchy gets in the way.</p><p>Still, I feel like this is taking too much time and is not bringing me a "clean" fix but an ugly mess. Some test nodes are disconnected, but this is a summary image of my miserable day:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lxSy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lxSy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 424w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 848w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 1272w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lxSy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png" width="575" height="699" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:699,&quot;width&quot;:575,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90842,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lxSy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 424w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 848w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 1272w, https://substackcdn.com/image/fetch/$s_!lxSy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0449ce5-4b02-481c-bd67-8bcf7da3b73a_575x699.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>These two days haven't been completely wasted, because I definitely understood some new things about the VFX graph. But I think it's time to take a step back, because I can foresee lots of additional work to have each effect implementing my dirty workaround.</p><p>What step back? Well, I think I will fall back to "option three": disabling the zone repositioning. I explained that it wasn't a <em>real</em> fix anyway, so I'll get rid of it and implement the fixed point arithmetic someday (not in the prototype). </p><p></p><p></p><h2>2024-08-14 - Disabling the zone repositioning</h2><p>I started the day by tidying up a bit after the last two days of struggle.</p><p>I committed my "almost done" version and then disabled the additional calculations I introduced, practically going back to the previous version of the VFX.</p><p>Now, it's time for node-editor detox and finally doing some progress.</p><div><hr></div><p>Disabling the zone repositioning took a few minutes and, of course, avoided the glitch.</p><p>I put the feature behind a flag, so if I want to re-enable I just need to change a constant.</p><p>That might be useful for debugging, or if end up finding a clean fix on the VFX graph side of things.</p><pre><code>// from WorldManagerBhv.cs
        private void swapZones() {
            Zone rPrev = m_rCurrentZone;
            m_rCurrentZone = m_rAdjacentZone;
            m_rAdjacentZone = rPrev;

            if (PR_Defs.bZONE_REPOSITIONING_ON) {
                Vector3 vOffset = -m_rCurrentZone.getOffset();
                m_rAdjacentZone.adjustOffset(vOffset);
                m_rCurrentZone.adjustOffset(vOffset);
            }

            m_iZoneLayerSwitch01 = 1 - getZoneLayerSwitch();
        }</code></pre><div><hr></div><p>Now what?</p><p>Well, I added back the possibility to damage and destroy enemies. I think it's reasonable to also reintroduce the player health management, and have the enemies able to attack and destroy the player.</p><p>Before thinking about adding real "attacks", let's do what I did in the PoC: the enemies damage the player when they're very close.</p><div><hr></div><p>As first step, I'm going to implement a <em>test</em> behaviour to send, via inspector, the input events to the player state manager. I did it for the enemies, as shown at the end of  <a href="https://particularreality.substack.com/i/145197610/enemy-presentation-layer">week 21</a>, and I think I'm going to do it for each element that can be tested individually.</p><div><hr></div><p>I started by duplicating the `<code>TestEnemyBhv</code>` to a `<code>TestPlayerBhv</code>` and changing it accordingly, but that showed an obvious opportunity for refactoring so that the common part of any test class is factored out to an abstract base class (`<code>ATestBhv</code>`).</p><p>The refactoring was easy, but a small issue remains: the `<code>RTLocation</code>` type I defined (wrapping a zone identifier and a platform identifier) can't be automatically serialized by Unity, so this is what I get into the inspector (I marked the problems with a red X):</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZIt-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZIt-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 424w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 848w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 1272w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZIt-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png" width="509" height="560" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:560,&quot;width&quot;:509,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38450,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZIt-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 424w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 848w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 1272w, https://substackcdn.com/image/fetch/$s_!ZIt-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d44e40e-ba3a-4742-ab07-f8fdb82aa60a_509x560.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tomorrow I'll try to fix it implementing a custom "property drawer" or changing the definition of `<code>RTLocation</code>` so that it can work with the default serialization.</p><p></p><p></p><h2>2024-08-15 - Player refactoring</h2><p>Well, it looks like removing the `<code>readonly</code>` attribute from the fields of `<code>RTLocation</code>` is enough to make the default Unity serialization work and have the field shown in the inspector.</p><p>I don't like it, but I can live with it, and it was quick.</p><p>I could probably keep the `<code>readonly</code>` and customize the visualization in the inspector so that it is actually shown as a non-editable text field, and doesn't take multiple lines, but I won't spend time on a debug feature that will only be used by me. At least, not today!</p><p>About the use of `<code>RTLocation</code>` parameter in the `<code>teleport</code>` call activated by the <em>ProButton</em>: I will just expose two integers and convert to my custom data type inside the button code:</p><pre><code>// from TestPlayerBhv.cs
        [ProButton]
        void teleport(int iZoneId, int iPlatformId) {
            RTLocation dest = new RTLocation(
                new Id&lt;RTZone&gt;(iZoneId),
                new Id&lt;RTPlatform&gt;(iPlatformId)
            );
            getStateManager().teleport(dest);
        }</code></pre><p>And here's the updated panel.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nm4a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nm4a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 424w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 848w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 1272w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nm4a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png" width="512" height="713" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:713,&quot;width&quot;:512,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:45873,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nm4a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 424w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 848w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 1272w, https://substackcdn.com/image/fetch/$s_!nm4a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff715664b-44fe-49e6-91f4-0b0dce0212fb_512x713.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>During weeks 3/4 of advanced prototyping, as I explained at the time, I just ported the minimal code needed to have the player movement working. Then, when adding the body tracked avatar, I did a little more work in that area.</p><p>Now, finally, it's time to properly refactor the player management so that it fits the current architecture.</p><p>The movement and teleportation should keep working in the same way, and I will add new stuff to handle health and mana levels. </p><p>This will naturally flow into the handling of the "high level" game loop, because something will need to happen when the health level reaches zero. A game over? A restart from the latest checkpoint? A mandatory gameplay "rewind", Braid style? I don't know yet.</p><p>Anyway, let's get started.</p><div><hr></div><p>I implemented the handling of the "input signals" (`<code>adjustHealth</code>`, `<code>adjustMana</code>`, `<code>teleport</code>`) so that they correctly affect the player state, and checked (in editor) that they work properly, by attaching the `<code>TestPlayerBhv</code>` to the player and using the buttons.</p><p>Now, I should be ready to add to the enemies logic the part which actually causes the health adjustment on the player entity.</p><p>Tomorrow I'm off for a day out, but I plan to make up for it on Saturday.</p><p></p><p></p><h2>2024-08-17 - Enemy attacks</h2><p>I'm going to implement the basic "energy leeching" attack I had in the PoC.</p><p>This should mean, essentially:</p><ul><li><p>add the logic which lowers the player health when the enemy is close enough</p></li><li><p>update the presentation of the enemies so that they play another sound and change color while they're dealing damage</p></li></ul><p>Let's go!</p><div><hr></div><p>At least for now, in the enemy lifecycle state machine (discussed in <a href="https://particularreality.substack.com/i/145197610/enemy-and-enemywave-state-machines">week 21</a>), I decided to simply split the `<code>core</code>` state into:</p><ul><li><p>`<code>looking_for_player</code>`</p></li><li><p>`<code>damaging_player</code>`</p></li></ul><p>What logic did I implement?</p><p>In the `<code>looking_for_player</code>`, the enemy simply steers towards the player. When the distance is less than a certain threshold, the state machine switches to the `<code>damaging_player</code>`, which applies the damage and also keeps steering towards the player, but with different acceleration, so that it feels a bit "sticky" to the target.</p><p>On the presentation front, I just had to handle the new state, setting the VFX color to the player cyan (as if they're stealing the player health) and playing a different sound effect.</p><p>I also had to fix the calculation of the "enemy to player" vector, which was not properly implemented and broke after disabling the zone repositioning a couple of days ago. </p><p>That's basically it! Let' complete the week with a short video showing two enemy attacking and affecting the health bar on the left wrist (shown around second 10):</p><div id="youtube2-ZxoCDgiXX1w" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ZxoCDgiXX1w&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ZxoCDgiXX1w?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Next week I have to catch up with a few tasks, so I'm going to take a break from <em>Particular Reality</em>.</p><p>I should be back at work the following week, so stay tuned!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Particular Reality: Year One]]></title><description><![CDATA[Prototyping Recap]]></description><link>https://particularreality.substack.com/p/particular-reality-year-one</link><guid isPermaLink="false">https://particularreality.substack.com/p/particular-reality-year-one</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sun, 11 Aug 2024 16:01:22 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>Happy birthday, Particular Reality</h2><p>While I announced <em>Particular Reality</em> at the end of September 2023, I started prototyping it a bit earlier, on August the 5th. This means that this week marked the first birthday of the project!</p><p>I think it's the perfect moment to summarize what I've been doing, try to draw a balance of the work so far, and write a few words about how I plan to proceed.</p><div id="youtube2-bB0BDoU9el8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;bB0BDoU9el8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/bB0BDoU9el8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p><h2>How much time did I work on the project?</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!z3IJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!z3IJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 424w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 848w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 1272w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!z3IJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png" width="1456" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:78614,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!z3IJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 424w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 848w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 1272w, https://substackcdn.com/image/fetch/$s_!z3IJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc754575f-99c3-4fc4-80f8-40628dd2af1b_1920x800.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As shown by the yearly "heat-map" above, in the past year I managed to do:</p><ul><li><p>16 full-immersion days of "Proof Of Concept prototyping"</p><ul><li><p>roughly 10 hours/day</p></li></ul></li><li><p>23 weeks of part-time "Advanced Prototyping"</p><ul><li><p>roughly 20 hours/week (4 hours/day, 5 days/week)</p></li></ul></li></ul><p>That's strictly development time working on the game and writing the DevLog entries related to that work, so not considering auxiliary tasks like:</p><ul><li><p>working on the game website and social media accounts</p></li><li><p>writing "extra" articles (like this one you're reading now) </p></li><li><p>preparing material exclusively useful for social media </p></li><li><p>dealing with hardware/software problems preventing me to work </p></li></ul><p>The "extra" articles required a very variable amount of effort. Some were written in a couple of hours, while others required a lot of work. In some case, the write-up itself was pretty fast, but was about a very time intensive task.</p><p>All things considered, not too bad but not great either.</p><p>Unfortunately, when other work or family related priorities arise, <em>Particular Reality</em> gets paused.</p><p>It's the price to pay for low-stress creative freedom, as discussed <a href="https://particularreality.substack.com/p/how-to-pick-a-game-project">in a previous article</a>.</p><p></p><h2>Did I fulfil my own expectations?</h2><p>If you're a developer, you're probably familiar with the awesome Hofstadter's Law:</p><blockquote><p>It always takes longer than you expect, even when you take into account Hofstadter's Law.</p></blockquote><p>Of course, this also applied to the development of <em>Particular Reality</em>.</p><p>I was quite happy of the results I got in only 16 days of full immersion, the phase I labelled "Proof of Concept Prototyping". For the next phase, the "Advanced Prototyping", the idea was reaching roughly the same features of the PoC, but properly engineering them, so that they can be extended and be the backbone of the full game.</p><p>If at the end of the PoC developement you would have asked me an estimate for this "Advanced Prototyping" phase, I would have probably guessed "about 3 months".</p><p>Instead, 23 weeks of development later, I'm not done yet. </p><p>And honestly, it's fine. I won't rush it. Successful estimates in software development are mostly a fairy tale anyway. </p><p></p><h2>What did I get done, and what not?</h2><p>I did some significant work on the game world management, going from a static, hardcoded demo map to an extensible, dynamic level management built around the idea of interconnected "zones" which can be designed indipendently.</p><p>The portal management itself was significantly improved from the version of the PoC, and I introduced the "bridge portals", which allow going from one zone to another.</p><p>Then, I introduced a player avatar, using upper body tracking (in the PoC I used "default", basic hand tracking), and did a lot of work related to the capture and playback of body motion, that I'm going to use for displaying "tutorial" gestures and for enemy animations. </p><p>I started working on a modular, extensible architecture that should allow me to load, save, pause and rewind the game.</p><p>Recently, I finally put back some enemies, implementing a wave management system and some other details that will allow me to have different kinds of enemies with different characteristics.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Byia!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Byia!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 424w, https://substackcdn.com/image/fetch/$s_!Byia!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 848w, https://substackcdn.com/image/fetch/$s_!Byia!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 1272w, https://substackcdn.com/image/fetch/$s_!Byia!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Byia!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png" width="668" height="1128" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1128,&quot;width&quot;:668,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:101054,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Byia!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 424w, https://substackcdn.com/image/fetch/$s_!Byia!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 848w, https://substackcdn.com/image/fetch/$s_!Byia!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 1272w, https://substackcdn.com/image/fetch/$s_!Byia!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F52aa8b7b-c36d-4ca6-a96c-ab346fbdc85a_668x1128.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>What's still missing, then, which was available in the PoC? </p><ul><li><p>it was possible to to shoot fireballs</p></li><li><p>there was player health/mana management, and related pickups</p></li><li><p>it was briefly possible (before I scrapped the feature from the PoC, to keep the scope limited) to pick up and use a melee weapon</p></li></ul><p>I will proceed to add back in these features in the upcoming development weeks, each time trying to implement them in an extensible, robust way. For example, I don't just want to shoot fireballs, I want a subsystem that can activate different gameplay actions depending on the player equipment and gestures. Fireballs will just be one of many usages of such subsystem.</p><p></p><h2>Did anybody care about all this?</h2><p>So far, the project has not received significant attention.</p><p>The DevLog has only a few subscribers, and social media following is scarce too.</p><p>Still, there's a small set of loyal readers which make me happy, and some friends who support me with <em>likes</em> or reposting them to their audience. That's super-appreciated.</p><p>I hoped in a little more recognition, but I don't care that much. I know very well that there's an abundance of good projects out there, often better presented, and that the attention span of people online (myself included) is very short these days. </p><p>The core <em>hook</em> of the game should be its locomotion system, and that's something that is difficult to show in a convincing way to a mainstream audience. A handful of VR gamers and developers interacted with me enthusiastically about the concept, and that was refreshing.</p><p>I know very well that games catch people's eyes with cool visuals, before everything else, and the <em>Particular Reality</em> prototype graphics is currently awful: a bunch of placeholder elements and test colors picked almost randomly, with a couple of particle VFXs thrown in, a feeble attempt to push the imagination towards the right direction. </p><p>But I don't want to focus on the visuals right now: I want to complete the core gameplay systems first. I'm trying to play the long game, and I'm accepting that most people won't care at all about the project at its current stage.</p><p>It's also about not picking too many battles at the same time. I'm working hard to keep the gameplay logic separated from the presentation, so that I will be able, when the time comes, to focus on the visuals without breaking the gameplay (too much).</p><p>That said, I plan to change the most awful elements (like the red/green bridge portal *covers*) soon, provided it's something that doesn't take too much time. When I get to have all the PoC features implemented in the current version of the game, I'll capture new videos for the website and try to push them a little on social media.</p><p>Long story short: I'm not satisfied, but I'm not disappointed either. I will be disappointed only if the game will fail to gain any attention even when it will feature "real" graphics and audio.</p><p>Which might very well happen, but hey, let's hope not.</p><p>Meanwhile, if you want to push the project towards some kind of "social media respectability", you can follow its account on your favourite platform (or, even better, on all of them!).</p><p>And of course, if you're interested in articles like this one, consider subscribing to the DevLog.</p><p>Thanks for your support!</p><p></p><h2>Year One Articles</h2><p>To complete this year-long recap, I'm going to provide an "index" of the DevLog material I published so far, separating the "extras".</p><p>If this is the first time you run into an article of the DevLog, well, there's quite a lot of stuff you might want to read!</p><p></p><div><hr></div><h3>Extras</h3><ul><li><p><a href="https://particularreality.substack.com/p/introducing-particular-reality">Introducing "Particular Reality"!</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;6b45f428-13cd-4cf0-9809-c0bec2459742&quot;,&quot;caption&quot;:&quot;Hello! After many years of VR development for industrial and heritage applications, I decided to finally roll up my sleeves and start working on a VR game, &#8220;Particular Reality&#8221;.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Introducing \&quot;Particular Reality\&quot;!&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-09-27T15:30:22.704Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/TOUcYKtXXLg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/introducing-particular-reality&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137389742,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:2,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/september-2023-interlude">September 2023 interlude - The Unity fiasco, Quest 3 and more</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;47332485-cf03-4f2b-b668-f61ba48613e9&quot;,&quot;caption&quot;:&quot;I had set my prototyping deadline to August 20th because I knew I would be busy after that (and for the whole month of September). Indeed, I was, and I did zero further development on the prototype.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;September 2023 interlude&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-21T17:15:12.454Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/m_wuX2Sciow&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/september-2023-interlude&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137895048,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/coding-conventions-and-unity-best">Coding conventions and Unity best practices - With a fair bit on the worst practices too</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;c6d084b7-588a-4cba-85c6-cfe1e43745f9&quot;,&quot;caption&quot;:&quot;The code for the first 16 days of \&quot;game-jammy\&quot; prototyping is not something to show to the world, but in the upcoming entries, where I'll start doing things properly (at least, at the best of my capabilities, and for my taste), I'll be sharing some snippet here and there.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Coding conventions and Unity best practices&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-28T17:00:16.352Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74ee3b22-2f2d-416a-b597-f106d3b67d45_2560x1040.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/coding-conventions-and-unity-best&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:138342332,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/how-to-pick-a-game-project">How to pick a game project - Or, at least, how I did it</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;fdec82a2-8ff2-40b3-8f27-037930355b75&quot;,&quot;caption&quot;:&quot;As an extra post before restarting development on Particular Reality, I thought it could be interesting to write about why I decided to work on this game and not another.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;How to pick a game project&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-01-13T18:00:38.178Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4fbfa668-1dd5-4a30-8f9b-6582e4026894_400x400.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/how-to-pick-a-game-project&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:140648204,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/deep-dive-into-body-tracking">Deep dive into body tracking - Tales from the Meta SDK</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;51110236-cb35-4d13-a05e-ebe84afd8614&quot;,&quot;caption&quot;:&quot;Before proceeding with implementing new features, I wanted to gain a deeper understanding of the part of the Meta SDK that takes care of the body tracking data and its processing.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Deep dive into body tracking&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-02-24T15:00:48.445Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07324576-09c1-4379-93b3-e93c7197e2e4_1515x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/deep-dive-into-body-tracking&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:141959169,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/a-new-development-pc">A new development PC - Hardware and software setup</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;fb588e92-5caa-4f0e-8c32-571e4e47d7d6&quot;,&quot;caption&quot;:&quot;The dark side of solo development&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;A new development PC&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-04-13T16:30:29.034Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa084cb25-e483-415a-bcf0-5bba3ded7b64_1440x1080.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/a-new-development-pc&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:143549259,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/the-road-towards-a-better-devlog">The road towards a better DevLog - Featuring: a static site generator written in Rust</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;18960f83-5cf4-4179-bb65-0cc403856a72&quot;,&quot;caption&quot;:&quot;There's a famous story about Donald Knuth developing TeX (which then evolved into LaTeX) because he didn't like how his books looked when printed. It took years.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;The road towards a better DevLog&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-05-19T20:15:02.986Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7497f3cf-f812-469e-9ea7-38e61c0b8dd7_1920x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/the-road-towards-a-better-devlog&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:144783641,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><div><hr></div><h3>Proof of Concept Prototyping </h3><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-01-2023-08-05">day 01: Setup, hexagonal grid</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;42546097-b45b-4976-ac40-78c2431ad6f0&quot;,&quot;caption&quot;:&quot;Sixteen days with nothing else on my calendar, ideas distilled through years, and the thrill of an empty project repository: #prototyping for the next game by BinaryCharm starts now! #vr #indieDev #gameDev&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 01 [2023-08-05]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-09-28T17:00:20.696Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2ac3ba4a-4edd-4df9-8163-767a95b78156_692x568.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-01-2023-08-05&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137347171,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-02-2023-08-06">day 02 - Basic input, target platform selection</a>]</p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;9e51c31e-9db5-432c-9724-c4371ee48438&quot;,&quot;caption&quot;:&quot;I started the day with some more reading of the Meta documentation, and ended up installing the Meta XR Simulator and doing some additional project configuration to minimize build times.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 02 [2023-08-06]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-09-29T17:00:18.091Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/_0wyMDIQOHU&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-02-2023-08-06&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137495047,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-03-2023-08-07">day 03 - Teleporting between platforms (portal logic)</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1f2e29b2-e985-4b45-b4c0-7c079c37b288&quot;,&quot;caption&quot;:&quot;The next major task on my quest to build the Particular Reality locomotion system is allowing movement to a target platform by going through a portal. Not sure about the visual details yet, but it's not important at this stage.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 03 [2023-08-07]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-09-30T17:00:13.066Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffacb2c66-56cf-48d6-8a28-4c42bde4788e_1024x1024.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-03-2023-08-07&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137498443,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-04-2023-08-08">day 04 - Teleporting between platforms (portal visualization)</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;60b963b0-a3c4-41e9-9219-6a2aabd252c0&quot;,&quot;caption&quot;:&quot;It's portal rendering day!&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 04 [2023-08-08]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-01T17:00:17.127Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/6jRloHam0mk&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-04-2023-08-08&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137499537,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-05-2023-08-09">day 05 - Combat system basics</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;5b146779-604c-447e-9850-99596c197048&quot;,&quot;caption&quot;:&quot;I don't have a detailed vision on what I want for the combat system, but I have some ideas to get started.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 05 [2023-08-09]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-02T17:00:13.864Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/7imR7-hrDUE&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-05-2023-08-09&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137554713,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-06-2023-08-10">day 06 - Hand poses recognition</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;781e9ca1-003f-49be-b67c-06c248dc97ec&quot;,&quot;caption&quot;:&quot;I have some basic ideas about the hand input I need to handle, that I'm going to explain along the way.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 06 [2023-08-10]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-03T17:00:07.979Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/R1aooiQtMz8&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-06-2023-08-10&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137609309,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-07-2023-08-11">day 07 - Improving portals</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;d9859f15-ccad-4008-8601-fbb3afa398fb&quot;,&quot;caption&quot;:&quot;As first task for today, I'd like to implement proper input handling for portal control.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 07 [2023-08-11]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-04T17:00:19.057Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/l-3jJ0kM0yk&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-07-2023-08-11&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137654069,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-08-2023-08-12">day 08 - Enemy basics</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;b56a4558-9f3b-426d-8f7e-dfa38f2f82cd&quot;,&quot;caption&quot;:&quot;In my original plans, yesterday I also would have implemented a ranged attack, but the locomotion refinement took more time than expected.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 08 [2023-08-12]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-05T17:00:08.973Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/_XDvxWJea_I&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-08-2023-08-12&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137655522,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-09-2023-08-13">day 09 - Sound effects</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ca2aaa6d-5ef1-4a22-8be5-a760c485ec6b&quot;,&quot;caption&quot;:&quot;I've been doing some thinking about what to do today, even considering my testing session of last night.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 09 [2023-08-13]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-06T17:00:38.471Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/n2OtFww6LrA&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-09-2023-08-13&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137703974,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-10-2023-08-14">day 10 - Health and mana</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;e5748765-f707-4be5-9adc-dcb7214556ac&quot;,&quot;caption&quot;:&quot;It's time to add some kind of \&quot;player health\&quot; system.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 10 [2023-08-14]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-07T17:00:40.729Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/XGzrZi4Gejg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-10-2023-08-14&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137704489,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-11-2023-08-15">day 11 - Fireballs</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;1edb59f6-3fc2-495e-b135-2afbf4da3074&quot;,&quot;caption&quot;:&quot;So, what can we use the collected mana for?&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 11 [2023-08-15]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-08T17:00:36.692Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/J3vgvLGwdX4&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-11-2023-08-15&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137705019,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-12-2023-08-16">day 12 - Health and mana pickups</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;78d2d64b-3a0d-4422-9451-a23c8a212628&quot;,&quot;caption&quot;:&quot;Including today, I have 5 days left for building the prototype.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 12 [2023-08-16]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-09T17:00:23.795Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/sOkLYQrHCGI&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-12-2023-08-16&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137706079,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-13-2023-08-17">day 13 - Additional sfx, gameplay improvements</a>  </p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;670330cb-588a-4817-bbff-317d02beb215&quot;,&quot;caption&quot;:&quot;Let's start taking yesterday's TODO list and crossing off what we did.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 13 [2023-08-17]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-10T17:00:23.491Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F74d72f65-a691-4a89-80cf-ab50eaaac387_483x485.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-13-2023-08-17&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137706071,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-14-2023-08-18">day 14 - VFX research and testing</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;8832b8f3-aa9a-4012-a13e-ce6707656b45&quot;,&quot;caption&quot;:&quot;Something came up, and I had to skip working on the prototype this morning.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 14 [2023-08-18]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-11T17:00:25.208Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce0c80c-ec5d-4b6a-b91a-0449648e8da2_4000x2250.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-14-2023-08-18&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137786569,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-15-2023-08-19">day 15 - Switching to URP, enemies VFX</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;0c4f348b-dee1-441f-96f6-4dc54785cf47&quot;,&quot;caption&quot;:&quot;So, as first step, as discussed yesterday, I need to update my prototype project so that it uses URP and not the \&quot;built-in\&quot; render pipeline.&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 15 [2023-08-19]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-12T17:00:39.916Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/pIKz3IJ7tXg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-15-2023-08-19&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137786898,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/poc-prototype-day-16-2023-08-20">day 16 - More VFX, wrapping up</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;644ee403-b87d-4f74-be2a-d195ce3538f5&quot;,&quot;caption&quot;:&quot;Final day for this first prototyping session of Particular Reality!&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;PoC Prototype - day 16 [2023-08-20]&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-10-13T17:01:07.342Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/_jGtGUSaKX8&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/poc-prototype-day-16-2023-08-20&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:137787342,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><div><hr></div><h3>Advanced Prototyping</h3><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-01">Week 01 - Getting started with level management</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;4deee8f1-0717-446c-bc6e-23ab83a1371b&quot;,&quot;caption&quot;:&quot;2023-10-02 - A bit of planning&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #01&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-11-04T18:00:12.408Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F65f5dd29-465c-4ef3-a3d3-0d574ed666f5_1006x689.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-01&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:138480059,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-02">Week 02 - Replacing the hexagonal grid</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;28ddc943-3029-4bc2-aee5-e7df4ed38556&quot;,&quot;caption&quot;:&quot;2023-10-09 - Swapping implementations&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #02&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-11-11T18:00:16.160Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51972a89-6397-4b71-8a98-e72dff314850_512x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-02&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:138494571,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-03">Week 03 - Handling multiple zones</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;b04c336f-873d-4f33-8b53-76605ef878d4&quot;,&quot;caption&quot;:&quot;2023-10-16 - Design ideas for the game world&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #03&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-11-18T18:00:29.988Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261700ae-02c1-495f-956e-db12ac167672_3247x1102.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-03&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:138342279,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-04">Week 04 - Moving between zones</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;9db3b0c2-275d-4e0e-8ccf-51e172442d30&quot;,&quot;caption&quot;:&quot;2023-10-23 - Switching zones&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #04&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-11-25T18:00:40.708Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/iFmJ-vWqUDA&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-04&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:139024695,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-05">Week 05 - Zones, portals and camera layers</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7439b057-a064-43fe-bb4e-68251a8a2d0c&quot;,&quot;caption&quot;:&quot;2023-11-06 - Player refactoring&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #05&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-12-02T18:00:33.799Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/Tmn4i8s3jS4&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-05&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:139158073,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-06">Week 06 - The color of the sky, the size of the play area</a>  </p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7f396fd0-c8ef-4970-823f-afe14ef701bf&quot;,&quot;caption&quot;:&quot;2023-11-13 - Zone based skyboxes&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #06&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-12-09T18:00:29.678Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3f1c2f63-f377-4207-8c61-345656891ae6_512x512.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-06&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:139395127,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-07">Week 07 - Menu and mixed reality limbo</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;fc872c17-070a-48bf-8ca2-b41be8eeafd1&quot;,&quot;caption&quot;:&quot;2023-11-20 - Fixing portal and portal indicators&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #07&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-12-16T18:00:18.107Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe302635-ddd8-4fef-a188-dc4d5df7be86_1024x1024.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-07&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:139679133,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-08">Week 08 - When in doubt, use a state machine</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;0b6faa51-920e-42b1-bef2-88544cd46d7d&quot;,&quot;caption&quot;:&quot;2023-12-04 - Application state and game state&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #08&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-12-23T18:00:39.372Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9592143f-80f1-47d6-81f9-b7111740622a_392x400.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-08&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:139997937,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-09">Week 09 - Refactoring player and portals</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;c0b7e8bd-9b35-4cd9-a3a5-38b91cbda19b&quot;,&quot;caption&quot;:&quot;2023-12-11 - Passive zone management&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #09&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2023-12-30T18:00:28.691Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf5f0bcf-2658-4d78-b7c9-8966bb1eea6b_790x400.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-09&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:140023176,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-10">Week 10 - Pause, Save, Load</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;ab9ae876-a715-4abf-a614-01968584dd69&quot;,&quot;caption&quot;:&quot;2023-12-18 - Pause, pause, pause&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #10&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:true,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-01-06T18:00:44.293Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/ggwwZ-KbRPY&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-10&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:140075035,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-11">Week 11 - Revisiting zones and bridge portals</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;af5321df-27c6-4ddb-8901-8f28b3332159&quot;,&quot;caption&quot;:&quot;2024-01-15 - Zone management bugfixing&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #11&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-01-20T20:00:27.652Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/bZ66Zgu-vKQ&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-11&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:140873215,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-12">Week 12 - Bridge portals done right</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;946909fb-37ba-41d9-9111-ec9933d876d7&quot;,&quot;caption&quot;:&quot;2024-01-22 - Bridge portals animations&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #12&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-01-27T17:30:11.986Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01094775-c933-4944-b374-79b9553f57b4_1080x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-12&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:141100955,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-13">Week 13 - Improving game world management</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;cd37fe85-6ffd-47fc-aa8f-24f56f8828f9&quot;,&quot;caption&quot;:&quot;2024-02-05 - A generic Id type&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #13&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-02-10T17:30:39.923Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/youtube/w_728,c_limit/Ruvk26nm1l8&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-13&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:141554626,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-14">Week 14 - A particular player avatar</a> </p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;34d67fe3-f539-4ffd-8db1-2a068a8bc276&quot;,&quot;caption&quot;:&quot;2024-02-12 - Revisiting the Meta samples&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #14&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-02-17T17:00:58.205Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0c790837-16ba-47e5-8c0a-a41f419924b7_1080x1080.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-14&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:141759666,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-15">Week 15 - Simulation state history</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;56388522-9ff1-4f5d-97bd-327945e87acf&quot;,&quot;caption&quot;:&quot;2024-02-26 - Body pose data&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #15&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-03-02T17:00:40.182Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be3f47b3-0b2a-45f2-96b0-b1d70adac19a_808x808.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-15&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:142237297,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-16">Week 16 - Motion capture and playback</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;f5f11fab-b347-4d3b-8338-a2c2a31f3f2d&quot;,&quot;caption&quot;:&quot;2024-03-11 - Capture data, and when to save it&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #16&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-03-17T15:01:03.586Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a894e3db-c660-48ca-9179-d1424fcf7cf1_942x942.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-16&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:142675949,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-17">Week 17 - Motion clips management</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;309e9fe8-11ef-41df-a78d-8bb6b2da99a3&quot;,&quot;caption&quot;:&quot;2024-03-18 - Handling multiple clips&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #17&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-03-23T17:30:58.202Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f22ab5eb-052d-4e5a-b35f-6d8a534a1507_970x970.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-17&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:142888221,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-18">Week 18 - Additional clip management and debugging</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;349527b4-397b-4a90-9332-6ca1aa4c9b11&quot;,&quot;caption&quot;:&quot;2024-04-15 - Update, update, rollback&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #18&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-04-20T19:00:49.516Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d5343bfe-1045-4ac2-bd02-cbb6aed7f7cd_757x757.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-18&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:143788538,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-19">Week 19 - Clip playback on skinned meshes</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a02b54e9-f19b-439d-a77c-51e69b80abfa&quot;,&quot;caption&quot;:&quot;2024-04-22 - Back to (movement) basics&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #19&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-04-27T17:30:38.715Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ff86b5b-bbba-4100-bee9-ecebcd10bd32_823x823.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-19&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:144069734,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-20">Week 20 - Return of the enemies</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;93fa949c-043f-4d1b-bb04-63e86ba66e10&quot;,&quot;caption&quot;:&quot;2024-05-20 - Thinking about enemies&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #20&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-05-25T15:01:14.267Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-20&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:144966479,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-21">Week 21 - Enemies life cycle</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7f5db9fe-d623-4283-8fd8-0f5483094f82&quot;,&quot;caption&quot;:&quot;2024-05-27 - Creating and destroying enemies&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #21&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-06-01T16:00:53.094Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-21&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:145197610,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-22">Week 22 - Interacting with enemies</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;0eddbb87-076e-4d3d-a581-ae22da79538a&quot;,&quot;caption&quot;:&quot;2024-07-22 - Back at work&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #22&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-07-27T15:01:21.727Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/958b6d0c-739c-4336-bdcc-fd6add066382_824x824.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-22&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:147061130,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><ul><li><p><a href="https://particularreality.substack.com/p/advanced-prototyping-week-23">Week 23 - Clearing Enemy Waves</a></p></li></ul><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;7d48d9ce-5017-4463-b067-367b30c19e3e&quot;,&quot;caption&quot;:&quot;2024-07-29 - Revisiting state management&quot;,&quot;cta&quot;:null,&quot;showBylines&quot;:true,&quot;showDescription&quot;:true,&quot;showImage&quot;:true,&quot;size&quot;:&quot;sm&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Advanced Prototyping - Week #23&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:21580446,&quot;name&quot;:&quot;Dario Scarpa&quot;,&quot;bio&quot;:&quot;Creative bit-flipper. Game/XR programmer. Passionate about stories, interactive or not.&quot;,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7367be8-c23c-4de5-b125-498589bea23a_300x300.jpeg&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2024-08-03T17:01:00.210Z&quot;,&quot;cover_image&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b014e4e1-8a54-420f-b0ff-7eb5aaa76f93_903x903.png&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://particularreality.substack.com/p/advanced-prototyping-week-23&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:147304045,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:0,&quot;comment_count&quot;:0,&quot;publication_id&quot;:null,&quot;publication_name&quot;:&quot;Particular Reality DevLog&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F92620960-2805-4ad8-89d5-10c507030be8_512x512.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><p></p><div><hr></div><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #23]]></title><description><![CDATA[Clearing Enemy Waves]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-23</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-23</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 03 Aug 2024 17:01:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b014e4e1-8a54-420f-b0ff-7eb5aaa76f93_903x903.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-07-29 - Revisiting state management</h2><p>As it surfaced in the latest entry with the "delayed hit" bug, I have some problem in my state management code.</p><p>Today I want to revisit the current execution flow, because there are too many details I don't remember well enough to debug it.</p><div><hr></div><p>For starters, I'm going to check the data structures involved in the state management, and how they're related. I'm going to write a few notes, both for you and for future me.</p><blockquote><p>Warning: this is not a suggestion about how to organize things... I'm just analysing the current state of the project, so that I can improve upon it: there are definitely improvements to be done on many fronts (naming, performances etc)</p></blockquote><p>Let's start with a diagram:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Zo_9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Zo_9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 424w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 848w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 1272w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Zo_9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png" width="600" height="586" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:586,&quot;width&quot;:600,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27871,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Zo_9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 424w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 848w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 1272w, https://substackcdn.com/image/fetch/$s_!Zo_9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F20a6f575-67a5-441b-9dde-c12a0786fef0_600x586.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each one of these classes uses generic parameters so that it can be instantiated with custom data and logic. I'm skipping them to keep things short and simple. I just left in the diagram `<code>D</code>`, which is the custom data, and `<code>S</code>`, which is an enumeration of the state identifiers. </p><p>Obviously, the diagram is incomplete (only some data members are shown, and no methods are included): it would take too much time to be exhaustive, and it would not help to focus on the core aspects.</p><p>An `FSM` class contains the definition of the logic to be executed for each state, and a label (mostly useful for debugging). </p><ul><li><p>A single instance of  a class inheriting from `<code>FSM</code>` can be used, of course, by multiple state managers on multiple execution acting on different data items.</p></li></ul><p>`<code>FSMExecData</code>` contain some data common to every FSM execution, and a custom data element that changes depending on the FSM definition.</p><p>`<code>FSMExec</code>` contains an `<code>FSMExecData</code>` and some additional data, including an input queue.</p><ul><li><p>Why the split between `<code>FSMExec</code>` and `<code>FSMExecData</code>`? It's a clear separation between  transient/runtime data and data that can be saved and loaded.</p></li></ul><p>A class inheriting from `<code>AStateManager</code>` wraps a definition (`<code>FSM</code>`) and an execution (`<code>FSMExec</code>`) which operates on some data bound to the FSM (`<code>FSMExecData</code>`).</p><p>Every `<code>update()</code>` of a `<code>AStateManager</code>` object runs its logic and calls the `<code>update()</code>` methods of its child state managers.</p><ul><li><p>I like to keep a precise, deterministic and stable execution ordering. Never been a fan of the Unity approach using the <em>script execution order</em> to define what gets simulated first.</p></li></ul><div><hr></div><p>My time is almost over for today,  so I'm just going to do a bit of clean-up and take note of possible problems. I usually add some short comments in the code, like `<code>// !</code>`,  to mark something that needs further inspection, and then go and revisit the marked zones (using "find in files" or simply the git diff highlighting the changed lines).</p><p></p><h2>2024-07-30 - More fixes, more problems</h2><p>After yesterday's inspection, I feel ok with trying some changes. </p><div><hr></div><p>While doing some more testing, I noticed that even keeping the history window to two elements only, sometimes a collision did not cause a proper hit. There's no delay, in this case the hit doesn't show up at all: I get the audio/wave feedback, but the enemy doesn't bounce.</p><p>I changed the update logic in the `EnemyStateManager` so that the movement of the enemy gets adjusted in a single point, after every force that can alter it gets accumulated. I also changed the global FSM execution moving it from `Update` to `FixedUpdate`, which should make more sense. Using `Update` should be limited to the _presentation_ layer behaviours, and should do some extrapolation of the data coming from the state data. But that's something to revisit at a later stage of development.</p><p>After these changes, it looks like the hits always register properly.</p><div id="youtube2-cRLSyWKO4TA" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;cRLSyWKO4TA&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/cRLSyWKO4TA?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>Then, I went and tried to understand the "history window" problem.</p><p>I found out that the `<code>GameStateManager</code>` did contain a separated `<code>SimStateManager</code>`, each wrapping a `<code>SimFSMExec</code>` initialized on one `<code>SimFSMExecData</code>` of the state history array. </p><p>That doesn't feel very sensible: a single `<code>SimStateManager</code>` should suffice, and should be able to set the (single instanced) `<code>SimFSMExec</code>` execution it wraps to the desired `<code>SimFSMExecData</code>` in the history buffer.</p><p>I guess I didn't want to change the `<code>FSMExec</code>` API while I was doing the save/load (which is when I drafted this, if I remember correctly), and as it didn't allow to change the wrapped `<code>FSMExecData</code>`, I worked around the problem doing this silly thing.</p><p>Having multiple `<code>FSMExec</code>` instances meant having different input queues, which probably introduced the "delayed" input problem discussed last week.</p><p>I changed the `<code>FSMExec</code>` API and eliminated the "multiple `<code>SimStateManager</code>`" anomaly.</p><div><hr></div><p>Unfortunately, the fix doesn't work well. Other issues with other elements of the game appear. There must be some subtle issue I'm overlooking.</p><p>I think it's time for a strategic decision: I will temporarily disable the history buffer, and only use a single simulation state. I was working on the enemies, I want to keep working on the enemies.</p><p>Taking a significant detour to fix something that will be needed by other features, not yet developed, doesn't feel right.</p><p>With a single simulation state, it looks like everything works properly. Actually, remember the annoying "one frame glitch" that showed up when stepping into a portal? It's gone, so it's probably related to that too (and/or to other execution order fixes I did today). </p><p>To celebrate the unexpected good news, a short capture using a Quest build (instead of Meta Link).</p><div id="youtube2-ivypi636zyU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;ivypi636zyU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/ivypi636zyU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>You can notice a couple of glitches: the avatar VFX briefly disappears, and at the end, when the zone changes, the enemies visualization is somewhat off. We'll get there.</p><p>But tomorrow, I will start <em>popping</em> enemies. </p><p></p><h2>2024-07-31 - Popping enemies</h2><p>My goal for today should be pretty simple: handling the destruction of enemies.</p><p>I already did the heavy lifting in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-21">week #21</a>, preparing and testing the full enemy life cycle.</p><p>So, it should only be a matter of sending to the enemy FSM the "pop" signal when appropriate, and everything should work.</p><div><hr></div><p>Usually, enemies have an "health" value. Hitting them decreases such value.</p><p>The current health value can change the enemy behaviour, depending on the context and the kind of game. When the health value reaches zero, the enemy gets eliminated.</p><p>This kind of approach introduces a problem, which is making players aware of the current enemy health level. I don't want visible health bars (one of my design principles is "fully diegetic"), so I will need to find something, on the visualization side, which gives at least a rough indication.</p><p>In the debug visualization, instead, I can definitely use a health bar similar to the one I'm using on the player wrist.</p><p>Anyway, let's start introducing the "damage" logic.</p><div><hr></div><p>To apply a damage, one should calculate the impact strength.</p><p>The simplest approximation that comes to my mind is considering the magnitude of the relative velocity of the "hitting" hand and the enemy.</p><p>Something like this:</p><pre><code>public void applyHit(Vector3 vHitVelocity) {
    applyForce(vHitVelocity);
    Vector3 vRelativeVelocity = vHitVelocity - m_vVelocity;
    float fImpactForce = vRelativeVelocity.magnitude;
    float fHealthAdjustment = fImpactForce * fHIT_DAMAGE_FACTOR;
    m_fHealth01 = Mathf.Max(0f, m_fHealth01 - fHealthAdjustment);
}
</code></pre><p>In the enemy FSM logic, I have a state transition, from `core` to `destroying`, when the health value reaches zero.</p><p>After a bit of tuning of the `<code>fHIT_DAMAGE_FACTOR</code>` value, it looks like it's working properly.</p><div id="youtube2-TslKU1tKWr0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;TslKU1tKWr0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/TslKU1tKWr0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p><div><hr></div><p>I hoped to at least do the health bar visualization too, but something came up - I'm done for today!</p><p></p><h2>2024-08-01 - Enemy health bars</h2><p>First day of August! The <em>Particular Reality</em> birthday (August 5th) is approaching... </p><p>Today I'm going to add a debug visualization for the enemies health, and use it to make sure that the player hits affect it in a sensible way.</p><div><hr></div><p>I can reuse the `p_energy_bar` prefab I already implemented for the player health/mana I'm visualizing near the wrists.</p><p>I just need to make sure that they are only visualized when in debug mode, and set the displayed value to the health value read from the enemy instance data.</p><p>Additionally, I need to rotate the energy bar so that it faces the player.</p><p>It's easy enough, so I'm going to take the chance to show a code snippet:</p><pre><code>// skipped the "using" directives

namespace BinaryCharm.ParticularReality.DebugManagement {
    
    [RequireComponent(typeof(IStateManagerProvider&lt;EnemyStateManager&gt;))]
    public class DebugEnemyBhv : MonoBehaviour {

        [SerializeField] private Transform m_rDebugVisTr;
        [SerializeField] private EnergyBarBhv m_rEnergyBar;

        private void Update() {
            bool bIsDebugging = DebugManagerBhv.i().isDebugging();
            
            if (bIsDebugging != m_rDebugVisTr.gameObject.activeSelf) {
                m_rDebugVisTr.gameObject.SetActive(bIsDebugging);
            }
            if (!bIsDebugging) return;

            IStateManagerProvider&lt;EnemyStateManager&gt;[] rSMPs =
                GetComponents&lt;IStateManagerProvider&lt;EnemyStateManager&gt;&gt;();
            EnemyStateManager rESM = 
                rSMPs[rSMPs.Length - 1].getStateManager();

            StateManagement.Enemy.Data rData = rESM.getFSMData();
            m_rEnergyBar.setLevel(rData.m_fHealth01);
            m_rEnergyBar.gameObject.setRendered(rData.m_fHealth01 &gt; 0f);

            BodyInputManagerBhv rBI = BodyInputManagerBhv.i();
            Vector3 vEnemyToPlayerDir =
                (rBI.getBasePos() - transform.position.withY(0f)).normalized;
            m_rEnergyBar.transform.rotation =
                Quaternion.LookRotation(vEnemyToPlayerDir, Vector3.up);
        }
    }
}</code></pre><p>The only thing worth discussing in this snippet is the part where I fetch the `<code>EnemyStateManager</code>`.</p><p>I'm getting all the components implementing the `<code>IStateManagerProvider&lt;T&gt;</code>` interface, and using the last of the returned array as data source.</p><p>Why? Well, such interface is implemented both by `<code>EnemyBhv</code>` and `<code>EnemyTestBhv</code>`. Every enemy has a `<code>EnemyBhv</code>` attached to it, but if I want a "test enemy" which I can control via inspector, as shown in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-21">week #21</a>, I just need to also attach at runtime an `<code>EnemyTestBhv</code>`and it starts driving the enemy, using a local `<code>EnemyStateManager</code>` detached from the whole game simulation.</p><p>So, the debug visualization uses the data coming from `<code>EnemyTestBhv</code>` if present, and from `<code>EnemyBhv</code>` otherwise. </p><p>Why didn't I directly use `<code>GetComponent&lt;EnemyTestBhv&gt;()`</code> and do `<code>GetComponent&lt;EnemyBhv&gt;()</code>` if the first call gave `null` as result? Well, `<code>EnemyTestBhv</code>` is not currently in the gameplay logic package, but in the main project `<code>Assets</code>`, because it depends on `<code>com.cyborgAssets.inspectorButtonPro</code>` (which I don't want to put as dependency of the gameplay logic package). The `<code>IStateManagerProvider</code>` allows to have a loose coupling. In a future refactoring pass, I'm going to separate the debugging scripts into a separate package, and at that point I will have no problem adding the external dependency.</p><p>The final goal, in terms of project/packages organization, is having a setup which will allow me to easily strip unnecessary elements (testing/debugging) from a build to be released. Not that I'm sure any of this prototype version will remain in the version that is going to be released someday.</p><div><hr></div><p>After a bit of fiddling with scale and positioning, here's the result. I think it works pretty well: if I push gently there's a small health falloff, while if I hit more violently or two-handedly the falloff is significant, or even able to destroy the enemy in a single hit.</p><div id="youtube2-qSNnyFZmtEc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;qSNnyFZmtEc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/qSNnyFZmtEc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p><h2>2024-08-02 - Changing wave, pausing enemies</h2><p>Now that I can destroy enemies, it's time to take care of the "wave changes": in <a href="https://particularreality.substack.com/p/advanced-prototyping-week-20">week #20</a>, we assigned to each zone definition a number of enemy wave definitions. In <a href="https://particularreality.substack.com/p/advanced-prototyping-week-21">week #21</a>, we designed the FSM that should take care of the waves logic, which could be summarized by "materialize the enemies of each wave as long as the previous wave is cleared". We also took care of instantiating and destroying the enemies together with the zone that hosts them.</p><p>It's time to activate the logic "incrementing" the current wave counter when the current wave state is `<code>destroyed</code>` and see if everything works as expected.</p><p>Here's an excerpt from `<code>ZoneStateManager</code>`:</p><pre><code>var rExecData = m_rFsmExec.getExecData().getData();
int iNumWaves = rExecData.m_rEnemyWavesFSMEx.Length;
if (rExecData.m_iClearedEnemyWaves &lt; iNumWaves) {
    EnemyWaveStateManager rCurrWaveStateManager =
        m_rEnemyWavesStateManagers[rExecData.m_iClearedEnemyWaves];
    EnemyWave.EState waveState = rCurrWaveStateManager.getState();
    if (waveState == EnemyWave.EState.hidden) {
        rCurrWaveStateManager.materializeEnemies();
    } else if (waveState == EnemyWave.EState.destroyed) {
        ++rExecData.m_iClearedEnemyWaves;
    }
}</code></pre><div><hr></div><p>Cool, it works!</p><p>I still have some time, but I don't want to start big tasks at the end of the week. Let's see the TODO list: </p><ul><li><p>go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system</p></li></ul><p>So, I don't want to touch the global VFX deltaTime setting, because I want to be able to control the simulation speed of different VFX elements in a different way.</p><div><hr></div><p>After checking the documentation, I started remembering that I had already faced this issue while working on the portal VFX. when implementing the <em>pause</em> feature for the first time.</p><p>Unfortunately, it looks like the VFX graph doesn't allow to manually call an "update" of the particles simulation passing a custom <em>delta time</em>. That's very annoying.</p><p>The only workaround that I can find some traces about is disabling the automatic particle update options and replicate the logic myself, passing the <em>delta time</em> as a `<code>float</code>` parameter.</p><p>Here&#8217;s the panel with the <em>Particle Update Options</em>:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DatM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DatM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 424w, https://substackcdn.com/image/fetch/$s_!DatM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 848w, https://substackcdn.com/image/fetch/$s_!DatM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 1272w, https://substackcdn.com/image/fetch/$s_!DatM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DatM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png" width="390" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:390,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13402,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DatM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 424w, https://substackcdn.com/image/fetch/$s_!DatM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 848w, https://substackcdn.com/image/fetch/$s_!DatM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 1272w, https://substackcdn.com/image/fetch/$s_!DatM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd02b0e7-8c8c-4952-8bea-3b929549b427_390x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>That shouldn't be too hard, but still annoying and I particularly hate the idea of having to do it for each VFX. Maybe there's a way to make a function and reuse it? I need to dig deeper. And I hate using node based editors.</p><p>I'm almost out of time, so for this week I'll be happy enough to apply the same hack I did in the case of the portal VFX: calling <em>pause</em> on the particles simulation. Yep, at least, the `<code>VisualEffect</code>` API allows to do that.</p><div><hr></div><p>To end the week, a short video capture where I show the "pausing" of the enemies and the clearing a wave. At the end of the video, the next wave appears.</p><div id="youtube2-s9WT3A7kDW4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;s9WT3A7kDW4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/s9WT3A7kDW4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>If you look carefully, around 00:23, there's an ugly glitching of the enemies particles. I think that's also due to automatic update in the VFX graph simulation.</p><p>When I change zone, as discussed long time ago, I move things around so that the current zone is always at the world origin. But the automatic particle update calculates the velocity on the basis of the world position change of the enemy, not its "logic" velocity (which is not affected by the offset I'm applying to move the zone). </p><p>I could work around that too passing the velocity to the VFX myself. Or, as it usually happens, I could spawn the first wave only after the player enters the zone, and not when it gets instantiated (which is when the player is on the <em>bridge portal</em> platform that acts as an entrance to the zone). But I kind like that you can see what awaits you on the other side of the portal!</p><p>Deep down, I know very well that the moment of the enemies appearance should be a design decision not influenced by technical limitations, so I'll end up fixing the problem anyway, because I know it's there and it would bother me even if not visible to the player.</p><div><hr></div><p>Prepare for something special next week!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #22]]></title><description><![CDATA[Interacting with enemies]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-22</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-22</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 27 Jul 2024 15:01:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/958b6d0c-739c-4336-bdcc-fd6add066382_824x824.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-07-22 - Back at work</h2><p>Hello again! Yes, I'm alive, and <em>Particular Reality</em> too.</p><p>Unfortunately, I had to take a pretty long break from the project development: my latest DevLog entry stopped at May 31. Almost two months! I feel awful about it.</p><p>That said, it's the harsh reality of this kind of "passion project" that, when other priorities arise, it is necessary to put things on hold.</p><p>But now, finally, I'm back, and hopefully I can stay on track for a while.</p><p>Now, were was I?</p><div><hr></div><p>Before my (unwanted and unexpected) break, I was working on the enemies management.</p><p>More specifically, I had took care of the enemies creation and destruction, pairing it to the "zones" creation and destruction. But there was still a lot to do.</p><p>Let's check the TODO list from last time:</p><ul><li><p>go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system</p></li><li><p>handle basic interaction with the enemies, so that the player can destroy them and clear waves</p></li></ul><p>I'm going to start from the second task.</p><div><hr></div><p>In the PoC, the player could destroy enemies by punching them (so, with a direct collision) and indirectly, by shooting a fireball.</p><p>We're not really focusing on combat techniques, and I'm not even sure I want the fireballs in the final game, so I'm going to keep it simple for now, and only allow destruction via direct hit.</p><p>Still, as we're now dealing with the "layered" architecture, it's going to be a little more complicated that what I did at the beginning.</p><p>Let's go back and see what I can "recycle", and what must be done completely different.</p><div><hr></div><p>I'm doing (on purpose) a pretty basic usage of Unity, but I definitely want to at least use the collision detection system.  This means having `<code>Collider</code>` components on the game objects in the scene, together with scripts with collision handler callbacks (that the engine will call when a collision happens).</p><p>The idea is that in my case these collision callbacks will do very little: essentially, record the event so that the simulation update can read it and take it into account in the update logic.</p><div><hr></div><p>How does this fit the current architecture?</p><p>The core "problem" is that the <em>presentation</em> layer, by definition, should only render the state of the game. But the collider component and collision handling script are on the enemy game object, in the Unity scene, and so it comes natural to think about those elements as part of the presentation. </p><p>Ignoring that feeling and thinking about their functionality (detecting events happening in the simulation, and altering the state consequently), I can consider them as a source of "internal input" data to push towards the state machines of the "logic elements" these "scene elements" are bound to.</p><p>Specifically, let's say I update the position of an "scene enemy" by setting it to the "enemy state" position (read from the `<code>EnemyStateManager</code>`). If the new position triggers a collision, I can generate (and store) a logic event that I will fetch and process during the calculation of the following game state (shown in the next frame).</p><p>Until now, I have only been dealing with this kind of data flow (coming <em>from</em> the scene) for the player input (fetching the body tracking data). Now, I'm going to try to do the same with the enemies.</p><p>How to do that in practice? For today, I'm going to just read a bit of code I don't remember anymore, and tomorrow I'm going to start implementing the changes.</p><p></p><h2>2024-07-23 -&nbsp;Implementing the collision data flow</h2><p>Ok, a quick overview of what I plan to do before starting to write some code.</p><p>As I anticipated, the core point is that I have the state management separated from the presentation.</p><p>Let's consider a single enemy: </p><ul><li><p>it has an `<code>EnemyStateManager</code>`, implementing its logic, which operates on  an `<code>Enemy.Data</code>` element (the part that goes into savegames)</p></li><li><p>it has a subtree of nodes in the Unity scene, with an `<code>EnemyBhv</code>` on the subtree root.</p></li></ul><p>`<code>EnemyBhv</code>` fetches the FSM execution state, and updates the scene nodes so that they visualize correctly such state.</p><p>Now, we need to have some data flowing in the opposite direction. Why?</p><p>As discussed yesterday, the collision detection happens in the scene, with Unity calling some callbacks when `<code>Collider</code>` components are properly setup on the relevant nodes.</p><p>When a valid collision happens, I will store its data into an input signal that will be sent to the `<code>EnemyStateManager</code>`, so that it can properly update the enemy state according to the collision data.</p><div><hr></div><p>Ok, it took me a while (this is what happens when you stop working on something for almost two months!), but I almost got things working.</p><p>I added an `<code>hit()</code>` method to the enemy, and also updated the testing script that shows up in the video at the end of <a href="https://particularreality.substack.com/p/advanced-prototyping-week-21">Week #21</a>.</p><p>Using the testing behaviour, I can "hit" the enemy pressing a button in the inspector, and see that the enemy velocity changes because of the applied force.</p><p>So why I wrote that I <em>almost</em> got it working?</p><p>Wearing the headset and trying to hit the enemy with a punch, like I did in the initial prototype, does not work..</p><p>This means I have a problem in the part which actually detects the collision in the scene.</p><p>The problem might be on the enemy side, but could also be related to the hands.</p><p>I remember using a sphere collider approximating each hand, for quick and easy collisions. But maybe something broke while I was working on the "particular" avatar?</p><p>It's late: we'll find out tomorrow.</p><p></p><h2>2024-07-24 - Restoring hand colliders</h2><p>This is a good moment to make a copy of the repository pointing it at the end of the proof of concept development: while working on the new architecture and features, I had to break a good portion of it. Adding stuff is important, but cleaning up old cruft on the way is also necessary, so I removed or disabled many things. That said, having a working copy of the PoC can be useful, to use it as reference/guide while replacing subsystems and components with the new, improved implementation.</p><div><hr></div><p>The "PoC" project works properly (you never know until you try), and I went in and checked how I handled the enemies/hands interactions.</p><p>Of course, something must always come up to interfere with working peacefully: <em>Air Link</em> doesn't work. I guess that some update broke something at some point in the last months. I already noticed it while working on another project, but I ignored the problem hoping it would fix itself with another update. That hasn't happened yet, unfortunately.</p><p>Irritating, but I don't want to waste the day trying to debug the issue, so I'll keep using the cable for a while.</p><div><hr></div><p>As I guessed yesterday, while working on the player avatar I had skipped the invisible "hitter" spheres attached to the hands. Restoring them, the collision gets registered and the enemies react to the collision.</p><p>But something is off in the way the enemies move: there's a delay, and they go in the wrong direction. I need to dig deeper.</p><div><hr></div><p>I went back to the PoC project and started porting some of the logic I implemented there, mainly the snippets handling enemy movement and its adjustment after a collision.</p><p>During the process, as I hoped, I had an idea about the odd behaviour I noticed while testing the new collision handling: I did not transform the "hit" vector (coming from the collision detection in the Unity scene) from the "scene" world space to the "zone relative" space that I use in the state management layer.</p><p>Additionally, I noticed that I don't keep track of the player position in such space: until now, I only needed the platform and zone identifiers. But now I need it, so I can steer the enemies not only towards the "player platform" but towards the exact player body position. No doubt that it will soon be useful in other contexts, too.</p><p>Tomorrow I'll try to take care of both issues.</p><p></p><h2>2024-07-25 - From world to zone space</h2><p>Following the intuition I had yesterday, I started coding and added a few methods to handle the transformation of vectors and quaternions from world space to the <em>Zone</em> local space.</p><p>Then, I used these methods in the "right place", which is the point of the code where the collision data recorded by the behaviour (on the scene node) is fetched and used to send an <em>hit</em> input to the FSM execution. This way, the "hit" event processed by the state management layer contains properly transformed data.</p><p>I also used this approach to steer the enemies towards the player, as I did in the PoC.</p><p>This time, I added a minimum distance check, to simplify testing: the enemies start going towards the player only when it's close enough.</p><p>This way I won't find myself submerged by all the enemies populating the current zone.</p><div><hr></div><p>Well, to my surprise, things started working right after the code started compiling. At least in terms of positions and directions. I'll show you what I mean...</p><div id="youtube2-DPnAU0ibWdg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;DPnAU0ibWdg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/DPnAU0ibWdg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>While the direction the spheres move towards looks reasonable, there's an insane delay from the moment I hit the spheres to the instant they react to the collision.</p><p>A one frame delay was expected considering the data flow I implemented, but this is way worse.</p><p>Tomorrow I'll revisit my steps and hopefully fix the problem.</p><p></p><h2>2024-07-26 - Debugging the interaction delay</h2><p>I'm going to think about possible causes for the delay in the interaction I observed yesterday and try to fix it.</p><div><hr></div><p>As you know if you've been reading the past entries, I'm "all in" on visual debugging.</p><p>So, for starters, I visualized colliders for hands and enemies when in debug mode.</p><p>Then, I added back in the "collision wave" feedback to show hits, and I also changed the enemy particles color to white when a collision is happening. I'm cheating a little, because I'm managing such waves directly in the visualization layer. This would not work in terms of frame-accurate load or rewind system, but it's good for debugging.</p><p>I also capped the enemy velocity to a low value, so hitting an enemy doesn't send it too far away, and it can come back to be punched again. </p><p>While doing these changes, I also found that I had left both the "old" and new movement calculation (before and after porting the PoC code). That definitely didn't help.</p><p>This is the result: as you can see, the collision detection works properly, but the delay is still there.</p><div id="youtube2-3q1Yvuz4sn8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;3q1Yvuz4sn8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/3q1Yvuz4sn8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p>My enemy "testing" behaviour currently only takes "hit" signals through the inspector buttons, and that works instantly, there's no observable delay.</p><p>Let's also bind the "hit" signalling to the hands collision and see what happens.</p><div id="youtube2-rvyc1_ir2RE" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;rvyc1_ir2RE&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/rvyc1_ir2RE?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Ha! Now we're talking!</p><p>Now I know the enemy state manager works properly too, because the testing behaviour calls into it too. In theory, the enemy state manager update logic should be called every `<code>FixedUpdate</code>` both in the testing code and in the enemies that get spawned in a `<code>Zone</code>` according to the level description. But something is obviously wrong, because the non-testing enemies react to hits with a massive delay.</p><p>Testing again, I feel like the delay is around 2 seconds, which makes no sense. Unless... oh no, I think I know what's going on.</p><div><hr></div><p>Yep, found it.</p><p>I don't remember if I wrote about this, but currently my state data is organized hierarchically in three layers, like this:</p><pre><code>`AppState` -&gt; `GameState` -&gt; `SimState[]`</code></pre><p>Basically, `<code>AppState</code>` contains some transient data and one `<code>GameState</code>`, which is what gets saved/loaded. `<code>SimState</code>` (which contains the player/enemy/world state of a certain instant) is the data structure that gets visualized by the presentation layer, basically representing a frame.</p><p>The `<code>GameState</code>` contains a "window" of a certain number of `<code>SimState</code>` structures implemented through a ring buffer. I constantly write to this buffer, overwriting old entries so that I only keep track of the latest `<code>iSIM_HISTORY_SIZE</code>` states. Currently, I had `iSIM_HISTORY_SIZE` set to 120... do you see where I'm going?</p><p>The current logic, when going from frame `<code>x</code>` to frame `<code>x+1</code>`, copies the previous state and then executes the update. But it doesn't copy the queued input, which is kept separated... and not cleared. So, it then gets processed after a full iteration into the `<code>SimState[]</code>` array has happened,  which is a little less than two seconds later.</p><p>It's the first time I notice this because, I guess, it's the first time I queue an input to a state machine to the next frame. Yikes!</p><p>To verify that the problem is that, I just had to momentarily set</p><pre><code>public const int iSIM_HISTORY_SIZE = 2;</code></pre><p>...and guess what, "it works".</p><p>As it always happens, it looks obvious now, and I feel very dumb for not having thought about it instantly. But then again, after two months far from the project, I didn't even remember about this simulation state "window".</p><div><hr></div><p>It's too late for a proper fix, but I'm happy enough to know exactly where to start on Monday.</p><p>I'm going to</p><ul><li><p>fix this state machine execution anomaly, a critical point of the whole game architecture</p></li><li><p>proceed with the enemies interaction, handling their destruction so that I can finally test the "waves" system</p></li></ul><p>And let's not forget I have another task from the previous TODO list:</p><ul><li><p>go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system</p></li></ul><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #21]]></title><description><![CDATA[Enemies life cycle]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-21</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-21</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 01 Jun 2024 16:00:53 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dsEo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-05-27 - Creating and destroying enemies</h2><p>This week I'm super-busy, but I don't want to stop working on the project. I'll try to put in at least a couple hours every day, and maybe I'll do some extras on Saturday to balance things out.</p><p>That said: time to build upon the groundwork laid last week and spawn some enemies!</p><div><hr></div><p>As previously discussed, in the "state management" layer, at least for now, it is the zone state manager which handles the enemy waves state update. I established that an enemy can't cross a zone boundary, so it felt the natural place in the data hierarchy of the game.</p><p>Consequently, in terms of instantiation and destruction of the enemies prefabs, it makes sense to similarly handle it together with the instantiation and destruction of the zone itself.</p><p>Will it be flexible enough to cover all cases the game will need? Probably not, but it should be quick to do, and it fits the current architecture.</p><div><hr></div><p>I temporarily changed the wave definition for the `<code>corridorZone</code>` from two purple sphere to one purple and one green: this way, I don't need to reach the `<code>rightTriZone</code>` to test the spawn of different kinds of enemies.</p><p>Then, I successfully activated the instantiation and destruction of enemy instances, together with the instantiation and destruction of the platforms: I can go around the game world and the purple/green prefabs properly enter and exit the scene hierarchy.</p><p>There's a "small" problem: the enemies get spawned at the zone origin. Why?</p><div><hr></div><p>I quickly figured it out: I was querying for the platform position from the zone definition in the presentation layer, which - obviously - is not ready yet, as I'm building the wave of enemies that will be part of exactly <em>that</em> zone definition.</p><p>So, I was bit by a default value returned if the actual value is not available, with a nice `<code>// TOFIX</code>` comment on the side. It turns out the `<code>// TOFIX</code>` comments don't help in avoiding errors if you never go and actually fix the problems you know are there!</p><p>I guess I know where to start tomorrow...</p><p></p><h2>2024-05-28 -&nbsp;Fixing the spawn positions</h2><p>Ok then, let's try to solve properly the enemy spawn positioning issue from yesterday.</p><div><hr></div><p>Until now, I didn't really need the platform positions in the state layer.</p><p>They don't move, so I just calculated the proper offset of a platform on the basis of its coordinates, and set that offset when instantiating its game object.</p><p>This is different, because enemies will need to move around.</p><p>I could use the spawn definition to calculate the enemy spawn position, like I do for the platforms, but that fix would stop being useful as soon as I want to move them, which is definitely going to happen very soon.</p><p>So, let's do the right thing and start handling the enemy positioning from the state layer.</p><div><hr></div><p>I added a `<code>PosRot</code>` in the `Enemy.State` structure, to store the zone-relative position and rotation of the enemy entity.</p><p>Conveniently, in the scene, I'm parenting the enemy `<code>GameObjects</code>` to the "zone parent" node. This way, I can set local position and rotation on the basis of the `<code>PosRot</code>` in the state, and everything should work fine even when I move the zones around (read the entries for weeks between 4 and 7 to know more about that).</p><p>I calculated the initial pose so that I can have a correct startup state, and the rest followed naturally from there.</p><p>Here's a screenshot showing a wave of enemies spawned on the correct platforms, as specified in the JSON definition on the right:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dsEo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dsEo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 424w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 848w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 1272w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dsEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png" width="1280" height="800" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:232802,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dsEo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 424w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 848w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 1272w, https://substackcdn.com/image/fetch/$s_!dsEo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9acd970-fef3-427f-b25b-1d5aaf88cc69_1280x800.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>2024-05-29 - Getting rid of `<code>ZonePlatformSettings</code>`</h2><p>There's something I "remembered" about the project while working on yesterday's fix which is bothering me a little.</p><p>I left as parameters defined in the code the radius of an hexagonal platform and the distance between two platforms. </p><p>Potentially, this would allow me to change those values from zone to zone, but I'm not currently defining these values in the zone description files: I have specific values hardcoded in `<code>PR_Defs.cs</code>`. </p><p>But I still pass the values around, which is a bit annoying when they could just be global constants.</p><p>I feel it's time for a design decision. Do I really want to have zones where these parameters change?</p><p>Honestly, I think I don't! The single platform radius must fit into the play area, or the whole concept of the game locomotion breaks. I could make them smaller, but that would make movement more cumbersome: you need to be able to easily step into a portal to go to an adjacent platform.</p><p>So, definitely not the radius change. What about the distance between platforms? That might be less problematic, but I don't see it adding any gameplay value. It could maybe change the aesthetics of zones a bit, but it could also be a bit confusing, and it would limit the kind of visual effects I want to add.</p><p>All things considered, I think I can lock these two values and simplify the code.</p><p>It's fine trying to be generic while things shape up, but I think there's value in periodically revising these decisions and trying to keep things simple.</p><div><hr></div><p>Done: I got rid of the `<code>ZonePlatformsSettings</code>` structure and all its usages, simplifying the code in many places.</p><p>I guess today was one of those days where you just remove unnecessary code, which is always good (well, at least when nothing breaks during the process).</p><p></p><p></p><h2>2024-05-30 - Enemy and EnemyWave state machines</h2><p>While we want to have different kind of enemies with different behaviours, there's a core life cycle which can be shared by all of them: they appear in the scene, execute their behaviour (which could include roaming around, attacking the player, defending a platform etc) and, ultimately, get destroyed and disappear.</p><p>It's time to start working on this, because to be able to test the "wave change", I need to be able to destroy enemies.</p><div><hr></div><p>Let's start with an important detail: a `GameObject` for an enemy entity that gets instantiated when we enter a zone, as we did on Monday, can be "active but doing nothing",  and so it must be able to represent a `hidden` state: we can't assume that, when we spawn it, it must appear in the scene. Consequently, we must explicitly define and handle a `hidden` state as parte of the enemy life cycle. I'm keeping instantiation and destruction of instances confined to few, specific moments. This will be valuable in the future.</p><p>Well, do you miss my state machine diagrams? It's time to prepare one...</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6Lq_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6Lq_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 424w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 848w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 1272w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6Lq_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png" width="527" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:527,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35539,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6Lq_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 424w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 848w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 1272w, https://substackcdn.com/image/fetch/$s_!6Lq_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad4465e6-a1e0-4475-9819-580d48a02e49_527x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><ul><li><p>an enemy instantiated but not yet "alive" is going to be in the `<code>hidden</code>` state</p></li><li><p>when its wave gets activated, the enemy will transition to the `<code>materializing</code>` state, which will involve an animation</p></li><li><p>when the animation is completed, the enemy will enter its `<code>core</code>` state, which for now could be an idle loop. At some point, in this state we're going to execute a nested FSM taking care of all the cool stuff an enemy will be able to do when active</p></li><li><p>when hit, an enemy will get destroyed, transitioning to the `<code>destroying</code>` state, involving another animation</p></li><li><p>when the destruction animation is completed, the enemy will reach is final state, `<code>destroyed</code>`</p></li></ul><p>In my first draft, I sent the enemy back to `<code>hidden</code>` after `<code>destroying</code>`, but then I decided that it's better to keep them separated. They might visually be the same (enemy not visible), but logically speaking they're different: one is ready to enter in action, the other has been destroyed.</p><p>Additionally, they might <em>not</em> be visually similar: we might decide to show some remains for a destroyed enemy, especially while developing, for debugging purposes.</p><p>What about the FSM for a wave?</p><p>The structure should be pretty similar, but the logic a bit different:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xd-H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xd-H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 424w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 848w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xd-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png" width="527" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:527,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37340,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Xd-H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 424w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 848w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 1272w, https://substackcdn.com/image/fetch/$s_!Xd-H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9109a00-ac16-4fd8-a4a9-442e1e614424_527x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I avoided using the <em>spawn</em> terminology and used <em>materialization</em> to emphasize that we are not creating the objects at that point, but activating them: creation and destruction is handled separately as we already discussed. </p><div><hr></div><p>To complete the day, I implemented the two state machines into `<code>EnemyStateManager</code>` and `<code>EnemyWaveStateManager</code>`.</p><p>A "blind", untested implementation following the diagrams: tomorrow, I'll try to see it in action.</p><p></p><p></p><h2>2024-05-31 - Enemy presentation layer</h2><p>We instantiate and destroy enemies game in the scene according to the active zones, and we have defined a basic FSM handling their state updates.</p><p>It's time to do another important step: implement the presentation layer of an enemy, which basically means "setting" its `<code>GameObject</code>` so that it correctly represents what is described by its state.</p><div><hr></div><p>To get started, I went back to the basic enemy implementation I did for the Proof of Concept prototype.</p><p>I used two simple procedural animations for the spawn and destruction of the "purple spheres", consisting of a scale change driven by an easing function.</p><p>Then, I have the particles VFX, and four sound effects:</p><ul><li><p>`<code>enemy_spawn</code>` for the materialization</p></li><li><p>`<code>enemy_alive</code>` with a looping sound for the `core` state</p></li><li><p>`<code>enemy_pop</code>` for the destruction</p></li><li><p>`<code>enemy_leech</code>` used when the enemy was damaging the player</p></li></ul><p>We don't need `<code>enemy_leech</code>` yet, but I definitely must use the other sounds.</p><div><hr></div><p>I adapted the two simple animations and the sound playback so that they are driven by an `<code>EnemyStateManager</code>`.</p><p>The system works like this</p><ul><li><p>a presentation behaviour (`<code>EnemyBhv</code>`) on the enemy prefab is "bound" to an `<code>EnemyStateManager</code>` by setting the data needed to fetch from the simulation state a specific `<code>EnemyStateManager</code>` (what zone? what wave? what enemy in the wave?)</p></li><li><p>during gameplay, that binding data is used to fetch the right `<code>EnemyStateManager</code>` from the whole game simulation</p></li><li><p>the `<code>EnemyStateManager</code>` data is read and used to "configure" the prefab with a `<code>set(EnemyStateManager rESM)</code>` method</p></li></ul><p>I don't remember how much I went into the details, but it's exactly how I already implemented the portals and the bridge portals "covers".</p><p>I remember that testing them was a bit annoying, so this time I'm going to do a bit of extra work to make debugging and tuning easy.</p><div><hr></div><p>Ok, I'm pretty happy about the result: I modified `<code>EnemyBhv</code>` a bit and did a `<code>TestEnemyBhv</code>` that can drive `EnemyBhv` with manual input via the inspector.</p><p>When I add a `<code>TestEnemyBhv</code>` to an enemy `<code>GameObject</code>`, it disables the `<code>EnemyBhv</code>`, meaning that it doesn't run its `<code>Update()</code>` method (which fetches the `<code>EnemyStateManager</code>` of the enemy and calls `<code>set(EnemyStateManager rESM)</code>` to configure the `<code>GameObject</code>`).</p><p>Instead `<code>TestEnemyBhv</code>` hijacks the processing and calls the `<code>set</code>` method with its own `<code>EnemyStateManager</code>`, operating on test data accessible through the inspector.</p><p>So, via inspector UI, I can</p><ul><li><p>set any init state and data (as if they're coming from a save game), and initialize the execution with those settings, pressing "Reinit"</p></li><li><p>inspect the current state data, and see how it changes following the FSM execution </p></li><li><p>use some buttons to send the events that the FSM can receive (in this case, `<code>Materialize</code>` and `<code>Destroy</code>`), and check that the processing works as expected</p></li><li><p>stop the normal, time based update, and execute the FSM one timestep at a time, pressing the "Step" button, or directly manipulate the state data (in this case, the animation progress)</p></li></ul><p>Let's see all this in action with a short video:</p><div id="youtube2-VKx6D_EK_RU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;VKx6D_EK_RU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/VKx6D_EK_RU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>This should cover all my needs, and I might extend it to save and load state configurations to test more complex situations.</p><p>With a little more work, I should also be able to generalize most of the test behaviour so that it works with anything built using my FSM-based gameplay system, but I'll think about that another time.</p><div><hr></div><p>Going back to the enemy presentation, this is a good start but it's not perfect: the VFX update doesn't use my simulation time, which sucks.</p><p>But I'm out of time, so, let's write a TODO list for next week, and call it a day.</p><ul><li><p>go back to the VFX graph stuff and see how to control the VFX simulation time, then bind it to my gameplay system</p></li><li><p>handle basic interaction with the enemies, so that the player can destroy them and clear waves</p></li></ul><p>As soon as these two points are done, I will be ready to port to the new implementation the enemy AI from the PoC (basic movement towards the player, and energy leeching attack).</p><p>Until next time!</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #20]]></title><description><![CDATA[Return of the enemies]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-20</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-20</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 25 May 2024 15:01:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZNCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-05-20 - Thinking about enemies</h2><p>After the three weeks break I devoted to the <a href="https://particularreality.substack.com/p/the-road-towards-a-better-devlog">static site generator</a>, I really need a minute of planning and design before jumping into the code.</p><p>Today I only have a couple hours, so I'm going to focus on that.</p><p>First, let's write an updated TODO list.</p><div><hr></div><ul><li><p>main game loop</p><ul><li><p>improve the <em>pause/unpause</em> visualization and interactions (now we access it with the "system" pinching gesture, or with the test "left hand thumbs down" gesture)</p></li><li><p>implement (more or less explicit) access to the <em>save/load</em> system in VR (now we just use keypresses for testing)</p></li><li><p>implement the time rewind mechanic, which is also related to the "embodiment" or the detachment of the avatar (like pause/unpause and) which also is part of the pause/unpause and save/load features</p></li></ul></li><li><p>better avatar management</p><ul><li><p>visual distinction for embodied/disembodied states</p></li><li><p>interaction between avatar and portals (when you stick an arm into a portal, it must not disappear but be different from the part which is outside the portal)</p></li></ul></li><li><p>better body mocap</p><ul><li><p>possibility to show a reference skeleton coming from some selected keyframes, useful to capture looping and "connected" clips</p></li><li><p>possibility to tune the capture frequency and to resave a clip lowering it</p></li></ul></li><li><p>performance</p><ul><li><p>add a pooling system to recycle dynamic objects</p></li></ul></li></ul><p>Ok, these are bullet points extracted from previous TODOs and I have also written a few new items.</p><p>I roughly sorted the items by putting the most important things first.</p><p>I definitely want to reach the point where I have a robust, complete main loop.</p><p>But I was thinking that, to have something interesting to distinguish the different states (both in terms of save/load that of time rewind), it's time to put back some enemies in the game.</p><div><hr></div><p>In the initial "16 day" PoC prototype, for the enemies, I went for the <a href="https://particularreality.substack.com/p/poc-prototype-day-08-2023-08-12">minimalistic approach</a> which was appropriate for that stage.</p><p>Enemies were simple spheres, and their behaviour was simply "float towards the player and collide with them to drain some energy". They had</p><ul><li><p>a collider the player could hit (directly with the hands, or shooting fireballs)</p></li><li><p>two simple procedural animations for spawning and destruction</p></li><li><p>VFX and SFX to make them a little more interesting</p></li></ul><p>Everything was pretty much hardcoded. Waves with increasing number of enemies were progressively spawned according to a simple rule.</p><p>Now, it's time to set-up things properly, in a more flexible way so that I can spawn and handle different kinds of enemies, with distinctive behaviours and characteristics.</p><p>Let's try and put down a few ideas: I might change things at a later stage of development, but I need to start somewhere.</p><p>I think there are two core approaches that could suit the game in interesting ways.</p><p>One, more focused on exploration, could involve going around the game world progressively unlocking new areas, maybe with some puzzles and power-ups. In that case, enemies should respawn when entering a <em>zone</em> you have previously cleared, or going around the world would get boring pretty fast. There could be some "special" boss enemies that don't come back once you get rid of them, of course. This would be in some sense a "metroidvania"-like approach. Each <em>zone</em> would define a number of specific enemies to place at specific points, maybe adding a bit of randomness.</p><p>The second approach would be somewhat simpler, more linear: I would define for each <em>zone</em> some <em>waves</em> of enemies, and when the players clear them, they can access the following <em>zone</em>, never going back.</p><p>Waves could be defined by hand for each <em>zone</em> (even in that case adding some procedural/random variation, maybe). An excellent example of this structure, with lots of twists that made it more interesting, is <em>Hades</em>.</p><p>I like both approaches, but I'm a bit scared by the design complexity of the first one, and I feel like the second can work better with a more casual audience too. </p><p>But can I cheat and implement an enemies management system which could potentially serve both, and postpone high level design decisions?</p><div><hr></div><p>Let's say that a set of enemies spawned at a single time is called `<code>EnemyWave</code>`.</p><p>A manager for these waves should take care of the specified enemies entities, both in terms of spawning and destruction.</p><p>I could make it more "active" or "passive", depending on how I want to wire it to the rest of the gameplay code.</p><p>Let's say I want to keep it "passive", it could have this API:</p><ul><li><p>`<code>void spawnEnemies(EnemyWave rEnemyWave)</code>`</p></li><li><p>`<code>void clearEnemies()</code>`</p></li><li><p>`<code>bool isSomeEnemyAlive()</code>`</p></li></ul><p>Could this simple set of functionalities allow me to implement all the scenarios I'm considering?</p><p>Let's try and imagine how.</p><ul><li><p>Metroidvania-like approach: a `<code>Zone</code>` defines a single `<code>EnemyWave</code>`, describing the enemies that respawn (via a call to `<code>spawnEnemies(rZoneWave)</code>`) every time the player enters the area. Clearing the wave is not mandatory to proceed to a nearby `<code>Zone</code>`, so the portals "locking" is not related to the fact that the wave has been cleared or not. When a player exits, any enemies still alive would be automatically cleared calling `<code>clearEnemies()</code>`.</p></li><li><p><em>Hades</em>-like approach: a `<code>Zone</code>` defines one or more `<code>EnemyWave</code>`. When the player enters the `<code>Zone</code>`, the portals lock, and a counter of cleared waves (let's say `<code>iClearedZonesCnt</code>`) is set to `0`. Then, while that counter is less than the number of waves defined for the zone, if there are no alive enemies (checked calling `<code>isSomeEnemyAlive()</code>`), a new `<code>spawnEnemies(rZoneWaves[iClearedZonesCnt])</code>` is called, and the counter gets incremented. When there are no enemies alive, and no more waves to spawn, the exit portals unlock</p></li><li><p>additionally, a survival mode: there's a single `<code>Zone</code>`, with an endless stream of new waves, which get generated as needed (when the manager detects there are no alive enemies)</p></li></ul><p>This all sounds pretty reasonable, so I think that tomorrow I'm going to start implementing all this. I still need to think about how to define an `<code>EnemyWave</code>`, but I can definitely start with a basic definition and then improve it later, without even changing the method signatures.</p><p>For starters, it could be a list of pairs, where each pair is made of an enemy type and the zone location where to spawn them.</p><p>What about the enemy types? As first step, I'm going to put back in the spheres of the PoC, maybe doing a couple of variants. Then, I'd like to define a more sophisticated enemy type that makes use of the mocap features I implemented recently. In terms of behaviour, I'm going to define a set of basic features that can be composed to implement different "AI" for the enemies.</p><p>I'm going to define a perception system, or fake it (what does an enemy know about the world?)</p><p>Then I'm going to have a path finding system, so that an enemy can calculate how to reach a specific platform. And different kind of enemies could move in different ways (floating, jumping to an adjacent platform, teleporting....). What about attacks? Some enemies could only damage the player with a direct collision, while others might have ranged attacks, shooting projectiles or energy waves. Another interesting point might be having some sort of "group" behaviour when multiple enemies coordinate together.</p><p>There's plenty of possibilities, and in the spirit of prototyping, I'm going to try and implement many options and test them. Then, I'll decide which one work well together and get rid of the others</p><p>Tomorrow I'll start drafting the code.</p><p></p><h2>2024-05-21 - Enemy waves definition</h2><p>Yesterday I wrote a lot, and didn't touch the code. Today I'm going to do quite the opposite, starting to implement what I described yesterday!</p><p>I'm going to start with the data definition of the enemy waves, taking care of their persistence too, so that I can load them similarly to how I load a zone description containing a bunch of platforms arranged in a specific way.</p><div><hr></div><p>I decided to keep the enemy waves definitions separated from the zone definitions, at least for now.</p><p>I added a simple enumeration in the global definitions of the game, to have a first, basic, enemy type identifier:</p><pre><code>// from PR_Defs.cs
    public enum EEnemyType
    {
        purpleSphere,
        greenSphere
    }</code></pre><p>Then I defined an `<code>EnemySpawnDesc</code>`, with information about the type of enemy and their spawn coordinates.</p><pre><code>// EnemySpawnDesc.cs
using BinaryCharm.ParticularReality.HexGrids;
using Newtonsoft.Json;

namespace BinaryCharm.ParticularReality.LevelManagement {
    public struct EnemySpawnDesc {

        [JsonProperty("eEnemyType")]
        public readonly EEnemyType m_eEnemyType;

        [JsonProperty("hexCoords")]
        public readonly Hex m_hexCoords;

        [JsonConstructor]
        public EnemySpawnDesc(
                EEnemyType eEnemyType,
                Hex hexCoords) {
            m_eEnemyType = eEnemyType;
            m_hexCoords = hexCoords;
        }

    }
}</code></pre><p>An `<code>EnemyWaveDesc</code>` is a simple aggregate of `<code>EnemySpawnDesc</code>`. I also added a string id, which might be useful while debugging (to distinguish specific waves).</p><pre><code>// EnemyWaveDesc.cs
using Newtonsoft.Json;

namespace BinaryCharm.ParticularReality.LevelManagement {
    public struct EnemyWaveDesc {
        [JsonProperty("sId")]
        public readonly string m_sId;

        [JsonProperty("rEnemySpawns")]
        public readonly EnemySpawnDesc[] m_rEnemySpawns;

        [JsonConstructor]
        public EnemyWaveDesc(
                string sId,
                EnemySpawnDesc[] rEnemySpawns) {
            m_sId = sId;
            m_rEnemySpawns = rEnemySpawns;
        }

    }
}</code></pre><p>Finally, I prepared a quick generator method to automatically prepare waves of enemies that could be suitable for a given `<code>Zone</code>`. This will help me with testing, considering that I don't have a level editor yet.</p><p>I pass in a dictionary asking to randomly put in the zone a certain number of enemies of specific types, and the method, provided there's enough platforms available (I also excluded the "bridge" platforms), assigns a random platform to each desired enemy instance.</p><p>I used a kind of Fisher&#8211;Yates shuffle algorithm: I put the identifiers of the suitable platforms into an array, and pick random identifiers from such array, progressively excluding them from the selection to avoid repetitions, so that I can only have one enemy per platform. I really like this algorithm as it has linear complexity, doesn't use extra data structures, and at the same time it's pretty intuitive. Intuitive enough that I haven't checked an "official" implementation but just wrote the following code, and it might very well contain bugs - you've been warned.</p><pre><code>public class RandomEnemyWaveGenerator : IEnemyWaveGenerator
{
    public EnemyWaveDesc getWave(
            string sId,
            ZoneDesc zoneDesc, 
            Dictionary&lt;EEnemyType, int&gt; rEnemyTypeToCountMap) {
        int iTotEnemies = 0;
        foreach(var kv in rEnemyTypeToCountMap) {
            iTotEnemies += kv.Value;
        }

        PlatformDesc[] rZonePlatforms = zoneDesc.getPlaformsDescs();
        List&lt;int&gt; rNonPortalPlatforms = new List&lt;int&gt;();
        for (int i = 0; i &lt; rZonePlatforms.Length; ++i) {
            PlatformDesc platform = rZonePlatforms[i];
            if (!platform.isBridge()) {
                rNonPortalPlatforms.Add(i);
            }
        }

        if (rNonPortalPlatforms.Count &lt; iTotEnemies) {
            throw new System.Exception("Too many enemies for this zone!");
        }
        EnemySpawnDesc[] rSelected = new EnemySpawnDesc[iTotEnemies];


        int[] rSuitableIndices = rNonPortalPlatforms.ToArray();
        int iUsedPlatforms = 0;
        foreach (var kv in rEnemyTypeToCountMap) {
            for (int i = 0; i &lt; kv.Value; ++i) {
                int r = UnityEngine.Random.Range(
                    iUsedPlatforms, rSuitableIndices.Length
                );
                int iRandomIdx = rSuitableIndices[r];
                PlatformDesc randomUnusedPlatform = rZonePlatforms[iRandomIdx];
                rSelected[iUsedPlatforms] = new EnemySpawnDesc(
                    kv.Key,
                    randomUnusedPlatform.m_hexCoords
                );
                int tmp = rSuitableIndices[iUsedPlatforms];
                rSuitableIndices[iUsedPlatforms] = rSuitableIndices[r];
                rSuitableIndices[r] = tmp;
                ++iUsedPlatforms;
            }
        }
        return new EnemyWaveDesc(sId, rSelected);
    }
}</code></pre><p></p><h2>2024-05-22 - Assigning wave definitions to zones</h2><p>I could spawn wave definitions on the fly, but I prefer to take another preliminary step.</p><p>I want to generate some wave definitions for some of the zones in my current "game world", save them once, and then just load them. Exactly like I did with the <em>zone</em> definitions.</p><p>Having the enemies spawn consistently on the same platforms will be helpful while testing, and it will be definitely needed at some point by the "real" game: the only difference is that the waves are going  to be defined through a level editor.</p><div><hr></div><p>I defined another simple data type, collecting the wave definitions for a specific zone:</p><pre><code>using BinaryCharm.Interfaces;

using Newtonsoft.Json;

namespace BinaryCharm.ParticularReality.LevelManagement {
    public struct ZoneEnemyWavesDesc : IHasStringId {

        [JsonProperty("sId")]
        private readonly string m_sId;

        [JsonProperty("rWaves")]
        private readonly EnemyWaveDesc[] m_rWaves;

        [JsonConstructor]
        public ZoneEnemyWavesDesc(
                string sId,
                EnemyWaveDesc[] rWaves) {
            m_sId = sId;
            m_rWaves = rWaves;
        }

        public string getId() {
            return m_sId;
        }
        public EnemyWaveDesc[] getWaves() {
            return m_rWaves;
        }
    }
}</code></pre><p>I added to `<code>PersistenceUtils</code>` some utility methods to automatically save/load the `<code>ZoneEnemyWavesDesc</code>` objects to a specific directory, using their id as filename. Very similar to what I already did for the zones.</p><p>Let's define a few increasingly difficult "fights" for three zones of our test world:</p><ul><li><p>`<code>corridorZone</code>`:</p><ul><li><p>`<code>corridorWave0</code>`: 2 purple spheres</p></li></ul></li><li><p>`<code>leftTriZone</code>`:</p><ul><li><p>`<code>leftTriWave0</code>`: 3 purple spheres</p></li><li><p>`<code>leftTriWave1</code>`: 6 purple spheres</p></li></ul></li><li><p>`rightTriZone`</p><ul><li><p>`<code>rightTriWave0</code>`: 1 green sphere, 2 purple spheres</p></li><li><p>`<code>rightTriWave1</code>`: 3 green spheres, 2 purple spheres</p></li><li><p>`<code>rightTriWave2</code>`: 6 green spheres, 6 purple spheres</p></li></ul></li></ul><p>The code to generate the desired waves is a (verbose) transposition of these definitions.</p><pre><code>// called on startup, after loading ZoneDescs
static void WriteTestWaves(Dictionary&lt;string, ZoneDesc&gt; rZoneDescs) {
    RandomEnemyWaveGenerator rRandomWaveGen =
        new RandomEnemyWaveGenerator();

    EnemyWaveDesc rCorridorWave0 = rRandomWaveGen.getWave(
        "corridorWave0",
        rZoneDescs["corridorZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.purpleSphere, 2 }
        }
    );

    EnemyWaveDesc rLeftTriWave0 = rRandomWaveGen.getWave(
        "leftTriWave0",
        rZoneDescs["leftTriZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.purpleSphere, 3 }
        }
    );
    EnemyWaveDesc rLeftTriWave1 = rRandomWaveGen.getWave(
        "leftTriWave1",
        rZoneDescs["leftTriZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.purpleSphere, 6 }
        }
    );

    EnemyWaveDesc rRightTriWave0 = rRandomWaveGen.getWave(
        "rightTriWave0",
        rZoneDescs["rightTriZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.greenSphere, 1 },
            { EEnemyType.purpleSphere, 2 }
        }
    );
    EnemyWaveDesc rRightTriWave1 = rRandomWaveGen.getWave(
        "rightTriWave1",
        rZoneDescs["rightTriZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.greenSphere, 3 },
            { EEnemyType.purpleSphere, 6 }
        }
    );

    EnemyWaveDesc rRightTriWave2 = rRandomWaveGen.getWave(
        "rightTriWave0",
        rZoneDescs["rightTriZone"],
        new Dictionary&lt;EEnemyType, int&gt;() {
            { EEnemyType.greenSphere, 6 },
            { EEnemyType.purpleSphere, 6 }
        }
    );

    ZoneEnemyWavesDesc corridorWaves = new ZoneEnemyWavesDesc(
        "corridorZone", new EnemyWaveDesc[] {
            rCorridorWave0
        }
    );

    ZoneEnemyWavesDesc leftTreeWaves = new ZoneEnemyWavesDesc(
        "leftTriZone", new EnemyWaveDesc[] {
            rLeftTriWave0,
            rLeftTriWave1
        }
    );

    ZoneEnemyWavesDesc rightTreeWaves = new ZoneEnemyWavesDesc(
        "rightTriZone", new EnemyWaveDesc[] {
            rRightTriWave0,
            rRightTriWave1,
            rRightTriWave2,
        }
    );

    PersistenceUtils.storeEnemyWaveDesc(corridorWaves);
    PersistenceUtils.storeEnemyWaveDesc(leftTreeWaves);
    PersistenceUtils.storeEnemyWaveDesc(rightTreeWaves);
}</code></pre><p>Running this code gets me three JSON files in such directory, containing data that looks reasonable at a quick glance.</p><p>Tomorrow, I'll load these definitions and proceed.</p><p></p><h2>2024-05-23 - Loading waves and initializing their state</h2><p>So, on startup, where I actually load the zone definitions, I will also load the waves definitions.</p><p>Yesterday, by giving them the same string identifier, I established an implicit mapping from the `<code>ZoneEnemyWaveDesc</code>` to the `<code>ZoneDesc</code>` they refer to .</p><p>This is very basic and inflexible, but still a tiny bit better that saving the `<code>ZoneEnemyWaveDesc</code>` directly into the `<code>ZoneDesc</code>`: I can easily try different `<code>ZoneEnemyWaveDesc</code>` instances for a single `<code>Zone</code>` without having to touch the zone description.</p><div><hr></div><p>You might remember from past articles that I'm only loading the "world" data definitions at startup,  using to build a better representation to use at runtime (switching from `<code>string</code>` based identifiers to `<code>int</code>` based identifiers in the process). </p><p>Now, I augmented the `<code>RTZone</code>` (`<code>RT</code>` for "runtime") data type by adding an array of `<code>EnemyWaveDesc</code>`, which will be empty if there's no wave defined for that zone.</p><pre><code>// rZoneEnemyWavesDescs is a Dictionary&lt;string, ZoneEnemyWavesDesc&gt;
// containing all the loaded ZoneEnemyWavesDesc 

EnemyWaveDesc[] rEnemyWavesZone;
if (rZoneEnemyWavesDescs.TryGetValue(
        sZoneLabel, 
        out ZoneEnemyWavesDesc rZoneEnemyWavesDesc)) {
    rEnemyWavesZone = rZoneEnemyWavesDesc.getWaves();
} else {
    rEnemyWavesZone = new EnemyWaveDesc[0];
}

rZones[zId] = new RTZone(
    sZoneLabel, 
    zId, 
    zd.m_sSkyboxMatId, 
    rPlatforms,
    rEnemyWavesZone // &lt;--- new field
);</code></pre><p>Crude, but ok for now.</p><div><hr></div><p>We are done with the "static data" layer, now we need to go up in the architecture we have defined in the past.</p><p>First, we need to build the <em>state</em> management for the enemies, and finally we will have to implement the <em>presentation</em> of the state, managing the scene game objects which visualize the state.</p><p>The state management involves the <em>dynamic</em> data about enemies: the information which goes into a save game (and in the state history to be used by the rewind system).</p><div><hr></div><p>I followed the path I already traced when implementing the zones/platforms. </p><p>I defined in the namespace `<code>BinaryCharm.ParticularReality.StateManagement</code>`:</p><ul><li><p>`<code>EnemyWave</code>` and `<code>EnemyWaveStateManager</code>`</p></li><li><p>`<code>Enemy</code>` and `<code>EnemyStateManager</code>`</p></li></ul><p>This hierarchical organization is pretty flexible, because I can define some update logic for a whole wave, and for each enemy.</p><p>I plugged an array of `<code>EnemyWaveStateManager</code>` into `<code>ZoneStateManager</code>`, together with the `<code>PlatformStateManager</code>` instances. </p><p>It looks like the more reasonable place, assuming enemies can't change zone.</p><p>Notice that since the `<code>ZoneStateManager</code>` update is executed for all the active zones (so, current and adjacent, if any), when I'm done you should be able to see enemies through a portal to an adjacent zone, as one would expect. Unless I want to spawn them <em>after</em> you enter the new zone for gameplay reasons.</p><div><hr></div><p>There's quite a while of boilerplate to write to have the execution logic work in the layered architecture I established. In fact, I'm wondering if I should give some thought to simplifying things a bit.</p><p>Anyway, I can't complete this step today - fingers crossed for tomorrow!</p><p></p><h2>2024-05-24 - State update, prefabs setup</h2><p>I need to complete the state management, and then handle the instantiation and destruction of enemy game objects in the scene.</p><p>I'm going to keep things simple and just define two states for an enemy:</p><ul><li><p>hidden: the instance is ready but not displayed</p></li><li><p>idle: the instance is displayed, and stands still on its spawning platform</p></li></ul><p>Actually, I'm going to define the same two states for an enemy wave too.</p><div><hr></div><p>I added the enemy waves state and a counter of the cleared waves to the `<code>WorldZone.State</code>` structure, that until now only contained the state data for the platforms in that zone.</p><p>In the state update, if the counter is less than the number of waves for the zone, I know I have an active wave, and that I must update the simulation of its enemies.</p><p>When all the enemies of the wave will get into the "dead" state, I will consider the wave itself cleared and increment the counter, switching to the next wave (if any).</p><p>But I currently don't have a dead state nor a way to destroy enemies, so my first goal is just being able to spawn the first wave of enemies for a zone, if any, at their correct spawn position.</p><div><hr></div><p>I should have completed a basic implementation of the state management, and it's finally time to move to the representation layer, and put something on the screen.</p><p>For starters, I need to fetch back the enemy prefab from the PoC prototype.</p><p>I'm going to strip from it the behaviours that had to do with its gameplay logic (movement, interaction with the player) and just keep the functionality which is strictly correlated to visualization.</p><p>This separation of concerns adds a lot of complexity to the architecture of the game, but it will hopefully prove worthwhile, allowing me to do all kinds of cool stuff in a robust way. It's the kind of thing that separates a throwaway prototype, or a jam game, from the real thing.</p><div><hr></div><p>I copied the old prefab and cleaned it up. Then, I duplicated it and made an alternate version changing the color to green.</p><p>I then added references to these two prefabs to my stupid simple resource manager, so that I can easily instantiate an enemy prefab of the desired type (specified by the `<code>EEnemyType</code>` enumeration).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZNCc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZNCc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZNCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png" width="640" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:640,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:258904,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZNCc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 424w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 848w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 1272w, https://substackcdn.com/image/fetch/$s_!ZNCc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6158b9b8-63b5-4749-bdf6-0a269785405a_640x640.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I hoped to actually complete the wave instantiation, but there's still a lot of work to do. I need to "connect" the presentation layer to the state management. </p><p>As soon as I have the enemies correctly spawning according to the wave definitions, I will then proceed to add back in the possibility to destroy them. Otherwise, I can't "clear" a wave and get the next one to spawn. That should basically be my TODO list for the next week of development.</p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[The road towards a better DevLog]]></title><description><![CDATA[Featuring: a static site generator written in Rust]]></description><link>https://particularreality.substack.com/p/the-road-towards-a-better-devlog</link><guid isPermaLink="false">https://particularreality.substack.com/p/the-road-towards-a-better-devlog</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sun, 19 May 2024 20:15:02 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/7497f3cf-f812-469e-9ea7-38e61c0b8dd7_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There's a famous story about Donald Knuth developing TeX (which then evolved into LaTeX) because he didn't like how his books looked when printed. It took years.</p><p>I'm no Knuth by far, but I decided to spiritually follow his tracks and take some weeks off the project to make something about <em>how</em> I publish the DevLog, hopefully improving the experience for both me and the readers.</p><p></p><h3>Frustrations about post editing/management</h3><p>There are quite a few things that annoy me about the current editing process:</p><ul><li><p>I write in Markdown, so I need to manually convert some formatting when I prepare a post for publishing using the Substack in-browser editor</p></li><li><p>I don't have the level of control I'd like when inserting images and videos</p></li><li><p>I can't use directly embed mermaid diagrams, which I find very useful. I'm forced to convert them to images, which is suboptimal</p></li><li><p>I can't keep the "final version" of a post, with its resources, into a repository</p></li></ul><p></p><h3>Frustrations about content visualization</h3><p>I find the Substack visualization good for generic text content, but not great for technical posts containing code snippets:</p><ul><li><p>there's no syntax highlighting</p></li><li><p>code blocks wrap, while it would be better to keep the indentation and let the user scroll</p></li><li><p>the fanciful HTML formatting of emails sent by Substack makes the code blocks unreadable (even when the code is less than 78 character columns wide, something which was fine even in text only emails as specified by RFC2822 a couple of decades ago)</p></li><li><p>there are annoying popups</p></li></ul><p></p><h3>Frustrations about the state of the Internet</h3><p>I thought that using a platform like Substack would help me reach a wider audience, but it doesn't feel like it is helping. Actually, I sometimes feel like it is damaging me: I keep reading about social media platforms sabotaging each other lowering the reach of posts including links, or links to content hosted on specific platforms. </p><p>Additionally, some people are actively avoiding Substack as a matter of principle, after some controversial content was not banned from the platform.</p><p>Do I agree with that? Do I implicitly endorse Substack by publishing content there?</p><p>Why do I even need to spend time considering these things?</p><p>Isn't developing a videogame and documenting the process stressful enough?</p><p>I don't care at all about the competition between tech giants, nor I want to be dragged into debates on freedom of speech and censorship.</p><p>I stay humble: <em>maybe</em> I'll give an opinion on "tabs vs spaces", on a good day.</p><p></p><h3>Frustrations about other static site generators</h3><p>I used <em>Hugo</em> for the Binary Charm website, and it wasn't a pleasant experience.</p><p>I quickly checked <em>Zola</em> and I felt like I would have hated that too.</p><p>I'm not saying they are bad, but I felt like, in terms of design, I wanted something different.</p><p>It was about controlling the processing logic and being able to define higher level data types on the source side. Basically, I wanted to do more processing inside the generator itself, and less on the "web" side of things. Template systems like Jinja allow to put a bit of logic into them, but they are limited, the syntax is awful, and debugging is basically trial and error.</p><p>So, around February 2023, I started working on a static website generator written in Rust, with the long term goal to handle the generation of my personal website, featuring a portfolio section done the way I wanted. I worked on it on and off, prioritizing other things (like <em>Particular Reality</em>, of course!).</p><p>The first website I published that used the generator was actually the <a href="https://www.particular-reality.com">game homepage</a>, when I announced the project last year. A very simple "monopage" use case.</p><p>After some other months, at the beginning of 2024, I finally managed to add a first version of the <a href="https://dario.scarpa.dev/portfolio/">portfolio management</a> features, and I reached my initial goal of generating a new version of of my personal website, organized the way I wanted.</p><p>The generator was still pretty simple, with a few hacks and hardcoded things here and there, but did its job and I was quite happy about the result. </p><p>And, of course, it was something that gave me the joy of <strong>working the way I wanted</strong>, which should never be underestimated, especially when one is working on something personal with no strict time or budgeting constraints (that often force sacrifices and trade-offs on the technical side of things).</p><p></p><h3>Extending my generator to handle the DevLog</h3><p>The new stage I reached with my generator, paired with the unsatisfaction I discussed in the first paragraphs, pushed me to do another round of development, this time aiming to handle the DevLog entries, so that I can self-host them and fix all the issues I have with the current state of things.</p><p>As an added bonus, I will be able to port to the new system also some articles I wrote a long time ago, finally killing the Wordpress instance I am still forced to keep around at the time of writing.</p><p>Yes, Wordpress. Guess what? A dozen years ago I started using Wordpress for my website because it looked like the "wise" decision, the "not reinvent the wheel" approach, but even that time, I quickly realized that I <em>hated it</em> and that I did not <em>really</em> need it.</p><div><hr></div><p>In my generator, every item is defined by a YAML file and a "side folder" with the same name. </p><p>The YAML file contains a "front-matter" with metadata about the item, and an optional body (a Markdown text).</p><p>The "side folder", if needed, contains one or more files making up the item. For example, an image resource side folder will contain a single file, while a video resource side folder will contain both the video file and an image, to be used as "cover" of the video when it's not playing. </p><p>The ability to define in the generator itself high level structured data (like a <em>portfolio entry</em>) is what makes it so convenient to use for me. I can define custom processing for each kind of item: for example, an image resource will produce multiple output images, at different resolutions, to automatically have a <em>responsive</em> image in the output. For video content, I'm currently embedding the Youtube version but I could automatically transcode the source video to a more compact, web-friendly format, and embed that.</p><p>To handle "articles", I revised the handling of the core resource type of the generator, the "page" one, which actually produces a web page in the website and can embed other "media" resources.</p><p>I had to add custom processing so that the Markdown text of an article can</p><ul><li><p>call "generator side" logic, via <em>shortcodes</em></p></li><li><p>handle syntax highlighting for code blocks</p></li><li><p>handle Mermaid diagrams definitions</p></li></ul><p>This is a video showing the generation in action at the current stage of development:</p><div id="youtube2-Qm-jdEvNmvA" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Qm-jdEvNmvA&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Qm-jdEvNmvA?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p><h3>What's missing?</h3><p>I still have to work on the DevLog "index" containing summaries for all the articles, and highlighting the latest entry. I must also add <em>tags</em>, something which I already did for my portfolio entries.</p><p>Then, I need to add the generation of <em>sitemap</em> and <em>RSS feed</em>. Yes, the RSS feed: it's time for their resurrection.</p><p>In visual terms, I'm not crazy about the colors used by the syntax highlighting and the Mermaid diagrams. I would also like to add support for a <em>dark theme</em> to the website, and add a dark/light switcher.</p><p>It shouldn't be <em>that much</em> work, and the hard parts should be done, but I decided to stop for now, and go back to the game for some weeks. Then, I'll take another "break" and come back to work on the generator, completing these last things.</p><p></p><h3>What then?</h3><p>I might change my mind, but the idea is port the old articles, a couple per week, to the new format, until the self-hosted version catches up. I might keep using Substack to publish a short summary of the articles, linking to the full length version, so I can still benefit from the newsletter and social functionalities offered by the platform. Or I might stop using it completely. We'll see how things go!</p><p>Meanwhile, see you in a week for some game development - much better than this web stuff.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #19]]></title><description><![CDATA[Clip playback on skinned meshes]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-19</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-19</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 27 Apr 2024 17:30:38 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9ff86b5b-bbba-4100-bee9-ecebcd10bd32_823x823.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-04-22 - Back to (movement) basics</h2><p>After last week failure in modifying "blindly" the character rig to playback saved animation clips, let's approach the problem differently, ready to accept that it might be time to dig into the SDK code and understand it deeply.</p><div><hr></div><p>I quickly checked <a href="https://particularreality.substack.com/p/advanced-prototyping-week-14">week #14</a> to see which sample was my starting point, and noticed that, in that occasion, I refused to let the <em>Project Setup Tool</em> fix the <em>outstanding issue</em> "Body Tracking Joint Set should be set to Full Body".</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jAOg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jAOg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 424w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 848w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 1272w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jAOg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png" width="483" height="276" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:276,&quot;width&quot;:483,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31276,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jAOg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 424w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 848w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 1272w, https://substackcdn.com/image/fetch/$s_!jAOg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fef837a2a-63c1-45c1-8fa7-18c0edbd2ca8_483x276.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And in those same days, I was wondering why the <em>generative legs</em> in the samples didn't work.</p><p>Silly me.</p><p>I let the <em>Project Setup Tool</em> apply the fix, and guess what? The legs started working.</p><p>Not that I need them, but it's good to know that the sample is running properly. Almost properly: the cup material is broken.</p><p>The scene I selected as starting point was `<code>MovementISDKIntegration</code>`, because it has the interesting feature of altering the input data to have the character bones follow some rules and constraints. While capturing this short clip, for example, in reality I'm completely closing my right hand in a fist, but the character hand adapts to a pose suitable to wrap the cup.</p><div id="youtube2-Ubs-7mDBRsM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Ubs-7mDBRsM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Ubs-7mDBRsM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Not sure I'm going to use this, but it's interesting.</p><p>Now, let's see what <a href="https://developer.oculus.com/documentation/unity/move-samples/?locale=en_GB">the documentation</a> said about this sample scene and the character rig.</p><div><hr></div><p>While checking the documentation, I went back to the <a href="https://github.com/oculus-samples/Unity-Movement">Unity-Movement repository</a> and noticed that, just a few days ago, a new major release of the package (v5.0.0) has been released (my project currently includes v4.2.1).</p><p>This is a bit annoying: I was ready to start analysing the package code, but it doesn't make sense to analyse an outdated version. On the other side, if I update the package, I will almost surely need to repeat the steps I did last week to "clone" the needed elements from the <em>Samples</em>.</p><p>And to make things worse, to even try, I must also update the other Meta SDK packages, because this release depends on v63, and I have v62 in the project.</p><p>Boring! But it must be done. I really don't feel like doing it today, so I'm calling it a day a bit early.</p><p></p><h2>2024-04-23 - Yet another SDK update</h2><p>Surprise, surprise, I was on `<code>v62</code>` and `<code>v64</code>` is already available. I totally overlooked v63.</p><p>So, I'm going to switch the core SDK packages from `<code>v62</code>` to `<code>v64</code>`, and then I'm going to upgrade `<code>com.meta.movement</code>` from `<code>v4.2.1</code>` to `<code>v5.0.1</code>` - which was released... today! Glad I didn't install  `<code>v5.0.0</code>` yesterday. </p><p>Let's upgrade everything and see if the project explodes.</p><div><hr></div><p>So, my scene is broken as expected, and I assume it's because of the prefabs I duplicated last week, which should be re-duplicated or updated from outside the Unity editor.</p><p>There's something else which bothers me, tough: opening the sample scene I'm using as reference, `<code>MovementISDKIntegration</code>`, it looks like they introduced a problem with the hands of the robot character, which don't perfectly match the tracking.</p><p>Or is it because of something I did? It shouldn't be, but to be sure... let's test in an empty project.</p><div><hr></div><p>I created an empty projects, imported the same SDK packages I'm using in the <em>Particular Reality</em> project, and tested the same scene. </p><p>The problem is still there - on the upside, the cup has the correct material.</p><div id="youtube2-7ei9DB38yEg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;7ei9DB38yEg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/7ei9DB38yEg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>This is quite annoying, because one would like to at least start from something working well before doing changes.</p><p>I verified that the problem only affects the robot character. If I open the `<code>MovementHighFidelity</code>` scene, the hands are correct.</p><div><hr></div><p>I rolled back the `<code>com.movement.meta</code>` package to `<code>4.2.1</code>`, but keeping the `<code>v64</code>` upgrade.</p><p>Everything works again, which is nice: the `<code>v64</code>` upgrade doesn't break anything.</p><p>It's worth doing a commit just with the `<code>v64</code>` update, and then think about what to do with `<code>com.meta.movement</code>`.</p><p>As a side note, when I need to try out different versions of packages, my favourite way of doing is editing the `<code>package.json</code>` file directly. It's much faster than using the <em>package manager</em>, and using version control on the file I can quickly discard changes to rollback packages. Highly recommended.</p><div><hr></div><p>So, the fact that a major release change breaks thing is pretty normal (that's usually the important difference between a major version upgrade and a minor version).</p><p>What options do I have?</p><ul><li><p>A) upgrade to `<code>v5.0.1</code>`, update my scene by re-cloning the sample prefabs, and ignore the hand tracking imperfection on the robot character I'm using for my tests</p></li><li><p>B) stay on `<code>v4.2.1</code>`, analyse that and keep developing my things as if the new version doesn't exist</p></li></ul><div><hr></div><p>I was seriously considering option B, but then I started comparing the character rigs of `<code>v4.2.1</code>` and of `<code>v5.0.1</code>`, opening them side by side in two projects (the <em>Particular Reality</em> project rolled back on `<code>v4.2.1</code>`, the empty project from an hour ago on `<code>v5.0.1</code>`).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8LKq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8LKq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 424w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 848w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 1272w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8LKq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png" width="938" height="492" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:492,&quot;width&quot;:938,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74685,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8LKq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 424w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 848w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 1272w, https://substackcdn.com/image/fetch/$s_!8LKq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0afc7d88-7e8d-4419-9a0c-62166e2fc2f9_938x492.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The setup in the new version is significantly simpler: it looks like the folks at <em>Meta</em> did some serious clean-up. This is very valuable to my eyes, so I definitely prefer upgrading.</p><p>I can live with the imperfect hands matching for a while, and with some luck, they're going to publish an update which fixes the issue before I even get to the point where I feel like I need to fix it myself.</p><p>So, let's update "my" prefabs and see if I manage to have my scene working on `<code>v5.0.1</code>`.</p><div><hr></div><p>To only update what's needed, I compared (using <em>WinMerge</em>) the source files I duplicated from the samples last week with the same files in the new version of the package.</p><p>I found out that basically the only two files I need to update are</p><ul><li><p>`<code>ArmatureSkinningUpdateRetargetSkeletonProcessor.prefab</code>`</p></li><li><p>`<code>ArmatureSkinningUpdateRetarget.prefab</code>`</p></li></ul><p>So I'm copying the new versions of these files overwriting their "counterparts"</p><ul><li><p>`<code>PRAvatar_SkinningUpdateRetargetSkeletonProcessor.prefab</code>`</p></li><li><p>`<code>PRAvatar_SkinningUpdateRetarget.prefab</code>`</p></li></ul><p>...and I'm fixing their references to other cloned elements, using the `<code>guid</code>` replacement technique I discussed last week.</p><div><hr></div><p>Ok, everything works again, and I'm now running `<code>v64</code>` and `<code>v5.0.1</code>` in my project.</p><p>I also introduced the hands displacement problem inherited from the samples, but that was expected.</p><p>Maybe tomorrow I'll be able to make some actual progress.</p><p></p><h2>2024-04-24 - More visual debugging</h2><p>Last week testing, when I used the "runtime gizmos", showed that the bones of the skinned mesh weren't properly rotated.</p><p>I wrote:</p><blockquote><p>Everything looks working fine with the "skeleton" visualization because, I'm assuming, it doesn't really use the bones rotations, but just the joints positions, drawing lines between them.</p></blockquote><p>After thinking some more, I think that assumption was incorrect, because I remembered that I can show the joints axis by setting on `<code>PlayerAvatarDebugGizmos</code>` an option inherited by `<code>SkeletonDebugGizmos</code>`.</p><p>And they are fine:</p><div id="youtube2-DXhL-4TVkD4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;DXhL-4TVkD4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/DXhL-4TVkD4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Good.</p><p>Now, let's give another look at everything and see if I can figure out why the skinned mesh animation is so broken.</p><div><hr></div><p>While re-examining the scene, I noticed that the skinned character fetches data from an `<code>OVRBody</code>`, while the clip recorder saves data fetched from a `<code>Body</code>`. I forgot about this difference in the data flow.</p><p>Such script was attached to a node named `<code>OVRBody</code>`, which made things more confusing. Have I ever discussed my awful memory?</p><p>Anyway, I might have found the root of the problem. Fingers crossed. Let's recap and think about the body tracking data flow in the scene.</p><div><hr></div><p>I discussed `<code>Body</code>` and `<code>OVRBody</code>` in my <a href="https://particularreality.substack.com/p/deep-dive-into-body-tracking">"deep dive" article</a>.</p><p>Let's take the data flow diagram at the end of that article:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_Hk3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_Hk3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 424w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 848w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 1272w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_Hk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png" width="390" height="600" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:600,&quot;width&quot;:390,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27250,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_Hk3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 424w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 848w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 1272w, https://substackcdn.com/image/fetch/$s_!_Hk3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94f4b312-4cd9-4c53-bfe7-6a0c23e924f4_390x600.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So, using a flowchart-like notation I'm making up on the spot, this is the current state of things:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eOco!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eOco!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 424w, https://substackcdn.com/image/fetch/$s_!eOco!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 848w, https://substackcdn.com/image/fetch/$s_!eOco!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 1272w, https://substackcdn.com/image/fetch/$s_!eOco!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eOco!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png" width="617" height="685" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5838e082-ad05-458d-8721-da23c371c831_617x685.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:685,&quot;width&quot;:617,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38819,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!eOco!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 424w, https://substackcdn.com/image/fetch/$s_!eOco!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 848w, https://substackcdn.com/image/fetch/$s_!eOco!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 1272w, https://substackcdn.com/image/fetch/$s_!eOco!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5838e082-ad05-458d-8721-da23c371c831_617x685.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now, when I implemented `<code>UpperBodyCharacterDataProviderBhv</code>` last week, while fiddling with the data, I did actually <em>somewhat</em> revert the processing done in `<code>FromOVRBodyDataSource</code>`, but maybe I did something wrong there?</p><div><hr></div><p>Last week I wrote:</p><blockquote><p>I might hack around this, saving the data from the skinned mesh animation after the processing, but I don't really like that idea.</p></blockquote><p>but in fact I'm already saving data that is "more processed" than it should be.</p><p>I'm saving the `<code>Body DATA</code>` into the clip, and not the "lower level" `<code>OVRBody DATA</code>`.</p><p>Basically, I could simplify the flow and avoid the "inversion" of the processing done by `<code>FromOVRBodyDataSource</code>`  (that I'm currently doing in `<code>UpperBodyCharacterDataProviderBhv</code>`).</p><p>This is the plan:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jZ4R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jZ4R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 424w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 848w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 1272w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jZ4R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png" width="617" height="685" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:685,&quot;width&quot;:617,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35726,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jZ4R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 424w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 848w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 1272w, https://substackcdn.com/image/fetch/$s_!jZ4R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb507fde9-3b22-42bd-9cc7-1c4bcd5e3418_617x685.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><p>I modified the data flow (partially: I didn't complete the "skeleton" path, which now is basically mirrored because I'm skipping the  `<code>FromOVRBodyDataSource</code>` processing).</p><p>But... the `<code>SkinnedMesh</code>` behaves exactly as it did before. I guess my "inversion" of the `<code>FromOVRBodyDataSource</code>` processing was correct, after all.</p><p>So where's the problem?</p><p>Time for more visually aided debugging.</p><p>I'm going to do a test visualization based on the "runtime gizmos" we already used, but that uses the interfaces exposed by `<code>OVRBody</code>`, exactly like the skinned mesh rendering should be doing.</p><div><hr></div><p>Here's my new debugging script:</p><pre><code>using System;
using System.Collections.Generic;

using UnityEngine;

using Oculus.Interaction.Body.Input;
using BinaryCharm.ADT;

namespace BinaryCharm.ParticularReality.DebugManagement
{
    public class DebugAnimPlayerBhv : MonoBehaviour {
        
        private OVRSkeleton.IOVRSkeletonDataProvider m_rSkeletonDataProvider;

        private Dictionary&lt;BodyJointId, GameObject&gt; m_rJointToGizmoMap =
            new Dictionary&lt;BodyJointId, GameObject&gt;();


        private void Start() {
            initRuntimeGizmos();
        }
        private void Update() {
            updateRuntimeGizmos();
        }

        private void initRuntimeGizmos() {
            Action&lt;BodyJointId&gt; addRuntimeGizmo = (BodyJointId id) =&gt; {
                m_rJointToGizmoMap.Add(
                    id,
                    DebugManagerBhv.i().InstantiateGizmo(
                        null, PosRot.DEFAULT, 0.05f, "RG_" + id.ToString()
                    )
                );
            };

            addRuntimeGizmo(BodyJointId.Body_Root);
            addRuntimeGizmo(BodyJointId.Body_Hips);
            addRuntimeGizmo(BodyJointId.Body_Head);
            addRuntimeGizmo(BodyJointId.Body_LeftHandPalm);
            addRuntimeGizmo(BodyJointId.Body_RightHandPalm);
            addRuntimeGizmo(BodyJointId.Body_LeftArmUpper);
            addRuntimeGizmo(BodyJointId.Body_RightArmUpper);

            m_rSkeletonDataProvider =
                GetComponent&lt;OVRSkeleton.IOVRSkeletonDataProvider&gt;();
        }
        private void updateRuntimeGizmos() {
            OVRSkeleton.SkeletonPoseData skeletonPoseData =
                m_rSkeletonDataProvider.GetSkeletonPoseData();
            if (!skeletonPoseData.IsDataValid) return;

            foreach (var kv in m_rJointToGizmoMap) {
                int iJointId = (int) kv.Key;

                Vector3 vPos = skeletonPoseData.BoneTranslations[iJointId]
                    .FromFlippedZVector3f();
                Quaternion qRot = skeletonPoseData.BoneRotations[iJointId]
                    .FromFlippedZQuatf();
                kv.Value.transform.SetPositionAndRotation(vPos, qRot);                
            }
        }
    }
}</code></pre><p>It's pretty simple. I spawn in the scene a bunch of runtime gizmos thanks to an utility method in `<code>DebugManagerBhv</code>`, one for each "key joint" I want to see. Then, I update their transforms on the basis of the  `<code>SkeletonPoseData</code>` fetched from a script implementing the `<code>OVRSkeleton.IOVRSkeletonDataProvider</code>` interface that is expected to be on the same node.</p><p>In terms of our data flow diagram, this new visualization is based on `<code>OVRBody DATA</code>` like the `<code>SkinnedMesh</code>` character, instead of using `<code>Body DATA</code>` like the `<code>SkeletonDebugGizmos</code>` visualization does.</p><p>I decided to only show a few "key" joints, but with bigger gizmos. Less clutter, and easier to focus on the critical joints (root, hips, shoulders, hands).</p><p>Let's see what comes up!</p><div id="youtube2-aKO3EGO2LhI" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;aKO3EGO2LhI&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/aKO3EGO2LhI?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Well, this new debug visualization tells me two important things.</p><p>First, I can now be 100% sure that the joints data provided by `<code>UpperBodyCharacterDataProviderBhv</code>` is correct. </p><p>Applying `<code>DebugAnimPlayerBhv</code>` on both character rigs, the one using the live data from the headset and the one using data from the recorded clip, gives consistent results, properly positioning the runtime gizmos on both.</p><p>Notice the runtime gizmos on my hands, at the beginning of the video, and how the gizmos on the character on the right move consistently with the (mirrored) skeleton on the left.</p><p>Second, I can see the difference in the pose of a runtime gizmo and of the actual bone of the `<code>SkinnedMeshRenderer</code>`.</p><p>I compared, using the <em>Scene View</em> of the Unity editor, the <em>hips</em> bone pose (using the editor gizmo) and the pose as calculated by the clip player (shown using the "runtime gizmo"):</p><div id="youtube2-mMoFysP4wM4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;mMoFysP4wM4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/mMoFysP4wM4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Look at the two <em>gizmos</em>: not only the orientations are completely different, but the `<code>SkinnedMeshRenderer</code>` bone doesn't rotate at all. </p><p>Actually, not a single bone rotates, coming back to my intuition of last week: I thought that the skeleton visualization worked because it only needed positions while the rotations where wrong. </p><p>Then, I realized that it was using rotations after all. But I wasn't sure the data was correct, because I had two data paths with different processing. So, I changed that and became sure I had the correct data in place, with this new debug visualization.</p><p>At this point, I'm pretty confident that the issue is in the SDK code that takes the joints data and actually adjusts the bones.</p><p>Hopefully tomorrow I'll finally find the root cause.</p><p></p><h2>2024-04-25 - Finding and fixing the bug</h2><p>After the analysis conducted yesterday, I surrender to the idea of looking at the SDK code of the scripts involved in the character animation.</p><p>On the main node, there are these two scripts:</p><ul><li><p>`<code>RetargetingLayer</code>`</p></li><li><p>`<code>SkeletonProcessAggregator</code>`</p></li></ul><p>My idea since the beginning has been that these script access the skeleton data by calling the `<code>GetSkeletonPoseData()</code>` of the `<code>OVRSkeleton.IOVRSkeletonDataProvider</code>` interface.</p><p>It's not obvious where this happens (inheritance, partial classes...), so I'm going to set a breakpoint in my implementation of such method, in `<code>UpperBodyCharacterDataProviderBhv</code>`, and step into the execution, hopefully understanding why the code sets the positions of the bones but not the rotations.</p><div><hr></div><p>After stepping for a while without noticing anything that looked problematic, I saw something that triggered my internal alarms.</p><p>Let's see the `<code>SkeletonPoseData</code>` returned by the `<code>GetSkeletonPoseData()</code>`:</p><pre><code>    public struct SkeletonPoseData
    {
        public OVRPlugin.Posef RootPose { get; set; }
        public float RootScale { get; set; }
        public OVRPlugin.Quatf[] BoneRotations { get; set; }
        public bool IsDataValid { get; set; }
        public bool IsDataHighConfidence { get; set; }
        public OVRPlugin.Vector3f[] BoneTranslations { get; set; }
        public int SkeletonChangedCount { get; set; }
    }</code></pre><p>To avoid some useless recomputations, at some points, the SDK code checks the `<code>SkeletonChangedCount</code>` field, and only acts if it changed.</p><p>But I don't remember setting this value in my implementation...</p><div><hr></div><p>Ooops. </p><p>This was the code at the end of my `GetSkeletonPoseData()` method:</p><pre><code>            return new OVRSkeleton.SkeletonPoseData {
                IsDataValid = true,
                IsDataHighConfidence = true,
                RootPose = rootPose,
                RootScale = 1.0f,
                BoneRotations = m_rBoneRotations,
                BoneTranslations = m_rBoneTranslations
            };</code></pre><p>Unfortunately, I had not set at all the `<code>SkeletonChangedCount</code>` value. </p><p>Let's fix it incrementing a counter at each call and returning such value into the `<code>struct</code>`.</p><pre><code>            ++m_iChangedCount;
            return new OVRSkeleton.SkeletonPoseData {
                IsDataValid = true,
                IsDataHighConfidence = true,
                RootPose = rootPose,
                RootScale = 1.0f,
                BoneRotations = m_rBoneRotations,
                BoneTranslations = m_rBoneTranslations,
                SkeletonChangedCount = m_iChangedCount
            };</code></pre><p>The horror for the stupidity of the error, but also, the joy in seeing everything finally working!</p><p>A constructor would have forced me to initialize all the needed fields, but in this case the language couldn't support me in "not forgetting" to initialize and use that field, and a small distraction ended up costing me quite a bit of time.</p><p>Well, it's late, but this <em>needs</em> a celebration video where I record a clip and use it to high five myself.</p><div id="youtube2-hdgZ44YSibQ" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;hdgZ44YSibQ&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/hdgZ44YSibQ?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>I posted the last seconds on Twitter etc, but only you, dear readers, get the full story and the full clip.</p><p></p><h2>2024-04-26 - Clean-up Friday</h2><p>The las couple days have been a bit "heavy" and the weekend is around the corner, so I'm going to take it lightly and don't start implementing new features.</p><p>I want to do a bit of cleanup, because my code of the last couple days was mostly for testing and finding out what was wrong, and I took some shortcuts on the way. </p><p>Then, I want to capture a clip I can use for a cool "screenshot Saturday" update tomorrow (the last clip of that kind that I published is from more than two months ago, yikes!).</p><div><hr></div><p>I removed the clutter and committed my changes to the repository.</p><p>I went back to the data path using `<code>Body</code>` data and not `<code>OVRBody</code>`, as it was innocent.</p><p>I'm doing a little more processing this way, but it feels cleaner as I'm operating on higher level data not using the Oculus vector and quaternion types but the Unity ones.</p><p>I left the other data path commented out in the code, in case I need to go back to it.</p><p>Then, I did a few minor changes to be able to capture a nice clip.</p><p>Namely, I: </p><ul><li><p>disabled the "skeleton" visualization unless debug mode active</p></li><li><p>added a gesture to show/hide the mocap UI</p></li><li><p>made the "playback" character invisible if no clip is playing</p></li><li><p>fixed a culling bug that made the body vfx disappear once in a while</p></li></ul><p>Then, I pushed a build to the Quest and recorded a motion clip in which I played the part of an enemy attacking the player as soon as they get on the platform. Then, I enabled the Quest video recording and "acted" the other part of the interaction.</p><div id="youtube2-fl560wGyDzY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;fl560wGyDzY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/fl560wGyDzY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>It's just a glimpse into a possible future, and not a real gameplay interaction, but it should be cool for a promotional clip. The whole "behind the scenes" truth is just for my elite audience of DevLog readers!</p><div><hr></div><p>Another week is over. How's things? Not bad! </p><p>I finally have a decent set of features related to body tracking.</p><p>The process started with the introduction of the `<code>SkinnedMesh</code>` avatar for the player character in Week #14,  and now I can use that kind of character to playback motion clips, too. </p><p>I can conveniently record and edit those motion clips directly from VR, which is great.</p><p>Even if I'm not 100% done, I don't see major roadblocks in proximity. What's missing? A couple of minor things I already mentioned in the past:</p><ul><li><p>showing some "keyframe" references to be able to easily capture clips that can flow into each other</p></li><li><p>being able to tune the clip frequency so that I can use clips with different "quality" as needed</p></li></ul><p>I decided to postpone the implementation of these features to the moment when I'm going to actually start capturing and using clips.</p><p>I'm going to capture clips to act as "visual tutorial", showing the player the gestures they have to perform, and I'm also going to use them to prototype the enemies animations (movement, attacks, defences).</p><p>So what am I going to do, if not this, next week?</p><p>That's going to be a surprise, and it might take a little longer than a week.</p><p>Two weeks, maybe three. See you then!</p><p></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Advanced Prototyping - Week #18]]></title><description><![CDATA[Additional clip management and debugging]]></description><link>https://particularreality.substack.com/p/advanced-prototyping-week-18</link><guid isPermaLink="false">https://particularreality.substack.com/p/advanced-prototyping-week-18</guid><dc:creator><![CDATA[Dario Scarpa]]></dc:creator><pubDate>Sat, 20 Apr 2024 19:00:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d5343bfe-1045-4ac2-bd02-cbb6aed7f7cd_757x757.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>2024-04-15 - Update, update, rollback</h2><p>I'm finally back at work on the project, after a three weeks long break due to other work commitments and to the <a href="https://particularreality.substack.com/p/a-new-development-pc">setup of a new PC</a>.</p><p>It always take a little time to go back to a project after a break, but unfortunately I can't avoid some interruptions. Let's start by copy/pasting the TODO I left myself at the end of week 17:</p><ul><li><p>I still need to add the tuning of the capture frequency, which shouldn't take long if I use the same UI control I used for the speed.</p></li><li><p>I want to be able to visualize a reference skeleton when recording clips, to help in capturing looping clips</p></li><li><p>I need to be able to playback a clip on a skinned mesh and not only using the debug gizmos (which only work in editor!)</p></li><li><p>after doing that, I need to test everything with an Android build running standalone on Quest (I've been using Airlink for now)</p></li></ul><p>I'd say that the capture frequency setup is not a priority.  I want to capture on device, so I need to have everything running there, including the reference skeleton to capture looping clips.</p><p>All this implies that it makes sense to start from the third item of the TODO list: I need to be able to playback clips using as target a suitable skinned mesh, and not only the editor-only skeleton visualization.</p><div><hr></div><p>I had already cloned the repository of the project and installed Unity during my PC setup last week, but I only had tested that it was running in editor, with no headset connected. Now, instead, using Oculus Link, something is broken: I have a never seen before exception thrown from `<code>XRPass.cs</code>`, a file part of the `<code>com.unity.render-pipelines.core</code>` package.</p><p>The fun begins (not). Do I have a problem with the new PC setup, did I forget to commit something to the repository, or what? Let's dig deeper...</p><div><hr></div><p>On my old PC, I was working on the project using Unity 2022.3.19f1.</p><p>While doing my new PC setup, in a moment of optimism, I installed the suggested release, which was just a few minor versions ahead: 2022.3.23f1.</p><p>It is reasonable to install the "old" version to see if the problem is related to the new release (or maybe to some of the packages I'm using that needs to be updated too).</p><p>And, surprise: since the day I installed Unity on the new PC, there's a new minor release available, the 2022.3.24f1. With some luck, the new minor release could fix the problem. Worth a shot...</p><div><hr></div><p>I installed Unity 2022.3.24f1, but unfortunately the problem was still there. I also updated some packages from the package manager. Meanwhile, I googled the exception, and found a thread about the <a href="https://forum.unity.com/threads/xrpass-getviewport-exceptions-in-editor.1577922/">same problem I was having</a>.</p><p>Well, at least is not just me, I guess. There's no fixes/workaround suggested, and the post is from today (!). I could try to find a workaround, but I prefer to let Unity employees do their job, and focus on my game. Time to surrender and install <em>exactly</em> the same version of Unity I was using on the old pc, which is probably what I should have done in first place. Silly optimism!</p><div><hr></div><p>Guess what, with version 2022.3.19f1, the exception disappeared. Yay.</p><p>Still, the "particular" player avatar doesn't show up. This looks more like an error of mine.</p><div><hr></div><p>I went and compared using <em>WinMerge</em> the two copies of the project, on old and new pc. </p><p>This is exactly the kind of thing that makes me keep the old pc (and data) around for now, until I'm sure everything is in order on the new system.</p><p>I did find the problem, a couple of modified files in the <em>Samples</em> of the `<code>com.meta.movement</code>` package which I left out of the repository (around 800 Mb!). My bad for modifying those files directly, instead of making copies of them (placed in my packages) and modify the copies.</p><div><hr></div><p>After copying the modified samples, the project works well (and runs significantly faster than on the old PC, which will be great to improve my iteration time).</p><p>When I thought it was over, the Oculus App prompted me for an app and drivers update. And... I have the v64 firmware update available on the headset too, which I'm actually quite curious about, since I heard it improves the passthrough quality.</p><p>I'm almost too scared to try if everything keeps working after the updates... should I leave it for tomorrow?</p><p>No, I need to know.</p><div><hr></div><p>Pheew, everything worked. Oculus app updated, firmware v64 running on the headset, and the Unity project looks fine. I lost an afternoon, but maybe tomorrow I will actually be able to do some progress.</p><p></p><h2>2024-04-16 - Removing the sample assets</h2><p>I have very little time today, but let's see if I manage to get at least one thing in.</p><div><hr></div><p>There's quite a lot of SDK code related to the animation of a skinned through body tracking data.</p><p>I'm talking about code in the namespace `<code>Oculus.Movement</code>` (from the `<code>com.meta.movement</code>` package).</p><p>If needed, I will analyse it in depth, like I did for the `<code>Oculus.Interaction.Body.Input</code>` namespace (from the `<code>com.meta.xr.sdk.interaction</code>` package) in the `<a href="https://particularreality.substack.com/p/deep-dive-into-body-tracking">Deep dive into body tracking</a>` special post.</p><p>But first, I'm going to try to cheat and use (almost) "blindly" the player character setup which I already have in the Unity scene as a starting point. I'm going to try to clone it and just change the data source, so that it uses data I pass in (from an animation clip) instead of the body tracking data coming from the headset.</p><div><hr></div><p>Before cloning the character, I want to remove the dependency from the <em>Samples</em> assets I discussed yesterday, because otherwise I would make it worse: I would duplicate something depending on assets which shouldn't be in the project in the first place.</p><p>This is more time consuming that it should be, because some dependencies can be not obvious. </p><div><hr></div><p>Something particular annoying are the prefab hierarchies, like this one:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kZCA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kZCA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 424w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 848w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 1272w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kZCA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png" width="344" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:344,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6200,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kZCA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 424w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 848w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 1272w, https://substackcdn.com/image/fetch/$s_!kZCA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff2ea6921-68a0-482f-980f-f918c721ddb8_344x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>So, in the scene I have `<code>ArmatureSkinningUpdateRetargetSkeletonProcessor</code>`, but if I duplicate it in my package and rename it to `<code>PRAvatar_SkinningUpdateRetargetSkeletonProcessor</code>`, that duplicate will still have as <em>variant parent</em>  the `<code>ArmatureSkinningUpdateRetarget</code>` prefab.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!P9sA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!P9sA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!P9sA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png" width="720" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/73d37464-d333-45be-9508-600a0e49ee04_720x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:10740,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!P9sA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!P9sA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F73d37464-d333-45be-9508-600a0e49ee04_720x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>And that makes sense. But if want to proceed with my cloning, and duplicate `<code>ArmatureSkinningUpdateRetarget</code>` to `<code>PRAvatar_SkinningUpdateRetarget</code>`, there's no way (AFAIK) in Unity to "reparent" `<code>PRAvatar_SkinningUpdateRetargetSkeletonProcessor</code>` to `<code>PRAvatar_SkinningUpdateRetarget</code>`.</p><p>This is a case where I resort to violence, which mean editing the YAML of the prefabs to do the reparenting manually.  The process is not obvious, so I'm going to describe it.</p><div><hr></div><p>When you duplicate an asset, the duplicate gets a new `<code>guid</code>`. These global identifiers are used by Unity to handle references between assets. Thanks to this approach, one can freely rename assets without breaking stuff.</p><p>So, to do the reparenting, we must change, in the child prefab, the references to the parent prefab `<code>guid</code>`, replacing them with references to the "cloned father-to-be". You can find the `<code>guid</code>` of an asset opening with a text editor its `<code>.meta</code>` file.</p><p>Let's see, for example, the contents of `<code>ArmatureSkinningUpdateRetarget.prefab.meta</code>`:</p><pre><code>fileFormatVersion: 2
guid: eb6bc10f92728a546a3d8c144d0ef787
PrefabImporter:
  externalObjects: {}
  userData: 
  assetBundleName: 
  assetBundleVariant: </code></pre><p>We also need to take note of the "cloned father-to-be" prefab, checking `PRAvatar_SkinningUpdateRetarget.prefab.meta`.</p><p>Done that, we know that</p><ul><li><p>`<code>ArmatureSkinningUpdateRetarget</code>` has `<code>guid</code>` `<code>eb6bc10f92728a546a3d8c144d0ef787</code>`</p></li><li><p>`<code>PRAvatar_SkinningUpdateRetarget</code>` has `<code>guid</code>` `<code>521e1b23a8494cd438c7a90ea68f6d82</code>`</p></li></ul><p>At this point, we can open with a text editor the child prefab, `<code>PRAvatar_SkinningUpdateRetargetSkeletonProcessor.prefab</code>`, and do a "replace all" that changes every occurrence of `<code>eb6bc10f92728a546a3d8c144d0ef787</code>` to `<code>521e1b23a8494cd438c7a90ea68f6d82</code>`.</p><p>After doing that, when I go back to Unity, I can verify in the inspector that the re-parenting has happened, so now we have:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-NdV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-NdV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-NdV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png" width="720" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12255,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-NdV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!-NdV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8ed06962-6158-4880-bfb8-a4947ec28efe_720x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><p>I repeated the process to reparent `<code>PRAvatar_SkinningUpdateRetarget</code>` so that it points to `<code>PRAvatar_SkinningUpdateTPose</code>` and not `<code>ArmatureSkinningUpdateTPose</code>`.</p><p>As soon as I had my "complete" replica of the prefab hierarchy, I cloned the character and duplicated other referenced assets, so that the references point to the copy and not to the version coming from the <em>Samples</em>.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ilLE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ilLE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ilLE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png" width="720" height="230" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:230,&quot;width&quot;:720,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:11805,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ilLE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 424w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 848w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 1272w, https://substackcdn.com/image/fetch/$s_!ilLE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe50c24f6-700d-49a5-ac38-bcf3326d88ba_720x230.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><p>I put in the scene two character hierarchies: the original, and the new copy.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E3iQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E3iQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 424w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 848w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 1272w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E3iQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png" width="383" height="99" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:99,&quot;width&quot;:383,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:11378,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E3iQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 424w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 848w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 1272w, https://substackcdn.com/image/fetch/$s_!E3iQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b2b1a8-ac19-45b1-9bb8-e5537008623d_383x99.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>At this point, I can enable/disable the two `<code>Character</code>` and `<code>PR_Character</code>` nodes and they behave the same.</p><div><hr></div><p>After a bit of supplementary trial and error for assets that I overlooked, I got my body tracked avatar working properly, and with no dependency from the <em>Samples</em> assets, exactly as I wanted.</p><p>The ultimate test was deleting the <em>Samples</em> folder of the movement package and test that everything was still working.</p><p>Here's my version of the assets. I'm not sure of the role of everything, but at lest now I know exactly which elements are in play.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EJ48!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EJ48!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 424w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 848w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 1272w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EJ48!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png" width="377" height="322" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:322,&quot;width&quot;:377,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24172,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!EJ48!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 424w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 848w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 1272w, https://substackcdn.com/image/fetch/$s_!EJ48!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F915b4bf4-1497-4216-a38e-2bf4e4522af6_377x322.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Tomorrow I should hopefully manage to have multiple instances of my character rig running, with two different data sources activating the mesh animation (live tracking data from the headset, and recorded tracking data from a motion clip).</p><h2>2024-04-17 - Playback on SkinnedMeshRenderer</h2><p>Before wiring the different input data, I have to do a couple of minor things to prepare the character rig to exist in multiple instances.</p><p>First, I need to refactor the hack that I did a few weeks ago to scale down the legs to `<code>0.01f</code>`: that is currently done by `<code>PlayerAvatarBhv</code>`, which also handles other things like the mana/health bars.</p><div><hr></div><p>I refactored the legs scaling moving it to a new `<code>UpperBodyCharacterBhv</code>` script.</p><p>Every instance of the character rig will have this behaviour, independently from where the animation data comes from.</p><div><hr></div><p>Now, I want to be able to easily differentiate between the player avatar and other instances, and the obvious thing that comes to my mind is changing color to the particles that make up the body.</p><div><hr></div><p>I added to the VFX of the body a parameter `<code>ParticlesColorGradient</code>`  to be able to set the color by script. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wAoJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wAoJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 424w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 848w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 1272w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wAoJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png" width="1021" height="339" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:339,&quot;width&quot;:1021,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59809,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!wAoJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 424w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 848w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 1272w, https://substackcdn.com/image/fetch/$s_!wAoJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe1439398-4c0f-4651-8b1f-f31059ecbc5c_1021x339.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The VFX node takes in a gradient, but being able to change the start color is sufficient.</p><p>This is the color change method I put into `<code>UpperBodyCharacterBhv</code>`:</p><pre><code>        public void setParticlesColor(Color c) {
            var rColorKeys = new GradientColorKey[] {
                new GradientColorKey(c, 0f),
                new GradientColorKey(Color.white, 1f),
            };
            var rAlphaKeys = new GradientAlphaKey[] {
                new GradientAlphaKey(1f, 0f),
                new GradientAlphaKey(1f, 1f)
            };
            Gradient rGradient = new Gradient();
            rGradient.SetKeys(rColorKeys, rAlphaKeys);

            m_rVFX.SetGradient("ParticlesColorGradient", rGradient);
        }</code></pre><div><hr></div><p>Finally, it's time to start feeding to a second instance of the character some animation clip data.</p><p>The `<code>PRAvatar_SkinningUpdateRetargetSkeletonProcessor</code>` prefab has a pretty complex setup, with a bunch of behaviours storing quite a lot of settings.</p><p>I don't see direct references to `<code>OVRBody</code>`, so hopefully the other behaviours only access the tracking data through the interfaces it implements.</p><p>So, what I'm going to do, hoping that the <em>Meta</em> folks did things in a sensible way, is substituting the `<code>OVRBody</code>` behaviour, that I know fetches the tracking data from the headset, with another behaviour (`<code>UpperBodyCharacterDataProviderBhv</code>`) implementing the same interfaces.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m3zY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m3zY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 424w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 848w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 1272w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m3zY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png" width="629" height="508" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:508,&quot;width&quot;:629,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:62735,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!m3zY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 424w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 848w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 1272w, https://substackcdn.com/image/fetch/$s_!m3zY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34246e3b-0d9f-4239-87a0-ec44836f14a8_629x508.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The screenshot shows what my modified setup looks like, after collapsing all the attached scripts. Notice that I have removed `<code>OVRBody</code>` and added `<code>UpperBodyCharacterDataProviderBhv</code>`, which has empty stubs for the methods implementing the interfaces</p><ul><li><p>`<code>OVRSkeleton.IOVRSkeletonDataProvider</code>`</p></li><li><p>`<code>OVRSkeletonRenderer.IOVRSkeletonRendererDataProvider</code>`</p></li></ul><p>I'm now going to implement these interfaces and then add to `<code>UpperBodyClipPlayerBhv</code>` a reference to this new behaviour, so that it can update its data, like it already does with the `<code>PlayerAvatarDebugGizmos</code>` scripts that shows the debug skeleton we've been using to visualize recorded clip until today.</p><div><hr></div><p>I implemented the interfaces and wired up the nodes appropriately, and it was finally time for testing. What happened?</p><p>Everybody loves videos featuring these kind of bugs, so here you are - I have no shame.</p><div id="youtube2-HBoZB26SkLw" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;HBoZB26SkLw&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/HBoZB26SkLw?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>It looks like I overlooked something - it's not that strange, as the data expected by the two visualization differs. I have a pretty good idea of where to look to fix it, but it's late - I'll get it working properly first thing tomorrow.</p><p></p><h2>2024-04-18 - Playback debugging</h2><p>As it often happens, the problem shown in yesterday's video clip is harder to fix than expected.</p><p>I did some progress, but I also found a serious issue shat is shown by in the following clips, where I have attached two "runtime gizmos" to the <em>hips</em> bone of both character rigs, the blueish one for the playing character and the pinkish one for the clip playback.</p><blockquote><p>The <em>runtime gizmo</em> I mentioned is a debug prefab I did a long time ago and always have around: you can make yours too in 5 minutes with zero 3D modeling skills, putting together three cones and three cylinders, and applying on them three unlit red/green/blue materials.</p></blockquote><p>As you can see, the <em>hips</em> bone of the clip playback doesn't turn, while the arrow sticking out from my belly reasonably rotates according to my body position.</p><div id="youtube2-H9WJ_zWQIWo" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;H9WJ_zWQIWo&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/H9WJ_zWQIWo?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Everything looks working fine with the "skeleton" visualization because, I'm assuming, it doesn't really use the bones rotations, but just the joints positions, drawing lines between them.</p><p>Trying to use the same information to animate a skinned mesh, instead, fails miserably.</p><p>Additionally, the hands don't move at all, so there must be some other issue too.</p><p>I might hack around this, saving the data from the skinned mesh animation after the processing, but I don't really like that idea. It looks like I will need to dig deeper.</p><p></p><h2>2024-04-19 - On-device serialization troubles</h2><p>I doubt I could solve the issues I found out about yesterday in one afternoon, so I decided to end the week doing some other stuff.</p><p>This way, I'm going to tick off something from my list, and will postpone the more challenging task to next week.</p><div><hr></div><p>Now that I have <em>something</em>, even if not correct, that should show up when running on Quest and not only in the Unity editor, I can make sure that the saving and loading of clips work on the headset too.</p><p>That's not to take for granted, as I'm using custom paths and file system access on Android has its own rules.</p><div><hr></div><p>I tried running a build, and indeed, even if it doesn't crash and it plays back the latest recorded clip (that gets automatically set as "current clip"), the UI with the list of saved clips doesn't update.</p><p>Something is off, let's see if I can figure it out.</p><div><hr></div><p>I don't remember if I already discussed the current save/load code, which is super basic.</p><p>Anyway, let's see the relevant parts.</p><pre><code>        private const string sANIMCLIPS_PATH = "_animclips/";
&#9;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
        private static readonly JsonSerializerSettings JSON_SER_SETTINGS =  
            new JsonSerializerSettings {  
                TypeNameHandling = TypeNameHandling.Auto,  
            };
        
        public static void storeAnimClip&lt;H, T&gt;(
                string sClipId, 
                AnimClip&lt;H, T&gt; rClip
            )
                where H : ICopyFrom&lt;H&gt;
                where T : ICopyFrom&lt;T&gt;, IInterpolable&lt;T&gt; {
            string sFilePath = sANIMCLIPS_PATH + sClipId + ".ac.json";
            store(sFilePath, rClip);
        }

        public static void store&lt;T&gt;(string sFilePath, T rData) {
            if (Application.platform == RuntimePlatform.Android) {
                sFilePath = Application.persistentDataPath + "/" + sFilePath;
            }
            string sDirPath = new FileInfo(sFilePath).Directory.FullName;
            if (!Directory.Exists(sDirPath)) {
                Directory.CreateDirectory(sDirPath);
            }

            Debug.Log("store to: " + sFilePath);
            string sJsonText = serialize&lt;T&gt;(rData);
            File.WriteAllText(sFilePath, sJsonText);
        }

        private static string serialize&lt;T&gt;(T rData) {
            string sRet = JsonConvert.SerializeObject(
                rData,
                Formatting.Indented,
                JSON_SER_SETTINGS
            );
            return sRet;
        }</code></pre><p>Good: while on PC I just save close to the project files, on Android I remembered that I must save into `<code>Application.persistentDataPath</code>`, and I'm also logging the file path in `<code>store</code>`. </p><div><hr></div><p>I tried saving a clip and then checked the game data path on device, which is:</p><pre><code>/sdcard/Android/data/com.BinaryCharm.ParticularRealityPrototypeURP/files/</code></pre><p>To access the Quest filesystem I use <em>SideQuest</em>. </p><p>I'm not sure why the <em>Meta Quest Developer Hub</em> only lets me access some areas of the device filesystem (in its <em>File Manager</em> window) and if there's any fixes for that, but well, <em>SideQuest</em> works, I'm going to use that.</p><p>Anyway, in <em>SideQuest</em> the data path contains an `<code>_animclips</code>` subdirectory, which is good news, but it's empty. Let's dig deeper...</p><div><hr></div><p>This is one of those cases where attaching the debugger to the application running natively on an attached Android device (in this case, the Quest headset) is invaluable. </p><p>It kills iteration time, but if something works on PC and not on device, one needs to see what's going on, and that's the best way. </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E88v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E88v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 424w, https://substackcdn.com/image/fetch/$s_!E88v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 848w, https://substackcdn.com/image/fetch/$s_!E88v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 1272w, https://substackcdn.com/image/fetch/$s_!E88v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E88v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png" width="455" height="238" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:238,&quot;width&quot;:455,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12449,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E88v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 424w, https://substackcdn.com/image/fetch/$s_!E88v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 848w, https://substackcdn.com/image/fetch/$s_!E88v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 1272w, https://substackcdn.com/image/fetch/$s_!E88v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F31cacdec-f53b-4a20-905d-1bc5f646616d_455x238.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>When I step into `<code>JsonConvert.SerializeObject</code>`, called by my `<code>serialize</code>` method, the control flow jumps out at some point. I know what that means: an exception got thrown.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ibHp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ibHp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 424w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 848w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 1272w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ibHp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png" width="546" height="171" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:171,&quot;width&quot;:546,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22261,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ibHp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 424w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 848w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 1272w, https://substackcdn.com/image/fetch/$s_!ibHp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f3bd2f0-6f1f-47e4-88da-81700015f032_546x171.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Unfortunately, in these cases when you are running on device, you don't get the exception conveniently shown in the Unity console, and you need to search into the device logs, conveniently accessible through <em>Meta Quest Developer Hub</em>. </p><p>Here's the problem:</p><pre><code>JsonSerializationException: Self referencing loop detected for property 'normalized' with type 'UnityEngine.Vector3'. Path 'rFrames[0].rJointPoses[0].vPos.normalized.normalized'.</code></pre><p>I remember running into this in the past, but not exactly when, nor what I did to fix it.</p><div><hr></div><p>After a quick google search, I remembered: this is one of the issues in the serialization of Unity types when using `<code>Newtonsoft.Json</code>` library. </p><p>But I added to my project <a href="https://github.com/applejag/Newtonsoft.Json-for-Unity.Converters">the additional package</a> that fixes these problems since the day I added any kind of serialization, exactly because I had already ran into the issue in the past. And it's working on PC. So what's going on?</p><p>I'm not even upset: is one of those frequent remainders that "write once, deploy everywhere" is and has always been not more than a <em>fairy tale</em>: in practice, there's all kinds of subtle platform differences that make things go sideways.</p><div><hr></div><p>Summarizing, the problem should be that, on Android, the JSON library is not using the custom converters provided by `<code>Newtonsoft.Json-for-Unity.Converters</code>`. </p><p>The documentation says:</p><blockquote><p>This package automatically adds all its converters to the `JsonConvert.DefaultSettings` if that value has been left untouched.</p></blockquote><p>How does that work? I glimpsed at the code in `<code>UnityConverterInitializer.cs</code>` and there's a part using reflection to find the suitable converters that makes my spider-sense tingle. It's happened to me in the past that code stripping has thrown away needed classes that weren't explicitly referenced. Maybe that's what's happening?</p><p>But I won't dig deeper if I don't need to, and the documentation also explains how to explicitly initialize the serialization settings providing additional converters, and that sounds worth a try. By referring to them, I might also avoid the classes to be stripped from the build.</p><pre><code>             new JsonSerializerSettings {
                 Converters = new JsonConverter[] {
                     new StringEnumConverter(),
                     new VersionConverter(),
                     new Vector2Converter(),
                     new Vector3Converter(),
                     new Vector4Converter(),
                     new Vector2IntConverter(),
                     new Vector3IntConverter(),
                     new QuaternionConverter(),
                     new ColorConverter(),
                     new Color32Converter(),
                     new Matrix4x4Converter(),
                     new LayerMaskConverter(),
                     new RangeIntConverter(),
                     new Hash128Converter(),
                     new BoundsConverter(),
                     new BoundsIntConverter(),
                     new PlaneConverter(),
                     new RectConverter(),
                     new RectIntConverter(),
                     new RectOffsetConverter()
                 },
                 ContractResolver = new UnityTypeContractResolver(),
                 TypeNameHandling = TypeNameHandling.Auto,
             };</code></pre><p>It's not pretty, because I need to enumerate the classes, but it's also simple and explicit - the kind of thing which is perfect to see if something works.</p><p>And guess what? It worked.</p><p>Here's the first clip of the clip management running on Quest and not on PC.</p><div id="youtube2-uDEGdeURmP8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;uDEGdeURmP8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/uDEGdeURmP8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p>Good, another small step forward has been done.</p><p>You can see how saving and loading is super-slow, locking the game for seconds, but that's fine for a development-only file format. In the final build of the game there won't be any JSON handling: at some point I will switch to tightly packed binary files and asynchronous file system operations.</p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://particularreality.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Particular Reality DevLog! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item></channel></rss>