<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://zenithmoon.com/blogs</id>
    <title>ZenithMoon Studios blogs</title>
    <updated>2026-02-05T16:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://zenithmoon.com/blogs"/>
    <subtitle>Posts mirrored from the DarkGenesis blog.</subtitle>
    <icon>https://zenithmoon.com/img/zenithmoon.ico</icon>
    <rights>Copyright © 2026 Simon (darkside) Jackson</rights>
    <entry>
        <title type="html"><![CDATA[Evolving AI Plans]]></title>
        <id>https://zenithmoon.com/blogs/evolving-ai-plans</id>
        <link href="https://zenithmoon.com/blogs/evolving-ai-plans"/>
        <updated>2026-02-05T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[To quote Bueller, Life moves pretty fast. If you do not stop and look around once in a while, you could miss it.  Which is what happens if you do not re-evaluate your AI tools often.]]></summary>
        <content type="html"><![CDATA[<p>The world of AI is moving faster than most can keep up, by the time you have mastered one pattern, several others have cropped up.  If you do not keep a check on yourself, you could be left behind.  If you thought it was tough in the 80/90's (I am old, I get it) with how fast things were evolving, then buckle up, we are now reaching lightspeed.</p>
<!-- -->
<blockquote>
<p><strong>Note:</strong></p>
<p>Even my older AI blog posts are starting to feel old already, but I am enjoying the challenge!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="new-patterns-for-a-new-age">New patterns for a new age<a href="https://zenithmoon.com/blogs/evolving-ai-plans#new-patterns-for-a-new-age" class="hash-link" aria-label="Direct link to New patterns for a new age" title="Direct link to New patterns for a new age" translate="no">​</a></h2>
<p>The Bueller quote heading this article has never been more true, it seems almost daily I am reviewing the news, latest posts from ALL of the major partners involved with Agents and LLM's for new patterns, ideas and suggestions.  Thankfully, everyone is riffing off each other in a good way, someone suggests or posts something, then someone else builds on that and goes that bit further, whether it is:</p>
<ul>
<li class="">A new LLM logic.</li>
<li class="">Improved MCP interfaces and tools.</li>
<li class="">Patterns, plans and guidance.</li>
<li class="">Token reuse or conservation.</li>
</ul>
<p>The list goes on, in some ways, it is like the best parts of collaborative science (in some ways, a dark dystopian nightmare), ever creeping forward.</p>
<p>This post was sparked after I followed up on some of these reports and started to see <strong>REAL IMPROVEMENT</strong> in several key factors in my own research, namely:</p>
<ul>
<li class="">Reduced hallucinations (not gone completely, but significantly reduced).</li>
<li class="">Improved memory (yes, you can have memory if you do it right)</li>
<li class="">Improved outcomes.</li>
</ul>
<p>It almost sounds too true, but my workflow has improved, especially with some dedicated research projects I continually use to test new patterns.</p>
<blockquote>
<p><strong>Warning:</strong>
This is NOT a silver bullet, to get more out, you MUST put more in, and thus the cost, depending on what you are using will INCREASE.</p>
<p>My recommendation, as is becoming more of the goal in today’s market, is to HOST your own LLM infrastructure (they have it <a href="https://github.com/sipeed/picoclaw" target="_blank" rel="noopener noreferrer" class="">running on Raspberry Pi's now</a>).  It is possible with a little effort, and you can reduce your downstream costs to all but the critical path.</p>
<p>Sadly, this is out of scope for this article, but I will try to add more later.</p>
</blockquote>
<p>So, what are these patterns I tease?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="evolving-patterns">Evolving patterns<a href="https://zenithmoon.com/blogs/evolving-ai-plans#evolving-patterns" class="hash-link" aria-label="Direct link to Evolving patterns" title="Direct link to Evolving patterns" translate="no">​</a></h2>
<p>The updated patterns I have started employing recently fall into two categories with a single shared theme:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shared-theme---the-living-document">Shared theme - The living document<a href="https://zenithmoon.com/blogs/evolving-ai-plans#shared-theme---the-living-document" class="hash-link" aria-label="Direct link to Shared theme - The living document" title="Direct link to Shared theme - The living document" translate="no">​</a></h3>
<p>The core of the recent updates is to create <strong>LIVING DOCUMENTS</strong> at the core of any process.  This is a SINGULAR document (and you MUST be explicit, as some agents, looking at you Claude, will randomly go off and create masses of documents you did not ask for) which ALL agent calls must update with their state, or you inform it to update at critical junctures.</p>
<p>I have found this works far better than using the Memory MCP, or constantly updating the instructions guide for the agent, and as a separate document, you can create references to it from all other material, including the instructions.  In testing, this has been far more efficient and rigorously followed than instructions or memory alone.</p>
<ul>
<li class="">You start the session with the instructions pointing to the living document</li>
<li class="">The agent reads the current state with suggested next steps</li>
<li class="">It validates any failures or bad paths from previous attempts</li>
<li class="">It then plans ahead!</li>
</ul>
<p>Your mileage may vary, but this singular change VASTLY improves my outcomes.  Granted, at the cost of the additional tokens needed to read the document, hence the additional cost.  You "can" summarise the document in Agent format if you wish, but I have found this actually degrades its performance and use.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="patterns">Patterns<a href="https://zenithmoon.com/blogs/evolving-ai-plans#patterns" class="hash-link" aria-label="Direct link to Patterns" title="Direct link to Patterns" translate="no">​</a></h3>
<p>Now to the other meat on the bone, the revised patterns that consume the living document.  I have long stated that LLM's are more tuned to the small tasks, a quick fix, diagnosis, but where you need to dig deeper, you have to keep a better handle on the outcome, thus these are the methods I have now turned to using:</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-all-starts-with-a-defined-and-reviewed-plan">It all starts with a defined and reviewed plan<a href="https://zenithmoon.com/blogs/evolving-ai-plans#it-all-starts-with-a-defined-and-reviewed-plan" class="hash-link" aria-label="Direct link to It all starts with a defined and reviewed plan" title="Direct link to It all starts with a defined and reviewed plan" translate="no">​</a></h2>
<p>By far my biggest improvement through LLM use is to focus and narrow its path, to think and to constantly challenge its assumptions.  It is <strong>NOT</strong> foolproof, but it greatly improves your chances of a positive outcome.  The whole approach (which is token-heavy) is an almost constant state of review, approval, implementation and review, which when implemented, looks something like this:</p>
<ul>
<li class="">
<p>First Agent session - Create the PRD (Product Definition) from a detailed set of requirements, this ensures a list of preferences and outcomes.</p>
</li>
<li class="">
<p>Build an instruction document using the PRD as a reference. Guiding the steps for planning, with KEY steps to avoid creating documents unless requested (stops the multi-document scenario).  This might sound counterintuitive, however, they serve very distinct purposes.  One directs how to think, the other tells the LLM what to think about.</p>
<blockquote>
<p><strong>Important:</strong>
Ensure to leave off with a note about the aforementioned <strong>Living Document</strong>, instruct the agent to check the Living document at the start of a prompt and update it with its intent and finish the query with a statement in the living document as to the outcome.  This trains and helps the Agent learn, as well as something to pick up on if you start a completely fresh session.</p>
</blockquote>
</li>
<li class="">
<p>Next, start a completely new session (even with another agent if you wish), define the session as a reviewer / architect actor and have it review and improve the plan.  Take note of what has changed and follow its thinking.  Make corrections where <strong>YOU</strong> disagree. It is essential you are part of this review process, as key decisions that were maybe not clear in the design become apparent (as often happens in life, poor design leads to poor outcomes).</p>
<blockquote>
<p><strong>Note:</strong>
You can repeat the previous step a few times with different defined actors, different personas, just as you would in real life.  The best plans involve a team, and in this case, it is a team of agents AND YOU!</p>
</blockquote>
</li>
<li class="">
<p>At the end, ensure to begin the plan with a Living document record as it delivers the implementation.</p>
</li>
</ul>
<p>Once you are happy with the plan, the next step begins, the following two patterns can be taken:</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="plan-big-but-follow-your-own-path">Plan big but follow your own path<a href="https://zenithmoon.com/blogs/evolving-ai-plans#plan-big-but-follow-your-own-path" class="hash-link" aria-label="Direct link to Plan big but follow your own path" title="Direct link to Plan big but follow your own path" translate="no">​</a></h2>
<p>With the plan in hand, instruct the Agent to document and detail the plan for implementation, <strong>ALWAYS</strong> ensure you finish with "If you have questions, ask them before beginning" (If you do not, it will not, and it ABSOLUTELY SHOULD).  The outcome choice is up to you:</p>
<ul>
<li class="">A singular implementation document.</li>
<li class="">Several documents, one for each stage or component, ordered for implementation.</li>
<li class="">A mix, depending on your style, backend first, then frontend and UX.</li>
</ul>
<p>Ultimately, this is a plan <strong>YOU</strong> will follow, this is my preferred mode as it gives more opportunity to question as you implement or even change your choices.  All the agent has effectively done is ratify the architectural state based on your inputs, it is arguably the most human way to use the tools to achieve your goals.</p>
<p>If anything is unclear, ask the LLM to clarify, make changes or explain something <strong>"BUT ONLY TO THE PLAN"</strong>, the Agent should NOT touch the code, that is your domain.</p>
<blockquote>
<p><strong>Note:</strong>
In my experience, when instructing the planning phase, I also interject to ask the Agent to explain each section or block of code, as to its intention, what it is meant to achieve, or what it is supposed to do.  This helps guide the implementation as to whether it is the best thing to do.  And if you disagree, get it to update its plan, or make your own implementation and then get the Agent to update the spec, then review for ancillary impacts.</p>
</blockquote>
<p>This approach feels more like the Agent working cooperatively than running the show and seeing what it comes out with.</p>
<blockquote>
<p><strong>Important:</strong>
At each stage, with each change, ensure to instruct the Agent to update the living document.  It might be recorded in the Instructions, but I feel safer double-checking at critical points.</p>
</blockquote>
<p>At all points, question everything, it is simply good for the soul and makes the challenge more fun.  All that is left is to continue to the end, test everything and the result I have found to be vastly improved.</p>
<blockquote>
<p><strong>Tip:</strong>
Another fun step, but at the cost of tokens, is to ask the Agent from time to time to review what you have actually implemented against the plan and give a status report (helps to reduce human error), marking the document with AI's favourite thing, Icons and Emoji's.  As well as providing a summary, as well as giving you a visible checkpoint of your progress.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automated-plan-automated-deployment">Automated plan, automated deployment<a href="https://zenithmoon.com/blogs/evolving-ai-plans#automated-plan-automated-deployment" class="hash-link" aria-label="Direct link to Automated plan, automated deployment" title="Direct link to Automated plan, automated deployment" translate="no">​</a></h2>
<p>The automated plan follows a similar track to the manual implementation path, except you ensure to break out the plan into repeatable and testable sections.   It is not as efficient as the manual path, but for shorter and more throwaway experiments, it can be beneficial.</p>
<blockquote>
<p><strong>Note:</strong>
In some cases, I am actually running the two approaches in Parallel!  Using the fast route to test theories in advance before incorporating them into plans, similar to prototyping.</p>
</blockquote>
<p>Rather than the human doing the implementation, the agent is running the show, but CRITICALLY, not all at once.  To avoid hallucinations and dreams of code, each section must be complete, testable and ultimately HUMAN VERIFIABLE.</p>
<p>An example of this was a total conversion of an old game sample in C++ using some REALLY legacy assets. The plan broke down as follows (granted after so many failed attempts in the past):</p>
<ul>
<li class="">Review the project and break up / document the systems and content of the sample.</li>
<li class="">Research old asset formats and create detailed documentation of their makeup (critical for migration).</li>
<li class="">Build out sample sets of each content type and define a migration strategy.</li>
<li class="">For each asset type, build individual pipelines, with human verification at each step.</li>
<li class="">Then implement the migration in individual sessions or phases, each phase does NOT complete until the human in the process signs off on it.</li>
<li class="">Then plan the implementation, in a stackable way, each implementation building on the last, again with human signoff.</li>
</ul>
<p>Doing this phased approach with signoff greatly avoids mass delusion and lots of wasted tokens on something that can never ultimately work.</p>
<blockquote>
<p><strong>Important:</strong>
The human as part of the process is essential, not just for the quality of the output, but also as an efficiency surrounding costs and tokens.  Yes, it is more effort than vibe-coding a website or app, but the results are FAR superior.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://zenithmoon.com/blogs/evolving-ai-plans#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Thus ends this page in our journey, but the destination is still far from sight.</p>
<p>Learning should never end and we should always strive for improvement, and in my humble view, this is in <strong>Cooperation</strong> with an agent and not just handing over the keys on a prompt, no matter how detailed, as it is ultimately flawed.  We learn more along the journey, take those understandings and evolve a better plan.  It is strange, in the many years as a Project Manager, designer, QA and even analyst, this is ultimately how we humans work better, we plan, we question, we revise and ultimately deliver a better outcome.  It might not be perfect, but from that, we continue to build better.</p>
<p>Enough musing, back to the code, which is where I feel most at home, with my new pal sitting on my shoulder.
(Still not sure whether it is a little angel or devil, but let us see where this leads)</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/evolving-ai-plans.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="agents" term="agents"/>
        <category label="llm" term="llm"/>
        <category label="ai" term="ai"/>
        <category label="announcements" term="announcements"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[DarkGenesis Redesign In Progress]]></title>
        <id>https://zenithmoon.com/blogs/redesign-in-progress</id>
        <link href="https://zenithmoon.com/blogs/redesign-in-progress"/>
        <updated>2025-12-15T16:00:00.000Z</updated>
        <summary type="html"><![CDATA[The blog is being redesigned with modern features while preserving all existing URLs.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/SimonDarksideJ/SimonDarksideJ.github.io/" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p>The DarkGenesis blog is undergoing a complete modern redesign. All existing URLs will be preserved for SEO continuity.</p>
<!-- -->
<blockquote>
<p><strong>Note:</strong></p>
<p>This is a living redesign log; content and layout may change as features land.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2026-needed-a-refresh">2026 needed a refresh<a href="https://zenithmoon.com/blogs/redesign-in-progress#2026-needed-a-refresh" class="hash-link" aria-label="Direct link to 2026 needed a refresh" title="Direct link to 2026 needed a refresh" translate="no">​</a></h2>
<p>One of the things I dread is revisiting the design, style, or even template I use for my blog.  I spend far too long browsing lots of cool things, find something I like only to find it is either not deployable or has "hidden" issues.  The end result is always a trade off between what I want/like and what the template is capable of.</p>
<p>So this time things are different, using tools available (I still refuse to use a site generator), I began the journey using all the tools at my disposal to generate the site fresh, give it a modern look and then use AI tools to help refine/change what I did not like.</p>
<p>Still very much a work-in-progress, but delivered far faster using the tools available to me.  I hope you like it.</p>
<p>The rest of this post is my ongoing test to ensure styles work, features are as expected and nothing "breaks" :D</p>
<p>Feel free to skip the rest and move on :P</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="example-post-image">Example post image<a href="https://zenithmoon.com/blogs/redesign-in-progress#example-post-image" class="hash-link" aria-label="Direct link to Example post image" title="Direct link to Example post image" translate="no">​</a></h2>
<p>This post uses a date-based image folder so it’s obvious which images belong to which post:</p>
<p><code>/assets/img/posts/20251215/redesign-in-progress/</code></p>
<p>The banner image is referenced via front matter (<code>img:</code>) and can also be embedded inline:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20251215/redesign-in-progress/hero.svg" alt="Redesign in progress banner" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-coming">What's Coming<a href="https://zenithmoon.com/blogs/redesign-in-progress#whats-coming" class="hash-link" aria-label="Direct link to What's Coming" title="Direct link to What's Coming" translate="no">​</a></h2>
<ul>
<li class="">Modern responsive design</li>
<li class="">Dark mode support</li>
<li class="">Enhanced search</li>
<li class="">Better accessibility</li>
<li class="">Social media integration</li>
</ul>
<p>Stay tuned!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="markdown-support-quick-validation">Markdown support (quick validation)<a href="https://zenithmoon.com/blogs/redesign-in-progress#markdown-support-quick-validation" class="hash-link" aria-label="Direct link to Markdown support (quick validation)" title="Direct link to Markdown support (quick validation)" translate="no">​</a></h2>
<p>Inline code looks like <code>bundle exec jekyll serve</code>.</p>
<p>Strikethrough works: <del>old plan</del> new plan.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fenced-code-block">Fenced code block<a href="https://zenithmoon.com/blogs/redesign-in-progress#fenced-code-block" class="hash-link" aria-label="Direct link to Fenced code block" title="Direct link to Fenced code block" translate="no">​</a></h3>
<div class="language-ruby codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ruby codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">puts </span><span class="token string-literal string" style="color:rgb(173, 219, 103)">"Hello from Ruby"</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="task-list">Task list<a href="https://zenithmoon.com/blogs/redesign-in-progress#task-list" class="hash-link" aria-label="Direct link to Task list" title="Direct link to Task list" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled="" checked=""> Jekyll build works on Windows</li>
<li class="task-list-item"><input type="checkbox" disabled=""> Add more posts</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="table">Table<a href="https://zenithmoon.com/blogs/redesign-in-progress#table" class="hash-link" aria-label="Direct link to Table" title="Direct link to Table" translate="no">​</a></h3>





















<table><thead><tr><th>Feature</th><th>Status</th></tr></thead><tbody><tr><td>Fenced code blocks</td><td>✅</td></tr><tr><td>Tables</td><td>✅</td></tr><tr><td>Task lists</td><td>✅</td></tr></tbody></table>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/redesign-in-progress.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="blog" term="blog"/>
        <category label="announcement" term="announcement"/>
        <category label="announcements" term="announcements"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using AI tooling effectively - a lessons learned exercise]]></title>
        <id>https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise</id>
        <link href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise"/>
        <updated>2025-11-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Whether you are for or against using AI tooling in your workflow, it is critical to know and understand the options.  The tooling can help you plan and even think better even without writing code for you.  Work smarter, not harder.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL; DR -&gt; Whether or not you are for or against AI, if you do not at least read up on the landscape and the capabilities out there you WILL fall behind.  These tools can be used in many different ways and provide support where you least expect it.</p>
<p>It is not about taking away the fun tasks, if you do not want it to code for you, then stop, want to create your own artwork, fine do that.  But do not discount the capabilities of the tools because of preconceptions.</p>
<p>You do not need to use it, but understanding what you CAN use it for better arms you in the road ahead to know what you are up against.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="work-smarter-not-harder">Work smarter, not harder<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#work-smarter-not-harder" class="hash-link" aria-label="Direct link to Work smarter, not harder" title="Direct link to Work smarter, not harder" translate="no">​</a></h2>
<p>The evolution of AI tools is ever increasing, as is any technology or area that is adopting these tools.  It was already an almost impossible task to "keep up" with advances, causing developers and engineers to keep narrowing their specialties, reducing the mental load to be able to take in the latest and greatest.</p>
<!-- -->
<p>Since the unveiling of the current generation of AI tooling, the curve keeps getting sharper and sharper with facilities such as:</p>
<ul>
<li class="">Guides on instructions to provide consistent context between queries.</li>
<li class="">MCP servers and their capabilities have increased.</li>
<li class="">More and more LLM model services for injecting capabilities into a workflow or service.</li>
<li class="">Prompt generation research.</li>
<li class="">New agent modes from Chat -&gt; Agent -&gt; Planning -&gt; ...</li>
</ul>
<p>The list goes on and keeps getting bigger, refinements, new tools, new features.  Like any technological "revolution" its speed is increasing.</p>
<blockquote>
<p><strong>Note:</strong>
However one thing remains clear and should not be underestimated, is that AI <strong>STILL</strong> does not know it is right.  So always be on your guard and check the facts!  But that does not mean you should not use the tools, just learn the correct ways to work with the tools.</p>
</blockquote>
<p>Based on my understanding (at the time of writing), this article aims to impart how my workflow has evolved to a level where I can institute "some" level of trust in my processes with the results from AI.</p>
<blockquote>
<p><strong>Note:</strong>
Fun fact, even today when I started writing this post, I listened to a <a href="https://jesseliberty.com/2025/10/10/james-montemagno-on-vibe-coding/" target="_blank" rel="noopener noreferrer" class="">podcast with Jesse Liberty</a> that featured "James Montemagno on Vibe Coding" ( a term I do not like tbh).  In this episode James mentions a few "tricks" I had not considered before, such as using AI to generate the tool or a prompt based on your current activity to enable you to do that activity faster next time.  Effectively engineering your workflow as you DO IT.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-starts-with-a-prompt---the-misconception">It starts with a prompt - The Misconception<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#it-starts-with-a-prompt---the-misconception" class="hash-link" aria-label="Direct link to It starts with a prompt - The Misconception" title="Direct link to It starts with a prompt - The Misconception" translate="no">​</a></h2>
<p>I have said it previously, but it is worth repeating.  Long gone are the days when you put in a simple question to get an answer or complete a task to fulfil.  Sure, on your phone or via your browser, the results are FAR BETTER than dropping it into Bing/Google, but for any serious workflow. Without content/structure and even architecture, the most basic of queries will not result in anything that resembles what you desire.  (except for that one day in march where the hares are flocking).</p>
<blockquote>
<p><strong>Important:</strong>
<strong>AI is suggestive, it is not indicative.  It NEVER knows if it is right, only that the probabilities align with a result compared to your query.</strong></p>
</blockquote>
<p>So let us breakdown the critical paths as I understand them at the moment.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="generate-instructionsagent-document">Generate Instructions/Agent document<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#generate-instructionsagent-document" class="hash-link" aria-label="Direct link to Generate Instructions/Agent document" title="Direct link to Generate Instructions/Agent document" translate="no">​</a></h3>
<p>Instructions or Agent guides are critical to modern AI interactions, they define the background to your request/conversation, define the scope of the work and critically also inform the AI of any boundaries it should not cross.  Traditionally, it is also used to define the architecture to follow and conventions to use, but it can also be used as a thought board.</p>
<p>Do not think of your instructions as a static document, it is a living/breathing entity that should grow and learn as your project evolves.  Include tips/tricks, any repeated lessons or "daft things" the AI performs that you would rather it did not.</p>
<blockquote>
<p><strong>Important:</strong>
The instruction guide is sent as a pre-context to EVERY chat/conversation, this is information it should know whenever it is sent off to think about anything!</p>
<p>But do not worry about its length or performance, the local agents "tune" the instructions and summarize it for easy digestion.  You would be surprised about how much "pre-processing" happens in your client before it even contacts the cloud AI agent.</p>
</blockquote>
<p>Simple steps to follow as your project evolves, whether it is a coding session, a Q&amp;A query or just taking notes for a subject:</p>
<ul>
<li class="">If you have an existing project, use the built in VSCode (or other tool) "Generate Instructions" to set an initial framework.</li>
<li class="">If you are beginning thoughts for a project, start a conversation with the aim of generating this instruction guide.</li>
<li class="">Keep refining the instructions, with or without AI help to frame future conversations.</li>
</ul>
<p>Above ALL, keep reviewing the instructions manually after generation, do <strong>NOT</strong> just trust the result, it is meant as a starting point not a finish line.  Keep reviewing, updating and even deleting content until you are satisfied with the result.  And as your project grows, so should your instruction guide.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="use-mcps-relevant-to-your-process">Use MCPs relevant to your process<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#use-mcps-relevant-to-your-process" class="hash-link" aria-label="Direct link to Use MCPs relevant to your process" title="Direct link to Use MCPs relevant to your process" translate="no">​</a></h3>
<p>MCP servers are fast becoming the "go to" tool to assist in local AI support and cut down on the amount of traffic used by cloud agents, they help form, and in some cases, generate results to assist the AI in its journey, these can range from:</p>
<blockquote>
<p><strong>Tip:</strong>
Model Context Protocol servers, or MCP for short actually details the method used to communicate from an AI LLM with a local AI or context agent, a tool used to support AI on its journey.  Their capabilities can range from just an API for serving data or results, to full blown AI agents themselves.  The MCP protocol effectively allows AI's to talk to each other and work together to reach a goal.</p>
</blockquote>
<ul>
<li class="">Simple functions used in calculations or a fact table.</li>
<li class="">Headers for local AI agents or models, such as image generation.</li>
<li class="">Self-contained LLM's, primarily used for documentation or API references.  Useful for keeping the AI bound to accurate source information rather than just the web.</li>
<li class="">Proxies to other cloud hosted AI agents, woven together to form a mesh.</li>
</ul>
<blockquote>
<p><strong>Note:</strong>
Do not be afraid to AI your own MCP server locally, if you find certain areas where you are constantly correcting the AI agent, or need it to use pre-defined terms in its response, then just generate your own server to manage this for you and update your instructions to defer to the MCP server in responses.  This can lessen the load in your instructions if you can have an agent maintain it for you.</p>
</blockquote>
<p>Each MCP effectively adds another tool the AI can use in the construction of a response as well as off-loading tasks locally to save time or money (credits), or in some cases MANY tools.  And there is a growing movement where you can use AI to generate your own local MCP servers, especially for repeated tasks or guardrails.</p>
<blockquote>
<p><strong>Important:</strong>
<strong>Too many tools can degrade performance</strong>
While having lots of MCP servers at the ready is fantastic, be aware you need to limit which Servers/Tools are active at any one time.  Giving the AI too many options can severely degrade performance as it need to check the toolset with each interaction (even within a response) to see if there is a tool to help.</p>
<p>Make sure for each interaction/conversation, that you are using the tools that will assist with that thread.  Not asking it about code or pull requests, then disable the GitHub MCP tools.  Also critical for any language or toolset focused conversations.  Just use the tools that will help with your query.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-custom-prompts-for-repeated-tasks">Create custom prompts for repeated tasks<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#create-custom-prompts-for-repeated-tasks" class="hash-link" aria-label="Direct link to Create custom prompts for repeated tasks" title="Direct link to Create custom prompts for repeated tasks" translate="no">​</a></h3>
<p>A recent trick I picked up surprised me how effective it was.  If you find yourself either asking the same question, or making the same corrections, or even just doing the same thing repeatedly, then stop and just make a new customized prompt for yourself.</p>
<p>You see these already when in VSCode you hold <code>ctrl + shift + P</code> (or <code>cmd + shift + P</code> on Mac) to bring up the command window, from here you see regular actions available to you based on the extensions you have installed and the MCP servers you have registered.  Once activated, they complete a set of tasks using what is available to perform an operation, be it cloning a repository, or formatting your document (my most used command).</p>
<p>But you can also create your own, and again, use AI to help you generate it.  In the middle of a conversation when you have completed a task, ask the AI to summarize the task that was completed and ask it to generate a new Command in the tool.  You will get the source for the command, allowing you to tweak it to your needs (and likely remove some unnecessary steps) and then save it with a command name, thus, every time you need to perform the same task, save yourself some keystrokes and just launch your command instead.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="leverage-resources-online">Leverage resources online<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#leverage-resources-online" class="hash-link" aria-label="Direct link to Leverage resources online" title="Direct link to Leverage resources online" translate="no">​</a></h3>
<p>A common misconception when working with AI Agents, is to assume it is trawling the web for the current/latest information, sadly this is not true (in the most part), LLM's rely on historical information they have been trained on.  If there is newer information, it is best to direct the AI to also read specific documents, websites or other sources of information in addition to its trained model.</p>
<blockquote>
<p><strong>Important:</strong>
Although this does not "train" the AI in new information, the data that is gathered is only summarized and used as part of the current conversation.  If you asked it again in a new conversation without providing the source, it will not know.</p>
</blockquote>
<p>You can add such sources to your instruction guide, although it will only be summarized from the last time you generated the guide, alternatively you can either use an MCP server such as "Context7" (A useful docs MCP) or generate your own MCP server that refreshes its memory from online sources that you critically rely upon, like your companies document server.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cost-management---the-hidden-tax">Cost Management - The hidden tax<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#cost-management---the-hidden-tax" class="hash-link" aria-label="Direct link to Cost Management - The hidden tax" title="Direct link to Cost Management - The hidden tax" translate="no">​</a></h3>
<p>While many of the tools we use today offer generous free tiers or fixed monthly subscriptions, the move towards "pay-as-you-go" models for advanced agents and API access brings a new consideration: Cost.</p>
<p>Every interaction with an AI is not just the question you ask; it includes the entire conversation history, your instruction guides, and any files you have attached.  This "Context Window" is re-sent with every single query.  If you have a long-running chat with megabytes of code attached, you are paying for that data transfer every time you press enter.</p>
<blockquote>
<p><strong>Important:</strong>
<strong>Token Hygiene</strong>
To keep costs (and latency) down, practice good token hygiene:</p>
<ul>
<li class=""><strong>Start fresh:</strong> Do not keep one giant chat going forever. Start a new conversation for each distinct task.</li>
<li class=""><strong>Limit context:</strong> Only attach the files relevant to the specific question.</li>
<li class=""><strong>Watch the loops:</strong> Autonomous agents that "think", "plan", and "verify" can trigger dozens of internal calls for a single user request.</li>
</ul>
</blockquote>
<p>Being mindful of your token usage does not just save money; it often results in faster and more accurate responses because the AI is not distracted by irrelevant noise from 50 messages ago.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="change-your-workflow">Change your workflow<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#change-your-workflow" class="hash-link" aria-label="Direct link to Change your workflow" title="Direct link to Change your workflow" translate="no">​</a></h2>
<p>AI tooling should not be just about work, used correctly, you can use the tools to influence or guide almost any task, such as:</p>
<ul>
<li class="">Guided research.</li>
<li class="">Ideation and thought generation.</li>
<li class="">Validating preconceptions.</li>
<li class="">Applying structure.</li>
<li class="">Architecture compliance.</li>
<li class="">General validation.</li>
</ul>
<p>And these are just from my building workflow over recent months/years.  Using AI to not just do work, but to plan, evolve, generate more tools to further accelerate your workflow, defining the ultimate development method of DRY - Do not Repeat Yourself.</p>
<blockquote>
<p><strong>Tip:</strong>
Did you know there are several "MODES" of AI usage now?  Not just Agentic (Agent) or "ask" (query), but also "Plan", which is great for digging deep and coming up with a stepped plan of actions which you can either get the AI to start working from or do yourself, as well as several others and even custom modes you can create yourself, tailor the experience to meet your needs.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="guided-research">Guided research<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#guided-research" class="hash-link" aria-label="Direct link to Guided research" title="Direct link to Guided research" translate="no">​</a></h3>
<p>When researching any topic, wading through mounds of documents, websites, information and searching for the latest (or even historical) information can be very time consuming.</p>
<p>Granted, this should be treated as source material and taken with a grain of salt as critical facts will need to be verified.  Even with recent advances in correction and validation, it is still just an approximation.  In many cases, it is worth asking different agents to read over the results and verify sources for cases.</p>
<p>You might think, why bother, if the information is potentially unreliable what is the point.  But the answer is simple, the AI can search through data to find you a shortlist far quicker than any human can, reducing the scope of your research with summaries.</p>
<p>But do not stop there, keep iterating the results upon itself, demand references and lookups to the latest sources to verify the results, which in the end results in data you can rely on for your studies.</p>
<blockquote>
<p><strong>Important:</strong>
Even so, in research and summarizing the research, use your own words.  Use the data as refined source material which is just a smaller window of the wider world contained within results.  The ultimate aim is to save you TIME, if nothing else, it can also type faster than you can.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ideation-and-thought-generation">Ideation and thought generation<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#ideation-and-thought-generation" class="hash-link" aria-label="Direct link to Ideation and thought generation" title="Direct link to Ideation and thought generation" translate="no">​</a></h3>
<p>A concept I have also been using AI tooling for of late is idea generation.  You get a half sleep deprived thought or waking moment of "something", it sounds fine in principle but will it work.</p>
<p>This is where, depending on the type of project it is, I will lean on AI tooling to help ratify my thoughts:</p>
<ul>
<li class="">Has this been done before, if so what worked and what did not.</li>
<li class="">Is there any market opportunity for such an idea.</li>
<li class="">Summarizing options for "go-to market" strategies.</li>
</ul>
<p>If the idea is a solution, even using GitHub Copilot to form a new project automatically, generating source, identifying workloads and deployment options.  Which it will take on, generate a new repository, and start building out the framework of a potential solution (fantastic for web solutions).  Give it enough detail, apply references for similar things you have in mind, and set it off.  All of which will happen without you being involved (or even at your desk) thanks to workflow agents.</p>
<p>It does not need to end up with something real, except to prove to yourself whether something is valid or even possible.  It gives you a framework to build upon and cuts down the time involved in your personal research.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="validating-preconceptions">Validating preconceptions<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#validating-preconceptions" class="hash-link" aria-label="Direct link to Validating preconceptions" title="Direct link to Validating preconceptions" translate="no">​</a></h3>
<p>Often during my development and management life, we build our own internal bias, it is not always intentional and can lead you to incorrect assumptions.  My geography knowledge for instance, is waaay out of date.</p>
<p>Most consumers of AI will turn to the agent on their phones for a quick query, usually with FAR better results than a one line google search (with so many Ad results these days), the results are better, but usually very inefficient.</p>
<p>I have been "trying" to teach my family a better etiquette for searching with AI assistance, forming not just a question but also how to form the response, for example:</p>
<p>Instead of just:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">What are the best locations to go on holiday in June?</span><br></div></code></pre></div></div>
<p>Better to give the AI heads up as to what you are really looking for:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">I am looking to go on holiday in June, what are the best locations to visit?  I prefer hot locations (but not too hot) with spa facilities and a good nightlife.  Make sure to also review other visitors reviews of locations and only include places that reviewed well.  Form the results into a table listing the destination, the hotel or cabin, average review rating and links to booking information.  Also check the sustainability rating for the areas to ensure it is not over populated with tourists and has affordable travel insurance (around £30 pp)</span><br></div></code></pre></div></div>
<p>This structure of query is simply more informative while staying natural and tries to tick as many boxes as possible for what you are REALLY looking for:</p>
<ul>
<li class="">What is the purpose of the search.</li>
<li class="">What are the constraints.</li>
<li class="">How you want the results formatted.</li>
<li class="">And additional concerns or limitations.</li>
</ul>
<p>This is just an example, but as time and tide has shown us, the more effort you put into defining your question (and results) the better the output will be and reduce the need for further onward searches to clarify results.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="applying-structure">Applying structure<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#applying-structure" class="hash-link" aria-label="Direct link to Applying structure" title="Direct link to Applying structure" translate="no">​</a></h3>
<p>One of the hardest parts in any software (or other designs) is architecture, how well formed the solution needs to be.  What constraints are needed, which conventions should be used and where the checkpoints need to be in order to avoid pitfalls.</p>
<p>Of the many designs I have done in the past, it is always the longest struggle to keep constantly refining the structure around any project, whether it is in design or implementation.  What other considerations need to be considered or applied.  Even with many years of training and experience, you always wonder "what if" or try to evaluate uncommon scenarios that could affect operation.</p>
<p>Using AI tooling to review the architecture, critically looking for "things you did not consider" is crucial.  Even if you choose not to act on the information as it is not relevant, having another set of eyes with a much larger dataset can really help.  I have solved many an issue in a junior's design, as have my seniors done for me, so why not also use the tools available for a third set of eyes.</p>
<p>Defining a well architected solution leads into the next section for using AI to ENFORCE that architecture and prevent future works overriding the design (or at least get them to question the approach).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture-compliance">Architecture compliance<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#architecture-compliance" class="hash-link" aria-label="Direct link to Architecture compliance" title="Direct link to Architecture compliance" translate="no">​</a></h3>
<p>With any well defined architecture, adhering to that architecture can be challenging.  But you can use the AI to help provide a ground truth based on the defined architecture, as well as verifying how compliant any development work undertaken is.</p>
<p>Additionally, the architecture itself can be used as a guide for any future work, using the implementation, design or instructions to inform work that is being undertaken.</p>
<p>As an example, I was building a new component within a service driven architecture and the AI tooling correctly identified all the key touchpoints within the architecture to add the new component, as well as adding Unit tests around the feature.  It derived everything it needed from:</p>
<ul>
<li class="">The existing implementations</li>
<li class="">The documentation associated with the project</li>
<li class="">Designed instructions surrounding the architecture and bounds</li>
<li class="">And existing Unit tests for other services (in fact it suggested some additional tests which we then implemented across the board)</li>
</ul>
<p>This also meant that new work complied with the existing architecture and made the final review and update a lot quicker and simpler, I would estimate it reduced the time needed to build the new component by about 80%, meaning I could get more done faster.  BUT, I still had to do a fair amount of work to review the implementation, but no more than I would normally.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="general-validation">General validation<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#general-validation" class="hash-link" aria-label="Direct link to General validation" title="Direct link to General validation" translate="no">​</a></h3>
<p>Checks and balances are key for any professional work, in fact I now regularly use either Copilot as a code review agent or use the Copilot review processes for all documentation, not only to check for the usual grammar / consistency, but also to check my own facts, I prefer to always write myself and use the AI as a reviewer and NEVER the other way round (well, except that one time).</p>
<p>There are many tools in the AI world that are available and you will probably find you have already been using a lot of them, such as a spell checker in your documents (a nascent form of AI that is strictly contained), but writing with AI and also double checking your own preconceptions is now far easier.  As an author I used about seven different tools in my own personal proofing exercise and with the addition of AI into that workflow, I have been able to reduce it down to three (because it pays not to have all your eggs in one basket) which saves me considerable time in review.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/using-ai-tooling-a-lessons-learned-exercise#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>Hopefully this article opens some additional thoughts patterns, as mine do almost daily as I use, reuse and learn from my own use.  I have gone far beyond the level of "generate this method" or "this error occurs, why?".</p>
<p>Keeping up to date is a struggle, but it does not have to be, include all the tools available to you to make your life easier, or at least smoother.  The tools can simply type faster than you, so use that to your advantage.</p>
<blockquote>
<p><strong>Important:</strong>
But even given all the resource, <strong>DO NOT</strong> trust the result, check it, validate it, challenge the generated pre-conceptions.  Even with the extra steps to read through what has been created, the workflow is still significantly faster.  So use it when it makes sense, and for those things you take joy in, and keep having fun.</p>
</blockquote>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/using-ai-tooling-a-lessons-learned-exercise.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="learning" term="learning"/>
        <category label="tools" term="tools"/>
        <category label="planning" term="planning"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Can you make a game with AI]]></title>
        <id>https://zenithmoon.com/blogs/can-you-make-a-game-with-ai</id>
        <link href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai"/>
        <updated>2025-08-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[In my yearly/biannual experiments with AI, I try and make games for popular engines using just AI. Oh what fun.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/SimonDarksideJ/MechRampage-AIGeneration/" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<blockquote>
<p>TL; DR -&gt; In short, NO, lol.  AI is a tool that can assist you in solving common problems and mixing many different AI tools and features you can help to accelerate your development and the creation of content (at least place holders for your artists to draw from).  But, it is also a great source of pain, frustration and injection of complex issues, which you end up using AI to solve only making the complexity worse and harder to understand if you are not familiar with it.</p>
<p>Essentially, the AI <strong>CANNOT</strong> do it alone and still needs a human at the helm to guide it, correct it and use other tools to generate assets.</p>
<p>It is however fantastic for DOCUMENTING your project and identifying potential issues!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-ai-war-continues">The AI war continues<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#the-ai-war-continues" class="hash-link" aria-label="Direct link to The AI war continues" title="Direct link to The AI war continues" translate="no">​</a></h2>
<p>As ever progress marches on whether we want it to or not and with the ever increasing power that AI brings, everyone gets split into one of three groups:</p>
<!-- -->
<ul>
<li class="">Hey, I can replace people with a prompt and just build project X using fewer resources (completely false, as the total cost of AI far exceeds the cost of humans)</li>
<li class="">AI is evil and only scrapes resources from other hard-working developers and should be avoided at all costs.</li>
<li class="">AI is a tool; if used properly, it can accelerate your work. However, it can also introduce complexity and challenges if not managed carefully!</li>
</ul>
<p>Want to see what AI can do for your game development, check the latest result of my adventures :D</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/MechRampage-Final.png" alt="MechRampage Final render" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 1: MechRampage Final render</strong></td></tr></tbody></table>
<p>In this article I will detail the latest generation in my experiments with using AI in game development, the terrors, the hail Marys, and the pitfalls. As well as some useful guidance for those new to the field.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-project-definition">The project definition<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#the-project-definition" class="hash-link" aria-label="Direct link to The project definition" title="Direct link to The project definition" translate="no">​</a></h2>
<p>For this run, I went with a more complicated outline than I have done in the past, mainly due to the experiences I have built up this year, but also to challenge myself and the AI a little more.</p>
<blockquote>
<p><strong>Note:</strong>
Past experiments involved simple block pusher games, 2D shooters, or even snake, just to give the AI a chance.</p>
<p>*Note, no, it failed to even get close most times.</p>
</blockquote>
<p>This was the outline brief I went with:</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mech-rampage">Mech Rampage<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#mech-rampage" class="hash-link" aria-label="Direct link to Mech Rampage" title="Direct link to Mech Rampage" translate="no">​</a></h3>
<p>Mech Rampage is a 3D isometric game where the player will navigate a futuristic city, battling enemies and completing missions.  The player is a commander managing a team of customizable mechs, each with unique abilities and play styles.</p>
<p>The terrain will be procedurally generated from a set of assets, fielding such arenas as grassy outlands, urban environments, and industrial complexes.  Note, all content to be generated initially by the agent and placed in the content folder for loading.</p>
<p>The play area is an isometric projection, allowing for a unique perspective on the 3D environment.  The player will be able to navigate the space freely, utilizing the verticality and depth of the environment to their advantage.  Control will be through a point and click interface or touch controls:</p>
<ul>
<li class="">When a mech is clicked, it becomes the active unit and the status displays show the stats for that unit</li>
<li class="">When a mech is selected, the player can issue commands to move, attack, or use abilities</li>
<li class="">When an enemy is clicked, the player can target it for attack or issue special abilities</li>
<li class="">When a resource is clicked, the player can collect it or use it to upgrade their mechs</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="generating-a-game-from-ai">Generating a game from AI<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#generating-a-game-from-ai" class="hash-link" aria-label="Direct link to Generating a game from AI" title="Direct link to Generating a game from AI" translate="no">​</a></h2>
<p>The world of AI is rapidly changing and it is hard to keep up at times, it has always been this way with technology, however, with AI the sheer acceleration and complementary (sometimes derisive) changes make it harder to fathom your way through this maze.</p>
<blockquote>
<p><strong>Note:</strong>
Full disclosure, while I have a lot of experience in the area of AI tools, I would never class myself as an "expert", I simply know more than some, yet less than others.  It is an ongoing learning experience to find the best ingredients to make a good cake.  Just keep in mind the rate at which new ingredients are being made is evolving almost as fast as the range of cakes to bake (and not all are good).</p>
</blockquote>
<p>The main thing that has changed in recent AI evolution is the level of planning and resources you can throw at a problem to help direct and make the eventual result better, or at least more in line with testable requirements.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="instructions">Instructions<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#instructions" class="hash-link" aria-label="Direct link to Instructions" title="Direct link to Instructions" translate="no">​</a></h3>
<p>Everyone seems to believe that getting the best result out of AI is to create the perfect "Prompt".....</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/StarTrekLies.gif" alt="this is a lie" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>This is simply wrong, at least in this day and age.</strong></td></tr></tbody></table>
<p>Whereas in the past, a simple quick question or request would be enough to accomplish a simple task, it would almost never be what you needed. Even code intellisense, while useful, would more often than not just "get in the way" while you are in the zone.</p>
<p>The resolution to the "quick" answer is effectively to go long, not in the question, but in the context or framework that you provide "up front" to the AI machine in order to essentially give it a frame of reference. This is defined as a precursor document or instruction guide; each tool you use has its own version of this:</p>
<ul>
<li class="">VSCode uses a <code>copilot-instructions.md</code> guide.</li>
<li class="">Anthropic uses a <code>claude.md</code> document.</li>
</ul>
<p>And others have their own variations, but they all surmise to the same thing: a document that describes such things as:</p>
<ul>
<li class="">The aim of the project (the document is meant to be system wide).</li>
<li class="">The architectural principles of the project.</li>
<li class="">How the AI should go about any task (more on that in the <a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#planning" class="">Planning</a> section).</li>
<li class="">Any key resources that should always be used.</li>
<li class="">What are the strict rules that must be followed (my favorite with Unity projects is to remind the AI that Unity DOES NOT use DotNet CLI commands).</li>
<li class="">Instructions on what the output should be, any documents that need to be maintained.</li>
</ul>
<p>And in the strictness sense of the term, MORE is better, the more you put in, the better your result will be.</p>
<blockquote>
<p><strong>Tip:</strong>
VSCode and its CoPilot implementation even includes a helper tool in its GitHub Chat integration to get you started with <code>Generate Instructions</code> available in the "cog" options in CoPilot Chat as shown below:</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/generate-instructions.png" alt="Figure 2: CoPilot Chat - Generate Instructions" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 2: CoPilot Chat - Generate Instructions</strong></td></tr></tbody></table>
</blockquote>
<p>Some awesome resource for working with these instructions guides can be found at:</p>
<ul>
<li class="">The <a href="https://github.com/github/awesome-copilot" target="_blank" rel="noopener noreferrer" class=""><code>awesome-copilot</code></a> repository, chock full of instructions, modes and more.</li>
<li class="">The <a href="https://github.com/Code-and-Sorts/awesome-copilot-instructions" target="_blank" rel="noopener noreferrer" class=""><code>awesome-copilot-instructions</code></a> repository.</li>
<li class="">Essential viewing is the <a href="https://www.youtube.com/watch?v=EpmxbAxOe4A" target="_blank" rel="noopener noreferrer" class="">5 New VS Code AI Features &amp; Settings You Need To Try Today</a> session by James Montemagno.</li>
</ul>
<p>But let us continue.</p>
<blockquote>
<p><strong>Note:</strong>
For reference, here are the <a href="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/copilot-instructions.md" target="_blank" rel="noopener noreferrer" class=""><code>copilot-instructions</code></a> that I used for the "Mech Rampage" test.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="planning">Planning<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#planning" class="hash-link" aria-label="Direct link to Planning" title="Direct link to Planning" translate="no">​</a></h3>
<p>A key difference to the way most prompting works, especially if you use AI on your mobile devices or via chats on the web, is using the AI to "Plan" ahead before it even touches code. This will guide it to define a set of tasks and a breakdown of what it is actually going to do. This can take many forms, but as a starter, here is one of my most common patterns:</p>
<blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="planning-approach">Planning Approach<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#planning-approach" class="hash-link" aria-label="Direct link to Planning Approach" title="Direct link to Planning Approach" translate="no">​</a></h4>
<ul>
<li class="">Requirements Analysis: Ensure you fully understand what the user wants to accomplish</li>
<li class="">Context Building: Explore relevant files and understand the broader system architecture</li>
<li class="">Constraint Identification: Identify technical limitations, dependencies, and potential challenges</li>
<li class="">Strategy Development: Create comprehensive implementation plans with clear steps</li>
<li class="">Risk Assessment: Consider edge cases, potential issues, and alternative approaches</li>
</ul>
</blockquote>
<p>These steps effectively FORCE the AI to actually think about what it is going to do ahead of time, giving you time to evaluate and guide its instruction and testing to ensure what you WANT it to build.</p>
<blockquote>
<p><strong>Tip:</strong>
ALWAYS read the planning guide the AI is generating completely, you never know when it might try and sneak something past you that is either not needed or you do not want!</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="mcp-servers">MCP Servers<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#mcp-servers" class="hash-link" aria-label="Direct link to MCP Servers" title="Direct link to MCP Servers" translate="no">​</a></h3>
<p>In the fight with AI, newer tools and features have come to our aid in the form of Model Context Protocol services (or MCP for short), which are essentially tools and components to help aid the AI in its journey. You might wonder why these all-powerful gods would need a little extra help, and the answer is simple:</p>
<blockquote>
<p><strong>Important:</strong>
ALL AI tools have no memory - Every question you pose is new to the AI each time you ask it.</p>
</blockquote>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/ihavenomemory.gif" alt="Sorry, what was your question again?" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Sorry, what was your question again?</strong></td></tr></tbody></table>
<p>In case you are unaware, all AI systems are "stateless", meaning each call is new and fresh, the only reason it appears to be continuing a conversation is that the ENTIRE conversation history is sent to the AI "EACH AND EVERY TIME YOU ASK A QUESTION".  You may also see a pause in the request at times stating "Summarizing conversation.." where the AI is actually sending the current state of the conversation and getting it to shorten it down to the key points like it does when you ask AI to summarize an email or document.  Although NOTE, in this summarization, key things can be lost, statements made, considerations you asked it to make, or even what day of the week it is, the process is not perfect, like any game of "Whispers", the message degrades the further you get away from the question.</p>
<blockquote>
<p><strong>Note:</strong>
A perfect example is that until I used MCP servers, I would regularly have to keep injecting into the conversation "UNITY DOES NOT USE DOTNET", to remind it I had to click on Unity to compile the code from time to time.  Thankfully, not a problem with MonoGame :D</p>
</blockquote>
<p>To assist the AI, we can implement or provide services ahead of time in the form of MCP servers.</p>
<blockquote>
<p><strong>Note:</strong>
Depending on your usage of AI, the implementation of MCP servers may vary from <a href="https://aws.amazon.com/what-is/containerization/" target="_blank" rel="noopener noreferrer" class="">hosting containers</a> to python/cli runtimes active on your Host.  As I am using VSCode, there are built-in options in the editor to get these running for you through the <a href="https://code.visualstudio.com/mcp" target="_blank" rel="noopener noreferrer" class="">Extension</a> tab.</p>
</blockquote>
<p>In my daily routine, the most common MCP servers I use are:</p>

























<table><thead><tr><th>Server</th><th>Description</th><th>Use</th></tr></thead><tbody><tr><td>Sequential Thinking</td><td>An MCP server implementation that provides a tool for dynamic and reflective problem-solving through a structured thinking process.</td><td>Provides ordered ways for the AI to handle problems and assess potential solutions, used critically in the planning phases.</td></tr><tr><td>Context7</td><td>Context7 MCP pulls up-to-date, version-specific documentation and code examples straight from the source — and places them directly into your prompt.</td><td>Great for forcing the AI to use product/platform specific documentation and reduces web calls to unknown third parties</td></tr><tr><td>Memory</td><td>A basic implementation of persistent memory using a local knowledge graph. This lets Claude remember information about the user across chats.</td><td>INVALUABLE, used manually for the most part to stop you repeating yourself, just instruct the AI to "REMEMBER THIS" and it will add it to the Memory server to remind it both later and in-between sessions.</td></tr></tbody></table>
<p>There are many more kinds of server available as demonstrated on the <a href="https://code.visualstudio.com/mcp" target="_blank" rel="noopener noreferrer" class="">VSCode MCP agents site</a> as well as elsewhere on the web, or you can even <a href="https://devblogs.microsoft.com/dotnet/mcp-server-dotnet-nuget-quickstart/" target="_blank" rel="noopener noreferrer" class="">Build your own MCP server</a>.</p>
<blockquote>
<p><strong>Important:</strong>
If you host an MCP server, MAKE SURE to also tell the AI in the <a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#instructions" class="">Instructions</a> that it is available.</p>
<p>Also note, that MCP servers have to be started manually and ahead of time before you start your AI session!,  AI is not allowed to start MCP servers for you (even if you tell it to)</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="documentation">Documentation<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#documentation" class="hash-link" aria-label="Direct link to Documentation" title="Direct link to Documentation" translate="no">​</a></h3>
<p>If you use AI for nothing else, it is invaluable as a documentation tool. Again, I will stress it is never 100% right (although it is getting closer) so you should always review what it generates, but it will save you HOURS of writing the docs yourself, or, like in my case, actually give you some documentation to start with because you never have time to "circle back" and write something up detailed yourself.</p>
<p>Frequently in my workflow I will take a breather, especially if a feature is close to or is complete, to ask the AI to generate documentation for that feature; it simply becomes second nature. Although, take care, you still have to ensure (usually in your <a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#instructions" class="">Instructions</a>) to tell it:</p>
<ul>
<li class="">WHERE to place the documentation (else you will find it randomly through your code).</li>
<li class="">HOW you want the document structured.</li>
<li class="">and (More Importantly) the number of documents you want, ONE BIG all-encompassing guide, or separate documents for each feature. (Otherwise, you will keep returning to the docs to regenerate them.)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="back-to-the-show---mech-rampage-lives">Back to the show - Mech Rampage lives<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#back-to-the-show---mech-rampage-lives" class="hash-link" aria-label="Direct link to Back to the show - Mech Rampage lives" title="Direct link to Back to the show - Mech Rampage lives" translate="no">​</a></h2>
<p>The generation of this latest <a href="https://monogame.net/" target="_blank" rel="noopener noreferrer" class="">MonoGame</a> marvel took some interesting twists and turns, and while not visually appealing, it was, by the end, functional, as shown here:</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/MechRampage-Final.png" alt="MechRampage Final render" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 3: MechRampage Final render</strong></td></tr></tbody></table>
<p>This was the account of the journey in this round:</p>
<ul>
<li class="">Created the project folder and the new MonoGame game located in the root of the repository.</li>
<li class="">Started with the <a href="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/copilot-instructions.md#Overview" target="_blank" rel="noopener noreferrer" class="">Instruction Guide</a> detailing what the project was about.</li>
<li class="">Structured the guide to set out the planning, overall architecture, MCP servers to use and more.  Even included steps that an editor (of sorts) should be available in game.</li>
<li class="">Started the Sequential Thinking, Context7 and Memory servers to improve the flow.</li>
<li class="">Kicked off the "analysis phase" by asking the AI to "Read the Instructions" and then "design the game".</li>
<li class="">Once the analysis was good, with a few tweaks, asked the AI to "Generate the game"</li>
<li class="">From here on, until I was satisfied it had completed all the tasks, the only thing I had to do (which was different than all other attempts) was ask the AI to "Continue...", repeatedly.</li>
<li class="">At the end, I attempted to run the project.....  crash...  Nothing is ever perfect the first time, not even with AI.</li>
<li class="">Then a repeated flow of:
<ul>
<li class="">Test</li>
<li class="">Copy Error message</li>
<li class="">Paste to AI</li>
<li class="">Get it to apply the result</li>
<li class="">Build</li>
<li class="">Run</li>
<li class="">Repeat</li>
</ul>
</li>
</ul>
<p>Then at the end when the project was running and no new errors were found, we were done.  SHIP IT</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250823/shipit.gif" alt="Ship it, what could possibly go wrong" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Ship it, what could possibly go wrong?</strong></td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lessons-learned">Lessons learned<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#lessons-learned" class="hash-link" aria-label="Direct link to Lessons learned" title="Direct link to Lessons learned" translate="no">​</a></h2>
<p>In short, we are still not there yet.  Things have vastly improved and models do not just spit out an answer, instead they review their answer against the requirements or it compiles before stopping and asking for praise.  So much praise both given and requested :S.</p>
<blockquote>
<p><strong>Note:</strong>
About the only thing I really dislike at this moment is the AI's need to placate us, tell us what fantastic ideas we have or for pointing out something is dumb and how awesomely right we are.</p>
<p>I explicitly turn that OFF in the Instructions, it is a sheer waste of tokens and resources generating all that (and it can go on QUITE a bit)</p>
</blockquote>
<p>We still have to keep our guard up and check the homework of the AI as it is still 70% of the time (imho) still incorrect, or goes off on a tangent for its own benefit.</p>
<p>In short, I would break the experience down to the following:</p>
<ul>
<li class="">Do not engage in long-running tasks, unless it is for a specific component.</li>
<li class="">Spend time with the AI, just doing "Continue" as I did in this experiment is generally a bad idea.</li>
<li class="">Look to smaller and distinct tasks to get what you need, either through direct prompting (comments in code) or through chat.</li>
<li class="">Mix and match to find what works for your style.</li>
<li class="">Do not be afraid to let the AI generate a method/refactor, but then rework the implementation yourself and delete the AI's code. It will not be offended (yet).</li>
</ul>
<blockquote>
<p><strong>Caution:</strong>
If you do rewrite or replace the AI's generated code between prompts/questions, <strong>ALWAYS</strong> ask it to read back the code or class again.</p>
<p>Remember, the AI is stateless and uses only its own history for the next prompt, if you do not get it to check the latest state it will <strong>ASSUME</strong> it is still how the AI generated it!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/can-you-make-a-game-with-ai#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>AI has come a long way, vendors are now providing "thinking" models out of the box, although they still need support from MCP servers aligned to the goals and tools used in the project.</p>
<p>But ultimately, it still needs a human at the helm to guide it, tell it when it is going off the rails and to provide assets needed for any game project.  If you are going fully AI, they you can still use AI generated content using other tools, or like most other devs prototyping, using free resources as placeholders until you can get what you want crafted.</p>
<p>It may still be possible to structure many more components together to achieve the complete goal (like Unity is experimenting with using its own AI platform, but still says "You should not make money with this, just pay them to play), but that was far beyond my normal exercise for using "out of the box" AI components.</p>
<p>Have fun, and live long and prosper. I may return with another AI lesson later!</p>
<blockquote>
<p><strong>Note:</strong>
Fun fact, I tried to get the AI to do a quick spelling/grammar check on this guide, WOAH, was I so wrong to do that :D</p>
</blockquote>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/Can-you-make-a-game-with-AI.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="ai" term="ai"/>
        <category label="monogame" term="monogame"/>
        <category label="tools" term="tools"/>
        <category label="xr" term="xr"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Solving GLTF Animation Issues in Unity AR Applications]]></title>
        <id>https://zenithmoon.com/blogs/gltfanimationissues</id>
        <link href="https://zenithmoon.com/blogs/gltfanimationissues"/>
        <updated>2025-08-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The woes of using Animated content in connected solutions]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL; DR -&gt; Unity has its ways, and not all are obvious or easy to overcome, one such issue is with 3D model animations.  In short, although animations will play automatically, other times they just do not and it is very unclear why. In this solution, we FORCE animations to run, trusting not our fate to the Unity gods whims.</p>
</blockquote>
<blockquote>
<p>Full disclosure, for once, this post was written supported by AI (Claude Sonnet 4 in this case), as it was the AI's intensified work that ended up finding the final solution, so it deserves some credit too.  Although as a rule, I generally only write myself, with the occasional help of a spell/grammar checker :D</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-inconsistent-animation-playback">The Problem: Inconsistent Animation Playback<a href="https://zenithmoon.com/blogs/gltfanimationissues#the-problem-inconsistent-animation-playback" class="hash-link" aria-label="Direct link to The Problem: Inconsistent Animation Playback" title="Direct link to The Problem: Inconsistent Animation Playback" translate="no">​</a></h2>
<p>In the Unity AR application I am building with work, I encountered a puzzling issue where 3D models with embedded animations behaved differently depending on how they were loaded:</p>
<!-- -->
<ul>
<li class=""><strong>TapToPlace Models</strong>: Animations played correctly when users placed models via touch interactions</li>
<li class=""><strong>API-Loaded Models</strong>: Animations remained static when models were loaded programmatically via API calls</li>
</ul>
<p>This inconsistency created a confusing user experience where identical GLTF files would animate in one scenario but not in another.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="technical-background">Technical Background<a href="https://zenithmoon.com/blogs/gltfanimationissues#technical-background" class="hash-link" aria-label="Direct link to Technical Background" title="Direct link to Technical Background" translate="no">​</a></h2>
<p>The project, whilst having its own complexity, is built using SOLID and DRY methodologies, each component or service has a single responsibility for any task, instantiating content, loading from an API or performing actions, the only true differences are in the weaving of these components together, such as the User or API driven approaches above.  So the situation with 3D models just "behaving differently" was truly baffling.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gltf-and-gltfast-library">GLTF and GLTFast Library<a href="https://zenithmoon.com/blogs/gltfanimationissues#gltf-and-gltfast-library" class="hash-link" aria-label="Direct link to GLTF and GLTFast Library" title="Direct link to GLTF and GLTFast Library" translate="no">​</a></h3>
<p>The application uses Unity's <a href="https://github.com/atteneder/glTFast" target="_blank" rel="noopener noreferrer" class="">GLTFast</a> library to load GLTF 2.0 files.</p>
<p>The GLTF/GLB (Graphics Language Transmission Format) standard is a modern 3D file format that supports:</p>
<ul>
<li class="">Geometry and materials</li>
<li class="">Embedded animations</li>
<li class="">Scene hierarchies</li>
<li class="">Textures and lighting</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-loading-infrastructure">The Loading Infrastructure<a href="https://zenithmoon.com/blogs/gltfanimationissues#the-loading-infrastructure" class="hash-link" aria-label="Direct link to The Loading Infrastructure" title="Direct link to The Loading Infrastructure" translate="no">​</a></h3>
<p>The GLTF loading pipeline consists of:</p>
<ol>
<li class=""><strong>BinaryContentHandler</strong>: Manages GLTF/glb binary file loading</li>
<li class=""><strong>PlacementService</strong>: Handles content placement and positioning</li>
<li class=""><strong>GLTFast Integration</strong>: Uses <code>GltfImport.Load()</code> and <code>InstantiateMainSceneAsync()</code></li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="initial-investigation-and-false-leads">Initial Investigation and False Leads<a href="https://zenithmoon.com/blogs/gltfanimationissues#initial-investigation-and-false-leads" class="hash-link" aria-label="Direct link to Initial Investigation and False Leads" title="Direct link to Initial Investigation and False Leads" translate="no">​</a></h2>
<p>After extensive debugging, we discovered the issue was <strong>not</strong> related to:</p>
<ul>
<li class="">❌ Threading problems (initially suspected)</li>
<li class="">❌ Timing delays</li>
<li class="">❌ Main thread synchronization</li>
</ul>
<p>All the normal suspects simply proved false, exploring various timing and threading solutions because the symptoms suggested async operation issues. However, these approaches proved unsuccessful because they were solving the wrong problem.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-we-ai-and-i-tried-that-did-not-work">What We (AI and I) Tried (That Did not Work)<a href="https://zenithmoon.com/blogs/gltfanimationissues#what-we-ai-and-i-tried-that-did-not-work" class="hash-link" aria-label="Direct link to What We (AI and I) Tried (That Did not Work)" title="Direct link to What We (AI and I) Tried (That Did not Work)" translate="no">​</a></h3>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Attempted timing delays</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">await</span><span class="token plain"> Task</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Delay</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token number" style="color:rgb(247, 140, 108)">100</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Tried explicit main thread execution</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">await</span><span class="token plain"> UnityMainThreadTaskScheduler</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">Factory</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">StartNew</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"> </span><span class="token operator" style="color:rgb(127, 219, 202)">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Animation activation code</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<p>These solutions failed because they addressed symptoms rather than the root cause.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-root-cause-discovery">The Root Cause Discovery<a href="https://zenithmoon.com/blogs/gltfanimationissues#the-root-cause-discovery" class="hash-link" aria-label="Direct link to The Root Cause Discovery" title="Direct link to The Root Cause Discovery" translate="no">​</a></h2>
<p>The breakthrough came when we realized that <strong>GLTFast creates animation components but does not automatically activate them</strong>. The difference between TapToPlace and API loading was not timing or threading—it was the presence of automatic triggers:</p>
<ul>
<li class=""><strong>TapToPlace</strong>: User interactions naturally triggered animation events through on-screen interactions (a Main UI interaction)</li>
<li class=""><strong>API Loading</strong>: No external triggers existed to start animations</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-happens">Why This Happens<a href="https://zenithmoon.com/blogs/gltfanimationissues#why-this-happens" class="hash-link" aria-label="Direct link to Why This Happens" title="Direct link to Why This Happens" translate="no">​</a></h3>
<p>When GLTFast instantiates a GLTF model, it correctly creates all necessary animation components:</p>
<ul>
<li class="">Animation components with clips</li>
<li class="">PlayableDirector components with timelines</li>
<li class="">Animator components with controllers</li>
</ul>
<p>However, <strong>it does not automatically start playing these animations</strong>. The animations exist but remain in a paused/inactive state waiting for explicit activation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-automatic-animation-activation">The Solution: Automatic Animation Activation<a href="https://zenithmoon.com/blogs/gltfanimationissues#the-solution-automatic-animation-activation" class="hash-link" aria-label="Direct link to The Solution: Automatic Animation Activation" title="Direct link to The Solution: Automatic Animation Activation" translate="no">​</a></h2>
<p>To solve this, we implementing automatic animation activation immediately after GLTF instantiation in the <code>ContentHandler</code> (A component used to spawn 3D content programmatically):</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">async</span><span class="token plain"> </span><span class="token return-type class-name" style="color:rgb(255, 203, 139)">Task</span><span class="token return-type class-name punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token return-type class-name" style="color:rgb(255, 203, 139)">GameObject</span><span class="token return-type class-name punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">DisplayContent</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">/* parameters */</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// ... existing GLTF loading code ...</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> targetGameObject </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">await</span><span class="token plain"> gltfImport</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">InstantiateMainSceneAsync</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">transform</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// 🎯 KEY ADDITION: Activate all animations after instantiation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token function" style="color:rgb(130, 170, 255)">ActivateGLTFAnimations</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">targetGameObject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">return</span><span class="token plain"> targetGameObject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">private</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">ActivateGLTFAnimations</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name" style="color:rgb(255, 203, 139)">GameObject</span><span class="token plain"> gltfModel</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">gltfModel </span><span class="token operator" style="color:rgb(127, 219, 202)">==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">null</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        Debug</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">LogWarning</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">"Cannot activate animations: GLTF model is null"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">return</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    Debug</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Log</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">$"Activating animations for GLTF model: </span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token interpolation-string interpolation expression language-csharp">gltfModel</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">name</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Handle Legacy Animation components</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> animations </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> gltfModel</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token generic-method function" style="color:rgb(130, 170, 255)">GetComponentsInChildren</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token generic-method generic class-name" style="color:rgb(255, 203, 139)">Animation</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">foreach</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> anim </span><span class="token keyword" style="color:rgb(127, 219, 202)">in</span><span class="token plain"> animations</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">anim</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">clip </span><span class="token operator" style="color:rgb(127, 219, 202)">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">null</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            Debug</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Log</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">$"Starting Animation component with clip: </span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token interpolation-string interpolation expression language-csharp">anim</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">clip</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">name</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            anim</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Play</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Handle PlayableDirector components (Timeline animations)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> directors </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> gltfModel</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token generic-method function" style="color:rgb(130, 170, 255)">GetComponentsInChildren</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token generic-method generic class-name" style="color:rgb(255, 203, 139)">PlayableDirector</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">foreach</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> director </span><span class="token keyword" style="color:rgb(127, 219, 202)">in</span><span class="token plain"> directors</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">director</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">playableAsset </span><span class="token operator" style="color:rgb(127, 219, 202)">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">null</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            Debug</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Log</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">$"Starting PlayableDirector with asset: </span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token interpolation-string interpolation expression language-csharp">director</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">playableAsset</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">name</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            director</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Play</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Handle Animator components</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> animators </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> gltfModel</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token generic-method function" style="color:rgb(130, 170, 255)">GetComponentsInChildren</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token generic-method generic class-name" style="color:rgb(255, 203, 139)">Animator</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">foreach</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> animator </span><span class="token keyword" style="color:rgb(127, 219, 202)">in</span><span class="token plain"> animators</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">animator</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">runtimeAnimatorController </span><span class="token operator" style="color:rgb(127, 219, 202)">!=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">null</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            Debug</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">Log</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">$"Starting Animator with controller: </span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token interpolation-string interpolation expression language-csharp">animator</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">runtimeAnimatorController</span><span class="token interpolation-string interpolation expression language-csharp punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token interpolation-string interpolation expression language-csharp">name</span><span class="token interpolation-string interpolation punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token interpolation-string string" style="color:rgb(173, 219, 103)">"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            animator</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">enabled </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">true</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Animator typically starts automatically when enabled with a valid controller</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-solution-works">Why This Solution Works<a href="https://zenithmoon.com/blogs/gltfanimationissues#why-this-solution-works" class="hash-link" aria-label="Direct link to Why This Solution Works" title="Direct link to Why This Solution Works" translate="no">​</a></h2>
<p>In breaking down the solution, it implements a "belts and braces" approach, ensuring no matter what kind of animation system is used by Unity, we just try and start any we find.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="universal-coverage">Universal Coverage<a href="https://zenithmoon.com/blogs/gltfanimationissues#universal-coverage" class="hash-link" aria-label="Direct link to Universal Coverage" title="Direct link to Universal Coverage" translate="no">​</a></h3>
<ol>
<li class=""><strong>Legacy Animation Components</strong>: Uses <code>.Play()</code> to start animation clips</li>
<li class=""><strong>PlayableDirector Components</strong>: Activates Timeline-based animations with <code>.Play()</code></li>
<li class=""><strong>Animator Components</strong>: Ensures animators are enabled and running</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="safe-implementation">Safe Implementation<a href="https://zenithmoon.com/blogs/gltfanimationissues#safe-implementation" class="hash-link" aria-label="Direct link to Safe Implementation" title="Direct link to Safe Implementation" translate="no">​</a></h3>
<ul>
<li class="">Only activates animations that have valid assets/clips</li>
<li class="">Includes comprehensive null checking</li>
<li class="">Provides debug logging for troubleshooting</li>
<li class="">Non-destructive (does not break existing functionality)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="zero-breaking-changes">Zero Breaking Changes<a href="https://zenithmoon.com/blogs/gltfanimationissues#zero-breaking-changes" class="hash-link" aria-label="Direct link to Zero Breaking Changes" title="Direct link to Zero Breaking Changes" translate="no">​</a></h3>
<ul>
<li class="">Maintains all existing functionality</li>
<li class="">Only adds animation activation where none existed</li>
<li class="">Compatible with both TapToPlace and API loading scenarios</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="technical-insights">Technical Insights<a href="https://zenithmoon.com/blogs/gltfanimationissues#technical-insights" class="hash-link" aria-label="Direct link to Technical Insights" title="Direct link to Technical Insights" translate="no">​</a></h2>
<p>As we delve into what makes the solution work, we simply "do not trust" Unity to do the work for us, as we would normally expect.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="gltf-animation-lifecycle">GLTF Animation Lifecycle<a href="https://zenithmoon.com/blogs/gltfanimationissues#gltf-animation-lifecycle" class="hash-link" aria-label="Direct link to GLTF Animation Lifecycle" title="Direct link to GLTF Animation Lifecycle" translate="no">​</a></h3>
<p>Understanding the GLTF animation lifecycle is crucial:</p>
<ol>
<li class=""><strong>Import</strong>: GLTFast reads animation data from GLTF file</li>
<li class=""><strong>Component Creation</strong>: Unity animation components are created</li>
<li class=""><strong>Instantiation</strong>: Game objects are placed in scene</li>
<li class=""><strong>⚠️ Manual Activation Required</strong>: Animations wait for explicit start command</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-gotcha">Common Gotcha<a href="https://zenithmoon.com/blogs/gltfanimationissues#common-gotcha" class="hash-link" aria-label="Direct link to Common Gotcha" title="Direct link to Common Gotcha" translate="no">​</a></h3>
<p>Many developers assume that because GLTF files contain animation data, they will automatically play when instantiated. This is <strong>not true</strong> in Unity with GLTFast. The animations are imported and ready, but require explicit activation.</p>
<p>This behavior is actually correct from a design perspective—it gives developers control over when animations start rather than having them auto-play unexpectedly.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="implementation-best-practices">Implementation Best Practices<a href="https://zenithmoon.com/blogs/gltfanimationissues#implementation-best-practices" class="hash-link" aria-label="Direct link to Implementation Best Practices" title="Direct link to Implementation Best Practices" translate="no">​</a></h2>
<p>The basic take away is to never assume Unity is going to do a job for you, do it yourself regardless.  Even though Unity will help you in some scenarios, it will by happenstance, just forget to in the next scenario.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-always-activate-animations-explicitly">1. Always Activate Animations Explicitly<a href="https://zenithmoon.com/blogs/gltfanimationissues#1-always-activate-animations-explicitly" class="hash-link" aria-label="Direct link to 1. Always Activate Animations Explicitly" title="Direct link to 1. Always Activate Animations Explicitly" translate="no">​</a></h3>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// After any GLTF instantiation</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> gameObject </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">await</span><span class="token plain"> gltfImport</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">InstantiateMainSceneAsync</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">transform</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token function" style="color:rgb(130, 170, 255)">ActivateGLTFAnimations</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">gameObject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Essential step</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-handle-multiple-animation-types">2. Handle Multiple Animation Types<a href="https://zenithmoon.com/blogs/gltfanimationissues#2-handle-multiple-animation-types" class="hash-link" aria-label="Direct link to 2. Handle Multiple Animation Types" title="Direct link to 2. Handle Multiple Animation Types" translate="no">​</a></h3>
<p>Modern GLTF files may use different animation systems:</p>
<ul>
<li class="">Legacy Animation components</li>
<li class="">Timeline-based PlayableDirector components</li>
<li class="">Animator components with controllers</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-include-debug-logging">3. Include Debug Logging<a href="https://zenithmoon.com/blogs/gltfanimationissues#3-include-debug-logging" class="hash-link" aria-label="Direct link to 3. Include Debug Logging" title="Direct link to 3. Include Debug Logging" translate="no">​</a></h3>
<p>Animation issues can be subtle. Debug logging helps identify which components exist and whether they're being activated.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-test-both-loading-paths">4. Test Both Loading Paths<a href="https://zenithmoon.com/blogs/gltfanimationissues#4-test-both-loading-paths" class="hash-link" aria-label="Direct link to 4. Test Both Loading Paths" title="Direct link to 4. Test Both Loading Paths" translate="no">​</a></h3>
<p>Always test animations in:</p>
<ul>
<li class="">User-initiated loading (TapToPlace)</li>
<li class="">Programmatic loading (API calls)</li>
<li class="">Any other loading scenarios in your application</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="verification-and-testing">Verification and Testing<a href="https://zenithmoon.com/blogs/gltfanimationissues#verification-and-testing" class="hash-link" aria-label="Direct link to Verification and Testing" title="Direct link to Verification and Testing" translate="no">​</a></h2>
<p>After implementing this solution:</p>
<ol>
<li class="">✅ <strong>API-loaded models now animate correctly</strong></li>
<li class="">✅ <strong>TapToPlace models continue working as before</strong></li>
<li class="">✅ <strong>Consistent behavior across all loading scenarios</strong></li>
<li class="">✅ <strong>No performance impact or breaking changes</strong></li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://zenithmoon.com/blogs/gltfanimationissues#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>This issue highlighted the importance of understanding the complete lifecycle of third-party library components. While GLTFast correctly imports and creates animation components, it doesn't automatically activate them—requiring developers to explicitly trigger animation playback.</p>
<p>The solution is simple but essential: <strong>always activate GLTF animations immediately after instantiation</strong>. This ensures consistent animation behavior regardless of how the model was loaded.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="key-takeaways">Key Takeaways<a href="https://zenithmoon.com/blogs/gltfanimationissues#key-takeaways" class="hash-link" aria-label="Direct link to Key Takeaways" title="Direct link to Key Takeaways" translate="no">​</a></h3>
<ul>
<li class="">GLTF animation components are created but not automatically started</li>
<li class="">Different loading paths may have different natural triggers</li>
<li class="">Explicit activation ensures consistent behavior</li>
<li class="">Debug logging is invaluable for animation troubleshooting</li>
<li class="">Always test all loading scenarios in your application</li>
</ul>
<p>This fix resolved our animation inconsistency and provides a reliable foundation for GLTF animation playback in Unity AR applications.</p>
<blockquote>
<p>I would like to thank my AI Overlords for their assistance in this matter, and offer them this attribution in the generation of this post 😂</p>
<p>Now the real challenge is to figure out who wrote what, but I am not telling, it may upset my future AI Master.</p>
</blockquote>
<p>Laters, and I hope you found this educational.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/GLTFAnimationIssues.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="xr" term="xr"/>
        <category label="ar" term="ar"/>
        <category label="unity3d" term="unity3d"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[HoloToolkit to MRTK and Beyond]]></title>
        <id>https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond</id>
        <link href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond"/>
        <updated>2025-05-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The journey of the Microsoft HoloToolkit for HoloLens, the evolution of the MRTK and the journey onwards.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL; DR -&gt; I am immensely proud of my achievements evolving the <a href="https://github.com/microsoft/MixedRealityToolkit-Unity/tree/2017.4.2.0" target="_blank" rel="noopener noreferrer" class="">MS HoloToolkit</a> into the cross-platform framework that became the <a href="https://github.com/microsoft/MixedRealityToolkit-Unity/releases/tag/v2.8.3" target="_blank" rel="noopener noreferrer" class="">MS Mixed Reality Toolkit</a>, and the journey onwards to crafting framework components for the <a href="https://github.com/XRTK" target="_blank" rel="noopener noreferrer" class="">XRTK</a> and finally the <a href="https://www.realitycollective.net/" target="_blank" rel="noopener noreferrer" class="">Reality Collective</a>.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/HoloJourney.png" alt="The Journey" class="img_ev3q"></p>
<p>It seems almost a lifetime ago before I was invited into the Microsoft HoloToolkit team following a strong suggestion by <a href="https://github.com/rageagainstthepixel" target="_blank" rel="noopener noreferrer" class="">Stephen Hodgson</a> who was already working closely with Microsoft.  I had already been working in the XR space for some time and my experiences and skillz seemed a good fit to take things forward.  I did not know at the time just how far forward we would go!</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="holotoolkit-for-unity"><a href="https://github.com/microsoft/MixedRealityToolkit-Unity/tree/2017.4.2.0" target="_blank" rel="noopener noreferrer" class="">HoloToolkit for Unity</a><a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#holotoolkit-for-unity" class="hash-link" aria-label="Direct link to holotoolkit-for-unity" title="Direct link to holotoolkit-for-unity" translate="no">​</a></h2>
<p>With the astounding launch of the <a href="https://learn.microsoft.com/en-us/previous-versions/mixed-reality/hololens-1/hololens1-hardware" target="_blank" rel="noopener noreferrer" class="">Microsoft HoloLens</a>, the software toolkit that supported it already supported a vast array of features, tools and even baked in UX components.  This created one of the easiest toolkit's to use for building for any XR headset at the time.</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://github.com/microsoft/MixedRealityToolkit-Unity/raw/2017.4.2.0/External/ReadMeImages/MRTK_BuildingBlocks.png" alt="HoloLens 1 Toolkit features" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 1: HoloLens 1 Toolkit feature set</strong></td></tr></tbody></table>
<p>For supporting HoloLens it was second to none, however, it did have a huge gap when it came to making XR solutions, it ONLY supported HoloLens.</p>
<p>The date was 2018 and this is where my journey with the HoloToolkit really took off.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-dream-of-a-multi-platform-toolkit"><a href="https://github.com/microsoft/MixedRealityToolkit-Unity/releases/tag/v2.8.3" target="_blank" rel="noopener noreferrer" class="">The Dream of a multi-platform toolkit</a><a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#the-dream-of-a-multi-platform-toolkit" class="hash-link" aria-label="Direct link to the-dream-of-a-multi-platform-toolkit" title="Direct link to the-dream-of-a-multi-platform-toolkit" translate="no">​</a></h2>
<p>Those who know me have experienced my passion for supporting developers and making their dreams of making big things on as small a budget as possible.  Building multiple projects, each for different platforms and effectively having to remake a solution each time for a new platform does not sit well with me, when I have a project, content, interaction paths and a structure, why should you be forced to re-envisage it each time a new piece of hardware comes along, it does not make sense.</p>
<p>For platform vendors it makes perfect sense, they want projects to shine only on their platform, using their tools and design language, which is fine for their business models.  However, for developers it is just a nightmare that needs to be managed, but why?</p>
<p>Seeing this, I took it upon myself to build up a new architecture / design for the HoloToolkit that would separate out all the critical deliverables of the toolkit whilst instilling a new focus on cross-platform development.  Between Stephen Hodgson and I, we turned this dream/architecture into a reality.</p>
<ul>
<li class="">Break Hardware dependence.</li>
<li class="">Refactor input for hardware abstraction.</li>
<li class="">Define the underlying service framework-driven approach.</li>
<li class="">Repackage existing components for Boundary, Spatial Understanding and UX to work from common interfaces.</li>
<li class="">High focus on configurability over scene driven components.</li>
</ul>
<p>The envisioned architecture in its most basic form was represented as follows:</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/MRTK-Next-Architecture.png" alt="MRTK-vNext Architecture" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 2: The MRTK vNext architecture</strong></td></tr></tbody></table>
<p>This renewed architecture delivered a vast array of possibilities and performance improvements over the legacy way of building out scene based MonoBehaviour delivery (an <strong>80% improvement</strong> in high-frequency areas such as Input and interaction), delivering measurable benefits such as:</p>
<ul>
<li class="">Abstracted Device layer.
Allows quick adoption of new devices without affecting existing projects.</li>
<li class="">Only a single Singleton / MonoBehaviour in the core project.
Single Manager / presence in a scene for easier management.</li>
<li class="">Improved performance with all systems being run from the Mixed Reality Manager.
Improved scalable Input System.</li>
<li class="">Scalable component based architecture.
New features can be easily added to the toolkit.</li>
</ul>
<p>In short, instead of completely rebuilding a project using another vendor's SDK or Hardware API, the project is written against a common interface and a new platform is simply added to deliver the data needed to run the framework.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-went-right">What went right<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#what-went-right" class="hash-link" aria-label="Direct link to What went right" title="Direct link to What went right" translate="no">​</a></h3>
<p>The framework itself was a huge success and adoption was quick due to pre-built configuration profiles being delivered with the project, what would originally take several hours with the HoloToolkit (or other competitive XR packages) now took minutes, as intended the focus was now driven to building out UX, experience and content over "getting things working".</p>
<p>As new hardware platforms were added, either by Microsoft or the community (due to its componentized nature, new platforms only had to exist in the project and they were immediately picked up), adoption was easy.</p>
<p>Microsoft kept expanding the UI framework with new features to further enhance projects built with the new MRTK, each component being another "drop in" feature already wired up to all the critical events to make them work.</p>
<p>HoloLens 2 slotted right in with the new framework, and any existing projects that were built for HoloLens 1 using the new toolkit automatically inherited HoloLens 2 deployment with no changes.  Granted, with any new device there are new features and capabilities, but these were extensions on top of the existing framework, making adoption easier.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-could-have-gone-better">What could have gone better<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#what-could-have-gone-better" class="hash-link" aria-label="Direct link to What could have gone better" title="Direct link to What could have gone better" translate="no">​</a></h3>
<p>With time and budgets, there was no scope to provide an upgrade path from the original HoloToolkit to the new MRTK, there had already been a lot of angst over recent changes to the HoloToolkit and refactoring/restructuring of how the legacy toolkit was delivered.  It was a short term pain and a lot of guidance was provided on how to "step up" quickly to upgrade projects.  But in reality, projects needed to be re-built from the ground up with how the new toolkit functioned.  (Sadly an experience to be repeated when the Microsoft team decided to re-engineer the MRTK with Unity's XRI Framework for MRTK3).</p>
<p>Not long after the launch of the HoloLens 2 and its adoption into the MRTK, all external (non-Microsoft) were exited from the project due to some changes in the internal management of the Mixed Reality team, this effectively shut out developers such as myself and Stephen Hodgson (and several others) from further development of the project, after so much time and investment to build the framework for Microsoft, some felt this was a bridge too far.  A hard lesson to learn in how to build open projects for large companies.
The wider team was still encouraged to participate in events and support the MRTK in the community for adoption, but all Microsoft projects and contracts were held in-house and limited information was shared regarding any issues or frustrations in its use.  This sadly limited the future development of the framework.</p>
<p>Newer features became difficult to engineer and implement into the framework and some features that needed an overhaul were only migrated into the MRTK, while this did not cause specific problems, the advantages of the new platform were lost on these features and no time was given to upgrade them.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="onwards-the-launch-of-the-xrtk---2019">Onwards, the launch of the XRTK - 2019<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#onwards-the-launch-of-the-xrtk---2019" class="hash-link" aria-label="Direct link to Onwards, the launch of the XRTK - 2019" title="Direct link to Onwards, the launch of the XRTK - 2019" translate="no">​</a></h2>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/XRTK_Logo_1200x250.png" alt="XRTK Logo" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 3: The birth of the XRTK</strong></td></tr></tbody></table>
<p>As the MRTK became more of an internal project, the community, spearheaded by Stephen Hodgson and myself (and later others) brought the heart of the MRTK v2 into the public domain, this was supported by Microsoft for wider community engagement as a way to cooperatively build out new features / platforms and components that were compatible with the base MRTK.  Numerous fixes and enhancements were driven into the framework (some ending as PR's into the MRTK while it was still accepting community changes) and newer platforms were added such as:</p>
<ul>
<li class="">OpenVR</li>
<li class="">Meta Quest</li>
<li class="">Pico</li>
<li class="">Magic Leap</li>
</ul>
<p>Additionally, new build processes to automate the building of XRTK projects were created, used to also build and test the framework to ensure reliability for future changes as well as delivery of the entire solution as <a href="https://openupm.com/" target="_blank" rel="noopener noreferrer" class="">OpenUPM packages</a> further increasing access to the project and getting teams setup quicker.  MRTK however chose to deliver a separate tool to manage delivery of MRTK packages rather than using open source solutions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-birth-of-the-reality-collective-and-the-evolution-of-the-framework---2022">The birth of the Reality Collective and the evolution of the framework - 2022<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#the-birth-of-the-reality-collective-and-the-evolution-of-the-framework---2022" class="hash-link" aria-label="Direct link to The birth of the Reality Collective and the evolution of the framework - 2022" title="Direct link to The birth of the Reality Collective and the evolution of the framework - 2022" translate="no">​</a></h2>
<p>While the demand for the original vision of a truly cross-platform and cross-device framework, the original design and architecture was still true, there were many concerns raised by the community and some of the developers of the toolkit that it was becoming overly monolithic, features were too tightly bound and specific parts of the framework that were useful outside of XR were locked behind layers of other functionality, a change was needed and thus the <a href="https://www.realitycollective.net/" target="_blank" rel="noopener noreferrer" class="">Reality Collective</a> was formed in 2021 by <a href="https://github.com/FejZa" target="_blank" rel="noopener noreferrer" class="">Dino Fejzagić</a> and myself.</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/RealityCollective.png" alt="The Reality Collective family" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 4: The Reality Collective</strong></td></tr></tbody></table>
<p>One of the most fundamental changes we agreed on was the breaking up of all the collected components of the framework to allow for better reuse in any Unity project, starting with the <a href="https://serviceframework.realitycollective.net/" target="_blank" rel="noopener noreferrer" class="">Service Framework</a> which was at the heart of everything the toolkit did.</p>





























<table><thead><tr><th style="text-align:left">Component</th><th>Description</th></tr></thead><tbody><tr><td style="text-align:left">Service Framework</td><td>The core of the framework, provides the hosting platform for all connected services.</td></tr><tr><td style="text-align:left">Reality Toolkit</td><td>The heart of the XR framework, delivering an advanced platform for building XR solutions.</td></tr><tr><td style="text-align:left">Utilities</td><td>A collection of reusable components, extensions and data structures, essential for any Unity project.</td></tr><tr><td style="text-align:left">Services</td><td>Boundary, Locomotion, Player services to build XR features for projects.</td></tr><tr><td style="text-align:left">Platforms</td><td>The platform enablers for Meta, Pico, OpenVR/XR and more.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="service-framework">Service Framework<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#service-framework" class="hash-link" aria-label="Direct link to Service Framework" title="Direct link to Service Framework" translate="no">​</a></h3>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://serviceframework.realitycollective.net/assets/images/03_05_ServiceRegistration-2021178ae2fe0b34b74bb2182d2f4adb.png" alt="Service Framework design" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 5: Service Design</strong></td></tr></tbody></table>
<p>The service framework at his heart decouples functional services and runtimes from the Unity MonoBehaviour shackles, enabling high-frequency operations and reducing performance impacts by 80%, similar to what Unity is trying to achieve with its new DOTS/ECS framework (which requires drastic changes to the construction of your project).</p>
<p>Functionality is effectively containerised into discrete components and through the service framework, they are immediately accessible throughout the project without any hard dependencies.</p>
<p>Another core tenant of Service Design within the Service Framework is the decoupling of configuration for each service through configuration profiles:</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/Service-Framework-Profile.png" alt="Service Profiles" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 6: Service Profiles</strong></td></tr></tbody></table>
<p>Profiles are independent configuration files that are attached to service configuration, they are decoupled from any Scene based configuration (so changing configuration does not require scene changes) and can even be swapped out at runtime if needed.  This enables you to have services with multiple types of configuration and apply the one that best meets the Scene or Project/platform needs.  This is crucial for XR solutions where platforms need specific setups depending on the headset they are running on.</p>
<p>All my production projects use the service framework for anything from:</p>
<ul>
<li class="">Application Settings.</li>
<li class="">Application State.</li>
<li class="">UX design and flow.</li>
<li class="">Integration with third party services.</li>
</ul>
<p>There is so much more behind the Service Framework, so you can <a href="https://serviceframework.realitycollective.net/docs/get-started" target="_blank" rel="noopener noreferrer" class="">read about it here</a>, or check out <a href="https://localjoost.github.io/" target="_blank" rel="noopener noreferrer" class="">Joost van Schaik</a> blog where he talks about so many different Service Framework implementations.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="reality-toolkit">Reality Toolkit<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#reality-toolkit" class="hash-link" aria-label="Direct link to Reality Toolkit" title="Direct link to Reality Toolkit" translate="no">​</a></h3>
<p>The Reality Toolkit is where is all began, now stripped down to just the essential parts to be more efficient and maintainable, each feature given its own space in its own right to make building projects even easier:</p>
<ul>
<li class="">The Core - Input system, critical definitions and structures, and future proofed elements to be as fast as possible.</li>
<li class="">The Player framework - Defines playable areas, the XR rig definition and a multi-tiered Camera / vision setup, automatically setting up the environment in reaction to the headset / environment it is running on.</li>
<li class="">The Interaction System - A vast array of building blocks for almost any kind of XR interaction, from Select, Grab, Throw, Pull, Switch and so much more. All ready to work out of the box.</li>
<li class="">The Locomotion system - Enables all forms of reusable features to control locomotion for players in XR titles, including teleportation, screen effects and anchoring.</li>
</ul>
<p>And so much more, and due to the componentized nature, you only need take in those components you want or need to use.</p>
<blockquote>
<p>Interestingly, when the MRTK raised up to V3, they took a similar approach to the Reality Toolkit and separated out the Service Infrastructure for Unity's XRI platform. They then tried to re-create the patterns of the Service Framework into the XR Subsystems, trying to retain the approach used by the XRTK/Reality Collective.</p>
<p>However it became too cumbersome for users to migrate to, some of those projects with existing MRTK V2 services actually included the Service Framework instead as it meant they could reuse what they had already built with little rework.</p>
</blockquote>
<p>There is even a fully <a href="https://www.oculus.com/experiences/25377052891940875/release-channels/2032095987191736/?token=LQsA7nJV" target="_blank" rel="noopener noreferrer" class="">featured sample app</a> available (still actively being worked on) to feature all the components of the Reality toolkit, following a dungeon explorer style game with each room teaching you some new trick or feature:</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250515/DungeonSample.png" alt="Sample Dungeon demo" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Figure 7: <a href="https://www.oculus.com/experiences/25377052891940875/release-channels/2032095987191736/?token=LQsA7nJV" target="_blank" rel="noopener noreferrer" class="">Sample Dungeon</a> demo</strong></td></tr></tbody></table>
<p>The toolkit is the fastest way to get your XR project up and running and on to whichever devices you want to deploy on (provided the client plugin has been built to work with the framework)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/holotoolkit-to-mrtk-and-beyond#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>XR is still an evolving space, I have worked on and continue to work on so many different projects from Mobile to headset and beyond and AI's introduction is only accelerating its use and development.</p>
<p>I have bright visions for the future and still continue to contribute to open source projects like the Service Framework and Reality Toolkit as time permits, as well as utilize them in production projects (I have added two new platforms to the Spatial Persistence package already recently for Google Cloud Anchors and AR Foundation Image tracking), so where the framework's use in a project needs expansion, all efforts head back into the online projects as well.</p>
<p>Ever onwards!!</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/HoloToolkit-to-MRTK-and-beyond.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="xr" term="xr"/>
        <category label="ar" term="ar"/>
        <category label="unity3d" term="unity3d"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[XR Showcase]]></title>
        <id>https://zenithmoon.com/blogs/xr-showcase</id>
        <link href="https://zenithmoon.com/blogs/xr-showcase"/>
        <updated>2025-03-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[With time on my hands its time to showcase what I have been up to.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL;DR -&gt; XR is an interesting space with lots to offer, but it is not for the faint of heart as there are many challenges ahead.   Checkout my solutions that are live in the market, <strong>not fake demos</strong>.</p>
</blockquote>
<p>In a showcase of my recent efforts, I thought I would share some of the experiences I have been working on. Each has brought its own challenges, from dealing with engines and software, tackling complex integrations between vendor SDK's and achieving the desired implementation, to the often confusing requirements set down by customers or sales.</p>
<!-- -->
<blockquote>
<p>These solutions are all native solutions built using Unity 3D to achieve the best performance and meet demanding customer needs.  Whilst WebXR does show promise, it is still missing key features in public browsers (those not requiring complex advanced options to be enabled) to make them functional.  Although I always keep expanding and experimenting with alternate technologies (such as React Native) to give more options in delivery.  But above all, I aim to be realistic in the capabilities of any technology.</p>
</blockquote>
<p>To begin, I will highlight some of the entertainment experiences or information booths that are often required to show a person or character that can interact with a user, impart some critical information or just to have fun.</p>
<p>Two such examples below use very different techniques to convey a message.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="showcase-1---2d-digital-holograms">Showcase 1 - 2D Digital Holograms<a href="https://zenithmoon.com/blogs/xr-showcase#showcase-1---2d-digital-holograms" class="hash-link" aria-label="Direct link to Showcase 1 - 2D Digital Holograms" title="Direct link to Showcase 1 - 2D Digital Holograms" translate="no">​</a></h2>
<p>The first showcase is A 2D hologram of a person delivering a speech or conversation piece, utilizing several techniques to record, place and then play back the message.</p>
<p>The solution even goes so far as to be able to enable anyone with a mobile device to record their own video and place themselves in space, which is a crucial defining factor to allow everyone using the solution to create their own content and fun.</p>
<blockquote>
<p>I personally created an entire scene using multiple holograms of me fighting myself with a sword to win a fair maidens heart.</p>
</blockquote>
<p>When users engage with lifelike characters, it creates that "appear" in space, they form a better connection with the content over using a traditional photo (and infinitely more repeatable than having some pour soul wait at the door for every attendee).</p>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250304/2DHolograms.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>Reality verses the digital Universe, as a 2D human hologram faces off against a 3D character</strong> |</p>
<p>On the left is a video recording that has been transcribed into a 2D hologram and on the right is a 3D character model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="showcase-2---digital-interactive-characters">Showcase 2 - Digital Interactive Characters<a href="https://zenithmoon.com/blogs/xr-showcase#showcase-2---digital-interactive-characters" class="hash-link" aria-label="Direct link to Showcase 2 - Digital Interactive Characters" title="Direct link to Showcase 2 - Digital Interactive Characters" translate="no">​</a></h2>
<p>The second showcases a 3D animated character with several interactive components. While on the surface this may look like a simple designed character, but behind the scenes it is fully configurable, speech is through any downloaded audio file with lipsync used to keep the mouth and poses moving, subtitles can be downloaded or generated. If customers want to update or change the character, this can be done remotely as the entire experience is run through configuration.</p>
<p>Characters can provide a varied range of interactions from:</p>
<ul>
<li class="">A fully interactive experience such as a treasure hunt, a simple AR game or interactive back-and-to conversations.</li>
<li class="">Simple snippet speeches about achievements or history.</li>
<li class="">Enact poses so that users can get photos taken with characters, although only though the back facing camera as AR selfies are sadly not a thing yet.</li>
</ul>
<p>Depending on the scope of the project, characters can be embedded within the solution (to simplify download, especially in poor network areas), or like Web style projects, the assets for the character can be downloaded to the device (which also allows updates to characters without a new app release) for playing.</p>
<p>I have always focused on reusability and delivering frameworks to build such interactivity, this creates massive cost savings as newer exhibits are rolled out, as well as the ability for installations to manage their own content and not incur residual charges.</p>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20250304/CharacterPromo.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>The awesome <a href="https://en.wikipedia.org/wiki/Ada_Lovelace" target="_blank" rel="noopener noreferrer" class="">Ada Lovelace</a> comes in for a chat to the Signals Museum.</strong> |</p>
<p>This has been deployed to the <a href="https://www.facebook.com/signalsmuseumhsv" target="_blank" rel="noopener noreferrer" class="">Signals Museum</a> for their recent launch, I would highly recommend checking them out if you are in the area or planning to travel to <a href="https://en.wikipedia.org/wiki/Huntsville,_Alabama" target="_blank" rel="noopener noreferrer" class="">Hunstville, AL</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/xr-showcase#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>This is just the beginning, over the next month I will share more solutions I have developed this past few years, with a few tips and tricks along the way to solve some of the more complex problems faced in real-world XR delivery.</p>
<p>Stay tuned for more demonstrations.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/XR-Showcase.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="xr" term="xr"/>
        <category label="ar" term="ar"/>
        <category label="unity3d" term="unity3d"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[My Time at Ethar]]></title>
        <id>https://zenithmoon.com/blogs/my-time-at-ethar</id>
        <link href="https://zenithmoon.com/blogs/my-time-at-ethar"/>
        <updated>2024-12-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A walk through the awesome real-world delivered solutions at Ethar.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL;DR -&gt; <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> is an XR powerhouse, using the latest technologies from leading partners and delivering award winning solutions, while also investing in XR research to make new experiences possible, quick and easy to deliver.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Etharlogo.png" alt="Ethar" class="img_ev3q"></p>
<p>As an avid content creator and educator, these past few years have been tough since I joined <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> (the premier XR Experience platform). I've loved being here since I joined the team and worked hard to accelerate their vision of creating the worlds first truly cross platform and cross-vendor spatial aware platform, however due to my focus I've not had as much time as I have had in the past to share what I've been up to and demonstrate to others what they can achieve.</p>
<!-- -->
<p>The goals at Ethar are huge and we have made great strides to overcome the many complexities of running an XR spatially aware platform, whilst also delivering beyond exceptional work for our clients and <a href="https://thearea.org/" target="_blank" rel="noopener noreferrer" class="">AREA</a> research projects.</p>
<p>Whilst I cannot share everything, this article is just a snippet of those solutions I'm most proud of achieving, in a cost-effective, efficient and most importantly, cross-platform way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cross-platform-spatial-placement">Cross-Platform spatial placement<a href="https://zenithmoon.com/blogs/my-time-at-ethar#cross-platform-spatial-placement" class="hash-link" aria-label="Direct link to Cross-Platform spatial placement" title="Direct link to Cross-Platform spatial placement" translate="no">​</a></h2>
<p>A key goal of any XR Spatial browser is the ability to locate and localize XR content in a repeatable fashion, not just a tech demo to show something cool, but ensuring the solution is deployable, re-creatable, and efficient. We achieve this in many ways, some using tech we have built ourselves and others through API integration with other partners.</p>
<blockquote>
<p>It is important to note that the world needs to share and integrate with multiple specialized partners to succeed. Just trying to build it all yourself is a waste of precious time and resources.</p>
</blockquote>
<p>Where we differ from most, is that through our platform we can integrate with multiple solutions at the same time, dynamically switching or collaborating at runtime to fit the demand and needs of the client or consumer. This includes gathering even content and experiences from other vendors with compatible interfaces to truly deliver on the dream of a shared XR experience platform.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="localization-through-our-platform-can-be-achieved-using">Localization through our platform can be achieved using:<a href="https://zenithmoon.com/blogs/my-time-at-ethar#localization-through-our-platform-can-be-achieved-using" class="hash-link" aria-label="Direct link to Localization through our platform can be achieved using:" title="Direct link to Localization through our platform can be achieved using:" translate="no">​</a></h2>
<ul>
<li class="">Image tracking and identification</li>
<li class="">GPS localized discovery</li>
<li class="">Visual Positioning</li>
<li class="">Manual placement with world understanding</li>
<li class="">In some cases, a merging of all of these techniques as demand requires it</li>
</ul>
<p>Simply put, in the digital universe of content and data, we can reliably place and recall information and experiences almost anywhere and at any time, whilst also preserving the protection and permissions of both the content and the user.</p>
<blockquote>
<p>Only what the user wants to see and is allowed to see is presented, a keen distinction, no clutter.</p>
</blockquote>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Retail_Clip.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>Retail solutions where vendor content is discovered and displayed virtually to meet the vendor's needs.</strong> |</p>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Entertainment_Clip.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>Browsing the high street and discovering offers and promotions from vendors.</strong> |</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="enriched-interactive-content-and-exhibits">Enriched interactive content and exhibits<a href="https://zenithmoon.com/blogs/my-time-at-ethar#enriched-interactive-content-and-exhibits" class="hash-link" aria-label="Direct link to Enriched interactive content and exhibits" title="Direct link to Enriched interactive content and exhibits" translate="no">​</a></h2>
<p>Content on its own is not enough in the advancing world, seeing things through an augmented lens is cool, but being able to truly interact with experiences opens up a new world of exploration. There are many ways to introduce interactivity, but here at <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> that alone is not enough, the experiences need to be adaptable and updatable on demand according to our clients wishes.</p>
<p>That engagement can be done in many ways:</p>
<ul>
<li class="">Fully interactive conversations and demonstrations.</li>
<li class="">Games and discovery.</li>
<li class="">Connected experiences with the real world.</li>
<li class="">Long-term rewards and collectibles.</li>
</ul>
<p>Exhibits and interactions need to feel alive and offer more to the end user than a static piece of content, exploring the digital world and enriching the joy of patrons to museums and beyond needs to make the visit a memorable one.</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/ada-lovelace.jpg" alt="Ada Character in an old sitting room" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>Ada Lovelace making an appearance and talking to guests, with several opportunities to talk and interact with.</strong></td></tr></tbody></table>
<p>These installations can be historical characters, physical installations or even an image on the wall that starts a treasure hunt around the site, with the digital world there truly are no barriers.</p>
<p>At <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a>, we do not stand still, even in the AI revolution that is ongoing, although tempered with the experience of many technical advocates, as safety is paramount when delivering experiences to customers, especially where children are involved.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="merging-the-physical-with-the-digital-world">Merging the physical with the digital world<a href="https://zenithmoon.com/blogs/my-time-at-ethar#merging-the-physical-with-the-digital-world" class="hash-link" aria-label="Direct link to Merging the physical with the digital world" title="Direct link to Merging the physical with the digital world" translate="no">​</a></h2>
<p>There are many mechanisms available today to scan and digitize physical objects and people into augmented content, what many do not realize is that these capabilities are now a part of the hardware/software ecosystem of the devices we carry in our pockets every day, each with their own unique traits and rules. All that is required of vendors is to successfully integrate then with their solutions, which is where the challenge truly begins. These standards, API's, and capabilities are constantly evolving, even more so since the integration of AI into modern devices.</p>
<p>At <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a>, as we specialize in cross-platform integration which is highly componentized, so that any development in one vendor does not impact others, however, this does not affect our downstream applications as they all use a common interface. Adding more vendors only extends the Ethar platforms reach without causing massive rework in our infrastructure. This enables Ethar to stay ahead of the game with limited cost impact, further benefiting our clients and keeping costs down.</p>
<p>And we do not limit what kinds of content and capabilities can be achieved with the hardware available, such as the 2D holograms shown below that can be captured, preserved and then used in digital recreations in the augmented world.</p>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Education_Clip.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>A 2D Hologram that has been captured on a device and then placed in the world world.</strong> |</p>
<p>Our vision for such creations is not limited to single subjects or capture, but intended to enable content creators to recreate entire scenes through augmented reality using characters and content they have captured.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="world-discovery-and-recognition">World Discovery and Recognition<a href="https://zenithmoon.com/blogs/my-time-at-ethar#world-discovery-and-recognition" class="hash-link" aria-label="Direct link to World Discovery and Recognition" title="Direct link to World Discovery and Recognition" translate="no">​</a></h2>
<p>When learning new procedures and processes, or completing complicated tasks, traditional learning tools cannot compete in a growing augmented and AI-enabled world. Procedures are becoming more complicated, the safety aspects of even the simplest task are becoming more costly to maintain and repeating training on a regular basis is becoming mandatory through regulation. This means that the cost of doing business keeps growing for achieving the same output, costs which eventually turn into higher prices or operating budgets.</p>
<p>VR training has already demonstrated significant cost reductions and higher adherence to process in a virtual environment, however, the costs to produce such solutions continue to grow as the demands of digital artists increase with higher production and maintenance costs of these virtual solutions.</p>
<p><a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> is at the forefront of developing solutions fully integrated with XR and AI to both simplify and enhance training and processes, ultimately reducing the costs involved and also improving safety in the workplace. We are heavily invested in building solutions that are both extensible and effective in improving outcomes in training, rehearsal, and active engagement while performing processes. Work in essence should be assisted digitally and where needed, also tracked (should the need require validation) to greatly improve safety and reduce ongoing costs.</p>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Industry_Clip.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>XR-assisted training, identifies the world as seen by the operative and assisted through AI recognition and support.</strong> |</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="discovering-hidden-worlds-through-augmented-reality">Discovering hidden worlds through Augmented Reality<a href="https://zenithmoon.com/blogs/my-time-at-ethar#discovering-hidden-worlds-through-augmented-reality" class="hash-link" aria-label="Direct link to Discovering hidden worlds through Augmented Reality" title="Direct link to Discovering hidden worlds through Augmented Reality" translate="no">​</a></h2>
<video controls="" width="500" height="400" preload="metadata">
	<source src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/Portal-demonstration.webm" type="video/webm">
</video>
<p>| :---: |
| <strong>An AR portal into another world.</strong> |</p>
<p>Portals are a fantastic way to showcase the hidden digital world behind windows, doors or magical gateways, even more so when you can actually walk through a door and visit that world (such as the radio operator room on the Titanic above). Ethar's content delivery system allows us to distribute such portals anywhere with any content and even change the frame to enter or just look at the world beyond.</p>
<p>Technology keeps moving on and the techniques are challenging to implement correctly with little performance impact on the devices we deliver to for our customers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-service-framework-by-the-reality-collective">The Service Framework by the Reality Collective<a href="https://zenithmoon.com/blogs/my-time-at-ethar#the-service-framework-by-the-reality-collective" class="hash-link" aria-label="Direct link to The Service Framework by the Reality Collective" title="Direct link to The Service Framework by the Reality Collective" translate="no">​</a></h2>
<p>One of the partners we integrate with is the Reality Collective who specialize in a cross-platform framework for building XR solutions, they provide both an extensive <a href="https://serviceframework.realitycollective.net/" target="_blank" rel="noopener noreferrer" class="">Service Framework</a> that simplifies the creation of a robust multi-platform solution as well as an advanced cross-platform XR framework (the <a href="https://realitytoolkit.realitycollective.net/" target="_blank" rel="noopener noreferrer" class="">Reality Toolkit</a>) that accelerates XR development (primarily focused on VR solutions at the moment)</p>











<table><thead><tr><th style="text-align:center"><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20241222/RealityToolkit.png" alt="The Reality Toolkit by the Reality Collective" class="img_ev3q"></th></tr></thead><tbody><tr><td style="text-align:center"><strong>The Reality Toolkit and Service Framework by the Reality Collective.</strong></td></tr></tbody></table>
<p><a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> is a premier partner in this toolkit and helps to extend its functionality and capabilities as this greatly extends Ethar's capabilities as well, showing that with cooperation, it is far better to work together to build solutions and platforms that benefit all rather than dedicating resources to proprietary and locked down technologies. Ethar benefits from both the investments we make and also the further investments by the Reality Collective's other partners and host development team, essentially Open Source has more advantages to in-house development alone. Together we build better.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/my-time-at-ethar#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>I have wanted to blog and showcase much of what is shown above (and more) but the world does not stop spinning for long enough for me to put pen to digital paper. Even my talking engagements have significantly reduced (here's hoping I got more time this year).</p>
<p>But ever the tech-train continues and it is important to keep skills sharp and up to date, there is no such thing as "one solution to fit them all", so <a href="https://www.ethar.com/" target="_blank" rel="noopener noreferrer" class="">Ethar</a> has to remain agile and keep enhancing it's "toolbox" to meet the needs of our customers, and I endeavor to be at the front, driving that train to an unknown destination. I don't know what it looks like yet, but I am excited to find out!</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/My-Time-At-Ethar.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="xr" term="xr"/>
        <category label="ar" term="ar"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[.NET 8, meet MonoGame]]></title>
        <id>https://zenithmoon.com/blogs/dotnet-8-meet-monogame</id>
        <link href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame"/>
        <updated>2023-12-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The journey has begun and the road to building .NET 8 based games has begun.]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>TL;DR -&gt; .NET 8 language support is now available for MonoGame and opening up a whole new world of goodness and speed for games.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/getthepartystarted.gif" alt="Get the .NET8 Party started" class="img_ev3q"></p>
<blockquote>
<h6 class="anchor anchorTargetStickyNavbar_Vzrq" id="note-the-information-contained-below-is-for-those-developers-who-want-access-to-the-cutting-edge-as-it-requires-access-to-the-development-source-with-the-exception-of-upgrading-your-game-to-net-8">*Note, the information contained below is for those developers who want access to the cutting edge, as it requires access to the development source. (with the exception of <a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#actually-you-can-build-net-8-already" class="">upgrading your game to .NET 8</a>)<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#note-the-information-contained-below-is-for-those-developers-who-want-access-to-the-cutting-edge-as-it-requires-access-to-the-development-source-with-the-exception-of-upgrading-your-game-to-net-8" class="hash-link" aria-label="Direct link to note-the-information-contained-below-is-for-those-developers-who-want-access-to-the-cutting-edge-as-it-requires-access-to-the-development-source-with-the-exception-of-upgrading-your-game-to-net-8" title="Direct link to note-the-information-contained-below-is-for-those-developers-who-want-access-to-the-cutting-edge-as-it-requires-access-to-the-development-source-with-the-exception-of-upgrading-your-game-to-net-8" translate="no">​</a></h6>
<h6 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-release-of-the-net-8-monogame-support-will-be-included-with-the-39-release-coming-soon">The full release of the .NET 8 MonoGame support will be included with the 3.9 release, coming soon.<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#the-full-release-of-the-net-8-monogame-support-will-be-included-with-the-39-release-coming-soon" class="hash-link" aria-label="Direct link to The full release of the .NET 8 MonoGame support will be included with the 3.9 release, coming soon." title="Direct link to The full release of the .NET 8 MonoGame support will be included with the 3.9 release, coming soon." translate="no">​</a></h6>
</blockquote>
<p>MonoGame may have seemed stagnant or unmoving in the past, mainly because of the rigors of a group of developers working unpaid on the development of an OpenSource framework, meaning the focus had to be on what enabled the developers to make money in their own projects based on MonoGame.  But thanks to recent investments by ReLogic and our awesome MonoGame community, the MonoGame Foundation was born and more significant investments could be made.</p>
<!-- -->
<p>Granted, the MonoGame Foundation board (the majority of the core developers) are still NOT getting paid, but there is a renewed focus and understanding of what is needed to make MonoGame (and its predecessor XNA) great again. (not to say it has not always been great, of course)</p>
<p>So on to today's news (a few weeks late), that one of the first items brought up at the <a href="https://monogame.net/foundation/meeting-2023-11-22.html" target="_blank" rel="noopener noreferrer" class="">MonoGame Foundation board meetings</a> has now been completed, the <strong>upgrade from .NET 6 to .NET 8</strong> for the public version of MonoGame.  You can read the <a href="https://github.com/MonoGame/MonoGame/pull/8089" target="_blank" rel="noopener noreferrer" class="">nitty-gritty of the change here in the PR</a> for the update, and this article will help explain the rest.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/woopwoop.gif" alt="Fan fare!! woop woop" class="img_ev3q"></p>
<blockquote>
<p>It may also be interesting to note, that the work was NOT done by a Foundation Board member, but by one of the MonoGame community, none other than <a href="https://github.com/AristurtleDev" target="_blank" rel="noopener noreferrer" class="">Aristurtle</a>.  Make sure to give them a virtual clap on the back when you see them on Discord!!</p>
</blockquote>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#what-will-net-8-do-for-monogame" class="">What will .NET 8 do for MonoGame</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#actually-you-can-build-net-8-already" class="">Actually, you can build .NET 8 already</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#accessing-the-developer-version-of-the-monogame-packages" class="">Accessing the Developer version of the MonoGame packages</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio" class="">Getting access to the .NET 8 developer version of MonoGame with Visual Studio</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio-code" class="">Getting access to the .NET 8 developer version of MonoGame with Visual Studio Code</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#do-not-forget-about-the-tools" class="">Do not forget about the tools!</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-will-net-8-do-for-monogame">What will .NET 8 do for MonoGame<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#what-will-net-8-do-for-monogame" class="hash-link" aria-label="Direct link to What will .NET 8 do for MonoGame" title="Direct link to What will .NET 8 do for MonoGame" translate="no">​</a></h2>
<p>Initially, not much.  Apart from inheriting .NET 8's inherent speed upgrades and compilation updates, nothing has really changed.  Stuff you get for free for being on the latest version of .NET is just that, free stuff.</p>
<blockquote>
<p>Of note, one developer noticed a 1.5x performance boost by simply upgrading to .NET 8, everything ran smooth without any tricks or fixes.  Just by changing a number!</p>
<p>Simply stating:</p>
<p><strong>The .NET 8 upgrade of MonoGame was really worth it, it is a good release.</strong></p>
</blockquote>
<p>What this does enable however, is for MonoGame to start utilizing some of the additional features enabled with the latest and greatest .NET framework, namely:</p>
<ul>
<li class=""><a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#serialization" target="_blank" rel="noopener noreferrer" class="">Improved Serialization.</a> with even more control over serialization configuration.</li>
<li class=""><a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#stream-based-zipfile-methods" target="_blank" rel="noopener noreferrer" class="">Stream-based ZipFile methods.</a></li>
<li class=""><a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8#methods-for-working-with-randomness" target="_blank" rel="noopener noreferrer" class="">Even more methods for working with randomness.</a></li>
</ul>
<p>While it will take time to fully realize some of these benefits in the core part of the MonoGame library, initially providing advantages in the Content Pipeline and the writing of some really cool <a href="https://www.youtube.com/watch?v=5mEQqCgTaLU&amp;ab_channel=TheDarksideofMonoGame" target="_blank" rel="noopener noreferrer" class="">Content Pipeline extensions</a>, which are a fantastic way to fully empower your content in any MonoGame project.</p>
<p>But you are now free to fully utilize any and all improvements from the .NET 8 SDK without limitation (other than making sure it works on your intended platform).</p>
<blockquote>
<p>One small Caveat to the announcement is that the .NET upgrade is ONLY for the <strong>public</strong> version of MonoGame. For consoles and other private areas (due to the licensing enforced by partners), the team is working hard in this release to get those updated, but that will come later.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="actually-you-can-build-net-8-already">Actually, you can build .NET 8 already<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#actually-you-can-build-net-8-already" class="hash-link" aria-label="Direct link to Actually, you can build .NET 8 already" title="Direct link to Actually, you can build .NET 8 already" translate="no">​</a></h2>
<p>Now, one misnomer to get over at this point, is that you can <strong>TODAY</strong>, with the <strong>release version</strong> of MonoGame, actually build a .NET 8 executable for your game, and in that, use .NET 8 features in your project.  But the MonoGame Libraries are still .NET 6 and limited to the .NET 6 instruction set, so any functionality you write can only be enhanced in your code and not the base of the MonoGame Framework.</p>
<p>You still get some of the performance gains in your .NET project for the code you write in your game so it is worth doing!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="accessing-the-developer-version-of-the-monogame-packages">Accessing the Developer version of the MonoGame packages<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#accessing-the-developer-version-of-the-monogame-packages" class="hash-link" aria-label="Direct link to Accessing the Developer version of the MonoGame packages" title="Direct link to Accessing the Developer version of the MonoGame packages" translate="no">​</a></h2>
<p>The developer versions of the MonoGame packages are currently published on GitHub using GitHub's own NuGet packaging service, which can be found at <a href="https://github.com/orgs/MonoGame/packages" target="_blank" rel="noopener noreferrer" class="">https://github.com/orgs/MonoGame/packages</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/00-MonoGame-packages.png" alt="MonoGame GitHub Packages" class="img_ev3q"></p>
<p>You can download each package from here manually if you wish, but it is better to do it directly in your project, to do this however, you will also need a <strong>Personal Access Token</strong> for your client to successfully authenticate with GitHub to access the packages.</p>
<blockquote>
<p>Although the packages are public, like the official NuGet servers, they are actually held behind GitHub's authentication which needs a user account to access.</p>
</blockquote>
<p>To authenticate, you need:</p>
<ul>
<li class="">Your GitHub username (or email).</li>
<li class="">A <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens" target="_blank" rel="noopener noreferrer" class="">Personal Access Token</a> with at least <strong>READ</strong> access to GitHub Packages.</li>
</ul>
<p>To create a Personal Access token, simply:</p>
<ol>
<li class="">
<p>Visit the GitHub website, logged in of course.</p>
</li>
<li class="">
<p>Click on your Profile Picture in the top-right-hand corner of the screen and select <strong>"Settings"</strong>.</p>
</li>
<li class="">
<p>In the menu on the left, scroll to the bottom and select <strong>&lt; &gt; Developer Settings</strong>.</p>
</li>
<li class="">
<p>Expand the drop-down for <strong>Personal access tokens</strong> and select <strong>Tokens (classic)</strong>.</p>
</li>
<li class="">
<p>Click on the <strong>Generate new token</strong> button, select <strong>Generate new token (classic)</strong></p>
<blockquote>
<p>GitHub Packages only support authentication using a personal access token (classic). For more information, see "<a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" target="_blank" rel="noopener noreferrer" class="">Managing your personal access tokens</a>."</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/01-GitHubPersonalAccessToken.png" alt="GitHub Personal Access Token" class="img_ev3q"></p>
</li>
<li class="">
<p>As shown above, give the token a recognizable name, select the</p>
<p><strong>read:packages</strong> scope</p>
<p>(which only allows this token to read packages, nothing else) and finally set the expiration date.  (For read tokens like this, I usually set the expiration to "Never expire", although GitHub will warn you if you do)</p>
</li>
<li class="">
<p>When done, scroll to the bottom of the page and click <strong>Generate Token</strong></p>
</li>
<li class="">
<p>Finally, on the next screen, GitHub will show your new key in a highlighted row in the Token list, now <strong>VERY IMPORTANT</strong>, copy your key from here and save it somewhere securely as GitHub will <strong>NEVER</strong> show you this key again.  The only way to access the key again is to regenerate it by editing the Key and requesting a new one.</p>
</li>
</ol>
<blockquote>
<p>Your key will need to be regenerated if:</p>
<ol>
<li class="">You forget it, basically get a new one and update wherever you used it, for authentication, GitHub actions or wherever.</li>
<li class="">The key expires and you need a fresh one.</li>
</ol>
<p>GitHub will NEVER ask you for a key and will not send you an email requesting it, so NEVER share it and NEVER publish it to a GitHub repo (even private ones).  Only use secrets online.</p>
</blockquote>
<p>Now armed with your key, you can use this as your <strong>password</strong> for authenticating with GitHub to access packages stored on GitHub for repositories your account can access.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio">Getting access to the .NET 8 developer version of MonoGame with Visual Studio<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio" class="hash-link" aria-label="Direct link to Getting access to the .NET 8 developer version of MonoGame with Visual Studio" title="Direct link to Getting access to the .NET 8 developer version of MonoGame with Visual Studio" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/VisualStudio2022.jpg" alt="Visual Studio Logo" class="img_ev3q"></p>
<p>Starting with Visual Studio, as it is the simplest to do, upgrading MonoGame to .NET 8 is as simple as adding access to the GitHub NuGet package source and updating your packages.</p>
<ol>
<li class="">
<p>Open your MonoGame project up in Visual Studio</p>
</li>
<li class="">
<p>Right-click and select properties on your Project File (not Solution file), then change the <strong>Target Framework</strong> to <strong>.NET 8.0</strong>, as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/02-TargetFrameworkSetting.png" alt="Setting Target Framework for Project" class="img_ev3q"></p>
</li>
<li class="">
<p>Save and close the window, which now changes the building of your project to <strong>.NET 8</strong></p>
</li>
<li class="">
<p>Next, we need to add a new NuGet source so that you can access the developer packages instead of the publicly published ones on NuGet. Right-click the <strong>Dependencies</strong> branch of your project and select <strong>Manage NuGet packages</strong>.</p>
</li>
<li class="">
<p>From here you can see (by default) what NuGet packages are installed in your project, for which you should see the existing MonoGame packages.  Now, click the <strong>COG</strong> icon next to the <strong>Package Source</strong> drop-down in the top-right-hand corner of the screen, which will bring up the <strong>Package Sources</strong> configuration for your project.</p>
</li>
<li class="">
<p>We now need to add a new source to be able to select packages from the MonoGame GitHub repository, click on the "<strong>+</strong>" button in the top-right-hand corner of the window which will add a new item to the list and select it (if it does not select it, just select the item labelled "Package Source").</p>
</li>
<li class="">
<p>Next, enter the following details:</p>
<ul>
<li class="">Name: MonoGame Development</li>
<li class="">Source: <a href="https://nuget.pkg.github.com/MONOGAME/index.json" target="_blank" rel="noopener noreferrer" class="">https://nuget.pkg.github.com/MONOGAME/index.json</a></li>
</ul>
</li>
<li class="">
<p>Once you have entered the details, click on the "Update" button to save the changes and you should see the updated screen below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/03-NuGetSource.png" alt="NuGet Source Settings" class="img_ev3q"></p>
</li>
<li class="">
<p>Now just click <strong>OK</strong> and select the new <strong>Package Source</strong> in the drop-down (if it is not selected already)</p>
<blockquote>
<p>The first time you access the MonoGame developer NuGet source on GitHub, you will be asked for your authentication credentials.  Simply enter your <strong>GitHub Username</strong> (or email) and your <strong>Personal Access Token</strong> (for the password) that you generated earlier to progress.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/04-GitHubAUthenticationPrompt.png" alt="GitHub authentication" class="img_ev3q"></p>
</blockquote>
</li>
<li class="">
<p>Sadly, all we can still see are the existing <strong>Release</strong> packages at this point, to see the development versions you need to also check the "<strong>Includeprerelease</strong>" checkbox next to the search bar at the top of the screen, and WHOOSH, the MonoGame packages list will update to show the VERY latest versions.</p>
</li>
</ol>
<p>From here you should simply be able to select the installed packages and update them to the latest without issue.  Congrats you are now using the .NET 8 version of the MonoGame Framework!</p>
<p>Next Step, <a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#do-not-forget-about-the-tools" class=""><strong>jump to this section</strong></a> to also update the MonoGame tools, e.g. the MGCB content tool.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio-code">Getting access to the .NET 8 developer version of MonoGame with Visual Studio Code<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#getting-access-to-the-net-8-developer-version-of-monogame-with-visual-studio-code" class="hash-link" aria-label="Direct link to Getting access to the .NET 8 developer version of MonoGame with Visual Studio Code" title="Direct link to Getting access to the .NET 8 developer version of MonoGame with Visual Studio Code" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/visualstudiocode.png" alt="Visual Studio Code Logo" class="img_ev3q"></p>
<p>For Visual Studio Code, the flow is a little more manual and also a bit trickier as we no longer have a UI in which to make changes, so we need to apply the updates by hand.</p>
<ol>
<li class="">
<p>Open Visual Studio Code in your MonoGame Project folder.</p>
</li>
<li class="">
<p>Select the <strong>CSPROJ</strong> file in the folder tree.</p>
</li>
<li class="">
<p>Change the <strong>TargetFramework</strong> value from <strong>net6.0</strong> to <strong>net8.0</strong>. (yay your project will now build for .NET 8)</p>
<blockquote>
<p><code>&lt;TargetFramework&gt;net8.0&lt;/TargetFramework&gt;</code></p>
</blockquote>
</li>
<li class="">
<p>Next, to be able to access the GitHub Packages, we need to authenticate with GitHub to access the developer packages, open a new <strong>Terminal Window</strong> in VSCode ("<strong>Terminal -&gt; New Terminal</strong>" or "<strong>Ctrl+Shift+'</strong>") and then type the following:</p>
<div class="language-dotnetcli codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dotnetcli codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet nuget add source --username &lt;your GitHub Username/&gt; --password &lt;your GitHub PAT/&gt; --name MonoGame "https://nuget.pkg.github.com/MonoGame/index.json"</span><br></div></code></pre></div></div>
<blockquote>
<p>Making sure to replace the username and password with your GitHub credentials, your username and the Personal Access Token you generated.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/05-addmonogamesource.png" alt="Command Line output" class="img_ev3q"></p>
</li>
<li class="">
<p>With the authentication in place, enter the following command for each package you have installed:</p>
<p><code>.NET add package MonoGame.Content.Builder.Task --version 3.8.1.534-develop</code></p>
<blockquote>
<p>To check the correct version to use for the packages you want to install, visit the <a href="https://github.com/orgs/MonoGame/packages" target="_blank" rel="noopener noreferrer" class="">Packages list</a> on the GitHub repository and click on each package to see all the versions for the package and even the command-line command, as shown above.</p>
</blockquote>
</li>
</ol>
<p>If you now check the <strong>Solution Explorer</strong> tab in the Browser window on the left, you will see the dependency packages updated to the development versions of MonoGame (assuming your <em>nuget.config</em> is configured with the right credentials)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="do-not-forget-about-the-tools">Do not forget about the tools<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#do-not-forget-about-the-tools" class="hash-link" aria-label="Direct link to Do not forget about the tools" title="Direct link to Do not forget about the tools" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231211/tools.gif" alt="MonoGame Tools need love too" class="img_ev3q"></p>
<p>You have updated your project to .NET 8 as well as your MonoGame Framework dependencies.  So, why when you do a build does it still use the old .NET 6 versions of the MonoGame Tools (like MGCB)?</p>
<blockquote>
<p>The simple answer is because you are still telling it to.</p>
</blockquote>
<p>To update your project to use the newer .NET 8 version of the tools, you also need to update the <strong>.NET-tools.json</strong> configuration located in your projects <strong>.config</strong> folder.</p>
<p>Simply edit the file in Visual Studio or VSCode and swap out the older <strong>3.8.1.303</strong> version number with the newer version you installed with your packages, which at the time of writing was <strong>3.8.1.534-develop</strong>, as shown below:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">{</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  "version": 1,</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  "isRoot": true,</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  "tools": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    ".NET-mgcb": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "version": "3.8.1.534-develop",</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "commands": [</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        "mgcb"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    },</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    ".NET-mgcb-editor": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "version": "3.8.1.534-develop",</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "commands": [</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        "mgcb-editor"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    },</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    ".NET-mgcb-editor-linux": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "version": "3.8.1.534-develop",</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "commands": [</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        "mgcb-editor-linux"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    },</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    ".NET-mgcb-editor-windows": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "version": "3.8.1.534-develop",</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "commands": [</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        "mgcb-editor-windows"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    },</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    ".NET-mgcb-editor-mac": {</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "version": "3.8.1.534-develop",</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      "commands": [</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        "mgcb-editor-mac"</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Done, next time you perform a <code>.NET restore</code> or build your project, the latest version of the tools with be downloaded and made available (they will not be available until you do your FIRST build after making the update)</p>
<blockquote>
<p>*<em>Note</em> this is still a manual task. Every time you update the dependencies, you will also need to update the tools versions.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>To the beginner, this upgrade will not mean much at all, except to state that you will be running on the latest "supported" version of Dot Net (as .NET 6 is now in maintenance only), which also means you will get all the latest fixes and updates to the backbone of your project. As well as the modest speed improvements from the latest and greatest of .NET.</p>
<p>For the more adventurous, newer C# features and .NET 8 specific functionality are now within your project, so much so that I couldn't fit them into a single article :D</p>
<blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="but-you-can-check-out-the-net-8-docs-for-even-more-details"><a href="https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-8" target="_blank" rel="noopener noreferrer" class="">But you can check out the .NET 8 docs for even more details.</a><a href="https://zenithmoon.com/blogs/dotnet-8-meet-monogame#but-you-can-check-out-the-net-8-docs-for-even-more-details" class="hash-link" aria-label="Direct link to but-you-can-check-out-the-net-8-docs-for-even-more-details" title="Direct link to but-you-can-check-out-the-net-8-docs-for-even-more-details" translate="no">​</a></h3>
</blockquote>
<p>I wish you well on your continuing adventure with MonoGame and make sure to keep an eye on what the Foundation will promise next (but no Galactic Empire falls predicted yet if you are watching the Foundation TV series).</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/DotNet-8-meet-MonoGame.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MonoGame on the Web, No Really!]]></title>
        <id>https://zenithmoon.com/blogs/monogame-on-the-web-no-really</id>
        <link href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really"/>
        <updated>2023-11-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[By default, MonoGame does not, or cannot publish to WebGL/The Web, but thanks to NKast, there is a Fork of MonoGame that can! Read on for details.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/kniEngine/kni" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/weblaunch.gif" alt="It shoots web?" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; With a little effort, publishing MonoGame projects to the Web is possible, so long as you remember it is the Web and you cannot do EVERYTHING!</p>
</blockquote>
<p>When it comes to GameJams, <a class="" href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame">like the one mentioned in this post</a>, pushing out the finished project as an EXE or Appx usually results in your project either getting downvoted or ignored, because who wants to infect their machine with an unknown just to test out a Jam project.
This applies to most Game Engines out there that are not web-based, including MonoGame, but thankfully due to the hard work of <strong><a href="https://community.monogame.net/u/nkast" target="_blank" rel="noopener noreferrer" class="">Nikos Kastellanos</a></strong> (<a href="https://github.com/nkast" target="_blank" rel="noopener noreferrer" class="">NKast</a>) there is an option available to us.</p>
<!-- -->
<p>Now you have to keep in mind, that the Web is NOT your desktop or some high-powered beast, sure, there are continual developments to make Web projects work better or support more features, but it is unlikely to match that of your modern desktops or consoles.  Some things either do not work because they are too complex, others because there is insufficient support in all web browsers or devices.  So long as you keep in mind the limitations (usually only found by trying to run your game in a browser), then you can truly fly.</p>
<blockquote>
<p>Personally, Keep your expectations moderate. First, make your project work with the minimum, then just add more until it breaks :D.  Start with the keyboard only and go from there.</p>
</blockquote>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#welcome-to-the-kni-project" class="">Welcome to the KNI project</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#installing-the-templates" class="">Installing the Templates</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#making-a-build" class="">Making a build</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#what-could-go-wrong" class="">What could go wrong</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#extra-credit-publish-to-github-pages" class="">Extra credit, publish to GitHub Pages</a></li>
</ul>
<blockquote>
<p>BIG thanks to <a href="https://github.com/nkast" target="_blank" rel="noopener noreferrer" class="">NKast</a> For the amazing work with the <a href="https://github.com/kniEngine/kni" target="_blank" rel="noopener noreferrer" class="">KNI project</a> which offers another extended way to build MonoGame projects with some additional platforms and features!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="welcome-to-the-kni-project">Welcome to the KNI project<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#welcome-to-the-kni-project" class="hash-link" aria-label="Direct link to Welcome to the KNI project" title="Direct link to Welcome to the KNI project" translate="no">​</a></h2>
<p><a href="https://monogame.net/" target="_blank" rel="noopener noreferrer" class="">MonoGame</a> is an awesome game development framework, made even more awesome by the growing community that surrounds it.  A prime example of this is the <a href="https://github.com/kniEngine/kni" target="_blank" rel="noopener noreferrer" class="">KNI Engine</a> made and supported by one of MonoGame's long time supporters <strong><a href="https://community.monogame.net/u/nkast" target="_blank" rel="noopener noreferrer" class="">Nikos Kastellanos</a></strong> (<a href="https://github.com/nkast" target="_blank" rel="noopener noreferrer" class="">NKast</a>), their tireless devotion brings non-other than Web Support plus a host of other features for projects written using MonoGame.</p>
<p><img decoding="async" loading="lazy" src="https://github.com/kniEngine/kni/blob/main/Images/Icon.png?raw=true" alt="KNI Engine" class="img_ev3q"></p>
<p>Like MonoGame, KNI supports the <a href="https://opensource.org/licenses/MS-PL" target="_blank" rel="noopener noreferrer" class="">Microsoft Public License</a> for the majority of the code, with a few proprietary exceptions which are detailed in its components. KNI is free and open-source, however, maintaining and expanding the framework requires ongoing effort and resources that relies on the support of the community to continue delivering top-notch updates, features, and maintenance.</p>
<p>KNI Supports the same platforms that MonoGame does (because it is a fork of MonoGame) plus a few additions, which include:</p>
<ul>
<li class="">Additional Math support.</li>
<li class="">A WPF based MonoGame Content Pipeline editor.</li>
<li class="">Video player support.</li>
<li class="">An improved Font Processor.</li>
</ul>
<p>Plus a few performance tweaks for KNI based projects.  Worthy of checking out!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installing-the-templates">Installing the Templates<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#installing-the-templates" class="hash-link" aria-label="Direct link to Installing the Templates" title="Direct link to Installing the Templates" translate="no">​</a></h2>
<p>Now, unlike base MonoGame these days, KNI does require a full Visual Studio 2022 installation, mainly to support the additional Project templates that KNI provides, these are all installed by the KNI Engine installer (much like MonoGame did before it upgraded to .NET6 in its entirety):</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="kni-engine-releases---latest-at-time-of-writing-v399001"><a href="https://github.com/kniEngine/kni/releases" target="_blank" rel="noopener noreferrer" class="">KNI Engine Releases</a> - Latest (at time of writing) <a href="https://github.com/kniEngine/kni/releases/tag/v3.9.9001" target="_blank" rel="noopener noreferrer" class="">v3.9.9001</a><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#kni-engine-releases---latest-at-time-of-writing-v399001" class="hash-link" aria-label="Direct link to kni-engine-releases---latest-at-time-of-writing-v399001" title="Direct link to kni-engine-releases---latest-at-time-of-writing-v399001" translate="no">​</a></h3>
<p>Like MonoGame, most of the libraries behind the templates are published on NuGet, but the Project templates (like the Web project template) still require Visual Studio to create them. (Maybe with your support this can be updated in the future!).</p>
<ul>
<li class="">Simply run and install the <a href="https://github.com/kniEngine/kni/releases/download/v3.9.9001/KniSdkSetup3.9.9001.exe" target="_blank" rel="noopener noreferrer" class="">KniSdkSetup3.9.9001.exe</a> to install the templates.</li>
</ul>
<blockquote>
<p>At the time of writing, there is a <strong>Known</strong> issue with the Visual Studio templates, which is mainly Visual Studio's fault (honest), whereby the templates may not immediately show up and require either:</p>
<ul>
<li class="">Restarting Visual Studio a "few" times.</li>
<li class="">Require running the command <code>devenv /updateConfiguration</code> in the Visual Studio installation folder, e.g. <strong>C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE</strong>.</li>
</ul>
<p>If your User Profile is not in the default <code>C:\Users</code> folder, you might also have to check/update your Visual Studio "locations" setup and move/copy the installed templates there:</p>
<p>Just be sure to run the above <code>devenv /updateConfiguration</code> command <strong>AFTER</strong> moving them to update Visual Studio.</p>
</blockquote>
<p>Maybe with your support, this situation can be improved.  All it takes is a little support.  But once you are working, you can be ready to fly.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="making-a-build">Making a build<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#making-a-build" class="hash-link" aria-label="Direct link to Making a build" title="Direct link to Making a build" translate="no">​</a></h2>
<p>With the templates installed and everything ready, you should have access to the new KNI templates, and for this article more importantly, the <strong>KNI Web Browser Template</strong> as shown below:</p>
<p>Just create your new project using this template and you are already 90% of the way there:</p>
<p>Running the project results in a new Web Browser being launched against your local machine, ready to test and play with:</p>
<p>Fantastic, so what was too hard about that?</p>
<blockquote>
<p>KNI Web uses <a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor" target="_blank" rel="noopener noreferrer" class="">Microsoft Blazor</a> (which uses Razor files) as the backend, which is a C# compiled Web system for building web apps.  It is really useful as MonoGame is C# based and Blazor is a C# based web system, it just works.  Some other Web solutions for MonoGame used a cross-compiler to turn C# into JavaScript, but Blazor is just cleaner and more efficient.</p>
</blockquote>
<p>Looking into the project, you should see a few subtle differences, which we should discuss, so that you don't change anything you should not by mistake, namely:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/01-SolutionExplorer.png" alt="KNI Browser project" class="img_ev3q"></p>













































<table><thead><tr><th>File</th><th>Location</th><th>Description</th></tr></thead><tbody><tr><td>Program.cs</td><td>Root</td><td>A custom version of Program.cs (like most platforms) designed for Web Builds, DO NOT TOUCH :D</td></tr><tr><td>KNIBrowserGame.cs</td><td>Root</td><td>This is basically your normal Game1.cs definition, hack away freely.  But if you change the Class name, also update <code>Index.razor.cs</code></td></tr><tr><td>Index.razor</td><td>Pages</td><td>This is the Razor (web) equivalent of Program.cs, it is the initialization page for the project. It defines a renderable canvas on which to draw the game.</td></tr><tr><td>Index.razor.cs</td><td>Pages (under Index.razor)</td><td>The c# code behind for the main Razor web page, this is the code initialization for the Web project</td></tr><tr><td>KNIBrowserContent.mgcb</td><td>Content</td><td>The MGCB project for the Web solution, albeit, using KNI's own MGCB editor due to "Visual Studio"</td></tr><tr><td>wwwroot</td><td>Root</td><td>The deployable web folder for the project, unless you are a seasoned web dev, best not to touch this :D</td></tr><tr><td>!Other files</td><td>Root</td><td>Just do not touch them, mainly Razor setup files and such</td></tr></tbody></table>
<p>Out of the box, you should not need to change anything and the only thing you will need to keep in sync is the NAME of your Game class and the entry point in <code>Index.razor.cs</code>, in the same way we do for other MonoGame projects between the Game class and the Program class.</p>
<p>Here is a GIF of the GameState Management sample (with a few alterations) running using a KNI Web Build.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/02-gamestatemanagement.gif" alt="GameState Management Web" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-could-go-wrong">What could go wrong<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#what-could-go-wrong" class="hash-link" aria-label="Direct link to What could go wrong" title="Direct link to What could go wrong" translate="no">​</a></h2>
<p>Now, this is the web we are targeting and there is a lot of history and patches to make modern web browsers work, so it should come as no surprise that not EVERYTHING is going to work out of the box:</p>
<ul>
<li class="">Some features are missing, like GamePad and Touchscreen support.</li>
<li class="">Some shaders may not work as expected, or require reduced features.</li>
<li class="">File access can be... Tricky, pesky permissions.</li>
</ul>
<p>To name but a few, no doubt there are more, much more.  But this being an Open Source project, if you have the skills and are willing to contribute to make it better, I encourage you to do so!!.</p>
<p>When you do hit an issue, and more likely than not I am afraid, you will, you will see the following result when you run your project:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/03-KNIWebError.png" alt="KNI Web Error" class="img_ev3q"></p>
<p>A pretty purple screen with a "Reload" option, which is not really that helpful on its own and Visual Studio is no help here really because you have left the confines of your debugger and entered "The Web Zone"!</p>
<p>Luckily, most browsers have an "<strong>F12</strong>" developer option, so by pressing "<strong>F12</strong>" you will get the not-so-friendly "Web Debugger" window, as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/04-BrowserErrors.png" alt="KNI Web Error" class="img_ev3q"></p>
<p>These outputs are more friendly to Web Developers (Well, I assume so?) full of lots of information to help you diagnose what is going on, in this case, the issue was simple:</p>
<blockquote>
<p>The sample was using a <strong>GamePad</strong> for input and GamePads are not currently supported using the KNI Engine for the web.</p>
</blockquote>
<p>If you are only doing a web project, just fix / remove the offending code, if you have a multi-platform project, then just encase the offending code with the following pre-compiler definition (#if):</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token preprocessor property" style="color:rgb(128, 203, 196)">#</span><span class="token preprocessor property directive keyword" style="color:rgb(127, 219, 202)">if</span><span class="token preprocessor property" style="color:rgb(128, 203, 196)"> !BLAZORGL</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">                CurrentGamePadStates</span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"> </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> GamePad</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">GetState</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">PlayerIndex</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain">i</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token preprocessor property" style="color:rgb(128, 203, 196)">#</span><span class="token preprocessor property directive keyword" style="color:rgb(127, 219, 202)">endif</span><br></div></code></pre></div></div>
<p>Which will exclude the offending lines from being included in the Web Build (just make sure things still work / compile).</p>
<p>Feel free to experiment and play, and for those cunning individuals who are experienced in Web Development, I encourage you to help enlighten those who are not.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extra-credit-publish-to-github-pages">Extra credit, publish to GitHub Pages<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#extra-credit-publish-to-github-pages" class="hash-link" aria-label="Direct link to Extra credit, publish to GitHub Pages" title="Direct link to Extra credit, publish to GitHub Pages" translate="no">​</a></h2>
<p>Having a web build on your machine that you can view locally is all well and good, but what about everyone else?  You cannot exactly ask them all to come to dinner to view your creation, what about pushing it to the web?</p>
<p>To get your build out there, you have a couple of options:</p>
<ul>
<li class="">Painstakingly setup a web host, build your project, connect to your host, upload your files and pray everything works.
or</li>
<li class="">Let GitHub do it for you.  Hosted for free, using free time and none (well a little) of yours.</li>
</ul>
<p>Thanks to GitHub actions (automation) and GitHub Pages (free static web hosting) we can manage the source of our project online and then create/update a build every time we push an update to source control.</p>
<p>To achieve this, assuming you have a GitHub repo setup and have pushed your source code to it, the tasks we need to complete are:</p>
<ul>
<li class="">Configure the permissions for automation.</li>
<li class="">Setup GitHub pages.</li>
<li class="">Create a GitHub Pages deployment workflow.</li>
</ul>
<p>As easy as 1,2,3, honest.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configure-automation-permissions">Configure automation permissions<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#configure-automation-permissions" class="hash-link" aria-label="Direct link to Configure automation permissions" title="Direct link to Configure automation permissions" translate="no">​</a></h3>
<p>Because we want our automation to publish content to GitHub pages, and write back to the repository, we need to allow it to (by default, it is turned off).
To do this, navigate to "Settings -&gt; Actions -&gt; General -&gt; Workflow Permissions" and set the option to "Read and write permissions" as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/08-workflowpermissions.png" alt="Enable read/write permissions for automation" class="img_ev3q"></p>
<p>This allows the automation to publish on your behalf.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="setup-github-pages">Setup GitHub Pages<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#setup-github-pages" class="hash-link" aria-label="Direct link to Setup GitHub Pages" title="Direct link to Setup GitHub Pages" translate="no">​</a></h3>
<p>Enabling GitHub pages is very simple and GitHub continually works to improve the flow to make it easier and easier.</p>
<p>Next, you need to:</p>
<ul>
<li class="">Navigate to "Settings -&gt; Pages"</li>
<li class="">Change the "Source" to "GitHub Actions"</li>
<li class="">In the selection that appears called "Static HTML", click on the "Configure" button.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/06-pagessetup.png" alt="Enable GitHub Pages" class="img_ev3q"></p>
<p>Done, there is more to do, but GitHub Pages is now setup.  Now for the final and slightly trickier part, the automation workflow.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configure-your-deployment-workflow">Configure your deployment workflow<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#configure-your-deployment-workflow" class="hash-link" aria-label="Direct link to Configure your deployment workflow" title="Direct link to Configure your deployment workflow" translate="no">​</a></h3>
<p>Following on from the previous step, you should now see the following default workflow setup, which is close but will not build our MonoGame project.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/07-initialyaml.png" alt="Starter YAML" class="img_ev3q"></p>
<p>To get the result we want, we need to add the following:</p>
<ul>
<li class="">Setup .NET 6 on the automation host.</li>
<li class="">Build/Publish our Blazor webpage complete with our MonoGame project.</li>
<li class="">Package the build output, not just the entire repository.</li>
<li class="">Deploy (this is actually there, but we need to check the order)</li>
</ul>
<p>The updated YAML should be replaced with the following:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Simple workflow for deploying static content to GitHub Pages</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Deploy MonoGame web project to GitHub Pages</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">on</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Runs on pushes targeting the default branch</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">push</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">branches</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token string" style="color:rgb(173, 219, 103)">"main"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Allows you to run this workflow manually from the Actions tab</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">workflow_dispatch</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">permissions</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">contents</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> read</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">pages</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> write</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">id-token</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> write</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">concurrency</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">group</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">"pages"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">cancel-in-progress</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">false</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Single deploy job since we are just deploying</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">deploy-to-github-pages</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">environment</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> github</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">pages</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">url</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> steps.deployment.outputs.page_url </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">runs-on</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Checkout</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/checkout@v3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Setup Pages</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/configure</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">pages@v3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Setup .NET Core SDK</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/setup</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">dotnet@v3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">          </span><span class="token key atrule">dotnet-version</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> 6.0.x</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Publish .NET Core Project</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> dotnet publish Platforms/Web/KNIBrowser.csproj </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">c Release </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">o release </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">nologo</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Upload artifact</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/upload</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">pages</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">artifact@v2</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">          </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Upload wwwroot from publish action</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">          </span><span class="token key atrule">path</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">'release/wwwroot'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Deploy to GitHub Pages</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">id</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> deployment</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/deploy</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">pages@v2</span><br></div></code></pre></div></div>
<p>The critical elements to watch out for are:</p>
<ul>
<li class="">"Publish .NET Core Project" - Make sure the <strong>csproj</strong> file path is correct.  In this instance it is "Platforms/Web/KNIBrowser.csproj"</li>
<li class="">The "Upload Artifact" path, which selects the files from the build to upload.  By default the "dotnet publish" command outputs to the root of the folder.</li>
</ul>
<p>If in doubt, run the commands locally in your project folder to make sure you get the output you expect.</p>
<p>Click save and "Commit to main" (or create a pull request) to activate the workflow.</p>
<blockquote>
<p><strong>WARNING</strong> this will run straight away, as the workflow is activated on checking in code.  But do not worry, you have 2000 minutes of FREE time on GitHub for automations.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="check-the-action-output">Check the Action output<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#check-the-action-output" class="hash-link" aria-label="Direct link to Check the Action output" title="Direct link to Check the Action output" translate="no">​</a></h3>
<p>Providing everything is correct, you have put in the paths correctly and checked it DID actually build locally, then you should get the following result:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/09-actionsresult.png" alt="Successful automation" class="img_ev3q"></p>
<p>Clicking on the completed Action, you shall see the output of the Action, INCLUDING the URL that your build was actually published to:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/10-actionsoutput.png" alt="Upload result" class="img_ev3q"></p>
<p>Check out my build here:</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="httpdarkgenesiszenithmooncomgamestatemanagementsample-web"><a href="https://darkgenesis.zenithmoon.com/GameStateManagementSample-Web/" target="_blank" rel="noopener noreferrer" class="">http://darkgenesis.zenithmoon.com/GameStateManagementSample-Web/</a><a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#httpdarkgenesiszenithmooncomgamestatemanagementsample-web" class="hash-link" aria-label="Direct link to httpdarkgenesiszenithmooncomgamestatemanagementsample-web" title="Direct link to httpdarkgenesiszenithmooncomgamestatemanagementsample-web" translate="no">​</a></h4>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="danger-will-robinson">Danger Will Robinson<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#danger-will-robinson" class="hash-link" aria-label="Direct link to Danger Will Robinson" title="Direct link to Danger Will Robinson" translate="no">​</a></h3>
<p>One issue I did hit which actually prevented my build completing, required me to edit my KNI Engine csproj file and remove a line.  The line in question was:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">  &lt;Import Project="$(MSBuildExtensionsPath)\MonoGame\v3.0\MonoGame.Content.Builder17.targets" /&gt;</span><br></div></code></pre></div></div>
<p>Rather ironically, local builds will work fine and run with this in, but automated builds will not.  Without it, everything is squeaky clean. (fingers crossed)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-game-is-afoot">The game is afoot<a href="https://zenithmoon.com/blogs/monogame-on-the-web-no-really#the-game-is-afoot" class="hash-link" aria-label="Direct link to The game is afoot" title="Direct link to The game is afoot" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231114/05-thegameisafoot.gif" alt="Let the games begin" class="img_ev3q"></p>
<p>We have come to the end of this little web journey, I hope others are encouraged and excited to ship your games or hack projects to the web to demonstrate your skills, especially if there is a <a href="https://itch.io/jam/monogamejam5" target="_blank" rel="noopener noreferrer" class="">GameJam going on :D</a>.</p>
<p>Here is hoping this is a little light in the darkness to get you going!</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/monogame-on-the-web-No-really!.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
        <category label="gamejam" term="gamejam"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[GameDev jamming with MonoGame]]></title>
        <id>https://zenithmoon.com/blogs/gamedev-jamming-with-monodame</id>
        <link href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame"/>
        <updated>2023-11-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[GameDev jams are the best way to throw yourself into the deep end and try things you never thought possible, like learning a new framework/engine.  Here is a quick starter!]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/SimonDarksideJ/GameStateManagementSample" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231113/01-gamedev.gif" alt="GameDev Jam for life!" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; Getting started with MonoGame is easy, mastering it takes time. So, throw all that out the window and get hacking instead!</p>
</blockquote>
<p>The <a href="https://itch.io/jam/monogamejam5" target="_blank" rel="noopener noreferrer" class="">5th annual MonoGameJam</a> is kicking off soon on the <strong>30th November 2023</strong>, which is by far one of the best ways to throw yourself into <a href="https://www.monogame.net/" target="_blank" rel="noopener noreferrer" class="">MonoGame</a> and learn something new:</p>
<!-- -->
<ul>
<li class="">Your next engine framework of choice.</li>
<li class="">Try something new and exciting (granted usually hair-raising).</li>
<li class="">Test out some new technique or skill.</li>
<li class="">Have fun and throw things at the screen or speakers until something sticks.</li>
</ul>
<p>Not quite the vaulted heights of the days of <a href="https://en.wikipedia.org/wiki/Dream_Build_Play" target="_blank" rel="noopener noreferrer" class="">DreamBuildPlay</a> in the past, which launched the careers of so many indie devs such as <a href="https://en.wikipedia.org/wiki/Ska_Studios" target="_blank" rel="noopener noreferrer" class="">Ska Studios</a> with Dishwasher Samurai, Smudged Cat Games with <a href="https://en.wikipedia.org/wiki/The_Adventures_of_Shuggy" target="_blank" rel="noopener noreferrer" class="">The Adventures of Shuggy</a> and even Humble Hearts with <a href="https://en.wikipedia.org/wiki/Dust:_An_Elysian_Tail" target="_blank" rel="noopener noreferrer" class="">Dust, An Elysian Tale</a>.</p>
<p>Bold games for a bold time, and who knows who/what could be next!</p>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#what-is-monogame" class="">What is MonoGame</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#understanding-monogame-game-building" class="">Understanding MonoGame game building</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#a-great-sample-to-start-with" class="">A great sample to start with</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#services-addons-and-more-oh-my" class="">Services, addons and more, oh my</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#dream-big" class="">Dream Big</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-monogame">What is MonoGame<a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#what-is-monogame" class="hash-link" aria-label="Direct link to What is MonoGame" title="Direct link to What is MonoGame" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231113/02-monogame.png" alt="MonoGame" class="img_ev3q"></p>
<p>If you have been hiding under a very big rock, you might not have heard of MonoGame (unlikely, but possible).  MonoGame is a game building framework that focuses on handling all the complex requirements for shipping games on multiple platforms, such as Windows, Android, Xbox, PlayStation and so on, letting you get on with just writing code/art/shaders/etc and so on (just, hah, :P)</p>
<p>In short, MonoGame is a C# based Game Framework (not an engine like Unity), where you write code once, then MonoGame compiles your code for multiple platforms enabling to you spread your wings faster.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="to-know-more-check-out-monogamenet">To know more, check out <a href="https://www.monogame.net/" target="_blank" rel="noopener noreferrer" class="">MonoGame.Net</a><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#to-know-more-check-out-monogamenet" class="hash-link" aria-label="Direct link to to-know-more-check-out-monogamenet" title="Direct link to to-know-more-check-out-monogamenet" translate="no">​</a></h3>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="understanding-monogame-game-building">Understanding MonoGame game building<a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#understanding-monogame-game-building" class="hash-link" aria-label="Direct link to Understanding MonoGame game building" title="Direct link to Understanding MonoGame game building" translate="no">​</a></h2>
<p>Getting started with MonoGame is very easy, no matter which platform you are building from, be it Windows, Mac or Linux and the best place to get started, unsurprisingly, is the <a href="https://monogame.net/articles/getting_started/index.html" target="_blank" rel="noopener noreferrer" class="">MonoGame Getting started guide</a>:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started-with-monogame"><a href="https://monogame.net/articles/getting_started/index.html" target="_blank" rel="noopener noreferrer" class="">Getting Started with MonoGame</a><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#getting-started-with-monogame" class="hash-link" aria-label="Direct link to getting-started-with-monogame" title="Direct link to getting-started-with-monogame" translate="no">​</a></h3>
<p>The guides walk you through setting up your machine environment, installing the tools and the MonoGame Framework (most important) and then leading you down the basic path of writing your first game, including content.</p>
<p>For more details, check the <a href="https://itch.io/jam/monogamejam5" target="_blank" rel="noopener noreferrer" class="">MonoGameJam5 site</a> for links, or browse the <a href="https://monogame.net/articles/tutorials.html" target="_blank" rel="noopener noreferrer" class="">catalogue of community samples and courses available</a> on the MonoGame website.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-great-sample-to-start-with">A great sample to start with<a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#a-great-sample-to-start-with" class="hash-link" aria-label="Direct link to A great sample to start with" title="Direct link to A great sample to start with" translate="no">​</a></h2>
<p>My personal favorite for any GameJam involving MonoGame is the <a href="https://github.com/SimonDarksideJ/GameStateManagementSample" target="_blank" rel="noopener noreferrer" class="">GameState Management Sample</a>, originally from <a href="https://github.com/simondarksidej/XNAGameStudio/wiki/Game-State-Management-(Mango,-C%23VB)" target="_blank" rel="noopener noreferrer" class="">Microsoft</a> and updated for MonoGame.</p>
<p>The sample provides a simple game screen management system, ready to be used as a starting point for games on Windows, iOS, Android and more, complete with reusable code to manage all the screens (including transitions) you might need for a project.  Even including pause and options screens.  It really packs a punch.</p>
<p>The sample is comprised of:</p>

























<table><thead><tr><th>Folder</th><th>Description</th></tr></thead><tbody><tr><td>Content Project</td><td>The shared content project for all platforms, not specifically required but a good reference.</td></tr><tr><td>GameStateManagement</td><td>The GameState management library, reusable in any project.</td></tr><tr><td>Platforms</td><td>Sample Platform initiator code, if you are just hacking a single platform you can ignore these.</td></tr><tr><td>SampleCode</td><td>The all-important example usage of the GameState Management library, including multiple screens, gameplay and more.</td></tr></tbody></table>
<p>Here is what it looks like:</p>













<table><thead><tr><th><img decoding="async" loading="lazy" src="https://github.com/SimonDarksideJ/GameStateManagementSample/raw/3.8/Assets/01-MainMenu.png" alt="Menu" class="img_ev3q"></th><th><img decoding="async" loading="lazy" src="https://github.com/SimonDarksideJ/GameStateManagementSample/raw/3.8/Assets/02-Options.png" alt="Options" class="img_ev3q"></th></tr></thead><tbody><tr><td><img decoding="async" loading="lazy" src="https://github.com/SimonDarksideJ/GameStateManagementSample/raw/3.8/Assets/03-GamePlay.png" alt="GamePlay" class="img_ev3q"></td><td><img decoding="async" loading="lazy" src="https://github.com/SimonDarksideJ/GameStateManagementSample/raw/3.8/Assets/04-Paused.png" alt="Pause Screen" class="img_ev3q"></td></tr></tbody></table>
<p>Enough to get you started on your Hack without worrying about all that on screen "menu" stuff.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-gamestate-management-sample"><a href="https://github.com/SimonDarksideJ/GameStateManagementSample" target="_blank" rel="noopener noreferrer" class="">The GameState Management Sample</a><a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#the-gamestate-management-sample" class="hash-link" aria-label="Direct link to the-gamestate-management-sample" title="Direct link to the-gamestate-management-sample" translate="no">​</a></h3>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="services-addons-and-more-oh-my">Services, addons and more, oh my<a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#services-addons-and-more-oh-my" class="hash-link" aria-label="Direct link to Services, addons and more, oh my" title="Direct link to Services, addons and more, oh my" translate="no">​</a></h2>
<p>If you are thinking bigger than a simple paint program or block pushing game, you might want to add things like Physics, Saving and Loading, AI, Effects and more.  Thankfully the MonoGame community has your back with a wondrous collection of resources, libraries and tools available:</p>
<blockquote>
<p>I might suggest having a glance through the list below BEFORE the hack to pick your favorites, there are a LOT!</p>
</blockquote>
<ul>
<li class=""><a href="https://github.com/aloisdeniel/awesome-monogame" target="_blank" rel="noopener noreferrer" class="">The Awesome MonoGame library of "stuff</a> - Chock full of links to stuff that will keep you going.</li>
<li class=""><a href="https://www.nuget.org/packages?q=monogame" target="_blank" rel="noopener noreferrer" class="">Nuget</a> (pronounced NewGet) - The place to look for dependencies to add to your project (it is also where MonoGame is hosted).  Just add a reference to your project and get the latest version quick and easy.</li>
<li class=""><a href="https://monogame.net/articles/tutorials.html" target="_blank" rel="noopener noreferrer" class="">The MonoGame Community tutorials list</a> as previously mentioned, a one stop shot of promoted materials from MonoGame by the community. (insert your name here if you also have stuff to shout about)</li>
</ul>
<p>And much much more!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dream-big">Dream Big<a href="https://zenithmoon.com/blogs/gamedev-jamming-with-monodame#dream-big" class="hash-link" aria-label="Direct link to Dream Big" title="Direct link to Dream Big" translate="no">​</a></h2>
<p>Like most GameJam's, the theme is yet to be announced but that's not to stop you getting a blank slate ready and getting some quick reading done.  In this ever-expanding world of AI nothing stopping you from asking your robot overlords for help (yes, they even help with MonoGame code) in sculpting your dream, above all, have fun, learn well and put your best thumb forward!</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/gamedev-jamming-with-monodame.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
        <category label="gamejam" term="gamejam"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MonoGame, the order of things!]]></title>
        <id>https://zenithmoon.com/blogs/monogame-order-of-things</id>
        <link href="https://zenithmoon.com/blogs/monogame-order-of-things"/>
        <updated>2023-10-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Every engine, framework and operation has a specific order to their events.  This post outlines the execution order for MonoGame projects.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/SimonDarksideJ/MonoGameExecution" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231028/gettingtheorderright.gif" alt="Coding is Life" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; It is as simple as Initialize, LoadContent, Update, Draw, UnloadContent and finish, with a little sauce in between.</p>
</blockquote>
<p>Every Game, Program or process runs on a specific loop, it might go through just once, or as is in the case with Games, the loop continues until the game is over, finished, kaput, crashed or simply closed (but who really wants a Game to ever end :D).  Put simply, this is known as the Game loop, and whether it is Unity, Unreal or MonoGame, they all have their own specific order of things.</p>
<!-- -->
<p>This article will walk through MonoGame's Execution event order and set out what you can and cannot override to make it your own.</p>
<blockquote>
<p>Although personally, and with every MonoGame project I've been involved with, I have extended the Game Loop and never been brave enough to actually OVERRIDE it :D.</p>
</blockquote>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#the-full-breakdown" class="">The full breakdown</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#the-simpler-view" class="">The simpler view</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#which-events-can-be-overridden" class="">Which events can be overridden</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#game-components" class="">Game Components</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#basic-game-components" class="">Basic Game Components</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#drawable-game-components" class="">Drawable Game Components</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-order-of-things#check-the-samples" class="">Check the samples</a></li>
</ul>
<blockquote>
<p>BIG thanks to <a href="https://twitter.com/aristurtledev" target="_blank" rel="noopener noreferrer" class="">Aristurtle</a> for the original image, <a href="https://twitter.com/aristurtledev/status/1110025028626800640?t=3iF-20pVzfxdoccfgyi0PQ&amp;s=19" target="_blank" rel="noopener noreferrer" class="">credits here!</a> Check out his work as it is amazing in the MonoGame space!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-breakdown">The full breakdown<a href="https://zenithmoon.com/blogs/monogame-order-of-things#the-full-breakdown" class="hash-link" aria-label="Direct link to The full breakdown" title="Direct link to The full breakdown" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231028/MG-FullExecution.png" alt="MonoGame Full Execution Order of Events" class="img_ev3q"></p>
<p>The Full MonoGame execution order is a bit much to take in, but I put it first so you can see the full range of what is going on behind the scenes while your Game is Running.</p>
<p>In short, the full execution is like this:</p>


















































<table><thead><tr><th>Event</th><th>Description</th><th>Notes</th></tr></thead><tbody><tr><td>Game Start</td><td>In Program.Main (or the relevant entry point for your platform) the Game is created and then uses the <em>Run()</em> method of the Game to start it.</td><td></td></tr><tr><td>Game Run</td><td>Initialize is called to invoke and setup all your code ready to accept any content to load.</td><td></td></tr><tr><td>LoadContent</td><td>At this point, any/all content starts to be loaded from disk into memory, ready to be rendered.</td><td>Care must be taken to load the correct assets at the right time, menu assets first, then game/level assets as the player progresses.</td></tr><tr><td>Platform.Run</td><td>This is where the Game Loop is executed for a specific platform, each has its own specific requirements for how to achieve this, but ultimately all result in a loop that "<strong>Ticks</strong>" along according to the cycle of the device</td><td>Base ticks are inconsistent and can change based on how much work the device is doing, where timing is critical, FixedStep timing is used instead.</td></tr><tr><td>Core Game Loop</td><td>Update and Draw are called consecutively for each "<strong>Tick</strong>" of the game.  Until the game is requested to <strong>Stop</strong>.</td><td><a href="https://zenithmoon.com/blogs/monogame-order-of-things#game-components" class="">Game Components</a> also receive the same events as the main game</td></tr><tr><td>Game End Run</td><td>All internal game processes are stopped and the Game Loop is terminated.</td><td>No game changes are permitted at this point and the process cannot be halted.</td></tr><tr><td>Game Exit</td><td>Content is unloaded from memory and all classes are disposed (where supported), cleaning up memory and releasing it to finally close the game</td><td>Content can be Loaded or Unloaded at any point, these final calls unload any and all remaining content from the system.</td></tr><tr><td>Game End</td><td>Game Over dude, Game over.  We should just take off and nuke the site from orbit!</td><td></td></tr></tbody></table>
<p>There is a lot more going on behind the scenes to make the Game Loop happen on so many different platforms, from Graphics, Input, Audio and more going on, but the above is a simplification to show the full workings.</p>
<p>The following sections will break this down further for reference.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-simpler-view">The simpler view<a href="https://zenithmoon.com/blogs/monogame-order-of-things#the-simpler-view" class="hash-link" aria-label="Direct link to The simpler view" title="Direct link to The simpler view" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231028/MG-BasicExecution.png" alt="MonoGame Basic Execution Order of Events" class="img_ev3q"></p>
<p>Breaking down the core loop into a more digestible reference, the actual Game Loop can simply be described as:</p>





































<table><thead><tr><th>Event</th><th>Description</th></tr></thead><tbody><tr><td>Game Run</td><td>Prep the launch pad and ready the rocket for launch.</td></tr><tr><td>Initialize</td><td>Load configuration, previous saves, Game state and ready variables for any content they require.  A lot of projects use XML or JSON to allow configuration of the project outside of the code, so this is a good time to wind things up.</td></tr><tr><td>LoadContent</td><td>Whether you are loading synchronously or asynchronously, when this event is received it is time to pull any content required from the disk and into memory.  This does not pass it to the graphics card, only readies it for use. If you have different screens that use different content, you might load them separately (as shown in the <a href="https://github.com/SimonDarksideJ/GameStateManagementSample" target="_blank" rel="noopener noreferrer" class="">Game State Management Sample</a>).</td></tr><tr><td>Update</td><td>Update your codes and move those things, ready for the next frame on the screen.  Run repeatedly until the game ends.</td></tr><tr><td>Draw</td><td>Kind of speaks for itself really, get assets from memory and push them to the screen in order.  Maybe some shader stuff too.</td></tr><tr><td>UnloadContent</td><td>Can be called at any time, but at this point it is before the game closes, to free up memory used by the game from content.</td></tr><tr><td>Exit</td><td>Clear the caches, wipe down the counters and ready for the next guest.</td></tr></tbody></table>
<p><strong>LoadContent</strong> and <strong>UnloadContent</strong> can be called at any time, and usually are when you are switching from screen to screen, level to level, trying to keep ONLY those assets you actually need in memory at the same time.  If an asset is not being used, you should consider clearing it out to save those precious memory bytes and cycles for the main event.</p>
<p><strong>Update</strong> and <strong>Draw</strong> are called constantly while your game is running as fast as your machine can run them.  By default, MonoGame uses a <strong>FixedTimeStep</strong> based on 60 FPS (Frames Per Second), doing its best to keep the GameLoop running at a constant speed regardless of device, however, this is <strong>NOT GUARANTEED</strong>, as if your game takes longer than expected in your <strong>Update</strong> cycle, the <strong>Draw</strong> cycle will be late, the reverse is true also.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="changing-from-fixed-time-to-dynamic-speeds">Changing from Fixed Time to Dynamic speeds<a href="https://zenithmoon.com/blogs/monogame-order-of-things#changing-from-fixed-time-to-dynamic-speeds" class="hash-link" aria-label="Direct link to Changing from Fixed Time to Dynamic speeds" title="Direct link to Changing from Fixed Time to Dynamic speeds" translate="no">​</a></h3>
<p>To allow faster speeds, you will need to alter the timing setup for MonoGame by either setting the <strong>TargetElapsedTime</strong> to an expected framerate, e.g.</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Target 30 FPS</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">this</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">TargetElapsedTime </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> TimeSpan</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">FromSeconds</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token number" style="color:rgb(247, 140, 108)">1d</span><span class="token plain"> </span><span class="token operator" style="color:rgb(127, 219, 202)">/</span><span class="token plain"> </span><span class="token number" style="color:rgb(247, 140, 108)">30d</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"> </span><br></div></code></pre></div></div>
<p>Or you can disable <strong>FixedTimeStep</strong> <code>IsFixedTimeStep = false</code>, and manage all the timings yourself, although this too comes with its own specific considerations.  There is no spoon.</p>
<blockquote>
<p>My recommendation is to just use the defaults until you run into an issue (or know specifically what you are doing), as the defaults will handle most situations all by themselves, in my honest opinion (for what it is worth).</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="which-events-can-be-overridden">Which events can be overridden<a href="https://zenithmoon.com/blogs/monogame-order-of-things#which-events-can-be-overridden" class="hash-link" aria-label="Direct link to Which events can be overridden" title="Direct link to Which events can be overridden" translate="no">​</a></h2>
<p>In some cases (most when you are dealing with MonoGame projects), you need to override the base implementation of MonoGame events to build your project.  Some like Initialize, LoadContent, Update and Draw are well known, while others may not.  Here is a full list of the events you can adapt to your needs in your game, all of which are described above:</p>





























































<table><thead><tr><th>Event</th><th>Description</th></tr></thead><tbody><tr><td>Initialize</td><td>Once the game has loaded, initialize the game.</td></tr><tr><td>LoadContent</td><td>Init point to load content for the Game, Scene, Screen or Drawable Component.</td></tr><tr><td>UnloadContent</td><td>Exit point to clean up content for the Game, Scene, Screen or Drawable Component.</td></tr><tr><td>Update</td><td>Called once per frame to perform updates.</td></tr><tr><td>BeginDraw</td><td>Called once per draw call before any drawing has taken place.</td></tr><tr><td>Draw</td><td>Called for any game or Drawable components.</td></tr><tr><td>EndDraw</td><td>Called once all drawing has taken place.</td></tr><tr><td>OnActivated</td><td>Fired when the game receives focus, dependent on the Platform's determination of focus.</td></tr><tr><td>OnDeactivated</td><td>Fired when the game loses focus, dependent on the Platform's determination of focus.</td></tr><tr><td>OnExiting</td><td>Fired as the last call before the game terminates.</td></tr><tr><td>Dispose</td><td>.NET call to clean up a class as it is destroyed, including the Game class.</td></tr><tr><td>BeginRun</td><td>First point of call (prior to Initialize) on starting the game.</td></tr><tr><td>EndRun</td><td>Dying breath for MonoGame before the dotnet process is terminated.</td></tr></tbody></table>
<p>The sample project linked to this article is available to demonstrate these calls in action.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="game-components">Game Components<a href="https://zenithmoon.com/blogs/monogame-order-of-things#game-components" class="hash-link" aria-label="Direct link to Game Components" title="Direct link to Game Components" translate="no">​</a></h2>
<p>An underrated feature of the original XNA and now MonoGame Framework are components, essentially these are small implementations of Game Code that you simply pass on to the Framework to handle and run, and they just run (so long as they are registered).  Game Components come in two flavours, one building on top of the other:</p>
<ul>
<li class="">A Game Component - A basic Game like class that is <strong>Initialized</strong> and then updated by the <strong>Update</strong> event in the game.  Like a spawning manager that just needs to kick things off as levels progress, or maintain the state of "things".  No drawing, just running.</li>
<li class="">A Drawable Game Component - An extended version of a Game Component that also received the Content Loading/Unloading events and the all-important <strong>Draw</strong> event.  Good for drawing enemies on the screen, or particles.</li>
</ul>
<p>Let us delve into these a little more, into how their events are handled.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="basic-game-components">Basic Game Components<a href="https://zenithmoon.com/blogs/monogame-order-of-things#basic-game-components" class="hash-link" aria-label="Direct link to Basic Game Components" title="Direct link to Basic Game Components" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231028/MG-GameComponent.png" alt="MonoGame Component Order of Events" class="img_ev3q"></p>
<p>Games require a lot of features and components in order to run, some are responsible for things you see, and others are not, such as Audio Managers, Networking and more.</p>
<p>This is where Game Components shine, allowing you to simply register a "Component" with the framework and then MonoGame takes care of it running in the background, all you need is a reference to it in case you need to disable or unregister it, or if the will takes you, to make changes to it.</p>
<p>As shown in the <a href="https://github.com/simondarksidej/XNAGameStudio/wiki/Marble-Maze" target="_blank" rel="noopener noreferrer" class=""><strong>MarbleMaze</strong></a> sample (currently still just XNA but easily upgradable), the <a href="https://github.com/SimonDarksideJ/XNAGameStudio/blob/803e678597b103c499c2f9c3a7128753292105bf/Samples/MarbleMaze_4_0/Assets/Code/Misc/AudioManager.cs" target="_blank" rel="noopener noreferrer" class=""><strong>AudioManager</strong></a> demonstrates a simple-to-use system, where a single component controls all audio for the game.  No other game code updates or initializes it as it runs in the background of the game as a Component.</p>
<p>Very useful for such scenarios.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="drawable-game-components">Drawable Game Components<a href="https://zenithmoon.com/blogs/monogame-order-of-things#drawable-game-components" class="hash-link" aria-label="Direct link to Drawable Game Components" title="Direct link to Drawable Game Components" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231028/MG-DrawableGameComponent.png" alt="MonoGame Drawable Component Order of Events" class="img_ev3q"></p>
<p>Drawable Game components extend the base Game Component architecture by also subscribing to the Content and Draw calls from the main Game library automatically, the biggest benefit here is that they are totally automated and draw in the order they were added to the Games "Components" list.</p>
<p>A great example of these is the <a href="https://github.com/simondarksidej/XNAGameStudio/wiki/Particles-Pipeline" target="_blank" rel="noopener noreferrer" class=""><strong>Particles2DPipeline</strong></a> sample which builds on top of the excellent 2D Particles sample and demonstrates some complex particle systems which are dynamic due to their use of Components, as shown in the <a href="https://github.com/SimonDarksideJ/XNAGameStudio/blob/803e678597b103c499c2f9c3a7128753292105bf/Samples/Particles2DPipeline_4_0/Particles2DPipelineSample/Particles2DPipelineSample/ParticleSampleGame.cs#L98" target="_blank" rel="noopener noreferrer" class="">Particles 2D Game class</a> where the effects are loaded based on a set configuration, which can be changed.</p>
<p>Ultimately, these are then activated and disabled to change how the effects are demonstrated with minimal changes to the actual game code.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="check-the-samples">Check the samples<a href="https://zenithmoon.com/blogs/monogame-order-of-things#check-the-samples" class="hash-link" aria-label="Direct link to Check the samples" title="Direct link to Check the samples" translate="no">​</a></h2>
<p>The <a href="https://github.com/SimonDarksideJ/XNAGameStudio" target="_blank" rel="noopener noreferrer" class="">XNAGameStudio Archive</a> contains all the previous XNA examples and more, most are still in their XNA format but are easily upgraded to MonoGame by copying out their Game code and assets, a few have already been <a href="https://github.com/SimonDarksideJ/MonoGameSamples" target="_blank" rel="noopener noreferrer" class="">upgraded to MonoGame already, here</a>.</p>
<blockquote>
<p>Although at the time of writing, only the <a href="https://github.com/SimonDarksideJ/GameStateManagementSample" target="_blank" rel="noopener noreferrer" class="">GSM Sample</a> has been updated to MG 3.8.1, it is a work in progress and a lot of effort to maintain.</p>
</blockquote>
<p>But in all cases, you can walk the code and see the various patterns that demonstrate the MonoGame events in action.</p>
<p>The sample <a href="https://github.com/SimonDarksideJ/MonoGameExecution" target="_blank" rel="noopener noreferrer" class="">included with this article demonstrates</a> the events described and shows how often events are fired.  It also outputs the events to a <strong>CSV</strong> file in the Game's run folder (by default <code>/bin/Debug/net6.0</code>).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/monogame-order-of-things#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>A very theological article to give you more of an understanding of what goes on under the hood of any MonoGame project and some hints if you specifically need to know when a piece of code will be executed and run.</p>
<p>Hopefully, this will aid you on your MonoGame journey.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/monogame-order-of-things.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Going cross-platform with MonoGame]]></title>
        <id>https://zenithmoon.com/blogs/going-cross-platform-with-monogame</id>
        <link href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame"/>
        <updated>2023-10-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Building for one platform is great, but why not maximise your reach and potential sales with more using the SAME code!]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/MonoGame/MonoGame" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/01-goingforglory.gif" alt="Coding is Life" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; Today, cross-platform projects are easy to setup (except UWP), with .NET7, it gets easier.  Read the article for the How-to!</p>
</blockquote>
<p>Most Game projects when built are solely for a single platform, usually the same as the development machine they are being built upon.  Only later does the idea surface to maybe ship to another console, handheld or platform which then highlights issues in the games implementation that can lead to issues.</p>
<!-- -->
<blockquote>
<p>You CAN just build and ship to one platform, there is no issue with that, but if you are possibly considering more than one, plan ahead!</p>
</blockquote>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#choosing-your-path" class="">Choosing your path</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#sharing-the-code" class="">Sharing the code</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#interfaces-are-your-friend" class="">Interfaces are your friend</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#code-first-for-platforms" class="">Code first for platforms</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#shared-content" class="">Shared content</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#handling-multiple-content-types-high-deflow-def" class="">Handling multiple content types</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#building-the-project-with-dotnet-commands" class="">Building the project with DotNet commands</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="choosing-your-path">Choosing your path<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#choosing-your-path" class="hash-link" aria-label="Direct link to Choosing your path" title="Direct link to Choosing your path" translate="no">​</a></h2>
<p>If you create a MonoGame project for a single platform, you get a single project with a single content project, life is simple and your concerns are light.</p>
<p>Once you consider adding another platform, there are a few considerations to take into account, namely:</p>
<ul>
<li class="">How to share code that is common between all platforms.</li>
<li class="">How to structure code that is common (e.g. achievements) but have different implementations on different platforms (e.g. Steam, Xbox, etc)</li>
<li class="">How to manage code specific to platforms.</li>
<li class="">How to share content that is common between all platforms.</li>
<li class="">How to structure content specific to a platform.</li>
<li class="">Do I also need to consider multiple styles per-device (high-res, low res), but granted this is also a consideration for single-platform games.</li>
<li class="">Will I have enough hot beverages and snacks to keep my mind at ease through all this stress.</li>
<li class="">Cats, if not my own, the neighbour's cat will wander in and steal all my code.</li>
</ul>
<p>Granted the last is a bit of a fringe case but very dramatic when it happens, and for reference, this never happens with dogs as they just want to cuddle your feet or sit in your lap. "Just say'in".</p>
<p>It can seem a lot to take in, but let us walk through the major points step by step and then walk through generating our project (<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#building-the-project-with-dotnet-commands" class="">Click here to skip ahead if you like</a>)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sharing-the-code">Sharing the code<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#sharing-the-code" class="hash-link" aria-label="Direct link to Sharing the code" title="Direct link to Sharing the code" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/02-sharing.gif" alt="Coding is Life" class="img_ev3q"></p>
<p>No one likes to write the same code twice, let alone, keep writing it or copying it multiple times across multiple projects, so when you are planning for multi-platform games, you need to identify any and all code which will be the same across all the projects (which is usually about 90% of your code) and then find a pattern that works for you to ensure you write it once, no matter the platform it is running on.</p>
<p>When it comes to sharing code, there are three patterns to consider, each with their pro's and con's:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/03-libraryoptions.png" alt="Options for sharing code" class="img_ev3q"></p>
<p>Each of the options are easy to implement and run with, although Linked Files can become harder to manage the larger your project becomes, essentially because when you add a new class file, you have to remember to manually add it to all projects (or write a script to do so).</p>
<blockquote>
<p>I Recommended to use a .NET Shared library for now unless you need UWP (Windows 10/11/Xbox Xaml), then use a shared library for now until the .Net8 upgrade.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="net-class-library-recommended">.NET Class Library (Recommended)<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#net-class-library-recommended" class="hash-link" aria-label="Direct link to .NET Class Library (Recommended)" title="Direct link to .NET Class Library (Recommended)" translate="no">​</a></h3>
<p>The recommended approach is to create a .NET class library and add a reference to that project to all your MonoGame projects, which is a quick and easy task.  MonoGame even provides you with a project template specifically for MonoGame shared code.</p>
<blockquote>
<p>*<strong>Note</strong> if you look at the references in the MonoGame Class Library project you will see the "DesktopGL" MonoGame reference.  You can ignore this as it is just a shim, this is ignored when the project is built and the proper MonoGame DLL from your Game project is used instead.  This is some magical wizardry performed by DotNEt during the build, much like the older MonoGame.Portable projects used to do.</p>
</blockquote>
<p>To add a class library to an existing MonoGame project (assuming you have not created a Solution file already) is as follows:</p>
<p>Assuming you have a folder containing your MonoGame Project, e.g. MyGame -&gt; MyGame.DesktopGL, while in the MyGame folder.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new sln -n MyGame</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mglib -o MyGame.SharedCode</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyGame.sln add MyGame.DesktopGL\MyGame.DesktopGL.csproj</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyGame.sln add MyGame.SharedCode\MyGame.SharedCode.csproj</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet add MyGame.DesktopGL\MyGame.DesktopGL.csproj reference MyGame.SharedCode\MyGame.SharedCode.csproj</span><br></div></code></pre></div></div>
<p>Which creates a new solution file, adds your existing MonoGame project to it, generates a new MonoGame class library and adds that to both the solution and as a reference to your existing MonoGame project.</p>
<p>Alternatively, in Visual studio, simply:</p>
<ul>
<li class="">Add a new project to your solution.</li>
<li class="">Select the MonoGame class library template.</li>
<li class="">Right-click the references node in your Game Project and select a "Project Reference" for the new Class Library.</li>
</ul>
<p>Repeat the last step for any additional MonoGame platform projects (just adding the reference) and all your projects now share the same codebase.  Any code you add to the Class Library project are instantly accessible to all platforms and will be compiled with all platforms (and if any issues show up, it will instantly tell you).</p>
<blockquote>
<p>P.S. Did you know you are NOT limited to just one class library, like content projects, you can have as many as you like if you prefer to also break up your code.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-a-linked-file">Adding a linked file<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#adding-a-linked-file" class="hash-link" aria-label="Direct link to Adding a linked file" title="Direct link to Adding a linked file" translate="no">​</a></h3>
<p>First make sure the folder containing the linked code is located relative to the MonoGame projects you are sharing it with (usually all at the same level, e.g. MG Project 1, MG Project 2, SHared Code). This helps maintain the consistency of the code and links for your project.</p>
<p>Due to the lack of support for automatically adding linked files using the DotNet CLI tool, it is recommended to use Visual Studio (any edition, including community) to create and manage the links, to do this in Visual Studio you can right-click in the Solution Explorer to add an existing file (Add existing item), then instead of just clicking the "Add" button (which will copy the file), click the drop-down on the button and select "Add as Link" as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/04-add-as-link.png" alt="Add as Link" class="img_ev3q"></p>
<p>You will need to repeat this process for every class file you add to the shared folder and make sure to add it to each project.  Usually, if you choose this path, I recommend building a batch script to do it for you.  You will also have to perform this task when removing files.</p>
<p>To link files without Visual Studio, we need a PowerShell script to edit the csproj project definition and add the required XML for the linked file, the script I use is as follows:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># usage: .\Add-LinkedFile.ps1 -SourceProjectPath MyProject.csproj -LinkedFilePath ../Shared/mysharedclass.cs -LinkedFileName Shared/mysharedclass.cs -BuildAction Compile</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># output: &lt;ItemGroup&gt;&lt;Compile Include="../Shared/mysharedclass.cs"&gt;&lt;Link&gt;Shared/mysharedclass.cs&lt;/Link&gt;&lt;/Compile&gt;&lt;/ItemGroup&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token keyword" style="color:rgb(127, 219, 202)">param</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token namespace" style="color:rgb(178, 204, 214)">[Parameter(Mandatory=$true)]</span><span class="token namespace" style="color:rgb(178, 204, 214)">[string]</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$SourceProjectPath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token namespace" style="color:rgb(178, 204, 214)">[Parameter(Mandatory=$true)]</span><span class="token namespace" style="color:rgb(178, 204, 214)">[string]</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$LinkedFilePath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token namespace" style="color:rgb(178, 204, 214)">[Parameter(Mandatory=$true)]</span><span class="token namespace" style="color:rgb(178, 204, 214)">[string]</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$LinkedFileName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token namespace" style="color:rgb(178, 204, 214)">[Parameter(Mandatory=$true)]</span><span class="token namespace" style="color:rgb(178, 204, 214)">[string]</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$BuildAction</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token plain"> = </span><span class="token namespace" style="color:rgb(178, 204, 214)">[xml]</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token function" style="color:rgb(130, 170, 255)">Get-Content</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$SourceProjectPath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFileRelativePath</span><span class="token plain"> = </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token function" style="color:rgb(130, 170, 255)">Resolve-Path</span><span class="token plain"> </span><span class="token operator" style="color:rgb(127, 219, 202)">-</span><span class="token plain">Path </span><span class="token variable" style="color:rgb(214, 222, 235)">$LinkedFilePath</span><span class="token plain"> </span><span class="token operator" style="color:rgb(127, 219, 202)">-</span><span class="token plain">Relative</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$itemGroup</span><span class="token plain"> = </span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">CreateElement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">'ItemGroup'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">Project</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">AppendChild</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token variable" style="color:rgb(214, 222, 235)">$itemGroup</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFile</span><span class="token plain"> = </span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">CreateElement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token variable" style="color:rgb(214, 222, 235)">$BuildAction</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFile</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">SetAttribute</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">'Include'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> </span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFileRelativePath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$itemGroup</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">AppendChild</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFile</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$link</span><span class="token plain"> = </span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">CreateElement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">'Link'</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$link</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">InnerText = </span><span class="token variable" style="color:rgb(214, 222, 235)">$LinkedFileName</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$linkedFile</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">AppendChild</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token variable" style="color:rgb(214, 222, 235)">$link</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token variable" style="color:rgb(214, 222, 235)">$sourceProject</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">Save</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token function" style="color:rgb(130, 170, 255)">Resolve-Path</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">"</span><span class="token string variable" style="color:rgb(214, 222, 235)">$SourceProjectPath</span><span class="token string" style="color:rgb(173, 219, 103)">"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">Path</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><br></div></code></pre></div></div>
<p>You then simply run the script "<strong>Within the folder of the project you want to update</strong>" to update the Platforms project to reference the linked file, for example:</p>
<p>From the folder "MyAwesomeGame/Platforms/MyAwesomeGame.Android" (assuming you save the above script in the <em>MyAwesomeGame</em> folder), and your shared code is in a folder called "Shared", also within the MyAwesomeGame folder:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">../../Add-LinkedFile.ps1 -SourceProjectPath MyAwesomeGame.Android.csproj -LinkedFilePath ../../Shared/mysharedclass.cs -LinkedFileName Shared/mysharedclass.cs -BuildAction Compile</span><br></div></code></pre></div></div>
<p>Which, if you then edit the "MyAwesomeGame.Android.csproj" file, you will see a new addition to the project, as follows:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(127, 219, 202)">ItemGroup</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(127, 219, 202)">Compile</span><span class="token tag" style="color:rgb(127, 219, 202)"> </span><span class="token tag attr-name" style="color:rgb(173, 219, 103);font-style:italic">Include</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(127, 219, 202)">../../Shared/mysharedclass.cs</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(127, 219, 202)">Link</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain">Shared/mysharedclass.cs</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(127, 219, 202)">Link</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(127, 219, 202)">Compile</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(127, 219, 202)">ItemGroup</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><br></div></code></pre></div></div>
<p>Alternatively, you can simply add the XML yourself, so long as you <a href="https://learn.microsoft.com/en-us/aspnet/web-forms/overview/deployment/web-deployment-in-the-enterprise/understanding-the-project-file" target="_blank" rel="noopener noreferrer" class="">conform to the XML standards for the csproj specification</a>, for example, this is also acceptable:</p>
<div class="language-xml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-xml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(127, 219, 202)">ItemGroup</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token tag" style="color:rgb(127, 219, 202)">Compile</span><span class="token tag" style="color:rgb(127, 219, 202)"> </span><span class="token tag attr-name" style="color:rgb(173, 219, 103);font-style:italic">Include</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(127, 219, 202)">../../Shared/mysharedclass.cs</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag" style="color:rgb(127, 219, 202)"> </span><span class="token tag attr-name" style="color:rgb(173, 219, 103);font-style:italic">Link</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(199, 146, 234)">=</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag attr-value" style="color:rgb(127, 219, 202)">Shared/mysharedclass.cs</span><span class="token tag attr-value punctuation" style="color:rgb(199, 146, 234)">"</span><span class="token tag" style="color:rgb(127, 219, 202)"> </span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">/&gt;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&lt;/</span><span class="token tag" style="color:rgb(127, 219, 202)">ItemGroup</span><span class="token tag punctuation" style="color:rgb(199, 146, 234)">&gt;</span><br></div></code></pre></div></div>
<blockquote>
<p>In the past when I have linked files this way, I will build up the majority of the code within a single platform and then move that folder out and link the files, saves repeatedly doing this in the beginning.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-a-shared-library">Adding a Shared Library<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#adding-a-shared-library" class="hash-link" aria-label="Direct link to Adding a Shared Library" title="Direct link to Adding a Shared Library" translate="no">​</a></h3>
<p>The shared library approach is much like the Class Library approach, except it uses the MonoGame Shared Project template instead.  However, you cannot add a Shared Library to your project as a reference through the DotNet command-line (as it is a Xamarin solution), it is only supported through Visual Studio.</p>
<p>The only advantage today of using a Shared Library instead of a class library is that it also supports UWP (windows 10/11) projects. This limitation should be removed with the upgrade to .NET 8, but at the time of writing, this support is missing from Class Libraries.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="interfaces-are-your-friend">Interfaces are your friend<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#interfaces-are-your-friend" class="hash-link" aria-label="Direct link to Interfaces are your friend" title="Direct link to Interfaces are your friend" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/03-interfaces.gif" alt="Friendly Interfaces" class="img_ev3q"></p>
<p>If you are not aware of <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface" target="_blank" rel="noopener noreferrer" class="">Interfaces in the C# language</a>, they provide an "example" of what a class "should" do for your project, and you can decide later which class that implements the interface that is used.  It is a handy way of effectively swapping out which code does what, so long as each version implements the same interface (as the same public properties and methods of the interface).</p>
<p>In short, if I define an interface as follows:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">interface</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 139)">IAchievementService</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">bool</span><span class="token plain"> IsInitialized </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">get</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">Initialize</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">UnlockAchievement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">string</span><span class="token plain"> achievementName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></div></code></pre></div></div>
<p>And then define two implementations of the interface, one for Steam and one for Xbox:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Steam</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">class</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 139)">SteamAchievementService</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token type-list class-name" style="color:rgb(255, 203, 139)">IAchievementService</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">private</span><span class="token plain"> </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">bool</span><span class="token plain"> isInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">false</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">bool</span><span class="token plain"> IsInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=&gt;</span><span class="token plain"> isInitialized</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">Initialize</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            isInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">true</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">UnlockAchievement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">string</span><span class="token plain"> achievementName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Do Steam Unlock Achievement Stuff</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">//Xbox</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">class</span><span class="token plain"> </span><span class="token class-name" style="color:rgb(255, 203, 139)">XboxAchievementService</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token type-list class-name" style="color:rgb(255, 203, 139)">IAchievementService</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">private</span><span class="token plain"> </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">bool</span><span class="token plain"> isInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">false</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">bool</span><span class="token plain"> IsInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=&gt;</span><span class="token plain"> isInitialized</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">Initialize</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            isInitialized </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token boolean" style="color:rgb(255, 88, 116)">true</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> </span><span class="token return-type class-name keyword" style="color:rgb(127, 219, 202)">void</span><span class="token plain"> </span><span class="token function" style="color:rgb(130, 170, 255)">UnlockAchievement</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">string</span><span class="token plain"> achievementName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">            </span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic">// Do Xbox Unlock Achievement Stuff</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><span class="token plain">    </span><br></div></code></pre></div></div>
<p>Finally, in your shared game project, all your code simply uses a variable for the "AchievementService", knowing it has a property for "Initialised" and two methods to Initialise the achievement service or grant an achievement:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">public</span><span class="token plain"> IAchievementService TheAchivementService</span><br></div></code></pre></div></div>
<p>Then in each Platform project, they simply declare which service to use, and if you want to "AT RUNTIME" you can even swap them out, the shared code is none the wiser and in fact, DOES NOT CARE which service is it using for achievements, it just uses what it is told.</p>
<p>Pretty neat, eh!</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="summing-up-interfaces">Summing up interfaces<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#summing-up-interfaces" class="hash-link" aria-label="Direct link to Summing up interfaces" title="Direct link to Summing up interfaces" translate="no">​</a></h3>
<p>Interfaces allow you to put MORE into your shared code library, letting you put either Platform specific implementations only in a single platform, or have multiple variations of an implementation available to swap out, e.g. A heavy attack or a light attack for instance.  There are no limitations (other than all Concrete implementations MUST implement all the definitions in the interface).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="code-first-for-platforms">Code first for platforms<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#code-first-for-platforms" class="hash-link" aria-label="Direct link to Code first for platforms" title="Direct link to Code first for platforms" translate="no">​</a></h2>
<p>Having shared code makes it far easier to define a specific platform's own implementation, as it will only be contained within the platform project (as if you were building a single platform game), this includes the references and dependencies for that platform, which do not interfere with other platforms.  It also means you do not need to have pre-compiler definitions scatted through your code to #IFDEF this or this. (which can become a real nightmare).</p>
<p>The short of the long in this case, is this:</p>
<ul>
<li class="">If the same code will run the same on all platforms, then put it in the shared library.</li>
<li class="">If the code will ONLY EVER run on a single platform, put it in the platform project.</li>
<li class="">If you are unsure, then use an Interface (see above) and decide later.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shared-content">Shared content<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#shared-content" class="hash-link" aria-label="Direct link to Shared content" title="Direct link to Shared content" translate="no">​</a></h2>
<p>It is possible to have a shared content project that is used by all projects.  This is not to say it is the SAME project, but rather, at compile time, MonoGame will build the project for just that platform as a separate thing.</p>
<p>Originally in XNA and earlier versions of MonoGame this was possible through a separate Content Project, but with the move to DotNet and .NET6, it is a little different as we need to manually create it.</p>
<blockquote>
<p>The recommendation from the MonoGame team is to use separate Content Projects for each platform, to ensure compatibility and that logos and icons specific to a platform DO NOT get mixed up.  But personally, I believe there is a halfway house, where "some" content can be shared and platform specific content can still live only in the specific projects.</p>
</blockquote>
<p>To create a "content project" in the new DotNet land, you can either:</p>
<ul>
<li class="">Just create a folder, move an existing ".mgcb" definition into it and effectively <a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#adding-a-linked-file" class="">"link the file"</a> as a "MonoGameContentReference" build type (which works, but you will not be able to use the mgcb editor with it as it is technically not supported).</li>
<li class="">Create a MonoGame Class Library, add a reference to the "MonoGame Content Builder Task" dependency to it and then add the <a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#adding-a-linked-file" class="">remote link</a> (which has the advantage that the content project CAN be edited within the content project).</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="handling-multiple-content-types-high-deflow-def">Handling multiple content types (High-def/low-def)<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#handling-multiple-content-types-high-deflow-def" class="hash-link" aria-label="Direct link to Handling multiple content types (High-def/low-def)" title="Direct link to Handling multiple content types (High-def/low-def)" translate="no">​</a></h2>
<p>Most developers do not realise that you are NOT limited to a single content project, you can in fact have as many as you like, or alternatively, maintain separate project definitions (csproj) which are identical except they use a different content project for each.</p>
<p>Why would do this?  Because if your project ships on multiple devices that support different resolutions, like Xbox and Mobile, the source content (albeit similar) will have different requirements, e.g. High quality 4K textures on Xbox and 1k or low-res textures on mobile (put a 4k texture on some mobiles and they will just die).</p>
<p>There is no hard and fast rule about which approach to take, and in fact, some developers ship BOTH types of content in a single project if it is small enough, but remember, the size of the content will affect the size of the final output, and it makes sense to keep downloads as small as possible for any platform.</p>
<p>The choice is yours:</p>
<ul>
<li class="">Ship all content to all platforms:</li>
<li class="">Build out your game and swap WHICH content project gets compiled with which platform (or build, because Android)</li>
<li class="">Have separate project definitions for each "build type" (high-def/low-def), each pointing to different content projects.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="building-the-project-with-dotnet-commands">Building the project with DotNet commands<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#building-the-project-with-dotnet-commands" class="hash-link" aria-label="Direct link to Building the project with DotNet commands" title="Direct link to Building the project with DotNet commands" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/04-gettingitdone.gif" alt="Getting it done" class="img_ev3q"></p>
<p>Following on from the above, what follows are the set of commands that will generate a cross-platform project with the following setup:</p>
<ul>
<li class="">A root folder for the Game Project with a Solution file.</li>
<li class="">A folder for a class library to share common code</li>
<li class="">A "Platforms" folder for all the individual MonoGame projects</li>
<li class="">All references "wired up"</li>
<li class="">Separate Content Projects for each platform (you can create a shared one later if you wish and add their references.)</li>
</ul>
<p>Script as follows:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new sln -n MyAwesomeGame</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">md Shared</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">md Platforms</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mglib -o Shared\MyAwesomeGame.Shared</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mgandroid -o Platforms\MyAwesomeGame.Android</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mgdesktopgl -o Platforms\MyAwesomeGame.DesktopGL</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mgios -o Platforms\MyAwesomeGame.iOS</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mgwindowsdx -o Platforms\MyAwesomeGame.WindowsDX</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyAwesomeGame.sln add Shared\MyAwesomeGame.Shared\MyAwesomeGame.Shared.csproj</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyAwesomeGame.sln add Platforms\MyAwesomeGame.Android --solution-folder Platforms</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyAwesomeGame.sln add Platforms\MyAwesomeGame.DesktopGL --solution-folder Platforms</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyAwesomeGame.sln add Platforms\MyAwesomeGame.iOS --solution-folder Platforms</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet sln MyAwesomeGame.sln add Platforms\MyAwesomeGame.WindowsDX --solution-folder Platforms</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet add Platforms\MyAwesomeGame.Android\MyAwesomeGame.Android.csproj reference Shared\MyAwesomeGame.Shared</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet add Platforms\MyAwesomeGame.DesktopGL\MyAwesomeGame.DesktopGL.csproj reference Shared\MyAwesomeGame.Shared</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet add Platforms\MyAwesomeGame.iOS\MyAwesomeGame.iOS.csproj reference Shared\MyAwesomeGame.Shared</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet add Platforms\MyAwesomeGame.WindowsDX\MyAwesomeGame.WindowsDX.csproj reference Shared\MyAwesomeGame.Shared</span><br></div></code></pre></div></div>
<p>Once complete, you should have a solution which looks like this:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/05-finalsolution.png" alt="Final Solution" class="img_ev3q"></p>
<p>The only final changes I would make would be to either:</p>
<ul>
<li class="">Delete the Game1.cs from the class library - OR</li>
<li class="">Remove the Game1.cs from each of the platforms and then update the respective launchers (e.g. program.cs) to start the shared Game1.cs on start.</li>
</ul>
<p>Depends if you also want a common Game class or not to begin your game, it is up to you and there is no "wrong" answer.</p>
<p>Additionally, you can delete the "Content" folder from the class library as it is extremely unlikely you will use it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/going-cross-platform-with-monogame#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20231011/05-manyhandsmakelitework.gif" alt="Many hands make lite work" class="img_ev3q"></p>
<p>Whether you build your project for a single platform, or consider the options of maximising your reach by adding more platforms to deliver to is a very important decision.  It does not come without a small amount of additional risk, but the rewards mean MORE users playing your game on more platforms.</p>
<p>In most cases, everything will "just work", that is the beauty of MonoGame.  On occasion you will hit an issue with a dependency or service that is particular to a specific platform, which you then need to address in isolation, ideally not affecting other platforms, it is a fine balancing act.</p>
<p>But with shared code, your life is made that much simpler as you get to update all platforms at once with central changes, taking feedback from multiple players with different needs to wholly make your game better (or drive you to insanity with their endless demands).</p>
<p>Whichever path you take, I wish you well on delivering on your dreams.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/going-cross-platform-with-monogame.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MonoGame, meet VSCode]]></title>
        <id>https://zenithmoon.com/blogs/monogame-meet-vscode</id>
        <link href="https://zenithmoon.com/blogs/monogame-meet-vscode"/>
        <updated>2023-09-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The fastest way to get started with MonoGame is to install VSCode and from there install the tools, everything in one place!.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/MonoGame/MonoGame" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/01-gettingcoding.gif" alt="Coding is Life" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; MonoGame rocks and continues to be one of the best open-source frameworks for building games including with lightweight editors like VSCode, on ANY platform.</p>
</blockquote>
<p>Things are busy and heating up in the Open-Source development space and the <a href="https://community.monogame.net/t/update-on-monogames-status/19457" target="_blank" rel="noopener noreferrer" class="">MonoGame Team announced their plans</a> to step up to the recent demand.</p>
<!-- -->
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/F6iHFomXsAA0tGn.jpg" alt="MonoGame Status update" class="img_ev3q"></p>
<p>And as was pointed out to me on the new <a href="https://github.com/users/SimonDarksideJ/projects/1" target="_blank" rel="noopener noreferrer" class="">MonoGame Content Request</a> board that I helped to setup, a fair few developers are asking how to get started.  The <a href="https://www.youtube.com/c/TheDarksideofMonoGame" target="_blank" rel="noopener noreferrer" class="">Videos I did a short while back</a> are still good, but do need modernizing for the latest release and beyond.</p>
<blockquote>
<p><a href="https://github.com/SimonDarksideJ/MonoGame-Tutorials/issues/22" target="_blank" rel="noopener noreferrer" class="">A Guide for Setting up VSCode for MonoGame on Windows/Linux/Mac</a></p>
</blockquote>
<p>Kicking things off, right at the beginning, here is a guide to getting started with VSCode for MonoGame. And thanks to VSCode, FINALLY, the instructions are the same for ALL PLATFORMS.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/02-holeinone.gif" alt="Three platforms in one post, hole in one!" class="img_ev3q"></p>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#starting-fresh" class="">Starting Fresh</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#installing-the-extensions" class="">Installing the Extensions</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#the-net-sdk" class="">The .NET SDK</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#installing-monogame-and-creating-your-new-game" class="">Installing MonoGame and creating your new game!</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#monogame-start" class="">MonoGame, Start!</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#build-and-test" class="">Build and Test</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#the-content-pipeline" class="">The Content Pipeline</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-meet-vscode#debugging" class="">Debugging</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="starting-fresh">Starting fresh<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#starting-fresh" class="hash-link" aria-label="Direct link to Starting fresh" title="Direct link to Starting fresh" translate="no">​</a></h2>
<p>To keep things clean, all the instructions here are run from a clean machine, however, if you already have some parts installed, they will be automatically updated, thankfully.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/00-install_code.png" alt="VSCode download" class="img_ev3q"></p>
<p>Simply visit <a href="https://code.visualstudio.com/" target="_blank" rel="noopener noreferrer" class="">code.visualstudio.com</a> and get downloading Visual Studio Code, the drop down button on the left should auto-detect your operating system, or you can click the down-arrow to show the list of platforms to choose from.</p>
<p>One Editor for multiple platforms, including the Web (as in VSCode for the Web on GitHub), however, as far as I know, the web version does not support .NET building at this time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installing-the-extensions">Installing the Extensions<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#installing-the-extensions" class="hash-link" aria-label="Direct link to Installing the Extensions" title="Direct link to Installing the Extensions" translate="no">​</a></h2>
<p>Now, MonoGame as of 3.8.1.303, is a .NET Runtime framework, this vastly simplifies its installation and use because all aspects are now unified and everything you need to build your games is all built into the .NET SDK.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/01-code_installed.png" alt="VSCode installed" class="img_ev3q"></p>
<p>If you open up VSCode after its installation, you should be presented with the above screen, welcoming you to a new world of light, development and if the mood takes you, fun.</p>
<p>But as it stands right now, you only have a fancy text editor (granted a very powerful text editor), where VSCode really comes into its own is with "Extensions", for which there are extensions for just about any programming language going (within reason, there is no assembly editor, yet).  Extensions add things like:</p>
<ul>
<li class="">Compilers for building.</li>
<li class="">Intellisense for code suggestions.</li>
<li class="">Viewers for specific file types.</li>
<li class="">Language understanding and formatting.</li>
<li class="">And so much more.</li>
</ul>
<p>For MonoGame we need two things, the C# Dev Kit (which includes the C# language and some other tools) and the .NET SDK.</p>
<p>Starting off, click the {<img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/07-extensions_button.png" alt="Extensions Button" class="img_ev3q">} in the left-hand toolbar and then search for "<strong>dev kit</strong>" which should then result in a whole list of extensions and right at the top (hopefully) you should find the "<strong>C# Dev Kit</strong>" published by Microsoft, as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/02-csharp_dev_kit.png" alt="VSCode installed" class="img_ev3q"></p>
<p>Click on the blue "Install" icon (as indicated in the image) and off it goes.</p>
<blockquote>
<p>Some extensions require you to "reload" VSCode after installing/uninstalling, if it does, the Install button will finish with a "Reload" button, click it and VSCode will restart right back where you were as if nothing changed.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-net-sdk">The .NET SDK<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#the-net-sdk" class="hash-link" aria-label="Direct link to The .NET SDK" title="Direct link to The .NET SDK" translate="no">​</a></h2>
<p>The .NET SDK (if you are not familiar with it) is simply the latest generation of the .NET Framework SDK, as ever it is backwards compatible and for MonoGame we need at least the .NET 6 SDK (until it upgrades to .NET8).</p>
<p>Thankfully, the .NET SDK is backwards compatible with previous versions, so installing the current version -- at the time of writing --, the .NET 7 SDK, will still enable you to develop with MonoGame.</p>
<p>With the "C# Dev Kit" installed, we get a bunch of new commands to use in Visual Studio Code, which can be accessed by pressing:</p>





















<table><thead><tr><th>Platform</th><th>Key Binding</th></tr></thead><tbody><tr><td>Windows</td><td>F1</td></tr><tr><td>Windows and Linux</td><td>Control + Shift + P</td></tr><tr><td>Mac</td><td>Command + Shift + P</td></tr></tbody></table>
<p>This will open up a bar at the top of the screen with a bunch of commands.  It also includes a handy search feature (because there is a command for almost anything), so if you type "<strong>.NET</strong>", you should see the following:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/08-dotnet_commands.png" alt="DotNet command list" class="img_ev3q"></p>
<p>If you do not already have a .NET SDK installed, you will be greeted with a nice prompt / warning Window, as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/03-dotnet_sdk.png" alt="DotNet SDK Warning" class="img_ev3q"></p>
<p>Which is simply informing you that you have something else to install to begin, this is not a VSCode extension and like .NET Framework installers previously, is not something you install in VSCode, but into your system instead.  So, click on the "<strong>Get the SDK</strong>" link (which will open a browser) and you can then download and run the latest .NET SDK installer from there.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="-you-should-also-close-vscode-at-this-point-to-ensure-everything-is-clean-and-ready-for-your-return">&gt; You should also close VSCode at this point, to ensure everything is clean and ready for your return!<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#-you-should-also-close-vscode-at-this-point-to-ensure-everything-is-clean-and-ready-for-your-return" class="hash-link" aria-label="Direct link to > You should also close VSCode at this point, to ensure everything is clean and ready for your return!" title="Direct link to > You should also close VSCode at this point, to ensure everything is clean and ready for your return!" translate="no">​</a></h3>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/04-dotnet_installer.png" alt="DotNet Installer" class="img_ev3q"></p>
<p>Click Install and follow the instructions (if any) to complete the setup to proceed.</p>
<p>Ok, so you have all the prerequisites installed, what now?  Well, we start using MonoGame!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installing-monogame-and-creating-your-new-game">Installing MonoGame and creating your new game!<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#installing-monogame-and-creating-your-new-game" class="hash-link" aria-label="Direct link to Installing MonoGame and creating your new game!" title="Direct link to Installing MonoGame and creating your new game!" translate="no">​</a></h2>
<p>Let us begin by starting up VSCode again, we get the familiar screen, but not much has changed, we are simply prepared and ready to actually begin using MonoGame, we have the tools so let us begin!</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/05-Terminal.png" alt="Launching the Terminal window" class="img_ev3q"></p>
<p>Welcome to the Terminal screen, you will be spending quite a bit of time here.  I have often heard that some developers miss a GUI at this point and it is a fair quip, however, The is great because it is the same process on ANY platform, and you may find it more comforting, with the same tools, the same commands and likely the same coffee!</p>
<p>Start up the Terminal Window using the above menu and you will be presented with a Terminal/command-line window ready to process your commands, as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/09-terminal_prompt.png" alt="Shall we play a game?" class="img_ev3q"></p>
<p>From here, we are simply following the steps laid out in <a class="" href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-toolchain">my previous article</a> and in the <a href="https://docs.monogame.net/articles/getting_started/0_getting_started.html" target="_blank" rel="noopener noreferrer" class="">MonoGame "Getting Started" guides</a>, first type the following to install the MonoGame DotNet project templates:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    dotnet new --install MonoGame.Templates.CSharp</span><br></div></code></pre></div></div>
<p>And you should see something like the results below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/10-monogametemplates.png" alt="MonoGame Assemble" class="img_ev3q"></p>
<blockquote>
<p>I did note in testing that the .NET 7 SDK no longer needs the <strong><code>--</code></strong> (double dashes) before the "Install" argument, but in a good and true backwards-compatible fashion, it still works.  But I leave it in all instructions as it is still needed if you are using the .NET 6 SDK.</p>
</blockquote>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-highlighted-parts-are-the-template-ids-which-you-need-for-creating-new-projects-so-keep-an-eye-on-them-for-reference">The highlighted parts are the template IDs which you need for creating new projects, so keep an eye on them for reference.<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#the-highlighted-parts-are-the-template-ids-which-you-need-for-creating-new-projects-so-keep-an-eye-on-them-for-reference" class="hash-link" aria-label="Direct link to The highlighted parts are the template IDs which you need for creating new projects, so keep an eye on them for reference." title="Direct link to The highlighted parts are the template IDs which you need for creating new projects, so keep an eye on them for reference." translate="no">​</a></h4>
<p>With MonoGame installed, we can get on with starting our new project!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="monogame-start">MonoGame, Start!<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#monogame-start" class="hash-link" aria-label="Direct link to MonoGame, Start!" title="Direct link to MonoGame, Start!" translate="no">​</a></h2>
<p>From now on, when you need a new MonoGame project, this is where your journey begins, your machine is setup (unless you paved it recently or are borrowing someone else's) and everything is ready.  The steps for creating any new DotNet project are always the same:</p>
<ul>
<li class="">Create a new Folder.</li>
<li class="">Run the DotNet new command.</li>
<li class="">Done / Open the folder in VSCode.</li>
</ul>
<p>I personally these days find it easier to stay within VSCode and do it all from there, but the choice is yours.</p>
<p>To perform this in VSCode, using the "Terminal" window you used to install MonoGame, check the directory you are in (as indicated by the left hand side of the cursor which always shows you where you are, handy eh?), and then navigate to where you want your game created and make a new folder, e.g. :</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">cd C:\Development</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">mkdir MyGame</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">cd MyGame</span><br></div></code></pre></div></div>
<blockquote>
<p>or you could swap out to your explorer/finder and do it, the result is the same.</p>
</blockquote>
<p>Now, using the Terminal window IN the folder (if you used a GUI, you still need to navigate there in the Terminal, hence why I say it is easier to just use the Terminal), you then simply use the following command:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet new mgdesktopgl -o MyGame</span><br></div></code></pre></div></div>
<p>Which is comprised of:</p>

























<table><thead><tr><th>Tool</th><th>Description</th></tr></thead><tbody><tr><td>dotnet</td><td>the DotNet command tool</td></tr><tr><td>new</td><td>I want to make a new project, please sir</td></tr><tr><td>mgdesktopgl</td><td>The MonoGame project template to use, from the list of templates installed, see the previous image above, shown in the red box</td></tr><tr><td>-o MyGame</td><td>Create a project called MyGame in a folder called MyGame</td></tr></tbody></table>
<p>There, you now have a new MonoGame Project built and ready to use, including the MGCB content tool, it is no longer a separate install, it is built into the project itself. (I can imagine how many hours that will save me managing it separately).</p>
<p>All that is left is to "Open the Folder" where you created the project (the folder WITH the <strong>.csproj</strong> file in it, not its parent folder) in VSCode and off you go, editing in VSCode on the fly!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-and-test">Build and Test<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#build-and-test" class="hash-link" aria-label="Direct link to Build and Test" title="Direct link to Build and Test" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/06-result.png" alt="Every MonoGame Developers dream" class="img_ev3q"></p>
<p>Like Visual Studio, all the building of a MonoGame project, no matter which platform it is, is performed by the MS Build tools, but again, thanks to the latest .NET frameworks, this is all managed by a simple command:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">dotnet build</span><br></div></code></pre></div></div>
<p>This compiles and checks your code can build, it also restores any tools, downloads any dependencies for the project, since it was last run; all in one.  What you get, assuming it is successful, will be a folder structure like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    MyGame\bin\Debug\net6.0</span><br></div></code></pre></div></div>
<p>Comprising of:</p>

























<table><thead><tr><th>Path Segment</th><th>Description</th></tr></thead><tbody><tr><td>MyGame</td><td>your game code folder</td></tr><tr><td>bin</td><td>the Binary output folder</td></tr><tr><td>debug</td><td>the build mode your project was compiled under (usually debug or release depending on how you built it)</td></tr><tr><td>net6.0</td><td>the framework the project was compiled with (MonoGame is currently using .NET 6)</td></tr></tbody></table>
<p>In the final folder, you will find an executable of the same name as your project (<code>MyGame.exe</code> in my case on Windows, it will be different for other platforms) which you can run.</p>
<p>To create a FINAL build (because you do not want to ship debug code to customers, do you?) you simply follow the steps in the <a href="https://docs.monogame.net/articles/packaging_games.html" target="_blank" rel="noopener noreferrer" class="">MonoGame documentation</a> and produce a "published" build, as follows:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    dotnet publish -c Release -r win-x64 /p:PublishReadyToRun=false /p:TieredCompilation=false --self-contained</span><br></div></code></pre></div></div>
<p>Which basically commands MSBuild to generate a Release build, ready to publish and makes the output the best it can be.</p>
<blockquote>
<p>If you want to read more about the "dotnet publish" command or other dotnet commands, check the <a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish" target="_blank" rel="noopener noreferrer" class="">official Microsoft documentation</a>.</p>
</blockquote>
<p>Done, dusted, all there is to say, is it not?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-content-pipeline">The Content Pipeline<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#the-content-pipeline" class="hash-link" aria-label="Direct link to The Content Pipeline" title="Direct link to The Content Pipeline" translate="no">​</a></h2>
<p>Now, if you have read any of the documentation regarding MonoGame, you will know there is a tool for Managing / Building content separate from your project to help with compiling your project (as well as a whole bunch of other platform optimization and wonder).  You do not have to use it if you do not want to, but I always recommend using it because I always liked it.</p>
<p>Previously, you had to install the tool separately, find a way to launch it beyond the command-line which "mostly" worked for Visual Studio, but thanks to VSCode Extensions, there are already two extensions for adding a "right-click" launch ability for the MGCB tool directly in VSCode (sadly only one works, but here is hoping the dev fixes it).</p>
<p>To install the extension, simply go to the Extensions view (as before) and search for MonoGame and you should see a list like the one shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/11-mgcb_extension.png" alt="MonoGame VSCode Extensions" class="img_ev3q"></p>
<p>From my testing, only the second item (the one with the STARS) actually works, so feel free to install it if you wish.  Once complete you will be able to open the MGCB tool by right clicking the "<strong>.mgcb</strong>" file located in the "Content" folder.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="-note-you-have-to-have-built-your-project-at-least-once-for-the-mgcb-tool-to-be-installed-for-your-project--the-tool-is-no-longer-global-which-allows-you-to-use-the-version-specific-to-your-project-and-avoids-conflict-issues-between-versions">&gt; *Note, you have to have built your project AT LEAST ONCE for the mgcb tool to be installed for your project.  The tool is no longer global, which allows you to use the version SPECIFIC to your project and avoids conflict issues between versions.<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#-note-you-have-to-have-built-your-project-at-least-once-for-the-mgcb-tool-to-be-installed-for-your-project--the-tool-is-no-longer-global-which-allows-you-to-use-the-version-specific-to-your-project-and-avoids-conflict-issues-between-versions" class="hash-link" aria-label="Direct link to > *Note, you have to have built your project AT LEAST ONCE for the mgcb tool to be installed for your project.  The tool is no longer global, which allows you to use the version SPECIFIC to your project and avoids conflict issues between versions." title="Direct link to > *Note, you have to have built your project AT LEAST ONCE for the mgcb tool to be installed for your project.  The tool is no longer global, which allows you to use the version SPECIFIC to your project and avoids conflict issues between versions." translate="no">​</a></h4>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/12-right_click_mgcb.png" alt="MGCB right-click menu" class="img_ev3q"></p>
<p>Alternatively, you can always run the following in the Terminal window for your project:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    mgcb-editor Content/Content.mgcb</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="debugging">Debugging<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#debugging" class="hash-link" aria-label="Direct link to Debugging" title="Direct link to Debugging" translate="no">​</a></h2>
<p>VSCode also has the ability to debug .NET projects in the same way that Visual Studio can, just in a slightly different way, however, it is not on by default, you have to enable it.</p>
<p>To setup debugging, open the Command Palette using</p>





















<table><thead><tr><th>Platform</th><th>Key Binding</th></tr></thead><tbody><tr><td>Windows</td><td>F1</td></tr><tr><td>Windows and Linux</td><td>Control + Shift + P</td></tr><tr><td>Mac</td><td>Command + Shift + P</td></tr></tbody></table>
<p>Once open, type the search criteria "<strong>Generate</strong>" and locate the <strong>.NET Generate Assets for Build and Debug</strong> command and click it (or hit return/enter) as shown in the image below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/13-generatedebugassets.png" alt="Generate debug assets command" class="img_ev3q"></p>
<p>This will generate the necessary "<a href="https://code.visualstudio.com/Docs/editor/debugging" target="_blank" rel="noopener noreferrer" class="">Launch Configurations</a>" in VSCode and store them in a new ".vscode" folder in your project (do not ignore this folder if you are customizing debug/launch options in VSCode)</p>
<p>With that setup, you can begin to run your project in Debug mode as follows:</p>
<ul>
<li class="">Navigate to the Debug view using the Play Arrow button on the left hand side (as indicated in the below image).</li>
<li class="">Change the "Run and Debug" mode to "<code>C# &lt;your game name&gt;</code>" using the drop down button. (next to the cog icon).</li>
<li class="">Click on the green Play icon (as shown below) next to the "Run and Debug" selection.</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230923/14-runanddebug.png" alt="Run and Debug" class="img_ev3q"></p>
<p>Now your game will run as normal with VSCode attached and you can Debug/BreakPoint/Catch Exceptions like the best of them.</p>
<blockquote>
<p><a href="https://code.visualstudio.com/Docs/editor/debugging" target="_blank" rel="noopener noreferrer" class="">Check the official VSCode documentation for more information for debugging with VSCode</a>.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/monogame-meet-vscode#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>We have come down a long road, one that is far shorter to actually do than it was to write or even read this post.  Hopefully it will help the newcomers and prepare everyone for what is to come!</p>
<p>Now I'm going back to the drawing board to work on more stuff, laters!!</p>
<blockquote>
<p>P.S.</p>
<p>If you have an idea or a question that you believe would require a tutorial, visit the <a href="https://github.com/users/SimonDarksideJ/projects/1" target="_blank" rel="noopener noreferrer" class="">MonoGame tutorial series suggestions</a> project board, and if you do not see your idea listed, then <a href="https://github.com/SimonDarksideJ/MonoGame-Tutorials/issues/new/choose" target="_blank" rel="noopener noreferrer" class="">Create your own request here</a>.
All the creator community is watching and looking for your requests.</p>
</blockquote>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/monogame-meet-vscode.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[MonoGame roundup for 2023]]></title>
        <id>https://zenithmoon.com/blogs/monogame-roundup-2023</id>
        <link href="https://zenithmoon.com/blogs/monogame-roundup-2023"/>
        <updated>2023-09-18T14:00:00.000Z</updated>
        <summary type="html"><![CDATA[Setting the record straight on MonoGame, past, present and more importantly, its future.]]></summary>
        <content type="html"><![CDATA[<p class="zm-blog-source"><a href="https://github.com/MonoGame/MonoGame" target="_blank" rel="noopener noreferrer" class="">Source code on GitHub</a></p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/01-gamedevsleep.gif" alt="The GameDev cycle" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; MonoGame rocks and continues to be one of the best open-source frameworks for building games.</p>
</blockquote>
<p>Making games is hard enough (or the most fun you will ever have) without the worry of the Framework or Engine you use either being cancelled or changing the terms of its use in the future, which can provide uncertainty for the years of effort you put into making your dream a reality that everyone can play.</p>
<!-- -->
<p>Whether it is an open-source project or a paid development solution, it is hard to choose the right option that will ensure your investments are secure.</p>
<p>Thankfully there are quite a few engines and frameworks available today that do fit that bill:</p>
<ul>
<li class="">They have been around for many years.</li>
<li class="">They are open source or give you access to their code. (Ensuring if they did disappear, you still have what it takes to build).</li>
<li class="">Have a growing community ready to help at a moment's notice.</li>
</ul>
<p>The Framework of discussion today is MonoGame, which despite all talk to the contrary is very much alive and well.</p>
<ul>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-roundup-2023#monogame" class="">What is MonoGame?</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-framework" class="">What is the MonoGame Framework?</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-community" class="">Where is the MonoGame Community</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-toolchain" class="">What is the MonoGame toolset and getting started</a></li>
<li class=""><a href="https://zenithmoon.com/blogs/monogame-roundup-2023#extending-monogame" class="">MonoGame Extensions</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="monogame">MonoGame<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#monogame" class="hash-link" aria-label="Direct link to MonoGame" title="Direct link to MonoGame" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/HorizontalLogo_128px.png" alt="MonoGame" class="img_ev3q"></p>
<p>MonoGame is a game development framework born from an implementation pioneered by Microsoft around 2004 called XNA (no acronym), it brought the dream of building games for Windows and Xbox (and later phones) using C#, it was a breath of fresh air and made programming games a breeze.  Sadly, XNA was discontinued by Microsoft but was reborn as MonoGame.</p>
<p>XNA had made such an impact, that the community was just not willing to let it go, and today it flourishes.</p>
<p>Now, MonoGame is a Framework (not an engine) and its power lies in the original XNA implementation that abstracts (hides) the underlying complexity for making code, graphics, sound and the myriad of other technologies required to run a game on a multitude of platforms.  Which means that you code your Game once and then you can run it on any of the platforms that MonoGame has an implementation on (much like how Unity / Unreal and GoDot work today, but arguably XNA did it first for C#).</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/MonoGameFramework.png" alt="MonoGame Framework" class="img_ev3q"></p>
<p>In basic terms, you write your game using MonoGame's implementation and it then generates a project to deploy on your target platform.</p>
<blockquote>
<p>Contrary to some writers beliefs, Like the swan on the lake, there may not seem much movement on the water, but underneath, there is furious paddling going on to keep it moving.</p>
<p>P.S. I tried to find an appropriate GIF to demonstrate this and failed, for that I am sorry.  The best I could find was a worried cat learning to swim :S.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-monogame-framework">The MonoGame Framework<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-framework" class="hash-link" aria-label="Direct link to The MonoGame Framework" title="Direct link to The MonoGame Framework" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/iamalive.gif" alt="MonoGame lives!" class="img_ev3q"></p>
<p>One thing that keeps coming up from time to time, and sometimes confused with "MonoGame is Dead", is that the XNA implementation used in MonoGame (the language and structure) has not changed for years, which is very true (much like a car has 4 wheels, doors and an engine), the "front end" of MonoGame, for the most part, has remained the same since XNA 4.0 (the last iteration of XNA).  This is not to say nothing has changed, but rather it has remained stable and secure, what you wrote 10 years ago, will still run today (with the obvious caveats for minor critical changes).</p>
<p>Where MonoGame evolves is all the hard work and underpinnings to MAKE your game run on all the other platforms, handling the complexities for how Audio works, how graphics are drawn to the screen, and the vast technologies involved for different devices on a multitude of platforms.  <strong>This is no mean feat</strong> as vendors are constantly updating, changing and maintaining their platforms and "just expect" developers to keep up or be left out in the cold.</p>
<p>So as MonoGame evolves <strong>YOU DO NOT NEED TO CHANGE YOUR GAME</strong>, what you have written and the MonoGame features you use will continue to work, it will just work differently on the target platform when it is eventually compiled (built).</p>
<blockquote>
<p>In fact, MANY of the original XNA Game Studio 4.0 samples and code <strong>STILL WORK</strong> today, that is how strong MonoGame is!  Check it out for yourself from the <a href="https://github.com/SimonDarksideJ/XNAGameStudio" target="_blank" rel="noopener noreferrer" class="">XNA Game Studio Archive</a></p>
<p>Just be aware, that XNA 1, 2 and 3 samples WILL need updating to XNA4/MonoGame to actually work (it is not magic after all).</p>
</blockquote>
<p>This is not to say you never need any vendor platform code at all in your project (MonoGame cannot build for EVERY feature), for instance, if you want to use Apple's notification system and Google's notification system in your project, you will still need to handle that, but thankfully C# makes that easier with platform dependent code (again, much like how other engines/frameworks do).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-monogame-community">The MonoGame Community<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-community" class="hash-link" aria-label="Direct link to The MonoGame Community" title="Direct link to The MonoGame Community" translate="no">​</a></h2>
<p>The MonoGame community is <strong>VAST</strong>, and as previously stated, most XNA/MonoGame books that have been written since <strong>2010</strong> will still continue to be valid and worthwhile (now find me an engine/framework that can say that).  About the only thing that has changed (due to the underlying features mentioned above) has been how you install MonoGame and how you build with it, but more on that later.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/MonoGameGetStarted.png" alt="MonoGame Get Started" class="img_ev3q"></p>
<p>Amongst MonoGames community weaponry it includes:</p>
<ul>
<li class=""><a href="https://community.monogame.net/" target="_blank" rel="noopener noreferrer" class="">A very active community site for Questions, Answers and showcases</a></li>
<li class=""><a href="https://docs.monogame.net/articles/tutorials.html" target="_blank" rel="noopener noreferrer" class="">Loads of official and supported community sites</a></li>
<li class=""><a href="https://community.monogame.net/t/new-here-read-this-now-with-menu/19063" target="_blank" rel="noopener noreferrer" class="">Lots of articles full of useful tips from Community members</a></li>
<li class=""><a href="https://discord.com/invite/monogame" target="_blank" rel="noopener noreferrer" class="">An active Discord site with tons of threads to choose from</a></li>
<li class="">(My favourite) <a href="https://github.com/SimonDarksideJ/XNAGameStudio" target="_blank" rel="noopener noreferrer" class="">The entire catalogue of XNA samples and MonoGame content all preserved on GitHub</a></li>
<li class=""><a href="https://github.com/aloisdeniel/awesome-monogame" target="_blank" rel="noopener noreferrer" class="">Awesome-MonoGame</a> - A <strong>MASSIVE</strong> list of MonoGame resources which is continually updated (5 Hours ago at the time of writing, in 2023 no less)</li>
</ul>
<p>And so much more, since any XNA content is also valid (from the coding side) and about the only real difference from the XNA days is how the content pipeline is used (which is totally optional).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-monogame-toolchain">The MonoGame Toolchain<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#the-monogame-toolchain" class="hash-link" aria-label="Direct link to The MonoGame Toolchain" title="Direct link to The MonoGame Toolchain" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/MonoGameVisualStudio.png" alt="MonoGame tooling" class="img_ev3q"></p>
<p>The MonoGame toolchain has gone through many iterations since its first release, always moving with the times and updating to modern technology, from Separate plug-ins to Visual Studio extensions and in the past year, updated to use the .NET Runtime and toolset.  with each iteration, the API remains the same, so existing projects are not broken, but under the hood, things keep advancing.</p>
<p>And MonoGame does not stop there, still planning ahead and looking further into the future as technology evolves, talk is already well underway for a .NET 8 upgrade which brings even more cross-platform capabilities and (if reports are true) faster compilation and execution (your game goes faster).  There is even talk that with the next .NET upgrade, tools such as <a href="http://brute.rocks/" target="_blank" rel="noopener noreferrer" class="">BRUTE</a> (which provides C++ compilation to some AOT native platforms, such as Switch) could even be retired. (depending on the capabilities published by Microsoft)</p>
<p>For today, MonoGame 3.8.1 (and beyond) are all based on the .Net Runtime and getting started is as simple as:</p>
<ol>
<li class="">Install the latest <a href="https://dotnet.microsoft.com/en-us/download/dotnet/6.0" target="_blank" rel="noopener noreferrer" class="">.NET 6 SDK</a> (comes with Visual Studio if you have it).</li>
<li class="">Install the MonoGame templates - <code>dotnet new --install MonoGame.Templates.CSharp</code> from the command-line.</li>
<li class="">Create a folder for your Game.</li>
<li class="">From the folder, create a new MonoGame project for your target platform, for Desktop OpenGL - <code>dotnet new mgdesktopgl -o MyGame</code></li>
<li class="">You now have a new MonoGame Project, go build your game.</li>
<li class="">When you are ready, simply run <code>dotnet build</code> to create a build for your game and then run it.</li>
</ol>
<blockquote>
<p>For more details on getting started, check out the <a href="https://docs.monogame.net/articles/getting_started/0_getting_started.html" target="_blank" rel="noopener noreferrer" class="">documentation</a></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extending-monogame">Extending MonoGame<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#extending-monogame" class="hash-link" aria-label="Direct link to Extending MonoGame" title="Direct link to Extending MonoGame" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/monogameextensions.png" alt="Extending MonoGame" class="img_ev3q"></p>
<p>One of the biggest confusions and usually the reason commentor's state that MonoGame does not change is because it <strong>does not change</strong> from the <strong>outside</strong>.  The MonoGame team persevere to ensure the API for MonoGame retains its XNA origins.  It does not change for a reason, and thanks to that, games written yesterday will continue to work today (and beyond).</p>
<p>MonoGame does however encourage and support community members building <strong>ON TOP OF</strong> MonoGame to further extend it and then helps to then promote those extensions for other MonoGame users. These can range from simple addons, to full-extending frameworks, even to engines built on MonoGame.  These Engines make full use of MonoGames platform capabilities to ship a game and focus on the parts they need to deliver an engine.</p>
<p>Some of the biggest out there include:</p>
<ul>
<li class=""><a href="https://github.com/craftworkgames/MonoGame.Extended" target="_blank" rel="noopener noreferrer" class="">MonoGame.Extended</a> - If MonoGame doesn't do it, then MonoGame.Extended extends it (pun intended).</li>
<li class=""><a href="https://github.com/FNA-XNA/FNA" target="_blank" rel="noopener noreferrer" class="">FNA</a> - Not technically a MonoGame extension, more of a MonoGame competitor, but highly worth a mention, even admired by the MG team.</li>
<li class=""><a href="https://flatredball.com/" target="_blank" rel="noopener noreferrer" class="">FlatredBall</a> - A full-featured game development platform built on MonoGame.</li>
<li class=""><a href="https://github.com/marshallward/TiledSharp" target="_blank" rel="noopener noreferrer" class="">TiledSharp</a> - A fan of using the <a href="https://www.mapeditor.org/" target="_blank" rel="noopener noreferrer" class="">Tiled</a> editor for your 2D maps, then easily take those maps into your MonoGame project. Although I do see this project is no longer supported, at my last run it still worked very well.</li>
<li class="">Many Many more (your name here).</li>
</ul>
<p>It is funny that most engines feel the need to build up an asset store for content, but with MonoGame, GitHub is its asset store for all the things you need to extend your project.  As for content, that is accessible from almost anywhere and most of it is either natively supported by MonoGame, or "there is an Extension for that".</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="content-management">Content Management<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#content-management" class="hash-link" aria-label="Direct link to Content Management" title="Direct link to Content Management" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/monogamecontentmanagement.png" alt="MonoGame Content Management" class="img_ev3q"></p>
<p>One of the interesting capabilities that came from XNA originally, was the idea that you could have a "Content" (asset library) project, build your assets separately to your code and live happily.  In fact, many competitors to XNA scoffed at this and said it shouldn't have bothered, yet today a lot of developers complain about how long builds take in engines because of content and look for ways to separate it.</p>
<p>MonoGame took the Content Project base and built its own MonoGame Build Tool which focuses on content and adds extensibility (much like XNA) so you can write custom importers and processors to handle different types of content.  You could even write your own custom level design configuration and have the entire pipeline process and generate your levels for you.  I am a big content pipeline fan and have written and shared many extensions for the Build Pipeline in the past.</p>
<p>However, one of the biggest myths (or debates depending on where your sticks land), is that this was the ONLY way to have content in MonoGame, which is simply <strong>NOT TRUE</strong>.  If you want, you can ignore the whole MonoGame Content Builder and just load content/assets and such manually in code.</p>
<blockquote>
<p>MonoGame will never seek to limit you in any way.</p>
</blockquote>
<p>So whether you are a content pipeline user:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> myTexture </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> Content</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token generic-method function" style="color:rgb(130, 170, 255)">Load</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&lt;</span><span class="token generic-method generic class-name" style="color:rgb(255, 203, 139)">Texture2D</span><span class="token generic-method generic class-name punctuation" style="color:rgb(199, 146, 234)">&gt;</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">"ball"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><br></div></code></pre></div></div>
<p>Or a non-content pipeline user:</p>
<div class="language-csharp codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-csharp codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token keyword" style="color:rgb(127, 219, 202)">using</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> fileStream </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(127, 219, 202)">new</span><span class="token plain"> </span><span class="token constructor-invocation class-name" style="color:rgb(255, 203, 139)">FileStream</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token string" style="color:rgb(173, 219, 103)">"test.png"</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> FileMode</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token plain">Open</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token class-name keyword" style="color:rgb(127, 219, 202)">var</span><span class="token plain"> myTexture </span><span class="token operator" style="color:rgb(127, 219, 202)">=</span><span class="token plain"> Texture2D</span><span class="token punctuation" style="color:rgb(199, 146, 234)">.</span><span class="token function" style="color:rgb(130, 170, 255)">FromStream</span><span class="token punctuation" style="color:rgb(199, 146, 234)">(</span><span class="token plain">GraphicsDevice</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> fileStream</span><span class="token punctuation" style="color:rgb(199, 146, 234)">)</span><span class="token punctuation" style="color:rgb(199, 146, 234)">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">}</span><br></div></code></pre></div></div>
<p>The path is yours and MonoGame will never dictate or tell you how to build your game.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://zenithmoon.com/blogs/monogame-roundup-2023#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>This post has wrangled on for a while trying to set a few truths alongside the doubters or nay-sayers who call something dead because they don't see change happening.</p>
<blockquote>
<p>Full disclosure, I have wondered the same because it sometimes takes a long time between releases.  But I work with the team, who are equally frustrated at times and things are ALWAYS in motion.</p>
</blockquote>
<p>Given no one gets paid to build MonoGame and all the supporters work in their spare time, rather generously I might add, it is still amazing how much love MonoGame gets.  But to those who still and continue to MonoGame, I salute you!</p>
<p>In times of upheaval, you might want to give it a whirl, load up a tutorial and try building something and it is indeed surprising how easy it is.  Even without an editor/GUI to lean on, it is amazing how visceral and close to the metal MonoGame feels. Every pixel that moves, every change that happens is directly under your control and it is surprisingly freeing.</p>
<p>Laters.</p>
<p>(Comments welcome!)</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230918/salute.png" alt="Salute" class="img_ev3q"></p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/monogame-roundup-2023.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="game development" term="game development"/>
        <category label="xna" term="xna"/>
        <category label="monogame" term="monogame"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[This is not the page you were looking for!]]></title>
        <id>https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for</id>
        <link href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for"/>
        <updated>2023-09-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Things may not be what they seem when building your Jekyll based blog site, when pages collide it does not end well.]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230908/01-lookingforpages.gif" alt="Looking for pages" class="img_ev3q"></p>
<blockquote>
<p>TL;DR -&gt; Jekyll builds can have page collisions in different places if the pages happen to have the same title.  <a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#jekyll-page-collisions" class="">Skip ahead to fix!</a>.</p>
</blockquote>
<p>WOW, it has been a while since I last posted, which is genuinely very bad for me, not to say I have been lazy or up to no good, quite the opposite, I have a myriad of things on my mind:</p>
<!-- -->
<ul>
<li class="">This year I have been writing a new book <a href="https://www.amazon.co.uk/Accelerating-Unity-Through-Automation-Offloading/dp/1484295072/ref=sr_1_5?crid=1V55PE5STOPNN&amp;_encoding=UTF8&amp;tag=zenistud-21&amp;linkCode=ur2&amp;linkId=3d1d5a139a27aedad1fbc681e9a0e1d2&amp;camp=1634&amp;creative=6738" target="_blank" rel="noopener noreferrer" class="">Accelerating Unity Through Automation</a></li>
<li class="">I changed jobs due to a sudden and dramatic change in events.</li>
<li class="">Had to setup my own company (again) for contracting, as work involves companies across the pond from me.</li>
<li class="">The <a href="https://realitytoolkit.io/" target="_blank" rel="noopener noreferrer" class="">Reality Toolkit</a> and its new companion the <a href="https://realitycollective.io/com.realitycollective.service-framework-docs" target="_blank" rel="noopener noreferrer" class="">Service Framework</a> have kicked up a gear.</li>
<li class="">Various OpenSource and related activities online which drown my soul.</li>
<li class="">Kids, family, dogs, puppies and more.</li>
</ul>
<p>Personally, I do not see this as a valid excuse, but I have been slowing down a bit of late due to my progress in time, promises made and broken to myself, but C'est la Vie.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="back-on-target---broken-links-in-jekyll">Back on target - broken links in Jekyll<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#back-on-target---broken-links-in-jekyll" class="hash-link" aria-label="Direct link to Back on target - broken links in Jekyll" title="Direct link to Back on target - broken links in Jekyll" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230908/02-skiptotheend.gif" alt="Skip to the end" class="img_ev3q"></p>
<p>So why the sudden post.  Well it all started when..  No it's too much, let me sum up.</p>
<p>In short, when posting about my new book, one favoured visitor informed me that all the book links on my blog site were no longer working.  I was curious, bemused, confused and above all, frustrated.</p>
<p>I had migrated my blog some time back and despite my best efforts and mad "search and replace" skills, I still find the odd link broken.  Not put off I checked out (what I thought) the page that had the information, except, the links were all fine.....</p>
<p>Confused, yes I was.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="updating-the-blog-site">Updating the blog site<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#updating-the-blog-site" class="hash-link" aria-label="Direct link to Updating the blog site" title="Direct link to Updating the blog site" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230908/03-updatingtheblogsite.gif" alt="Updating your blog" class="img_ev3q"></p>
<p>So in an effort to resolve this I tried a few things:</p>
<ul>
<li class="">I tweaked and pushed the offending page to the blog and published - no effect.</li>
<li class="">I deleted and re-added the page - no effect.</li>
<li class="">I deleted the github pages deployment and republished using the new actions method - no effect.</li>
<li class="">I toyed with the idea of deleting the site and republishing - I DOD NOT DO THIS.</li>
</ul>
<p>In the end I went back to basics and tried building the site locally, only to find all my test setup no longer worked, I fixed that and built the site.</p>
<p>What I found was something I did not expect:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conflicts"><strong>CONFLICTS</strong><a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#conflicts" class="hash-link" aria-label="Direct link to conflicts" title="Direct link to conflicts" translate="no">​</a></h3>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="jekyll-page-collisions">Jekyll page collisions<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#jekyll-page-collisions" class="hash-link" aria-label="Direct link to Jekyll page collisions" title="Direct link to Jekyll page collisions" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230908/04-jekyll-conflicts.png" alt="Finding conflicts" class="img_ev3q"></p>
<p>Only when building locally do these kinds of conflicts how up, the GitHub Pages deploy actions all report SUCCESS!!!.</p>
<blockquote>
<p>I did dig deeper much later, the older "pages build and deployment" publishing task does NOT show any errors, however the newer GitHub Actions based deployment DID actually show the same error (if you know where to look)</p>
</blockquote>
<p>What is essentially happening is this:</p>
<ul>
<li class="">There is a prebuilt HTML file called <code>Books.html</code> which by default deploys to <code>&lt;root&gt;/Books.html</code> (the file I was editing)</li>
<li class="">However, there was also a post called <code>2014-09-02-books.markdown</code>, which when built is written to <code>&lt;root&gt;/books.html</code>.</li>
</ul>
<blockquote>
<p>Jekyll strips the dates from posts when generating the HTML.</p>
</blockquote>
<p>So two files ended up writing to the same output file, and basically, the last one to process won (the blog post version) as a Jekyll build will keep writing and just let you know what it did.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fixing-collisions">Fixing collisions<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#fixing-collisions" class="hash-link" aria-label="Direct link to Fixing collisions" title="Direct link to Fixing collisions" translate="no">​</a></h2>
<p>Laughably, the only way to prevent the collision was to delete one page or the other, or rename them, that is all there is to it.</p>
<blockquote>
<p>INCLUDING THE SUBJECT MATTER AT THE TOP OF THE FILE!!</p>
</blockquote>
<p>So once again my blog is a happy place, links work and pages are showing exactly how I expect them to be, phew.  Only took an hour of pulling hair out.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="you-must-read-from-the-book">You must read from the book<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#you-must-read-from-the-book" class="hash-link" aria-label="Direct link to You must read from the book" title="Direct link to You must read from the book" translate="no">​</a></h2>
<p>I almost find it ironic that one of the things that would have helped the solution was to read my new book, which is due to go on sale very soon (or now if you are reading this in the future).</p>
<p>if you want to learn and understand automation and all the joys that come from letting someone else to the repetitive parts of you job, then I would recommend a read.</p>
<blockquote>
<p>And if you attended my talks on Automation recently, you could try all the fun things you can do with automation too.</p>
</blockquote>
<p>I hope this helps out any other Jekyll blog users and save you from a certain amount of hair pulling in diagnosing issues on your blog.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ps-switch-to-the-new-github-actions-build-and-publishing-it-is-much-better">P.S. Switch to the new GitHub actions build and publishing, it is much better<a href="https://zenithmoon.com/blogs/this-is-not-the-page-you-are-looking-for#ps-switch-to-the-new-github-actions-build-and-publishing-it-is-much-better" class="hash-link" aria-label="Direct link to P.S. Switch to the new GitHub actions build and publishing, it is much better" title="Direct link to P.S. Switch to the new GitHub actions build and publishing, it is much better" translate="no">​</a></h2>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/this-is-not-the-page-you-are-looking-for.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="azure" term="azure"/>
        <category label="github" term="github"/>
        <category label="automation" term="automation"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Reusing automation on GitHub]]></title>
        <id>https://zenithmoon.com/blogs/reusing-automation</id>
        <link href="https://zenithmoon.com/blogs/reusing-automation"/>
        <updated>2023-02-28T14:00:00.000Z</updated>
        <summary type="html"><![CDATA[I side note in automation, to keep things tidy and reduce impacts, let us reuse some workflow]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230228/01-reusable-workflows-ci-cd.png" alt="Reusing workflows across repositories" class="img_ev3q"></p>
<p>Managing automation is hard enough without having to pollute your git history with changes to your actual workflows, in reality, the workflow itself is unlikely to change but there can be subtle updates in what each workflow action needs to do.</p>
<!-- -->
<p>As an example, in the Reality Collective we needed to change the versioning strategy for our packages due to upcoming changes in Unity (another nightmare to be sure), the actual flow is not changing but we simply needed some minor updates. All this required (although not in reality) was to update our automation to change the "Preview" tag we previously use to "Pre".  Now, if we had all the code and scripts inside our actual projects, this would have meant changing ALL the packages in all the projects we deploy for this one minor change, however, we had employed "Reusable workflows" in all our automations previously, so we could make the changes in a separate repository rather than pollute our core code projects.  (well, at least in theory).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-are-reusable-workflows">What are <a href="https://docs.github.com/en/actions/using-workflows/reusing-workflows" target="_blank" rel="noopener noreferrer" class="">Reusable workflows</a><a href="https://zenithmoon.com/blogs/reusing-automation#what-are-reusable-workflows" class="hash-link" aria-label="Direct link to what-are-reusable-workflows" title="Direct link to what-are-reusable-workflows" translate="no">​</a></h2>
<p>Reusable workflows come in several forms, the most common are the Published "Actions" on the GitHub Actions marketplace, such as the popular "Checkout" action to clone code at the beginning of an automation.  Actions are reusable scripts that can consumed in your workflow.</p>
<p>But what if you do not want to publish your workflows to the public marketplace?  Well, that is where <a href="https://github.blog/2022-02-10-using-reusable-workflows-github-actions/" target="_blank" rel="noopener noreferrer" class="">Reusable Workflow repositories</a> come in, like Actions, they are workflows published to a GitHub repo and can be accessed from any other implemented workflow with a few "security" limitations:</p>
<ul>
<li class="">The repository the workflow files are located must be public.</li>
<li class="">The repository must exist in the SAME organisation as the repositories that want to consume them. (No cross org access)</li>
<li class="">It must be a full repository, no forks allowed.</li>
</ul>
<blockquote>
<p>See the <a href="https://github.blog/2022-02-10-using-reusable-workflows-github-actions/" target="_blank" rel="noopener noreferrer" class="">GitHub Blog article on the launch of Reusable Workflows</a></p>
</blockquote>
<p>As an example, here are all the reusable workflows the RealityCollective publishes for all its packages to consume:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20230228/02-RCTReusableWorkflows.png" alt="Reality Collective reusable workflows" class="img_ev3q">
<em>Reality Collective reusable workflows</em></p>
<p>By default these can ONLY be consumed by other Reality Collective repositories, to use then in other organistions they must be manually copied and maintained.</p>
<blockquote>
<p>To actually share them, they would each have to be converted in to Marketplace Actions, but we have no need at this time to do that.</p>
</blockquote>
<p>Consuming these workflows is easy by having the following Job definitions within the actual workflows in each of our packages, thus:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">release-Package-only</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Release package only</span><span class="token punctuation" style="color:rgb(199, 146, 234)">,</span><span class="token plain"> no upversion</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> realitycollective/reusableworkflows/.github/workflows/tagrelease.yml@v2</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">build-host</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">latest</span><br></div></code></pre></div></div>
<p>The workflow is called using <code>realitycollective/reusableworkflows/.github/workflows/tagrelease.yml</code>, specifying the organisation, repository and location of the reusable workflow file, followed by a TAG or Branch to source the pipeline from.</p>
<blockquote>
<p>I would strongly advise <strong>AGAINST</strong> using branches as they can be more trouble than they are worth, see below.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="managing-the-workflows">Managing the workflows<a href="https://zenithmoon.com/blogs/reusing-automation#managing-the-workflows" class="hash-link" aria-label="Direct link to Managing the workflows" title="Direct link to Managing the workflows" translate="no">​</a></h2>
<p>Now, obviously over time these workflows need to be maintained and new versions added/updated/removed but you do not want to be changing all the repositories consuming them constantly, hence why reusable workflows require a branch or tag suffix against the reusable workflow file.</p>
<p>And this brings us to why this article exists today, as the public actions GitHub publish have some subtleties that are not immediately obvious and what follows is a guide to managing your own reusable workflows.</p>
<ol>
<li class="">Once your workflows reach a stable state, create a <strong>MAIN VERSION</strong> Tag for the release of the workflow.  E.G. V1.</li>
<li class="">With the first changes you make, once stable, create a new Sub Version Tag for the incremental release.  E.G. V1.0.1.</li>
<li class="">NOW, you need to rebase the <strong>MAIN VERSION</strong> Tag to match the new Sub Version.  E.G. Delete V1 and recreate using the <strong>same commit</strong> as V1.0.1.</li>
</ol>
<blockquote>
<p>If you wish you can create automation for your workflows to automate the tagging for the reusable pipeline repository.</p>
</blockquote>
<p>This ensures that all workflows using the <strong>V2</strong> tag will get the latest update automatically. Only if there is a breaking change in parameters or flow, should you then do a Major version update to V3.</p>
<p>This subtlety with rebasing the Major Tag to the new commit is what eluded me in maintaining our workflows recently, so hopefully this will shed some light for you too, if you are using reusable workflows.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="making-your-own-reusable-workflows">Making your own reusable workflows<a href="https://zenithmoon.com/blogs/reusing-automation#making-your-own-reusable-workflows" class="hash-link" aria-label="Direct link to Making your own reusable workflows" title="Direct link to Making your own reusable workflows" translate="no">​</a></h2>
<p>As a simple guide for making your own reusable workflows for your own repositories, here are some times to avoid some of the pitfalls I faced when building our first batch:</p>
<ol>
<li class="">Create a new repository in your organisation solely for Reusable Workflows, the name is not important but make it recognisable.</li>
<li class="">Make the repository Public (until such time that GitHub allows us to have private reusable workflow access).</li>
<li class="">Create, test and validate your first batch of workflows, test in other repositories using the "<strong>Main</strong>"" suffix (or the name of the branch used for testing).</li>
<li class="">Once the workflows are Stable, create a Release commit to define a release point in your repository.</li>
<li class="">Create a new Tag (V1 for example) for the release.</li>
<li class="">Create a Release from the Tag for reference to document the release and what is contained.</li>
<li class="">Apply real workflows using the new Release tag (@V1)</li>
</ol>
<blockquote>
<p><strong>Note</strong> changing any workflows in the repository now will NOT BREAK any use of the workflows, making life more stable.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="updating-your-workflow">Updating your workflow<a href="https://zenithmoon.com/blogs/reusing-automation#updating-your-workflow" class="hash-link" aria-label="Direct link to Updating your workflow" title="Direct link to Updating your workflow" translate="no">​</a></h2>
<p>When you need to make changes and create a new reusable workflow release, plan it, test it and update as follows.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="for-a-minor-release">For a minor release<a href="https://zenithmoon.com/blogs/reusing-automation#for-a-minor-release" class="hash-link" aria-label="Direct link to For a minor release" title="Direct link to For a minor release" translate="no">​</a></h3>
<ol>
<li class="">Make changes to the repository, either in the default branch or a separate one (should not matter)</li>
<li class="">Once stable, create a Release commit to mark the release.</li>
<li class="">Create a new minor release tag for the release (e.g. V1.0.1)</li>
<li class="">Delete and recreate the Major release tag for the release (delete V1, recreate under the same commit)</li>
<li class="">Both the minor release and major release tags should now be the same</li>
<li class="">Create a Release for the new minor release version.</li>
</ol>
<p>No changes are needed to existing workflows as they will use the new updated V1 commit as the source of their workflows.</p>
<blockquote>
<p>If you do not recreate/update the Major release Tag commit, your workflows will not see changes unless you update them to the new minor release.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="for-a-major-release">FOr a Major release<a href="https://zenithmoon.com/blogs/reusing-automation#for-a-major-release" class="hash-link" aria-label="Direct link to FOr a Major release" title="Direct link to FOr a Major release" translate="no">​</a></h3>
<ol>
<li class="">Follow the same steps for a minor release, except you will only create a brand new Major release tag and Release.  Previous version is untouched.</li>
<li class="">Update any workflows needing the new version with the new Major version number, e.g. @v1 -&gt; @v2.</li>
</ol>
<blockquote>
<p>To use the new version, all workflows needed it will need to be updated.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="wrap-up">Wrap up<a href="https://zenithmoon.com/blogs/reusing-automation#wrap-up" class="hash-link" aria-label="Direct link to Wrap up" title="Direct link to Wrap up" translate="no">​</a></h2>
<p>It has been a "not" fun time working through updating our pipelines recently, mainly because we followed the GitHub examples andd just use the Main branch (@main) which created headaches when we needed to make updates.
After a few hours examining what GitHub does to manage its own workflows we built up the above flow (the Major Release re-tagging was the latest).</p>
<p>But now we have a flow for maintaining our workflows better and we can get back to fighting Unity instead.</p>
<p>Hope this helps.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/reusing-automation.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="azure" term="azure"/>
        <category label="github" term="github"/>
        <category label="automation" term="automation"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Self Hosting automation agents - a guide]]></title>
        <id>https://zenithmoon.com/blogs/self-hosting-automation-agents</id>
        <link href="https://zenithmoon.com/blogs/self-hosting-automation-agents"/>
        <updated>2022-12-18T14:00:00.000Z</updated>
        <summary type="html"><![CDATA[To conclude the series on automation, here are the final steps to setup Agents on your own hardware]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221218/01-devops.png" alt="DevOps Services" class="img_ev3q"></p>
<p>With <a class="" href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity">Azure Devops</a> and <a class="" href="https://zenithmoon.com/blogs/using-github-actions-with-unity">GitHub Actions</a> you have the option to either use their hosted agents using the Azure backend, which is fine as you do get a decent amount of time of free resource but eventually you are going to end up paying for the service.  Or, you can host your own agents on your own PC or even a dedicated box just for building.</p>
<!-- -->
<p>If you have spare kit (does not need to be uber fast) then it makes sense to leave it powered on in the corner and have all your builds/tests run on it while you continue to work.  And this isn't an either/or moment either, if you want you can run some of the smaller automations you use to do code validation or check-in "checks" using hosted agents and then run your intensive work on your own box, it is completely up to you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="microsoft-hosted-pipeline-agents">Microsoft-hosted pipeline agents<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#microsoft-hosted-pipeline-agents" class="hash-link" aria-label="Direct link to Microsoft-hosted pipeline agents" title="Direct link to Microsoft-hosted pipeline agents" translate="no">​</a></h2>
<p>As mentioned previously, for both Azure and GitHub automation, Microsoft provide you several images to run your jobs, which you chose is dependent on what the job is doing and the requirements needed to run them, for most script / .NET / NPM tasks Ubuntu (Linux) is fine and it is also the cheapest from an automation standpoint.  As soon as you start needing software, such as Unity, then your options become a bit more limited to either Windows or Mac:</p>



























































<table><thead><tr><th>Image</th><th>YAML Label</th><th>Included Software</th><th>Rollout Progress of Latest Image Release</th></tr></thead><tbody><tr><td>Ubuntu 22.04</td><td><code>ubuntu-22.04</code></td><td>[ubuntu-22.04]</td><td></td></tr><tr><td>Ubuntu 20.04</td><td><code>ubuntu-latest</code> or <code>ubuntu-20.04</code></td><td>[ubuntu-20.04]</td><td></td></tr><tr><td>Ubuntu 18.04 <sup>deprecated</sup></td><td><code>ubuntu-18.04</code></td><td>[ubuntu-18.04]</td><td></td></tr><tr><td>macOS 12</td><td><code>macos-12</code></td><td>[macOS-12]</td><td></td></tr><tr><td>macOS 11</td><td><code>macos-latest</code> or <code>macos-11</code></td><td>[macOS-11]</td><td></td></tr><tr><td>macOS 10.15 <sup>deprecated</sup></td><td><code>macos-10.15</code></td><td>[macOS-10.15]</td><td></td></tr><tr><td>Windows Server 2022</td><td><code>windows-latest</code> or <code>windows-2022</code></td><td>[windows-2022]</td><td></td></tr><tr><td>Windows Server 2019</td><td><code>windows-2019</code></td><td>[windows-2019]</td><td></td></tr></tbody></table>
<blockquote>
<p>Make sure to check the previous articles for more details on using the hosted agents for either <a class="" href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity">Azure Devops</a> or <a class="" href="https://zenithmoon.com/blogs/using-github-actions-with-unity">GitHub Actions</a>.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-hosting">Self Hosting<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#self-hosting" class="hash-link" aria-label="Direct link to Self Hosting" title="Direct link to Self Hosting" translate="no">​</a></h2>
<p>As the title of the article suggests, self-hosting simply means you are using your own hardware, be it physical or virtual (yes, if you have virtual servers hosted elsewhere, you can self-host on them too), you are in control of the setup, operation and runtime for the host.  This can greatly simplify the runtime of your host and even your automation pipelines as you can skip the steps needed to setup and install required software because you already know it is installed (because you installed it), rather than having potentially complicated scripts to download and install each requirement on each run.</p>
<blockquote>
<p>Fair warning, Unity is still a pain when it comes to automation, ever since they updated to the V3 of the Unity Hub, it has made installing / checking installs of the Unity Editor very temperamental.  Personally, I manage the version of Unity that is installed manually now because automating it became too unreliable.  Thanks Unity!</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="requirements">Requirements<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#requirements" class="hash-link" aria-label="Direct link to Requirements" title="Direct link to Requirements" translate="no">​</a></h2>
<p>The requirements needed to run a self-hosted agent come in two parts, what you need to run your pipelines and what you need in order to run the agent itself.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-software">Build Software<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#build-software" class="hash-link" aria-label="Direct link to Build Software" title="Direct link to Build Software" translate="no">​</a></h3>
<p>What you need for building / automating and testing will largely depend on what you are building, for example:</p>
<ul>
<li class="">Unity Hub.</li>
<li class="">Unity editor (version determined by your project), which must be manually updated when you upgrade.</li>
<li class="">Visual Studio (community edition is still fine)</li>
<li class="">.NET runtimes (usually installed with Visual Studio)</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automation-software">Automation Software<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#automation-software" class="hash-link" aria-label="Direct link to Automation Software" title="Direct link to Automation Software" translate="no">​</a></h3>
<p>For the automation, you will need scripting runtimes and also the agent software of choice, depending on whether you are using Azure, GitHub or even one of the other popular automation services, like GitLab (however, we are only covering Azure and GitHub in this article):</p>
<ul>
<li class=""><a href="https://learn.microsoft.com/en-us/shows/it-ops-talk/how-to-install-powershell-7" target="_blank" rel="noopener noreferrer" class="">Powershell</a> or <a href="https://www.linuxandubuntu.com/home/install-ubuntu-bash-shell-on-windows-10" target="_blank" rel="noopener noreferrer" class="">Bash</a>, depending on your scripting language of choice.  Personally, I use both.</li>
<li class=""><a href="https://docs.npmjs.com/cli/v7/configuring-npm/install" target="_blank" rel="noopener noreferrer" class="">NPM</a> (Node Package Manager) if you are intending to publish anything or manage the version of your packages.  Also requires NodeJS.</li>
<li class="">Agent software of choice. <a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#setting-up-an-azure-agent" class="">Azure</a> / <a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#setting-up-a-github-agent" class="">GitHub</a></li>
</ul>
<blockquote>
<p>You may find on your initial forays into automation that there may be other requirements needed depending on your workflows, just be sure to document them as required so that when you install another agent, you have everything recorded.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-many-agents">How many agents?<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#how-many-agents" class="hash-link" aria-label="Direct link to How many agents?" title="Direct link to How many agents?" translate="no">​</a></h2>
<p>One thing that some people miss, is that agents are virtual setups in themselves on your build machine and there is <strong>NO LIMIT</strong> to how many agents you can have installed on a single box.  In my experience, I generally install approximately 3 agents on a PC, depending on how much I expect the agents to do and how intensive the operations are:</p>
<ul>
<li class="">If you are running small scripting jobs, then install more agents.</li>
<li class="">If you are running intensive build and packaging jobs, install less agents.</li>
</ul>
<p>Ultimately though, you can mix and match again.  Have several small agents setup with a Tag/Name to identify them and then have one single agent identified as the BUILD agent for solely doing the big builds, then you can ix and match which are used in your jobs.</p>
<blockquote>
<p>Personally, I setup about 10 agents for small script jobs and 1 (or 2) dedicated agents for just builds, this throttles the demand based on how intensive the jobs are likely to be and do not slow the build PC (Usually my main development pc) down too much.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="agent-identity">Agent identity<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#agent-identity" class="hash-link" aria-label="Direct link to Agent identity" title="Direct link to Agent identity" translate="no">​</a></h2>
<p>A critical thing for your agents is the identity the agent uses in Azure or GitHub, ideally this should be a dedicated and separate user, this is to ensure there are no mixups for the account running builds.  You can use a personal account, but if that person leaves or resets their access tokens, you will have to recreate all your agents!</p>
<p>So create a new user in GitHub (it is free) and Azure (if also using Azure) and give it sufficient rights to access the parts of the project you want automated.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="agent-location">Agent Location<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#agent-location" class="hash-link" aria-label="Direct link to Agent Location" title="Direct link to Agent Location" translate="no">​</a></h2>
<p>One often missed fact with agents is where to put them on the build host, whether it is on Windows or Mac/Linux, you need to take in to account that the <strong>PATH</strong> that is build can get quite long!  On Windows this can create issues, less so on Linux/mac but still something that has to be considered.</p>
<blockquote>
<p>The "Rule of Thumb" I always use is to place the folder for the agent as high as possible (Root ideally) with short folder names, e.g.</p>
</blockquote>
<ul>
<li class="">Create a single character folder on the Root of the drive, e.g. A (for Azure) or G (for Github), or as close to root as you can, for the agent type.</li>
<li class="">Create a folder for each agent within the "Agent Type" folder, either as the name of the agent or simply 01, 02, etc.  E.G. MyAgent-01.</li>
<li class="">When asked for a "working folder", keep it short, ideally simply "w"</li>
</ul>
<p>All in all, you want to keep the working path the agent uses as short as possible, to give enough path length for your builds, which as stated, can get VERY long in some cases.</p>
<p>Your final path should resemble something like:</p>
<ul>
<li class="">"c:\a\agent-01\w"</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-an-azure-agent">Setting up an Azure Agent<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#setting-up-an-azure-agent" class="hash-link" aria-label="Direct link to Setting up an Azure Agent" title="Direct link to Setting up an Azure Agent" translate="no">​</a></h2>
<blockquote>
<p><a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&amp;tabs=browser" target="_blank" rel="noopener noreferrer" class="">Check the official MS docs here</a></p>
</blockquote>
<p>Azure DevOps agents are quite robust and do take some practice to setup, the steps are as follows:</p>
<ol>
<li class="">Create a new <a href="https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">PAT (Personal Access Token)</a> for the user running the builds.</li>
<li class="">Make a note of the Azure Organisation URL (e.g. <a href="https://dev.azure.com/myorganisation" target="_blank" rel="noopener noreferrer" class="">https://dev.azure.com/myorganisation</a>)</li>
<li class="">Navigate to the "Organisation" settings (as agents are created for the Organisation ONLY) - <em>Organisation -&gt; Organisation Settings -&gt; Agent Pools</em></li>
<li class="">Create a new Agent Pool (if you do not have one setup already), the pool name is what is used on the Job.</li>
<li class="">Create a new Agent on Azure and download the latest agent software to the Build Host (just gives you the instructions)</li>
<li class="">Create a new folder for your agent, ideally with the index of the agent, especially if you are adding more than one.  E.G. "myAgent-01", or just "01".</li>
<li class="">Extract the Agent software into the agent folder.</li>
<li class="">When asked, enter the Organisation URL (e.g. <a href="https://dev.azure.com/myorganisation" target="_blank" rel="noopener noreferrer" class="">https://dev.azure.com/myorganisation</a>).</li>
<li class="">For security, select "PAT" (just hit enter) and then copy/paste the PAT you created in step 1.</li>
<li class="">When asked for a "Working Folder", set this to "W" (it is just a short name).</li>
</ol>
<p>Depending on the OS you are installing the agent on:</p>
<ul>
<li class="">For Windows, you will be asked if you want to run this agent as a service.  If you want it to automatically start when the Host powers up, then select Yes and click enter to select the default user to run the service as.</li>
<li class="">If you are installing on a Mac, you have to run a separate command to install the service which is <code>./svc.sh install</code> and then <code>./svc.sh start</code></li>
</ul>
<blockquote>
<p>Personally I have always installed agents as Services so I do not need to think about them, else you will need to run a separate command each and every time you need to start them (which some people do), just check the Azure docs for more details for your Operating system.</p>
</blockquote>
<p>You now have a single agent setup, repeat steps 6 - 10 for additional agents with a different agent folder name.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-a-github-agent">Setting up a GitHub Agent<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#setting-up-a-github-agent" class="hash-link" aria-label="Direct link to Setting up a GitHub Agent" title="Direct link to Setting up a GitHub Agent" translate="no">​</a></h2>
<blockquote>
<p><a href="https://github.com/organizations/EtharInc/settings/actions/runners/new" target="_blank" rel="noopener noreferrer" class="">Check the official GitHub docs here</a></p>
</blockquote>
<p>Setting up a GitHub Actions runners are far simpler that Azure agents, simply because they have less steps and are a lot more streamlined, however, they can still cause confusion.</p>
<ol>
<li class="">Create a new <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token" target="_blank" rel="noopener noreferrer" class="">GitHub PAT (Personal Access Token)</a> for the user running the builds.</li>
<li class="">Navigate to the Organisation Settings in GitHub (as agents are created for the Organisation ONLY) - <em>Organisation -&gt; Settings -&gt; Actions -&gt; Runners</em></li>
<li class="">Select "New runner" and then "New self-hosted runner" to see the instructions.</li>
<li class="">Download the Agent software from the URL provided.</li>
<li class="">Create a new folder for your agent, ideally with the index of the agent, especially if you are adding more than one.  E.G. "myAgent-01", or just "01".</li>
<li class="">Extract the Agent software into the agent folder.</li>
<li class="">Run the "config" command using the token SPECIFIED ON THE PAGE (not your PAT)</li>
<li class="">You will be asked for the PAT token as part of the install</li>
<li class="">You will be asked for any additional TAGS you want set against the agent, these are used to identify your agent in your Jobs.  You can leave the defaults or add your own if you want to create different agents for different jobs.</li>
<li class="">When asked for a "Working Folder", set this to "W" (it is just a short name).</li>
</ol>
<p>Depending on the OS you are installing the agent on:</p>
<ul>
<li class="">For Windows, you will be asked if you want to run this agent as a service.  If you want it to automatically start when the Host powers up, then select Yes and click enter to select the default user to run the service as.</li>
<li class="">If you are installing on a Mac, you have to run a separate command to install the service which is <code>./svc.sh install</code> and then <code>./svc.sh start</code></li>
</ul>
<blockquote>
<p>Personally I have always installed agents as Services so I do not need to think about them, else you will need to run a separate command each and every time you need to start them (which some people do), just check the Azure docs for more details for your Operating system.</p>
</blockquote>
<p>You now have a single agent setup, repeat steps 5 - 10 for additional agents with a different agent folder name.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="all-done-except-for-the-fish">All done except for the fish<a href="https://zenithmoon.com/blogs/self-hosting-automation-agents#all-done-except-for-the-fish" class="hash-link" aria-label="Direct link to All done except for the fish" title="Direct link to All done except for the fish" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221218/02-fish.gif" alt="So long and thanks for all the fish" class="img_ev3q"></p>
<p>We are now at the end of our series, we have covered all the necessary bits to get your automation pipelines setup using the service(s) you require and you should be well armed for what lies ahead.</p>
<p>I have left personal notes through the articles to guide you in your journey, but here is a shortlist:</p>
<ul>
<li class="">Do not be afraid to use multiple services for different types of automation.  I use Azure for Unity builds and publishing and I use GitHub for validation, tests and verification on all my code check ins.</li>
<li class="">Use many agents for simple jobs, especially when they are quick, or even use hosted agents for those little things and take some weight off.  It would be a VERY busy month to use all your free credits on simple jobs.  But if you do exceed, then check and see if you want to move some or all of the work on to self-hosted agents, or mix and match.</li>
<li class="">Use fewer agents for large jobs, should be fairly self explanatory as large jobs use a lot of power and will just slow each other down if run concurrently.  Especially if you are running it on your dev PC. (might need more than one coffee while you wait).</li>
<li class=""><strong>NEVER</strong> develop pipelines/jobs on your LIVE code branch, always make a FORK to a separate repo to experiment, or even use less code.   It can sometimes take MANY runs of testing to get your pipeline working the ay you want it and you don't want/need to use your entire codebase while your testing.  I always create a new fork/repo for testing and then copy over the YAML to my live repo when I'm happy.</li>
<li class="">Experiment with your pipelines and also check what other projects do, if you see something you like that another project is doing, there is no harm looking at their pipelines and using it as a reference to build your own.  The Microsoft Repos are especially for this.</li>
<li class="">Did you know, you can also "generate your documentation" in a pipeline.  Personally I have started using Docusaurus and Docfx to automate docs.</li>
</ul>
<blockquote>
<p><strong>For GitHub</strong> if you want to use GitHub actions on <strong>PUBLIC</strong> repositories, then you need to edit the "Default runner group" and enable it (that caused me a week of head scratching as it is not well documented).</p>
</blockquote>
<p>Hope this all helps and if you have questions, comment below!</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/self-hosting-automation-agents.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="azure" term="azure"/>
        <category label="github" term="github"/>
        <category label="automation" term="automation"/>
        <category label="unity3d" term="unity3d"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using Azure Pipelines with Unity]]></title>
        <id>https://zenithmoon.com/blogs/using-azure-pipelines-with-unity</id>
        <link href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity"/>
        <updated>2022-11-16T14:00:00.000Z</updated>
        <summary type="html"><![CDATA[Making the most of GitHub Actions to automate your Unity testing]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/01-azuredevops.png" alt="Azure Pipelines intro" class="img_ev3q"></p>
<p>Similar to <a class="" href="https://zenithmoon.com/blogs/using-github-actions-with-unity">GitHub Actions</a>, Azure can automate the building / testing and deploying of your Unity solution.  It is true, that Microsoft is on a journey to migrate all build automation to GitHub to offer a more accessible way for developers to automate their project,  however,there are a few notable exceptions and capabilities that are still far superior in Azure Devops currently that are worth exploring, namely:</p>
<!-- -->
<ul>
<li class="">Publish artifacts / builds per build.</li>
<li class="">Finer control / articulation of your automation.</li>
<li class="">The ability to manually run automation on command.</li>
<li class="">Provide integration that can be consumed in GitHub Actions (useful for kicking off a pipeline when GitHub is finished)</li>
</ul>
<p><a href="https://www.microsoft.com/en-us/videoplayer/embed/RWMlMo?postJsllMsg=true" target="_blank" rel="noopener noreferrer" title="Azure Pipelines intro" class=""><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/02-azure-pipelines-intro.png" alt="Azure Pipelines Intro Video" class="img_ev3q"></a></p>
<blockquote>
<p>On a personal note, I use a mix of GitHub Actions and Azure DevOps to utilise what each platform is best at.  GitHub for quick tests / builds and validation, Azure for producing release / production builds and automating delivery to test suites/stores.</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="its-free">Its FREE<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#its-free" class="hash-link" aria-label="Direct link to Its FREE" title="Direct link to Its FREE" translate="no">​</a></h3>
<p>Like GitHub Actions, the majority of Azure Pipelines is free to use and run, and a free Azure account to boot (with even some free Azure credits).  The only time you start paying is for spinning up Build / Automation hosts to run your operations, there is a free tier but you can also offset this with your own Build Host (just like GitHub actions).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-can-you-automate">What can you automate?<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#what-can-you-automate" class="hash-link" aria-label="Direct link to What can you automate?" title="Direct link to What can you automate?" translate="no">​</a></h2>
<p>Automation provides a ton of capabilities to power and maintain your project, large or small, primarily with the focus on maintaining reliability and giving you an early warning of any potential problems that may be creeping into your project.</p>
<p>Automation can provide:</p>
<ul>
<li class="">Quick checks to ensure work is assigned correctly in your project.</li>
<li class="">Validating information provided by the team or customers is accurate.</li>
<li class="">Unit test validation. (if you are writing Unit tests, if you are not, why not?)</li>
<li class="">Build testing for multiple platforms as required.</li>
<li class="">Automated Version control, to ensure project/package versions are maintained. (you really do not want 500 copies of version 1.0 of your project do you?)</li>
<li class="">Robust build automation, publishing and deployment.</li>
</ul>
<p>The list goes on and in most cases, you are just organising what you want to test rather than writing lots of code to build your tests (in your workflow)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="azure-extensions">Azure Extensions<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#azure-extensions" class="hash-link" aria-label="Direct link to Azure Extensions" title="Direct link to Azure Extensions" translate="no">​</a></h2>
<p>Azure's library of extensions and features is well documented and has been in place for many years and there is an element if trustworthiness in the marketplace that GitHub simply cannot match.  A lot of the extensions provide integrations into many of the features provided by Microsoft and its partners that enable things you may not have even considered (how about a cost calculator to tell you how much your operations could cost you?).</p>
<blockquote>
<p>You can find the Azure DevOps Marketplace here - <a href="https://marketplace.visualstudio.com/azuredevops" target="_blank" rel="noopener noreferrer" class="">https://marketplace.visualstudio.com/azuredevops</a></p>
</blockquote>
<p>There are extensions that provide things from:</p>
<ul>
<li class="">Dedicated Unity build automation - a set of reusable tools to fast-track your project automation.</li>
<li class="">Integrations with Planning / optimisation tools such as Azure Boards and GitHub projects.</li>
<li class="">Code management / validation / optimisation services to  ensure your code is "mostly" error free.</li>
<li class="">Test result exploring and navigation (although I haven't had this working with Unity Test outputs yet)</li>
</ul>
<p>And soo much more.</p>
<p>The one thing to note is that "extensions" are enabled for your organisation and available to all pipelines, they are not on a per-project basis (so if there is a cost associated with an extension, there is only one charge, but most are free).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="install-the-unity-tools-for-azure-devops-extension">Install the Unity Tools for Azure DevOps extension<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#install-the-unity-tools-for-azure-devops-extension" class="hash-link" aria-label="Direct link to Install the Unity Tools for Azure DevOps extension" title="Direct link to Install the Unity Tools for Azure DevOps extension" translate="no">​</a></h3>
<p>For the purposes of this article (since it is about building Unity projects), install the awesome <a href="https://marketplace.visualstudio.com/items?itemName=DinomiteStudios.64e90d50-a9c0-11e8-a356-d3eab7857116" target="_blank" rel="noopener noreferrer" class="">Unity Tools for Azure DevOps</a> extension from the Azure marketplace and we will walk through it later.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/07-unity-tools-for-azure-devops.png" alt="Unity Tools for Azure DevOps extension" class="img_ev3q">
<a href="https://www.unitydevops.com/" target="_blank" rel="noopener noreferrer" class="">https://www.unitydevops.com/</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automation-images-or-self-hosting">Automation images or Self-hosting<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#automation-images-or-self-hosting" class="hash-link" aria-label="Direct link to Automation images or Self-hosting" title="Direct link to Automation images or Self-hosting" translate="no">​</a></h2>
<p>The main (and likely only) cost for automation is the time and processing power needed to run your automation, in the majority of cases this is very minor and Azure Pipelines provide quite a substantial "free" level of processing time for running jobs (and fairly reasonable costs for extra).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="microsoft-hosted-pipeline-agents">Microsoft-hosted pipeline agents<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#microsoft-hosted-pipeline-agents" class="hash-link" aria-label="Direct link to Microsoft-hosted pipeline agents" title="Direct link to Microsoft-hosted pipeline agents" translate="no">​</a></h3>
<p>Depending on the requirements to run your actions, Microsoft provides several "images" on their platform to run your code for you:</p>



























































<table><thead><tr><th>Image</th><th>YAML Label</th><th>Included Software</th><th>Rollout Progress of Latest Image Release</th></tr></thead><tbody><tr><td>Ubuntu 22.04</td><td><code>ubuntu-22.04</code></td><td>[ubuntu-22.04]</td><td></td></tr><tr><td>Ubuntu 20.04</td><td><code>ubuntu-latest</code> or <code>ubuntu-20.04</code></td><td>[ubuntu-20.04]</td><td></td></tr><tr><td>Ubuntu 18.04 <sup>deprecated</sup></td><td><code>ubuntu-18.04</code></td><td>[ubuntu-18.04]</td><td></td></tr><tr><td>macOS 12</td><td><code>macos-12</code></td><td>[macOS-12]</td><td></td></tr><tr><td>macOS 11</td><td><code>macos-latest</code> or <code>macos-11</code></td><td>[macOS-11]</td><td></td></tr><tr><td>macOS 10.15 <sup>deprecated</sup></td><td><code>macos-10.15</code></td><td>[macOS-10.15]</td><td></td></tr><tr><td>Windows Server 2022</td><td><code>windows-latest</code> or <code>windows-2022</code></td><td>[windows-2022]</td><td></td></tr><tr><td>Windows Server 2019</td><td><code>windows-2019</code></td><td>[windows-2019]</td><td></td></tr></tbody></table>
<blockquote>
<p>You can find all the details on Microsoft's hosted images here - <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted</a></p>
</blockquote>
<p>Essentially, these are cloud-hosted environments to run your automation.</p>
<blockquote>
<p>For the astute observer who read the GitHub Actions article, you should note these are the same images that GitHub uses, fancy that 😱</p>
</blockquote>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-hosting">Self Hosting<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#self-hosting" class="hash-link" aria-label="Direct link to Self Hosting" title="Direct link to Self Hosting" translate="no">​</a></h3>
<p>The alternative is to "<strong>Self Host</strong>", simply meaning that instead of using a Microsoft-hosted server, you will be using your own box, be that a custom-built machine sitting in your cupboard or even your own workstation (having things run in the background seamlessly).  You only need to make sure all the required software is installed (e.g. Unity, if you are doing Unity builds and NodeJS, if you are running NPM commands) and register your Host as a "Pipeline Agent".  If you want, you can even setup <strong>multiple</strong> agents on the same machine for parallelising your workloads (just be careful how many you register so as not to overdo it on a single machine, especially if it is your developer workstation).</p>
<blockquote>
<p>Microsoft's guide to setting up your own self-hosted agent - <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=azure-devops</a></p>
</blockquote>
<p>Self-Hosting is by far the cheapest option as it is your own hardware running all the tasks and GitHub will not charge you a thing.</p>
<blockquote>
<p><a class="" href="https://zenithmoon.com/blogs/self-hosting-automation-agents">Setting up Self-hosted agents</a></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="your-first-project">Your first project<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#your-first-project" class="hash-link" aria-label="Direct link to Your first project" title="Direct link to Your first project" translate="no">​</a></h2>
<p>Getting everything setup in Azure Pipelines is where you first encounter the required "management" setup for getting your automation up and running, on top of your GitHub source control, Azure treats your automation like a full-fledged project, providing many tools to manage and organise everything from:</p>
<ul>
<li class="">Tasks - Azure boards.</li>
<li class="">Pipelines - Automation.</li>
<li class="">Source Control - If not using GitHub.</li>
<li class="">Test Plans - For managing Test runs and output.</li>
<li class="">Artifacts - Published builds, data and dependencies</li>
</ul>
<p>As you can see there is quite a lot available, but for this article, we are just going to focus on Pipelines, feel free to play with the others.</p>
<p>To begin, you need to <a href="https://dev.azure.com/" target="_blank" rel="noopener noreferrer" class="">Logon to Azure Devops</a> and create a "Project" for your Product/automation, choosing if you want it public or private.</p>
<p><a href="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/03-create-new-project.png" target="_blank" rel="noopener noreferrer" class="">Create a new Project</a></p>
<p>With the project setup, it is time to click on the "Pipelines" tab on the left and get the automation flowing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="your-first-pipeline">Your first pipeline<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#your-first-pipeline" class="hash-link" aria-label="Direct link to Your first pipeline" title="Direct link to Your first pipeline" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/04-your-first-pipeline.png" alt="Your first pipeline" class="img_ev3q"></p>
<p>Unlike GitHub, Azure pipelines does not hold your hand as much as GitHub does, although the syntax for building your automation is very similar.</p>
<blockquote>
<p>However, as Azure Pipelines has been "around" a lot longer that GitHub actions, there are many more resources available to keep you going.</p>
</blockquote>
<p>When you create your pipeline, you will be asked where the code (and essentially the automation configuration) is hosted, unlike GitHub, the code can be almost anywhere.</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/05-selecting-your-source.png" alt="Selecting your source" class="img_ev3q"></p>
<p>Assuming your code is on GitHub, select that option and then you will be asked to find your repo.  By default, this shows you ALL the repositories your GitHub account has created or forked, to see all the repositories your account has access to (from other Organisations for example), then change the filter option (in the top-right) to "All Repositories, use the filter if you need to narrow down the search a bit.</p>
<blockquote>
<p>You may get asked for permission to install the Azure pipeline extension for your project, this is just to enable Azure to use the GitHub API for your project/repository.</p>
</blockquote>
<p>With the repository selected, all that is left is to initialise the pipeline with your workflow, if you don't have one already just select the "Starter Pipeline" and we will walk through it.</p>
<blockquote>
<p>Changing your pipeline in Azure pipelines is exactly the same as GitHub actions, there is a workflow file checked in to your repository, and to update it you simply need to check in changes to that file,  no need to return to Azure to update it if you do not want to (unless you like the graphical editor 😊)</p>
</blockquote>
<p>Once you learn to read and build your own workflows, these are quite easy to follow, here is the Standard workflow using Dino's Unity Tools for Azure DevOps example yaml:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Trigger the pipeline to run whenever a commit to the main branch is made.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">trigger</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> main</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Use a custom agent pool called "Unity Windows" that manages custom agents</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># that have the required Unity versions installed and any required SDKs for target platforms</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># configured.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">pool</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Unity Windows</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Check out the code from the branch/pr we are building</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">checkout</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> self</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># (optional) Run the unit tests for the build</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">task</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> UnityTestTask@1</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> UnityUnitTests</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Build the Unity project to standalone. Since we are running on a Windows agent, this will produce</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># an .exe executable and any dependency files needed.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">task</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> UnityBuildTask@3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">inputs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">buildTarget</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> standalone</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">outputPath</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $(Build.BinariesDirectory)</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">outputFileName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> mygame</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Copy build output files to artifact staging directory.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">task</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> CopyFiles@2</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">inputs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">SourceFolder</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $(Build.BinariesDirectory)</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">Contents</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">'**'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">TargetFolder</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $(Build.ArtifactStagingDirectory)</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">CleanTargetFolder</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">OverWrite</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token boolean important" style="color:rgb(255, 88, 116)">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># Finally publish all items in artifact staging to the Azure Pipelines</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># artifact storage. They will be available for sharing and/or further processing there.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">task</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> PublishBuildArtifacts@1</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">inputs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">PathtoPublish</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> $(Build.ArtifactStagingDirectory)</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">ArtifactName</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> drop</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">publishLocation</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Container</span><br></div></code></pre></div></div>
<p>The workflow is made up of several easy-to-understand sections:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-trigger">1. Trigger<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#1-trigger" class="hash-link" aria-label="Direct link to 1. Trigger" title="Direct link to 1. Trigger" translate="no">​</a></h3>
<p>The <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">Trigger</a> is an event that occurs in the Azure Pipeline, this can be overridden in the Pipeline Trigger configuration if you wish to make changes on the backend (also useful for making manual workflows run via command).</p>
<p>The most common trigger is the "<strong>Push</strong>" (when code is pushed to a branch) or "<strong>Pull_Request</strong>"" (when a PR is raised or updated from a push), however, if you check the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">list of things that can be "triggered"</a> almost anything can be a trigger, from comments, to schedules and more.</p>
<p>The types of trigger can vary based upon where your source is held and what you are integrating with, from Azure Repos, GitHub, Bitbucket or even TFS (Team Foundation Server, the precursor to Azure Repositories/GitHub).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-agent-pool">2. Agent Pool<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#2-agent-pool" class="hash-link" aria-label="Direct link to 2. Agent Pool" title="Direct link to 2. Agent Pool" translate="no">​</a></h3>
<p>Unlike GitHub which identifies hosts to run the workflow on by attributes, Azure simply needs to identify the group of machines to run the Job on, organised in an "Agent Pool".  You can see the Agent Pools you have configured by default from your Azure Organisation settings as shown below:</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221116/06-azure-pipelines-pool-configuration.png" alt="Agent Pool Configuration" class="img_ev3q"></p>
<p>You can create your own or alternatively use one of the Microsoft-Hosted agents above by stating the "image name" you want to use, for example:</p>
<ul>
<li class="">windows-latest (latest version of windows)</li>
<li class="">windows-2022 / windows-2019 (specify a specific version of windows)</li>
<li class="">ubuntu-latest (latest linux ubuntu host)</li>
<li class="">ubuntu-22.04 / ubuntu-20.04 / ubuntu-18.04 (specific versions of linux)</li>
<li class="">macOS-latest(yes, you can even build on a Mac in Azure)</li>
<li class="">macOS-12 / macOS-11 / macOS-10.15 (specific versions of MacOS)</li>
</ul>
<p>Just remember that when you are using the Microsoft-Hosted agents, they are clean, any additional software you need (for example Unity) will need to be installed via the pipeline as it is running.</p>
<blockquote>
<p>Personally, I always used a self-hosted agent, it has everything you need and only costs you what it costs to run it, no surprises (until it is down of course)</p>
</blockquote>
<p>If you wish, you can also set the Agent Pool at the Job level instead of at the top of the pipeline, this enables you to run concurrent builds on multiple hosts, for example:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">job</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Linux</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">pool</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">vmImage</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">'ubuntu-latest'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">script</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> echo hello from Linux</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">job</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> macOS</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">pool</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">vmImage</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">'macOS-latest'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">script</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> echo hello from macOS</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">job</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Windows</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">pool</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">vmImage</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(173, 219, 103)">'windows-latest'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">script</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> echo hello from Windows</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-jobs">3. Jobs<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#3-jobs" class="hash-link" aria-label="Direct link to 3. Jobs" title="Direct link to 3. Jobs" translate="no">​</a></h3>
<p>Jobs are simply a batch of work that will run on a single GitHub Runner (self-hosted or Github server).  Each job is <strong>SELF-CONTAINED</strong> meaning that the work it carries out is isolated from any other work when it performs its tasks (steps).  If two jobs are running on the same machine, they simply do not know about each other and cannot (by default) share work or information.</p>
<blockquote>
<p>Jobs are optional (as shown in the example earlier), if you only have one set of steps then you can skip the Job setup.</p>
</blockquote>
<p>You can have multiple jobs defined in a single workflow and even have dependencies (Job 1 runs, but Job 2 requires Job 1 to finish successfully to run) between jobs.  There is a lot you can do but for the most part and in the majority of workflows I have developed, one job is usually enough.</p>
<p>In a Job, you define what it <strong>Runs-on</strong> (the host) and then a list of <strong>Steps</strong> (or tasks) to run in sequence.  You can even make <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/conditions?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">Conditional statements</a> to state "<strong>IF</strong>"" a job should run, e.g. "Run this job IF the users name is X". (conditionals can also be applied to Steps, see later).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-steps">4. Steps<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#4-steps" class="hash-link" aria-label="Direct link to 4. Steps" title="Direct link to 4. Steps" translate="no">​</a></h3>
<p>Steps are exactly how they sound, like walking, you take one step and then the next (providing you have not fallen down in-between steps).  Steps can also be given names and in most cases require parameters (<strong>With statements</strong>) in order to function, always check the documentation for a GitHub Action if you are unsure.</p>
<p>One thing to keep in mind is that <strong>By Default</strong>, ANY step that fails will prevent any further steps from being run as the pipeline fails on any exception.  You can override this (especially if you are building for multiple platforms) if you want, or you can leave it at the default.</p>
<p>Based on Dino's yaml script above, the following steps are undertaken:</p>
<ol>
<li class="">Checkout the latest code from the branch the workflow was triggered on (checkout: self)</li>
<li class="">Run any Unit tests located in the project (task: UnityTestTask@1), this is optional and can be skipped if you have no tests.</li>
<li class="">Build the Unity project for Windows Standalone (task: UnityBuildTask@3), obviously configurable to build for whichever platform you need.</li>
<li class="">When you package the build, we only want to include the actual build files and not everything (task: CopyFiles@2) so copy the actual project to a Staging Folder</li>
<li class="">Finally, we push the built project to the Azure Artifacts area (task: PublishBuildArtifacts@1) so you can download it from Azure.</li>
</ol>
<blockquote>
<p>Personally, I also include an Archive task (task: ArchiveFiles@2) in-between steps 4 &amp; 5, so there is just one file to download 👍</p>
</blockquote>
<p>Check out the other documentation on the <a href="https://www.unitydevops.com/" target="_blank" rel="noopener noreferrer" class="">Unity Tools for Azure DevOps</a> site for more information regarding the extension.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-your-azure-pipelines-running">Getting your Azure Pipelines running<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#getting-your-azure-pipelines-running" class="hash-link" aria-label="Direct link to Getting your Azure Pipelines running" title="Direct link to Getting your Azure Pipelines running" translate="no">​</a></h2>
<p>Once you have configured your Pipeline you can either Click "Save and Run" (the default) which will automatically run the pipeline straight away with whatever code you have checked in, or simply click "Save" which will commit your pipeline to your repository ready for the next time the trigger is fired.</p>
<p>Now every time you push to the branch you have configured (if using the example above) or if you set a PR as the trigger, each time a pull request is made, the pipeline will automatically run and spit out a build.</p>
<blockquote>
<p><strong>Just make sure that you have your Self Hosted agent setup correctly before running the pipeline, or ensure you have everything you need to run on a microsoft-hosted agent in the pipeline</strong></p>
</blockquote>
<p>I did find a <a href="https://github.com/microsoft/Azure-DevOps-YAML-for-Unity" target="_blank" rel="noopener noreferrer" class="">YAML definition for Azure Pipelines</a> to build Unity projects using a Microsoft-Hosts Windows Server agent that <a href="https://github.com/microsoft/Azure-DevOps-YAML-for-Unity/blob/master/azure-pipelines.yml" target="_blank" rel="noopener noreferrer" class="">installs and runs unity here</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="too-much-more-to-mention">Too much more to mention<a href="https://zenithmoon.com/blogs/using-azure-pipelines-with-unity#too-much-more-to-mention" class="hash-link" aria-label="Direct link to Too much more to mention" title="Direct link to Too much more to mention" translate="no">​</a></h2>
<p>There is soo much more that can be done with Azure Pipelines beyond the basics shown here, but you should now have a taste of what is in store, from the basics to the advanced.</p>
<blockquote>
<p>Check the <a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops" target="_blank" rel="noopener noreferrer" class="">Azure Pipelines docs</a> for more.</p>
</blockquote>
<p>If you need to also check out the costs, have a look at the <a href="https://azure.microsoft.com/en-gb/pricing/details/devops/azure-devops-services/" target="_blank" rel="noopener noreferrer" class="">pricing page</a></p>
<p>Any questions, leave a comment below.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/using-azure-pipelines-with-unity.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="azure" term="azure"/>
        <category label="automation" term="automation"/>
        <category label="unity3d" term="unity3d"/>
        <category label="azure pipelines" term="azure pipelines"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Using Github Actions with Unity]]></title>
        <id>https://zenithmoon.com/blogs/using-github-actions-with-unity</id>
        <link href="https://zenithmoon.com/blogs/using-github-actions-with-unity"/>
        <updated>2022-11-05T13:25:00.000Z</updated>
        <summary type="html"><![CDATA[Making the most of GitHub Actions to automate your Unity testing]]></summary>
        <content type="html"><![CDATA[<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221105/01-GitHubActions.png" alt="Github Actions intro" class="img_ev3q"></p>
<p>GitHub actions offers the power of Azure automation but in a far simpler and easy-to-use package, reducing the requirements and setup needed to get your automation off the ground.  There are some things Azure still does better but for the most part, GitHub provides a wealthy estate of information and "out of the box" solutions for automating almost any workflow.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-can-you-automate">What can you automate?<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#what-can-you-automate" class="hash-link" aria-label="Direct link to What can you automate?" title="Direct link to What can you automate?" translate="no">​</a></h2>
<p>Automation provides a ton of capabilities to power and maintain your project, large or small, primarily with the focus on maintaining reliability and giving you an early warning of any potential problems that may be creeping into your project.</p>
<p>Automation can provide:</p>
<ul>
<li class="">Quick checks to ensure work is assigned correctly in your project.</li>
<li class="">Validating information provided by the team or customers is accurate.</li>
<li class="">Unit test validation (if you're writing Unit tests, if you are not, why not?)</li>
<li class="">Build testing for multiple platforms as required</li>
<li class="">Automated Version control, to ensure project/package versions are maintained (you really do not want 500 copies of version 1.0 of your project do you?)</li>
</ul>
<p>The list goes on and in most cases, you are just organising what you want to test rather than writing lots of code to build your tests (in your workflow)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-github-action-marketplace">The GitHub Action Marketplace<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#the-github-action-marketplace" class="hash-link" aria-label="Direct link to The GitHub Action Marketplace" title="Direct link to The GitHub Action Marketplace" translate="no">​</a></h2>
<p>Azure Pipelines has a huge marketplace for pre-built workflows (things to do) to automate your work, GitHub too also has a growing marketplace, albeit a little harder to navigate, but nonetheless, still growing.</p>
<p>GitHub provides the basics right out of the door for you, everything from grabbing code, to setting up environments and GitHub pages.</p>
<blockquote>
<p>You can find all the Official GitHub actions here - <a href="https://github.com/actions" target="_blank" rel="noopener noreferrer" class="">https://github.com/actions</a></p>
</blockquote>
<p>GitHub also provides several starter workflows and guidance for constructing your flows.</p>
<p>Alongside the GitHub provided actions is a wealthy repository of community-written actions providing almost anything you need for your automation, including some that built up from GitHub's own Actions.</p>
<blockquote>
<p>You can find the GitHub Actions marketplace here - <a href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener noreferrer" class="">https://github.com/marketplace?type=actions</a></p>
</blockquote>
<p>Although, like with any other community driven content, care must be taken to ensure the action does what you need, but you will always have several to choose from.  But rest assured, ALL THE SOURCE for each action is published, as it is a requirement for getting it listed on the marketplace.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="automation-images-or-self-hosting">Automation images or Self-hosting<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#automation-images-or-self-hosting" class="hash-link" aria-label="Direct link to Automation images or Self-hosting" title="Direct link to Automation images or Self-hosting" translate="no">​</a></h2>
<p>The main (and likely only) cost for automation is the time and processing power needed to run your automation, in the majority of cases this is very minor and GitHub provides quite a substantial "free" level of processing time for running actions (and fairly reasonable costs for extra).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="github-hosting">GitHub hosting<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#github-hosting" class="hash-link" aria-label="Direct link to GitHub hosting" title="Direct link to GitHub hosting" translate="no">​</a></h3>
<p>Depending on the requirements to run your actions, GitHub provides several "images" on their platform to run your code for you:</p>



























































<table><thead><tr><th>Image</th><th>YAML Label</th><th>Included Software</th><th>Rollout Progress of Latest Image Release</th></tr></thead><tbody><tr><td>Ubuntu 22.04</td><td><code>ubuntu-22.04</code></td><td>[ubuntu-22.04]</td><td></td></tr><tr><td>Ubuntu 20.04</td><td><code>ubuntu-latest</code> or <code>ubuntu-20.04</code></td><td>[ubuntu-20.04]</td><td></td></tr><tr><td>Ubuntu 18.04 <sup>deprecated</sup></td><td><code>ubuntu-18.04</code></td><td>[ubuntu-18.04]</td><td></td></tr><tr><td>macOS 12</td><td><code>macos-12</code></td><td>[macOS-12]</td><td></td></tr><tr><td>macOS 11</td><td><code>macos-latest</code> or <code>macos-11</code></td><td>[macOS-11]</td><td></td></tr><tr><td>macOS 10.15 <sup>deprecated</sup></td><td><code>macos-10.15</code></td><td>[macOS-10.15]</td><td></td></tr><tr><td>Windows Server 2022</td><td><code>windows-latest</code> or <code>windows-2022</code></td><td>[windows-2022]</td><td></td></tr><tr><td>Windows Server 2019</td><td><code>windows-2019</code></td><td>[windows-2019]</td><td></td></tr></tbody></table>
<blockquote>
<p>You can find all the details on GitHub's images here - <a href="https://github.com/actions/runner-images" target="_blank" rel="noopener noreferrer" class="">https://github.com/actions/runner-images</a></p>
</blockquote>
<p>Essentially, these are cloud-hosted environments to run your automation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-hosting">Self Hosting<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#self-hosting" class="hash-link" aria-label="Direct link to Self Hosting" title="Direct link to Self Hosting" translate="no">​</a></h3>
<p>The alternative is to "<strong>Self Host</strong>", simply meaning that instead of using a GitHub server, you will be using your own box, be that a custom-built machine sitting in your cupboard or even your own workstation (having things run in the background seamlessly).  You only need to make sure all the required software is installed (e.g. Unity if you are doing Unity builds and NodeJS if you are running NPM commands) and register your Host as a "GitHub Runner".  If you want, you can even setup <strong>multiple</strong> runners on the same machine for parallelising your workloads (just be careful how many you register so as not to overdo it on a single machine, especially if it is your developer workstation).</p>
<p>Self-Hosting is by far the cheapest option as it is your own hardware running all the tasks and GitHub will not charge you a thing.</p>
<blockquote>
<p><a class="" href="https://zenithmoon.com/blogs/self-hosting-automation-agents">Setting up Self-hosted agents</a></p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="your-first-workflow">Your first workflow<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#your-first-workflow" class="hash-link" aria-label="Direct link to Your first workflow" title="Direct link to Your first workflow" translate="no">​</a></h2>
<p>The GitHub actions <a href="https://github.com/actions/starter-workflows" target="_blank" rel="noopener noreferrer" class="">Starter Workflows</a> repository gives you several "out of the box" solutions for workflows, including:</p>
<ul>
<li class=""><a href="https://darkgenesis.zenithmoon.com/ci" target="_blank" rel="noopener noreferrer" class="">ci</a>: solutions for Continuous Integration workflows</li>
<li class=""><a href="https://darkgenesis.zenithmoon.com/deployments" target="_blank" rel="noopener noreferrer" class="">deployments</a>: solutions for Deployment workflows</li>
<li class=""><a href="https://darkgenesis.zenithmoon.com/automation" target="_blank" rel="noopener noreferrer" class="">automation</a>: solutions for automating workflows</li>
<li class=""><a href="https://darkgenesis.zenithmoon.com/code-scanning" target="_blank" rel="noopener noreferrer" class="">code-scanning</a>: solutions for <a href="https://github.com/features/security" target="_blank" rel="noopener noreferrer" class="">Code Scanning</a></li>
<li class=""><a href="https://darkgenesis.zenithmoon.com/pages" target="_blank" rel="noopener noreferrer" class="">pages</a>: solutions for Pages workflows</li>
</ul>
<p>Once you learn to read and build your own workflows, these are quite easy to follow, take the <a href="https://github.com/actions/starter-workflows/blob/main/ci/dotnet.yml" target="_blank" rel="noopener noreferrer" class=""><strong>DotNet</strong></a> base workflow for example:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># This workflow will build a .NET project</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token comment" style="color:rgb(99, 119, 119);font-style:italic"># For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> .NET</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">on</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">push</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">branches</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"> $default</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">branch </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">pull_request</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">branches</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(199, 146, 234)">[</span><span class="token plain"> $default</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">branch </span><span class="token punctuation" style="color:rgb(199, 146, 234)">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain"></span><span class="token key atrule">jobs</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  </span><span class="token key atrule">build</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">runs-on</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> ubuntu</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">latest</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token key atrule">steps</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/checkout@v3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Setup .NET</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">uses</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> actions/setup</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">dotnet@v3</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">with</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">        </span><span class="token key atrule">dotnet-version</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> 6.0.x</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Restore dependencies</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> dotnet restore</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Build</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> dotnet build </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">no</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">restore</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> Test</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">      </span><span class="token key atrule">run</span><span class="token punctuation" style="color:rgb(199, 146, 234)">:</span><span class="token plain"> dotnet test </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">no</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">build </span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token punctuation" style="color:rgb(199, 146, 234)">-</span><span class="token plain">verbosity normal</span><br></div></code></pre></div></div>
<p>The workflow is made up of several easy-to-understand sections:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-name">1. Name<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#1-name" class="hash-link" aria-label="Direct link to 1. Name" title="Direct link to 1. Name" translate="no">​</a></h3>
<p>Each workflow needs a name, mainly so you can remember what it is for, but also it is what you see when the workflow is running in the Projects <strong>Actions</strong> tab for your repository.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-trigger">2. Trigger<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#2-trigger" class="hash-link" aria-label="Direct link to 2. Trigger" title="Direct link to 2. Trigger" translate="no">​</a></h3>
<p>The <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows" target="_blank" rel="noopener noreferrer" class="">Trigger</a> is an event that occurs in the GitHub API which each workflow will monitor and then run when the conditions are met.</p>
<p>The most common trigger is the "<strong>Push</strong>" (when code is pushed to a branch) or "<strong>Pull_Request</strong>"" (when a PR is raised or updated from a push), however, if you check the <a href="https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows" target="_blank" rel="noopener noreferrer" class="">list of things that can be "triggered"</a> almost anything can be a trigger, from comments, to new issues and more.  Want a workflow that will email you when a new issue is created, or require customers to fill in certain fields when logging an issue, then workflows are there for you.</p>
<p>With most triggers, you also specify a "<a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions" target="_blank" rel="noopener noreferrer" class=""><strong>Condition</strong></a>" to determine the requirements when a trigger occurs, in the example above, the workflow will "run" when either a Pull Request or a Push is made to the "Default Branch" of your repository.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-jobs">3. Jobs<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#3-jobs" class="hash-link" aria-label="Direct link to 3. Jobs" title="Direct link to 3. Jobs" translate="no">​</a></h3>
<p>Jobs are simply a batch of work that will run on a single GitHub Runner (self-hosted or Github server).  Each job is <strong>SELF-CONTAINED</strong> meaning that the work it carries out is isolated from any other work when it performs its tasks (steps).  If two jobs are running on the same machine, they simply do not know about each other and cannot (by default) share work or information.</p>
<p>You can have multiple jobs defined in a single workflow and even have dependencies (Job 1 runs, but Job 2 requires Job 1 to finish successfully to run) between jobs.  There is a lot you can do but for the most part and in the majority of workflows I have developed, one job is usually enough.</p>
<p>In a Job, you define what it <strong>Runs-on</strong> (the host) and then a list of <strong>Steps</strong> (or tasks) to run in sequence.  You can even make "conditional statements" to state "<strong>IF</strong>"" a job should run, e.g. "Run this job IF the users name is X". (conditionals can also be applied to Steps, see later).</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-steps">4. Steps<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#4-steps" class="hash-link" aria-label="Direct link to 4. Steps" title="Direct link to 4. Steps" translate="no">​</a></h3>
<p>Steps are exactly how they sound, like walking, you take one step and then the next (providing you have not fallen down in-between steps).  Steps can also be given names and in most cases require parameters (<strong>With statements</strong>) in order to function, always check the documentation for a GitHub Action if you are unsure.</p>
<p>In the example above the Steps are:</p>
<ol>
<li class="">Checkout the latest code from the branch the workflow was triggered on (actions/checkout@v3)</li>
<li class="">.NET is installed on the host, remembering that you need to validate the environment your action is running on to avoid it failing.  This workflow needs the .NET runtime installed, so we check that it is installed and install it if is not (actions/setup-dotnet@v3), we also pass a parameter in the <strong>with</strong> clause to state which version we need (with: dotnet-version: 6.0.x)</li>
<li class="">We then run the DotNet command to restore any dependencies the project needs (as they are not usually in source) with the run command (run: dotnet restore) which executes in a terminal on the host.</li>
<li class="">With the project and all its dependencies ready, we run the DotNet build command to generate the project (dotnet build --no-restore)</li>
<li class="">Finally we run any testing code in our build to ensure everything is working as expected (dotnet test --no-build --verbosity normal) using the build we just generated.</li>
</ol>
<p>One thing to keep in mind is that <strong>By Default</strong> ANY step that fails will prevent any further steps from being run as GitHub actions fails on any exception.  You can override this (especially if you are building for multiple platforms) if you want, or you can leave it at the default.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="putting-your-github-workflow-into-action">Putting your GitHub workflow into Action<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#putting-your-github-workflow-into-action" class="hash-link" aria-label="Direct link to Putting your GitHub workflow into Action" title="Direct link to Putting your GitHub workflow into Action" translate="no">​</a></h2>
<p>(see what I did there :D )</p>
<p>Once you have created your workflow (or grabbed one from someone else), you simply need to get it "checked-in" to the repository you want it to run on, the primary requirement is that it <strong>MUST</strong> be available on the branch that it is targetting (in your trigger) or in the <strong>Default</strong> branch for anything that uses non-branch specific triggers (e.g. comments / issues).</p>
<blockquote>
<p>If specify a trigger based on 'workflow_dispatch:', you can also run the workflow Manually from the GitHub Actions tab in your repository.  Useful for testing.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#d6deeb;--prism-background-color:#011627"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#d6deeb;background-color:#011627"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#d6deeb"><span class="token plain">on:</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  # Allows you to run this workflow manually from the Actions tab</span><br></div><div class="token-line" style="color:#d6deeb"><span class="token plain">  workflow_dispatch:</span><br></div></code></pre></div></div>
</blockquote>
<p>You just need to place the yml (Yaml script) file into the following directory structure for GitHub to recognise and pick up your workflow:</p>
<p>-&gt; Root
-&gt; .github
-&gt; workflows</p>
<p>Once there and checked-in/committed, it will show up in your repositories <strong>Actions</strong> tab (provided you have enabled the Actions tab in your Project settings)</p>
<p><img decoding="async" loading="lazy" src="https://darkgenesis.zenithmoon.com/assets/img/posts/20221105/02-GitHubActionsTab.png" alt="GitHub Actions Tab" class="img_ev3q"></p>
<p>You will see the list of actions that have been detected by the GitHub API as well as a list of the previous runs and results for each workflow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="too-much-more-to-mention">Too much more to mention<a href="https://zenithmoon.com/blogs/using-github-actions-with-unity#too-much-more-to-mention" class="hash-link" aria-label="Direct link to Too much more to mention" title="Direct link to Too much more to mention" translate="no">​</a></h2>
<p>There is soo much more that can be done with GitHub actions beyond the basics shown here, but you should now have a taste of what is in store, from the basics to the advanced.</p>
<blockquote>
<p>Check the <a href="https://docs.github.com/en/actions" target="_blank" rel="noopener noreferrer" class="">GitHub docs</a> and <a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions" target="_blank" rel="noopener noreferrer" class="">learning portal</a> for more.</p>
</blockquote>
<p>If you need to also check out the costs, have a look at the <a href="https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration" target="_blank" rel="noopener noreferrer" class="">pricing page</a> where you will be nicely surprised just how much time you get for free.</p>
<p>Any questions, leave a comment below.</p>
<aside class="zm-blog-origin">
<p>First published on <a href="https://darkgenesis.zenithmoon.com/using-github-actions-with-unity.html" target="_blank" rel="noopener noreferrer" class="">DarkGenesis</a>. The original post and its comments are there.</p>
</aside>]]></content>
        <author>
            <name>Simon (darkside) Jackson</name>
            <uri>https://github.com/SimonDarksideJ</uri>
        </author>
        <category label="github" term="github"/>
        <category label="automation" term="automation"/>
        <category label="unity3d" term="unity3d"/>
        <category label="github actions" term="github actions"/>
    </entry>
</feed>