<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Nullsson]]></title><description><![CDATA[My programming substack]]></description><link>https://www.oskarmendel.me</link><image><url>https://substackcdn.com/image/fetch/$s_!FrXh!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1647ca12-40b9-4847-b71f-dd72b34f9c50_184x184.png</url><title>Nullsson</title><link>https://www.oskarmendel.me</link></image><generator>Substack</generator><lastBuildDate>Thu, 30 Apr 2026 01:59:28 GMT</lastBuildDate><atom:link href="https://www.oskarmendel.me/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Oskar Mendel]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[oskarmendel@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[oskarmendel@substack.com]]></itunes:email><itunes:name><![CDATA[Oskar Mendel]]></itunes:name></itunes:owner><itunes:author><![CDATA[Oskar Mendel]]></itunes:author><googleplay:owner><![CDATA[oskarmendel@substack.com]]></googleplay:owner><googleplay:email><![CDATA[oskarmendel@substack.com]]></googleplay:email><googleplay:author><![CDATA[Oskar Mendel]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Using Jai's Bindings Generator to Work with C Libraries]]></title><description><![CDATA[TL;DR If you're using Jai and need to interface with a C library, the built-in Bindings Generator makes it super easy to generate usable Jai code from C headers.]]></description><link>https://www.oskarmendel.me/p/using-jais-bindings-generator-to</link><guid isPermaLink="false">https://www.oskarmendel.me/p/using-jais-bindings-generator-to</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Fri, 11 Jul 2025 13:50:53 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5f0b669b-e60c-4424-b354-142888284de4_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2>TL;DR</h2><p>If you're using Jai and need to interface with a C library, the built-in Bindings Generator makes it super easy to generate usable Jai code from C headers. I used it to integrate the `cgltf` library into my 3D prototype, and it saved a ton of time. In this post, I&#8217;ll walk through the setup, show the script I used, and share some real output from the generated bindings.</p><h1>Using the Bindings Generator in Jai</h1><p>When I created Brainroll the goal was to create a game without using any libraries, it was a challenge and a learning experience but not something that I ever intended to do all the time. Sometimes the best approach is just to use a library rather than writing everything yourself. However when using a new language it is not rare for the libraries you find to not be available to you. Luckily Jai has a module available called "Bindings Generator" and it is a tool just for this specific case. This tool parses C/C++ code helping you with generating enums, structs and function declarations in order for you to call into the library using the FFI </p><h2>What Are Bindings?</h2><p>To generate bindings means that you're basically creating an interface to call code and use data structures from a library written in another language. An example is if you're building a simple dynamic library (.DLL or .so) you are not forced to load and call its functions through C, you can do it in almost any language.</p><h2>Using cgltf with Jai</h2><p>Recently, I&#8217;ve been working on a prototype for a 3D first-person game, and I wanted to save some time by using a library to load GLTF scenes. There&#8217;s no Jai library for that yet, so I looked elsewhere and found a C library called **cgltf**, which seemed solid. I decided to give it a try.</p><p>Since it is a C library it is quite trivial to set up a Jai script that generates bindings for it. This is the code I started out with:</p><pre><code>generate_bindings :: () -&gt; bool {
&nbsp; &nbsp; source_file := tprint("%/cgltf.c", SOURCE_PATH);
  
&nbsp; &nbsp; success := true;
&nbsp; &nbsp; extra: [..] string;
&nbsp; &nbsp; array_add(*extra, "-fPIC");
  
&nbsp; &nbsp; success &amp;&amp;= build_cpp_static_lib(lib_path, source_file, target = .WINDOWS, debug = false, extra = extra);
  
&nbsp; &nbsp; if !success
&nbsp; &nbsp; &nbsp; &nbsp; return false;
  
&nbsp; &nbsp; options: Generate_Bindings_Options;
&nbsp; &nbsp; options.os = .WINDOWS;
&nbsp; &nbsp; options.cpu = .X64;
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; using options;
  
&nbsp; &nbsp; &nbsp; &nbsp; array_add(*libnames, "cgltf");
&nbsp; &nbsp; &nbsp; &nbsp; array_add(*source_files, tprint("%/cgltf.h", SOURCE_PATH));
&nbsp; &nbsp; &nbsp; &nbsp; array_add(*typedef_prefixes_to_unwrap, "cgltf_");
  
&nbsp; &nbsp; &nbsp; &nbsp; generate_library_declarations = false;
&nbsp; &nbsp; &nbsp; &nbsp; try_to_preserve_comments = true;
&nbsp; &nbsp; &nbsp; &nbsp; omit_global_declarations = false;
&nbsp; &nbsp; &nbsp; &nbsp; auto_detect_enum_prefixes = true;
&nbsp; &nbsp; &nbsp; &nbsp; log_stripped_declarations = true;
&nbsp; &nbsp; &nbsp; &nbsp; generate_compile_time_struct_checks = false;
&nbsp; &nbsp; }
  
&nbsp; &nbsp; output_filename := "bindings.jai";
&nbsp; &nbsp; return generate_bindings(options, output_filename);
}</code></pre><p>The first part lets us specify flags when building the library into a `.lib` file. Jai provides functions like `build_cpp_static_lib` and `build_cpp_dynamic_lib` to help with this. This code uses clang I believe to compile the C code with the flags we give it.</p><p>The second part is where the bindings generator comes in. If you already have prebuilt libraries, you can skip the first step and only use the generator. It works just as simply. You provide system and architecture info, file paths, and tweak a set of options to customize the output. For example, I had it try to preserve comments.</p><h2>Some output from the generated bindings</h2><p>Here is some examples of what was output in the generated bindings:</p><pre><code>cgltf_file_type :: enum s32 {
&nbsp; &nbsp; invalid &nbsp;:: 0;
&nbsp; &nbsp; gltf &nbsp; &nbsp; :: 1;
&nbsp; &nbsp; glb &nbsp; &nbsp; &nbsp;:: 2;
&nbsp; &nbsp; max_enum :: 3;
  
&nbsp; &nbsp; cgltf_file_type_invalid &nbsp;:: invalid;
&nbsp; &nbsp; cgltf_file_type_gltf &nbsp; &nbsp; :: gltf;
&nbsp; &nbsp; cgltf_file_type_glb &nbsp; &nbsp; &nbsp;:: glb;
&nbsp; &nbsp; cgltf_file_type_max_enum :: max_enum;
}

...

cgltf_buffer :: struct {
&nbsp; &nbsp; name: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *u8;
&nbsp; &nbsp; size: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; u64;
&nbsp; &nbsp; uri: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;*u8;
&nbsp; &nbsp; data: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *void; /* loaded by cgltf_load_buffers */
&nbsp; &nbsp; data_free_method: cgltf_data_free_method;
&nbsp; &nbsp; extras: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cgltf_extras;
&nbsp; &nbsp; extensions_count: u64;
&nbsp; &nbsp; extensions: &nbsp; &nbsp; &nbsp; *cgltf_extension;
}

...

cgltf_parse :: (options: *cgltf_options, data: *void, size: u64, out_data: **cgltf_data) -&gt; cgltf_result #foreign cgltf;

cgltf_parse_file :: (options: *cgltf_options, path: *u8, out_data: **cgltf_data) -&gt; cgltf_result #foreign cgltf;</code></pre><p>As you can see we're getting enumerated types, data structures as well as functions from the public interface in the library. The way of calling a foreign function in Jai is done by using the `#foreign` directive followed by an identifier. The identifier is something that you can declare as a constant somewhere where you're using it. For example in my case I have:</p><pre><code>cgltf :: #library,no_dll "cgltf";</code></pre><p>This creates a global constant named `cgltf`, which refers to the compiled library.</p><h2>Notes on C++ libraries</h2><p>Using bindings like this is a great way to speed up development by leveraging existing tools. I also tried generating bindings for C++ libraries, but that didn&#8217;t go as smoothly. As a workaround, I wrote a C wrapper for the C++ library and then generated bindings for that instead. Even with the extra step, it was still a time-saver compared to writing the whole thing myself.</p><p>For me I wanted to have gizmos in my 3D model viewer to give users a friendly way with modifying size, rotation and position of models. I found a library called <a href="https://github.com/ddiakopoulos/tinygizmo">tinygizmo</a> this library uses a math library with abuses C++ templates which makes the bindings generator confused. Because I didn't find any other good C alternatives for libraries I decided to just to wrap all the C++ code with C functions and explicitly only export the C interface which worked. However for this library I precompiled the libraries using visual studio and only had bindingsgenerator generate bindings nothing else. The code for the C wrapper can be found in my fork here: <a href="https://github.com/brokenprogrammer/tinygizmo">tinygizmo fork</a>.</p><h2>Final thoughts</h2><p>This way of interacting with C in Jai is really one of the make it or break it features that I need in order to use the language for something productive. I really love the bindings generator and I will continue experimenting with it when making progress on my prototype.</p><p>If you are interested in seeting the full bindings generator and generated bindings for cgltf I have published a gist with the full code I use in my project here: <a href="https://gist.github.com/brokenprogrammer/44e03b915d183fbb934662ed9535817b">gist</a>.<br></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Nullsson is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Quake 2 Angelscript: Non-GPL implementations]]></title><description><![CDATA[Detailing the problems and solutions I faced when replacing GPL parts of the Quake 2 codebase.]]></description><link>https://www.oskarmendel.me/p/quake-2-angelscript-non-gpl-implementations</link><guid isPermaLink="false">https://www.oskarmendel.me/p/quake-2-angelscript-non-gpl-implementations</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Tue, 06 May 2025 14:38:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a3e63863-05fa-4c9b-a11a-39b7a7249a04_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As I finished my previous post about looking at Quake 2 I suck around the modding community to learn more. It is still fascinating and inspiring to me that they&#8217;re keeping this old game alive by constantly breathing new life into it with new ideas. As I was sticking around talking to people I noticed how Quake 1 has a slightly more popular community than Quake 2. Part of this is probably because the two games has a very different style to them, the first game is very gothic and lovecraftian while the second game is more in the direction of science fiction. </p><p>Another reason is that Quake 2 has a higher barrier for entry to get started with making your own mods. It is locked behind a native game DLL meaning that in order to modify it you need a native compiler &#8212; C for the original and C++ 17 or 20 for the re-release. This setup can be a big hurdle especially for someone who just want to make small tweaks to the game. In contrast Quake 1 uses Quake C, a modding language that has been around and unchanged for 20~ years that has cross-platform compilers just ready to go. Tools like <code>gmqcc</code> or more modern <code>fteqcc</code> makes it easy to get started without messing with older Visual C++ copies or the newer C++ libraries needed to be setup for the re-release.</p><p>To solve this problem, Paril who is a long time member of the Quake community and also developer on the official re-release projets at Nightdive created a project to port the entire game code into a fully scriptable version which greatly lowers the barrier of entry for modders to get to work on quake stuff. This project uses angelscript, a cross-platform scripting language that is also used within newer versions of the unreal engine. It greatly lowers the development cycle through hot-reloading where you basically only edit your source files and then reload inside the game to directly see changes. In addition it&#8217;s similar to C++ but its still not C++ so hopefully it can be attractive for both programmers and non-programmers.</p><p>At first I was kind of dismissive about this project because for me personally it didn&#8217;t really make sense to use a scripting language and I have mostly been against implementing support for them when I do game development but after reflecting a bit on it I realised that I am not really the main target audience. The goals of this project is more than just shortening the development cycle for existing developers or increasing velocity. It is to breathe even more life into the modding scene and more importantly give Quake 2 the love it deserves. I got in touch with Paril and asked to be brought on board on the project and I have since late february helped with various things to remove load of Paril to help the project progress faster and since thats what has taken up most of my spare-time programming I decided to do a small writeup on what I&#8217;ve done there so far even though its not the most groundbreaking work. </p><p>Some parts of the projects still relies on C++ code, we need code to actually load the angelscript and still keep some parts the interface with the game engine. Some of the code that was left is part of the official re-release DLL codebase which is licensed under GPL and we wanted to rewrite as much of this as possible in order to be able to place it under MIT which can make it easier for other engine developers to consider bringing in support for it to their engines. In order to rewrite these GPL implementations you can&#8217;t really have seen the existing implementation and you have to write a new one blindly so what we did was for Paril to provide me with an API and I basically filled in the blanks to the best of my ability.</p><div><hr></div><h2>Dynamic Bitset</h2><p>Some parts of the Q2 code used std::bitset to keep track of some stuff, one thing that I specifically know is to keep track of who picked up a specific item. Since the code is going to be used from Angelscript we needed another wrapper here and we decided to make our own type rather than using std::bitset and since we want it dynamically growable we will call it dynamic bitset because it will adjust it size depending on where the user attempts to set it's bits.</p><p>So already from the start we kind of knew the interface for this and just to make things like copying easier we decided to use std::vector to store the data using booleans to represent the bits. So basically what we had is this:</p><pre><code>dynamic_bitset(uint n);
dynamic_bitset(dynamic_bitset &amp;in);
dynamic_bitset &amp;opAssign(dynamic_bitset &amp;in);

void clear();
void resize(n);
uint size();

void set_all();
void flip_all();

bool get_bit(uint i);
void set_bit(uint i);
bool opIndex(uint i);

bool any();
bool all();
bool none();
bool opEquals(dynamic_bitset &amp;in);</code></pre><p>This interface is quite trivial to fill in especially when you're using a std::vector as your backing type, I wont fill all the functions in here because I think its not really anything interesting to discuss there so maybe you can consider that a exercise for the reader. </p><p>An interesting challenge for me was what if we cared more about memory? Technically a bool on our system is 1 byte meaning that it has 8 bits that we can controll. Using a full byte per bit like we would in the implementation using the std::vector. Per bit in this implementation we're actually wasting 7 bits that we could make use of if we wanted to. </p><p>Lets play around with the scenario where we are using uint64_t as the backing type and just go back to storing it in a normal array. Now per element in the array we have 64 available bits per element and the problem instead turns out to how we access the individual bits inside the storage.</p><p>Let's create a small fixed size structure just to play around with, this class just keeps 2 integers for us that we can access its bits out of. </p><pre><code>struct bitset
{
&#9;uint64_t bits[2] = {0};
}</code></pre><p>This bitset type has 128 available bytes inside it. If we were to attempt to index a specific bit for example bit number 42 we would first need to find which integer in the bits array that this bit belongs to. This problem is quite trivial because we know the size of each element in the array so the element index is simply one division away: `size_t element_index = target_bit / 64;`</p><p>In order to know which bit on the integer that we're trying to access we can use the modulo operator because it would wrap around for values higher than 64. The only last part is to shift the bit into place and use a bitwise &amp; in order to strip away the bits we're no longer interested in and we can without problem access the bit we're interested in:</p><pre><code>bool get_bit(size_t target_bit)
{
&#9;size_t element_index = target_bit / 64;
&#9;size_t bit_index = target_bit &amp; 64;

&#9;return (bits[element_index] &gt;&gt; bit_index) &amp; 0x1;
}</code></pre><p>The same logic can be applied for when we're setting bits:</p><pre><code>void set_bit(size_t target_bit, bool value)
{
&#9;size_t element_index = target_bit / 64;
&#9;size_t bit_index = target_bit &amp; 64;
&#9;uint64_t mask = (uint64_t(1) &lt;&lt; bit_index);

&#9;bits[element_index] = (bits[element_index] &amp; ~mask) | (uint64_t(value) &lt;&lt; bit_index);
}</code></pre><p>The idea here is that we use a bitmask which is a number with only the specific bits we're targeting is set. This mask is used to clear the target_bit inside the bits array, it works by having the target bit set in the `mask`, flipping the mask and using a bitwise and in order to always set the `target_bit` to zero inside the bits array. After that we are using a bitwise and to set the bit to whatever value that was passed into the parameters for the function. </p><p>If you're not that used to using the bit operators in the C-like languages this might seem pretty confusing but if we break stuff into smaller steps like this its not that hard to put together.</p><p>Something cool with this is that using more C++ features we're not forced to only use a fixed type, we are free to specify both the type and the number of bits we're expected the data structure to have like so:</p><pre><code>template&lt;typename T, size_t number_of_bits&gt;
struct bitset
{ 
&#9;static constexpr size_t bits_per_element = sizeof(T) * 8; 
&#9;static constexpr size_t number_of_elements = (number_of_bits + bits_per_element- 1) / bits_per_element;

&#9;bool get_bit(size_t target_bit);
&#9;void set_bit(size_t target_bit, bool value);
&#9;
&#9;T bits[number_of_elements] = {};
}</code></pre><p>In order to now turn this into a dynamic type we can move the number_of_bits to an internal value that we keep track of rather than passing as a template argument. Growing the bits array using realloc or something will not be that expensive as realistically you won't have to do that too many times, and here you can use the same type of technique as almost any dynamic array implementation uses to double the size everytime it needs a resize. </p><p>This addition to the original std::vector type was a fun little experiment.</p><div><hr></div><h2>Improving JSON API</h2><p>The angelscript implementation makes use of json to store its persistent state for save/load. The library yyjson was used as a backend implementation for parsing and creating json strings. This library is takes the numeric values it finds in string format and assigns it to three possible types:</p><ul><li><p>sint - signed integer.</p></li><li><p>uint - unsigned integer.</p></li><li><p>real - floating point.</p></li></ul><p>This itself is fine and makes sense because you would expect for example the number <code>-123 </code>to be signed and <code>123</code> to be unsigned by default. There is a problem however that arrise when trying to get the numeric values back into a C value because the functions the library provides to check what type a number is gives you back answers based on the three possible values we listed before.</p><p>This means that the library would take the number <code>123</code> but the function <code>yyjson_is_sint</code> would return false because the parsed number was an unsigned value not a signed value even though this value can fit in our signed types.</p><p>To work around this the angelscript code had to make some crazy checks for almost every value it read from the json and it was just extremely cumbersome to work with so my objective was to make the interface easier to work with and less code to write when actually using the library, the point was if we're expecting a <code>uin32_t</code> we should be able to just check if the value fits in that type and if it does just give it back as a <code>uint32_t</code>.</p><p>The yyjson wrapper that the Q2 Angelscript project uses has three different types that we need to take into account, they are <code>q2as_yyjson_mut_val</code>, <code>q2as_yyjson_mut_doc</code> and <code>q2as_yyjson_val</code>.</p><p>The <code>mut_val</code> and <code>mut_doc</code> are used for writing and they are coupled in the way that you feed a <code>mut_doc</code> a set of <code>mut_val</code>'s and then you can allow yyjson to generate a final string based of that. The <code>q2as_yyjson_val</code> type is what you're given back when you're reading a json document. </p><p>Getting started with the project I made an outline of what I needed to do. The doc type didn't need any specifiic logic more than just adding additional overloads for all of the different types that we are looking to support. The val and <code>mut_val</code> both needed verification functions that would allow us to check the underlying type, for example: <code>is_int16</code>. And the val type would also need to include a get function that allows you to directly get a value of desired type.</p><p>Normally when doing this type of work I don't start out with making generic functions as the requirements become more obvious if you make 1-2 hard-typed functions beforehand but for this specific case I just jumped into making a templated function from the start because all the expected types would be numeric and kind of similar.</p><p>So what I did was create a templated function called <code>q2as_type_can_be</code> whose goal is to answer the question: "Can this given type be the same as my templated argument?". This function ended up just more like a wrapper function because both the val and mut_val types uses an union for the three different supported types underneath and to be safe we decided to access the correct value depending on what the type was expected to store.</p><pre><code>template&lt;typename T&gt;
bool q2as_type_can_be(yyjson_val* val)
{
    if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT))
    {
        return q2as_type_in_range&lt;T, uint64_t&gt;(val-&gt;uni.u64);
    }
    else if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT))
    {
        return q2as_type_in_range&lt;T, int64_t&gt;(val-&gt;uni.i64);
    }
    else if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL))
    {
        return q2as_type_in_range&lt;T, double&gt;(val-&gt;uni.f64);
    }

    return false;
}</code></pre><p>So basically this function would just re-use the yyjson functionality of getting the expected type and pass it along to the `q2as_type_in_range` function which I just temporarily stubbed out to be defined as:</p><pre><code>template&lt;typename T, typename D&gt;
bool q2as_type_in_range(D value)
{
    auto max = std::numeric_limits&lt;T&gt;::max();
    auto min = std::numeric_limits&lt;T&gt;::min();
    if (value &lt;= max &amp;&amp; value &gt;= min)
    {
        return true;
    }

    return false;
}</code></pre><p>The templated arguments for this function was made to be T for the target type and D for the given type that yyjson stores. </p><p>For the function that val needed to just retrieve a value of specified type i made the decision to not make verification and instead trust the user to have done so beforehand, this made the resulting function be very simple because we can just return the stored value casted to the desired type like so:</p><pre><code>template&lt;typename T&gt;
T q2as_get_value(yyjson_val* val)
{
    if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT))
    {
        return (T)(val-&gt;uni.u64);
    }
    else if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT))
    {
        return (T)(val-&gt;uni.i64);
    }
    else if (yyjson_get_tag(val) == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL))
    {
        return (T)(val-&gt;uni.f64);
    }

    return 0;
}</code></pre><p>The last part and the part I didn't know had to be this exhaustive is the actual check for if one value's type fits inside another. We have numeric types with the three different properties that yyjson captures, unsigned integers, signed integers and floating point numbers. These are all different things and can't always be freely converted between eachother without loss of data and this loss is what we're checking for. </p><p>The following cases we can have are the following:</p><ul><li><p>Same types</p></li><li><p>Signed integer to signed integer</p></li><li><p>Unsigned integer to unsigned integer</p></li><li><p>Unsigned integer to signed integer</p></li><li><p>Signed integer to unsigned integer</p></li><li><p>Floating point to floating point</p></li><li><p>Integer to floating point</p></li><li><p>Floating point to integer</p></li></ul><p>Same types is the first and very trivial case as the value will always fit inside its own type so this is a simple check we can do and return early if it is the case.</p><pre><code>if constexpr (std::is_same_v&lt;TargetType, SourceType&gt;)
{
&#9;return true;
}</code></pre><p>When working with checking the types the C++ numeric_limits library has a lot of the helpers that we need to get a jump start. I used it to first check if the source or target types are integers as well as if they're signed or unsigned. I also used it to get the min and max values for our target type that we can use for checks later.</p><pre><code>constexpr bool is_target_integer = std::numeric_limits&lt;TargetType&gt;::is_integer;
constexpr bool is_source_integer = std::numeric_limits&lt;SourceType&gt;::is_integer;
constexpr bool is_target_signed = std::numeric_limits&lt;TargetType&gt;::is_signed;
constexpr bool is_source_signed = std::numeric_limits&lt;SourceType&gt;::is_signed;

constexpr TargetType max = std::numeric_limits&lt;TargetType&gt;::max();
constexpr TargetType min = std::numeric_limits&lt;TargetType&gt;::lowest();</code></pre><p>Having these variables makes it quite trivial to handle the different cases. First check I do is to see if both types are integers. If they are I can check for the different integer cases that we've prepared before. </p><p>If both integers are signed we just need to check that the value we've given fits between the min and max value of the target type. </p><p>If both are unsigned we just need to check that the value does not exceed the max limit of the target type. Here we do not need to care about the min value because all unsigned integers has the same min value and it cannot go bellow that.</p><p>If we're converting a unsigned to signed it is a similar case, we just need to check that the value is under the max value of the source type as the lowest values of an unsigned value will always fit in a signed one. Here I also decided to use static cast to the source type in order to prevent unsigned to signed promotion.</p><p>Finally if we're converting a signed value to unsigned we need to check that the value is above zero and less than or equals to the max value.</p><pre><code>if constexpr (is_target_integer &amp;&amp; is_source_integer)
{
&#9;if constexpr (is_target_signed &amp;&amp; is_source_signed)
&#9;{
&#9;&#9;return value &lt;= max &amp;&amp; value &gt;= min;
&#9;}

&#9;if constexpr (!is_target_signed &amp;&amp; !is_source_signed)
&#9;{
&#9;&#9;return value &lt;= max;
&#9;}

&#9;if constexpr (is_target_signed &amp;&amp; !is_source_signed)
&#9;{
&#9;&#9;return value &lt;= static_cast&lt;SourceType&gt;(max);
&#9;}

&#9;if constexpr (!is_target_signed &amp;&amp; is_source_signed)
&#9;{
&#9;&#9;return value &gt;= 0 &amp;&amp; static_cast&lt;uint64_t&gt;(value) &lt;= static_cast&lt;uint64_t&gt;(max);
&#9;}
}</code></pre><p>If both the target and source type is of floating type we can't do the exact same as for integers. The easiest solution I found was to just cast the values from source to target and perform a check if the value stays the same. Like this:</p><pre><code>if constexpr (!is_target_integer &amp;&amp; !is_source_integer)
{
&#9;if constexpr (std::is_same_v&lt;TargetType, float&gt;)
&#9;{
&#9;&#9;if (std::isinf(value) || std::isnan(value))
&#9;&#9;{
                return false;
&#9;&#9;}

&#9;&#9;float f_value = static_cast&lt;float&gt;(value);

&#9;&#9;// Check for loss of precision.
&#9;&#9;if (value != static_cast&lt;double&gt;(f_value))
&#9;&#9;{
                return false;
&#9;&#9;}
&#9;}

&#9;return value &lt;= max &amp;&amp; value &gt;= min;
}</code></pre><p>Thge check for floating point is only there because if we're trying to put a double into a float. Remember that the function that calls this can only send in uint64_t, int64_t and double as the source types because that is what we're getting from yyjson so we're allowed to make some assumptions here.</p><p>Integer to floating point is actually exactly the same, we perform a static_cast and check for loss of precision and wether the value fits within the min and max ranges for the target type, its really nothing more to it.</p><pre><code>if constexpr (!is_target_integer &amp;&amp; is_source_integer)
{
&#9;if constexpr (std::is_same_v&lt;TargetType, float&gt;)
&#9;{
&#9;&#9;float f_value = static_cast&lt;float&gt;(value);

&#9;&#9;// Check for loss of precision.
&#9;&#9;if (value != static_cast&lt;double&gt;(f_value))
&#9;&#9;{
&#9;&#9;&#9;return false;
&#9;&#9;}

&#9;&#9;return f_value &lt;= max &amp;&amp; f_value &gt;= min;
&#9;}
&#9;else
&#9;{
&#9;&#9;// Check for loss of precision.
&#9;&#9;if (static_cast&lt;SourceType&gt;(static_cast&lt;double&gt;(value)) != value)
&#9;&#9;{
&#9;&#9;&#9;return false;
&#9;&#9;}

&#9;&#9;double d_value = static_cast&lt;double&gt;(value);
&#9;&#9;return d_value &lt;= max &amp;&amp; d_value &gt;= min;
&#9;}
}</code></pre><p>The final step is floating point to an integer value and this is really not that different, the only edge case here is that we need to remember that if the floating point number contains decimals it will never be able to be represented as an integer hence we need to perform that check in addition to if the range is within bounds.</p><p>In order to check if the value has decimals we use std::trunc on the value and check if the value stays the same. The rason we use trunc floor is because when passing negative values to floor it rounds them down a full number rather than  just removing the decimals, almost like an inverted ceil.</p><p>The final code ends up like this:</p><pre><code>if constexpr (is_target_integer &amp;&amp; !is_source_integer)
{
&#9;if (std::isinf(value) || std::isnan(value))
&#9;{
&#9;&#9;return false;
&#9;}

&#9;// Don't allow decimals
&#9;if (std::trunc(value) != value)
&#9;{
&#9;&#9;return false;
&#9;}

&#9;if constexpr (is_target_signed)
&#9;{
&#9;&#9;double double_max = static_cast&lt;double&gt;(max);
&#9;&#9;double double_min = static_cast&lt;double&gt;(min);

&#9;&#9;return value &lt;= double_max &amp;&amp; value &gt;= double_min;
&#9;}

&#9;return value &gt;= 0 &amp;&amp; value &lt;= static_cast&lt;double&gt;(max);
}</code></pre><p>After this all I needed to do was add calls to the `q2as_type_can_be` with different type arguments in order to add the checks we needed for the project and it turned out quite nice. If you&#8217;re interested in seeing the full code you can view the <a href="https://gist.github.com/brokenprogrammer/c392ab0d12be5b0c00db74e5f56ac57c">gist here</a>.</p><div><hr></div><h2>gtime_t</h2><p><code>gtime_t </code>represents a timespan. The backing storage is a simple <code>int64_t</code> of milliseconds. The interface that angelscript uses is the following:</p><pre><code>class gtime_t
{
&#9;// properties
&#9;int64 milliseconds;
&#9;// behaviors
&#9;gtime_t(int64, timeunit_t);
&#9;gtime_t(float, timeunit_t);
&#9;gtime_t(const gtime_t&amp;in);
&#9;// methods
&#9;int64 secondsi() const;
&#9;float secondsf() const;
&#9;int64 minutesi() const;
&#9;float minutesf() const;
&#9;int64 frames() const;
&#9;bool opEquals(const gtime_t &amp;in) const;
&#9;int32 opCmp(const gtime_t &amp;in) const;
&#9;gtime_t &amp;opAssign(const gtime_t &amp;in);
&#9;gtime_t opSub(const gtime_t &amp;in) const;
&#9;gtime_t opAdd(const gtime_t &amp;in) const;
&#9;gtime_t opDiv(const int32 &amp;in) const;
&#9;gtime_t opMul(const int32 &amp;in) const;
&#9;gtime_t opDiv(const float &amp;in) const;
&#9;gtime_t opMul(const float &amp;in) const;
&#9;gtime_t opNeg() const;
&#9;gtime_t &amp;opSubAssign(const gtime_t &amp;in);
&#9;gtime_t &amp;opAddAssign(const gtime_t &amp;in);
&#9;gtime_t &amp;opDivAssign(const int32 &amp;in);
&#9;gtime_t &amp;opMulAssign(const int32 &amp;in);
&#9;gtime_t &amp;opDivAssign(const float &amp;in);
&#9;gtime_t &amp;opMulAssign(const float &amp;in);
&#9;bool opConv() const;
}</code></pre><p>I've not done a lot of work with time before but I got the suggestion to just make a wrapper around std::chrono because it contains basically anything needed for this to work as intended. Which I am fine with as I'm learning about the problem.</p><p>The chrono library in C++ contains a little bit about everything when it comes to time. There is a clock type, time point, duration, calendar daets and timezone information. We're mostly interested in the duration part of the library as the description from the official documentation is</p><blockquote><p>A duration consists of a span of time, defined as some number of ticks of some time unit. For example, "42 seconds" could be represented by a duration consisting of 42 ticks of a 1-second time unit.</p></blockquote><p>Fortunate for us it already contains a milliseconds helper type <code>std::chrono::milliseconds</code> whose definition is<code> std::chrono::duration&lt;/* int45 */,&nbsp;std::milli&gt;</code> where <code>int45</code> stands for an integer type of at least 45 bits. When using this type on an x64 machine it uses <code>long long</code> by default. the <code>std::milli</code> type is a helper type for <code>std::ratio</code> which is a C++ class used to perform rational arithmetic at compile time. A millisecond is the same as one thousandth (1/1000) hence as you can imagine the ratio for milli is defined as <code>std::ratio&lt;1, 1000&gt;</code>. </p><p>When looking this up I found something pretty crazy and that is that this library defines several literals to help you use the library. For example the library accepts values with the suffix of h for hours, min for minutes, s for seconds etc. Which means that you can write code like:</p><pre><code>std::cout &lt;&lt; "5 seconds is: " &lt;&lt; std::chrono::milliseconds(5s).count() &lt;&lt; " milliseconds.\n";</code></pre><p>I didn't even know you can do this but well, proven once again that C++ can do anything.</p><p>Anyway with this we can start with creating out new struct. I decided to name it <code>q2as_gtime</code> just to be clear that its a part of q2as. </p><pre><code>struct q2as_gtime
{
&#9;// properties
&#9;using milliseconds = std::chrono::milliseconds;
&#9;milliseconds _duration;
};</code></pre><p>If we look back to the behaviours of the Angelscript class we can see that it expects three different constructors, two of which expects a new type called `timeunit_t` however when looking at the C++ side of things the methods that it registers for these constructors is a helper function that just creates a new gtime using `from_*` methods that we will get to later. </p><pre><code>template&lt;typename T&gt;
static void Q2AS_gtime_t_timeunit_construct(T value, timeunit_t unit, gtime_t *t)
{
    if (unit == timeunit_t::ms)
        *t = gtime_t::from_ms((int64_t) value);
    else if (unit == timeunit_t::sec)
        *t = gtime_t::from_sec(value);
    else if (unit == timeunit_t::min)
        *t = gtime_t::from_min(value);
    else
        *t = gtime_t::from_hz(value);
}</code></pre><p>It is defined as a template because we intend to call it with `int64` and `float` which the Angelscript definition displays.</p><p>So setting those constructors aside for now we can be satisfied with just adding a normal constructor as well as the copy constructor that Angelscript expects.</p><pre><code>struct q2as_gtime
{
&#9;// properties
&#9;using milliseconds = std::chrono::milliseconds;
&#9;milliseconds _duration;
&#9;
&#9;// behaviours
&#9;q2as_gtime() = default;
&#9;q2as_gtime(const q2as_gtime&amp;) = default;
};</code></pre><p>Next up is the methods part of the definition. The next 5 methods defined in Angelscript are getter methods used to get whetever value is inside the gtime and return it in the expected format. This is where we can use the duration library to just do all the work for us. </p><p>In order to take one duration and convert it to another we just supply the old duration to the constructor of the new one with the expected format. </p><pre><code>int64_t seconds() const 
{
    return std::chrono::duration_cast&lt;std::chrono::duration&lt;int64_t&gt;&gt;(_duration).count();
}

float secondsf() const 
{
    return std::chrono::duration_cast&lt;std::chrono::duration&lt;float&gt;&gt;(_duration).count();
}</code></pre><p>We do not need to supply the second template argument to duration because the base time represented in duration is seconds which is the same as <code>std::ratio&lt;1, 1&gt;</code> in this case. Doing the same for the minutes methods requires a slight change because now we are actually converting the duration to another period. Relative to seconds a minute is a ratio of 60/1 so our new methods becomes:</p><pre><code>int64_t minutes() const 
{
    return std::chrono::duration_cast&lt;std::chrono::duration&lt;int64_t, std::ratio&lt;60&gt;&gt;&gt;(_duration).count();
}

float minutesf() const 
{
    return std::chrono::duration_cast&lt;std::chrono::duration&lt;float, std::ratio&lt;60&gt;&gt;&gt;(_duration).count();
}</code></pre><p>The last method to add is the frames whose job is to translate the gtime to a duration relative to the server frame time in the engine. The server frame time is globally accessible so we can just implement it by dividing the duration based on that.</p><pre><code>int64_t frames() const { return _duration.count() / gi.frame_time_ms; }</code></pre><p>The last methods that we need to implement in order to complete the interface are just the operator overloads and there is really no magic behind these we already have the underlying duration type which already has these overloads prepared for us so we just need to wrap them all in addition to the occasional cast to int64_t just to be safe cause the underlying duration that we created uses int64_t to store its value.</p><pre><code>q2as_gtime operator+(const q2as_gtime&amp; rhs) const
{
    return q2as_gtime(_duration + rhs._duration);
}

template&lt;typename T, typename = std::enable_if_t&lt;std::is_arithmetic_v&lt;T&gt;&gt;&gt;
q2as_gtime operator*(const T&amp; rhs) const
{
&#9;return q2as_gtime(_milliseconds(static_cast&lt;int64_t&gt;(_duration.count() * rhs)));
}

bool operator==(const q2as_gtime&amp; rhs) const
{
&#9;return _duration == rhs._duration;
}

// Basically the same for the other operators..</code></pre><p>The final thing that we did put off for later was the <code>from_ms</code>, <code>from_sec</code>, <code>from_min</code> and <code>from_hz</code> methods that the <code>Q2AS_gtime_t_timeunit_construct</code> helper function uses in order to  construct different gtimes. </p><p>These are not really different compared to how we made minutes, seconds or any other method. Here we will also utilize the duration_cast but we will always cast it to our milliseconds type, for example:</p><pre><code>template&lt;typename T&gt;
static q2as_gtime from_sec(const T&amp; seconds)
{
&#9;return q2as_gtime(std::chrono::duration_cast&lt;std::chrono::milliseconds&gt;(std::chrono::duration&lt;T&gt;(seconds)));
}</code></pre><p>This was really all work needed before swapping angelscript to use our new type. I proceeded with running the game and it worked as expected.</p><div><hr></div><p>This project is very fun to be a part of, it is very programmer heavy and solution oriented and there is tons of interesting problems to tackle still. If you&#8217;re interested in the Quake 2 Angelscript project you can find it on <a href="https://github.com/Paril/quake2-angelscript">Github</a>.</p><p>I am going on a 2 week motorcycle trip in the end of this month so I will probably not be able to prepare anything new to read on this website for a while but I plan on writing a bit about a model viewer I&#8217;ve been working on and posting about on twitter.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Nullsson is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Diving into Quake 2: Building a UI library for modders]]></title><description><![CDATA[Exploring the Quake 2 codebase by creating my first mod, a reusable UI library for modders.]]></description><link>https://www.oskarmendel.me/p/diving-into-quake-2-building-a-ui</link><guid isPermaLink="false">https://www.oskarmendel.me/p/diving-into-quake-2-building-a-ui</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Mon, 10 Mar 2025 20:29:54 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/75454c28-3ba7-40ae-8ccd-9d1720cb8749_2558x1437.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am not an old-school Quake player, I think the first Quake I ever tried was Quake-Live when it was playable through the browser and even then I think I just tried it for like a minute or two. In recent years I have often drifted towards older games, there is a certain magic with the arcade-like feeling that most old games have where there is close to zero realism and most design decisions are just revolved around fun. What really sparked my interest for Quake was the release of the Quake 2 Remaster, I bought this at a lan party and was surprised that it didn&#8217;t only have multiplayer in the form of some PvP deathmatch but also a Coop campaign.</p><p>We played the campaign for hours and during that time I grew really fond of the movement in the game, if you play the game like a normal shooter like half-life 2 or something the game won&#8217;t be that special (at least to me) but if you play it more like team-fortress 2 where you attempt to bunny jump and rocket jump in a way where you try to gain and maintain as much speed as possible it becomes really fun. For me this created somewhat of a subgame where I try to challenge myself to go as fast as I can all the time, not in a speedrun type of way where I am rushing to beat the game but rather in a fun type of way where you try to go fast and land cool trickshots.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This website is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Anyway after this evening I&#8217;ve been hooked on Quake in one way or another, currently my go-to game in the evenings is Quake Champions where I can just hop on for a quick deathmatch and close it down when I need to catch my breath.</p><div><hr></div><p>After finishing my latest post, I started thinking about what I wanted to do next. I could spend more time on my own engine, but that felt like an endless project. I thought about Brainroll and the effort it took me to get to a point where I was confident enough to put it on Steam and making a new game in 3D would probably be ten times the work. At the same time I was battling with some sort of burnout and lack of motivation and needed something to build that spart back up for me to get back into working on my own stuff. This got me thinking about using another engine and build something from that, this would mean that I can focus on game-code rather than building the engine and then building the game. I liked the idea of it but I didn&#8217;t like most of the modern alternatives available, I still prefer something that is code-first like my own engine and while you probably can do that in Unity, Godot and Unreal I just don&#8217;t feel like that&#8217;s the move right now. That got me thinking back to Quake, I remember that Quake was released as open source and after doing a bit of research I found a whole sea of Quake 1, 2 and 3 ports with modern codebases and some of them was so simple to get up and running that I just started tinkering a bit with the engine and after that I was kind of sold. </p><p>Quake 1 uses it&#8217;s own version of C called QuakeC and while I have nothing against it I kind of want to avoid it if I can. I rather just use C/C++ like I&#8217;m used to and also comes with the benefit of me being able to re-use the code I write for other projects. And after looking at some Quake 2 source ports I found that it&#8217;s a better match for me, which is great since its basically my first introduction to Quake.</p><p>I still don&#8217;t know for sure if I&#8217;m 100% going to use a Quake engine for my next game but I did decide to do something with it in order to learn and the first thing I decided to do was to create some sort of mod. I joined a community discord channel called &#8220;Map-Center&#8221; and talked a bit to the people in there, one of the guys I talked to (which turned out actually worked on developing the Quake 2 Remaster) had made a mod called &#8220;Horde mode&#8221; which sounded really cool, I loaded it up and it was a basically working horde mode where waves of monsters spawn and you need to kill them over and over again. This got me thinking a bit and I was curious about expanding that horde mode into some sort of vampire survivors type of gamemode with levels and upgrades. I decided that Im going to work towards that idea but dividing the idea into several smaller parts and since I have done a lot of UI work on Brainroll and in my own engine I decided to start with building something like what I had in my engine for Quake 2. This was also a good idea because if I end up using the Quake 2 engine for my own game I could re-use the library for my own project.</p><div><hr></div><p>Before you dive into the Quake 2 codebase it is important to have an idea of the architecture of the engine. I won&#8217;t go into depth about the engine but it is important to know that it operates on a client-server model. In Quake 2 the client and server is run in the same executable meaning that evertime you run the engine the server will always run as well (this can vary depending on which source port you&#8217;re using). All the game specific logic is built as server-side logic meaning that when making mods you&#8217;re modifying server-side code. On top of this the engine is built to dynamically load the game as a dll, meaning that as long as you implement the expected interface you can replace the game code and the renderer uses the same structure which makes it easy to port and modify. This structure is actually really clever for many reasons, one of which I&#8217;ve come to really love is that if I make my own mod and host a server for my friends to join, they can also play that mod with me without requiring them to set it up themselves.</p><p>Before diving into implementation, I had to understand how UI works in Quake 2. Unlike other engines, where UI is handled with direct access to rendering APIs, Quake 2 mods operate within strict limitations. The game-code (server-side) has no knowledge of the client&#8217;s screen size and no built-in functions for rendering UI. At first, this seemed like a deal-breaker. How do you build a UI when you can&#8217;t even draw directly to the screen?<br>To solve this, I explored how existing UIs in the game like the inventory and CTF team selection screen were implemented. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nJOv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nJOv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 424w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 848w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 1272w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nJOv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png" width="1040" height="773" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:773,&quot;width&quot;:1040,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100951,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nJOv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 424w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 848w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 1272w, https://substackcdn.com/image/fetch/$s_!nJOv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0cb815f-c3f5-4496-aaa2-fa5e1d0fc836_1040x773.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Quake 2 Inventory screen</figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MHU8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MHU8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 424w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 848w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 1272w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MHU8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png" width="1044" height="772" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:772,&quot;width&quot;:1044,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66936,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MHU8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 424w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 848w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 1272w, https://substackcdn.com/image/fetch/$s_!MHU8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52d7593-e025-4e92-ae44-e2a44c44dca2_1044x772.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Quake 2 CTF team select screen</figcaption></figure></div><p>This gave me something to go on because both of these are more or less accessible for a mod. The better of the two to look at is the CTF game mode&#8217;s source because it has actually built a somewhat re-usable code to build structured interfaces like the image above. It has support for various text and buttons which is a perfect starting point so I took a look at how it works. It turns out that Quake 2 has a built-in layouting language that sends UI elements as strings to the client through network messages. The client then parses these messages into elements and renders them to the screen. Once you spend some time to try understand the layouting language it is very simple, it is built using two positional tokens and values followed by a UI element for the renderer to draw, example: <code>xl 0 yt 0 string &#8220;Hello World&#8221;</code> which would translate to &#8220;Draw a string at position 0 from the left and 0 from the top&#8221;. There are several positional modifiers for each axis that can help with structuring and positioning elements on the screen.</p><ul><li><p>xl - X Left; Offset from left side of the screen.</p></li><li><p>xr - X Right; Offset from right side of the screen.</p></li><li><p>xv - X Virtual; Offset relative to a virtual 320x240 screen.</p></li><li><p>yt - Y Top; Offset from the top of the screen.</p></li><li><p>yb - Y Bottom; Offset from the bottom of the screen.</p></li><li><p>yv - Y ;Virtual Offset relative to a virtual 320x240 screen.</p></li></ul><p>When it comes to UI elements the layout has support for a lot of different commands to draw strings, images numbers and tables, . It even has complex nesting logic where you can embed if-statements into the logic however I did not play around with those.</p><p>This system is kind of what saved the idea for me to build the library because if I can render images and text I can overlay them in a way to build buttons etc for interesting interfaces. On top of all of this a very KEY factor for me is that I was working with the <a href="https://github.com/id-Software/quake2-rerelease-dll">re-release version of the Quake 2 game code</a>. This release was done using a slightly modified version of the Quake 2 engine which now also moves some of the client code into the game dll, specifically user interface rendering. That means that in my mod I have access to modifying the layout language as I please which helps a ton!</p><div><hr></div><p>My next goal became to build some sort of scratch area that I can use as some sort of playground when building the library. At first it was not obvious where this would be within the source code but I took a look at what exactly brought the inventory screen up for a player and it turns out all of this is build using &#8220;commands&#8221;. The commands are client commands that can be triggered from button presses or console commands and is interpreted by the server which then can do something for a client. The parsing of these commands is a long chain of if/else statement so I simply just added a new case to parse the command &#8220;customUI&#8221; along with the function to call if the player wrote that in their console. </p><p>The command to bring up the player&#8217;s inventory is &#8220;inven&#8221;, I originally looked at this command but it uses a special network message called <code>svc_inventory</code> which does not use layout strings so while I couldn&#8217;t mimic this function to 100% I could still use it as sort of a baseline for how to setup a command. Looking at the CTF code it sends it strings to the client with a message called <code>svc_layout</code>. Knowing this it was very simple to setup a small experiment that displays a small string on the screen.</p><pre><code>void Cmd_CustomUI_f(edict_t* ent)
{
&#9;int&#9;&#9;   i;
&#9;gclient_t* cl;

&#9;cl = ent-&gt;client;

&#9;cl-&gt;showscores = false;
&#9;cl-&gt;showinventory = false;
&#9;cl-&gt;showhelp = false;

&#9;globals.server_flags |= SERVER_FLAG_SLOW_TIME;

&#9;if (cl-&gt;showCustomUI)
&#9;{
&#9;&#9;cl-&gt;showCustomUI = false;
&#9;&#9;globals.server_flags &amp;= ~SERVER_FLAG_SLOW_TIME;
&#9;&#9;return;
&#9;}

&#9;cl-&gt;showCustomUI = true;

&#9;std::string UIString = "xl 0 yt 0 string \"Hello World\"";
&#9;gi.WriteByte(svc_layout);
&#9;gi.WriteString(UIString.c_str());
&#9;gi.unicast(ent, true);
}</code></pre><p>In order to keep displaying the layout on the screen I mimiced inventory and created my own boolean for <code>showCustomUI</code> inside the <code>g_client_t</code> structure. For this to take effect there is a check in <code>G_SetStats</code> where the variable needs to be added to a check.</p><pre><code>if (ent-&gt;client-&gt;showscores || ent-&gt;client-&gt;showhelp || ent-&gt;client-&gt;showeou || ent-&gt;client-&gt;showCustomUI)
&#9;ent-&gt;client-&gt;ps.stats[STAT_LAYOUTS] |= LAYOUTS_LAYOUT;</code></pre><p>And that is all we needed to set up in order to get our first custom layout drawing to the screen.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FW_F!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FW_F!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FW_F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2664863,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FW_F!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!FW_F!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd26d48a2-8516-4155-a2f1-4786172e3418_2559x1439.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Our layout string displaying &#8220;Hello World&#8221; in the top left corner.</figcaption></figure></div><p>Just because I&#8217;m a curious person I looked a bit into the limits of using this approach to render stuff to the screen. The client code is in some ways tied to the server code, this means that there are some structures that we can&#8217;t change. Creating our own network messages would require client modification to parse the new message which we can&#8217;t do and modifying the structure holding the parsed server data would also require client changes. Someone mentioned to me that there is something called configstrings which are a general way of sending stuff to clients, it has a flag called <code>CS_STATUSBAR</code> which is actually where the original HUD is kept. This configstrings are limited to 5184 bytes while a layout sent through <code>SVC_LAYOUT</code> is limited to 1024. There is also no way of batching a layout by sending several chained network messages so the hard cap for our network approach is 1024 bytes. </p><p>My idea here is if I were to hit the cap using network messages I could later change to using configstrings but for now it wasn&#8217;t an issue. Something I thought of was that later I could perhaps include some compression library to compress the layout strings before sending them, or just modify the layout language enough for it to take way less space, I set myself up to have some options basically.</p><div><hr></div><p>At this point I felt like I had enough information to actually start writing some code for my UI library. I had given it some thought and I wanted to make use of a very simple layouting algorithm as the one I had in Brainroll was too complex and required more information about the client unless I re-structured big parts of it which I did not feel like doing. This reminded me of a layout algorithm called <a href="https://halt.software/p/rectcut-for-dead-simple-ui-layouts">rect cut</a> which I had been wanting to try out for some time and it felt like a pretty great match actually because it operates on rectangles which the Quake 2 layout language also does.</p><p>The original article explains the algorithm very well so I&#8217;ll refer you to that if you&#8217;re intereted in exactly how it works but the gist of it is that you defined a rectangle and then cut that rectangle into smaller rectangles in order you build your UI.</p><p>So I implemented the very simple base for rect cut and created my own small layout</p><pre><code>struct imq2_rect
{
    float MinX;
    float MinY;
    float MaxX;
    float MaxY;
};

imq2_rect CutLeft(imq2_rect *Rectangle, float Value)
{
    float MinX = Rectangle-&gt;MinX;
    Rectangle-&gt;MinX = min(Rectangle-&gt;MaxX, Rectangle-&gt;MinX + Value);

    imq2_rect Result = { MinX, Rectangle-&gt;MinY, Rectangle-&gt;MinX, Rectangle-&gt;MaxY };
    return (Result);
}

imq2_rect CutRight(imq2_rect *Rectangle, float Value)
{
    float MaxX = Rectangle-&gt;MaxX;
    Rectangle-&gt;MaxX = max(Rectangle-&gt;MinX, Rectangle-&gt;MaxX - Value);

    imq2_rect Result = { Rectangle-&gt;MaxX, Rectangle-&gt;MinY, MaxX, Rectangle-&gt;MaxY };
    return (Result);
}

imq2_rect CutTop(imq2_rect *Rectangle, float Value)
{
    float MinY = Rectangle-&gt;MinY;
    Rectangle-&gt;MinY = min(Rectangle-&gt;MaxY, Rectangle-&gt;MinY + Value);

    imq2_rect Result = { Rectangle-&gt;MinX, MinY, Rectangle-&gt;MaxX, Rectangle-&gt;MinY };
    return (Result);
}

imq2_rect CutBottom(imq2_rect *Rectangle, float Value)
{
    float MaxY  = Rectangle-&gt;MaxY;
    Rectangle-&gt;MaxY = min(Rectangle-&gt;MinY, Rectangle-&gt;MaxY - Value);

    imq2_rect Result = { Rectangle-&gt;MinX, Rectangle-&gt;MaxY, Rectangle-&gt;MaxX, MaxY };
    return (Result);
}</code></pre><p>As you can see the code is very straight forward. The function accepts a rectangle, cuts the rectangle by the specified amount and returns it as a new rectangle. This works by mutating the given rectangle meaning that if we cut in the same direction several times we will be forming several columns in a row and through this can build more complex layouts.</p><p>At this point I was ready to draw the rectangles using the layout system. There was one problem however, there is no way to specify bounds of a rectangle through layout system in Quake. If you are to draw an image the image will be drawn in its full size scaled by the resolution, there is no way for you to give it a specific width and height. This is something that I needed for my stuff to work but luckily I had access to the parsing of the layout language and could simply add two new tokens <code>w</code> and <code>h</code> which lets you specify the width and height of the element you&#8217;re building. Of course none of the existing elements uses a width and height but there is also no element to just draw a colored rectangle so I added a new command for that called <code>picc</code>. With this in place I could in a very simple way translate a rectangle to it&#8217;s layout string through my awesome new FormatRect function.</p><pre><code>std::string FormatRect(imq2_rect Rect, int Color)
{
&#9;float x = Rect.MinX;
&#9;float y = Rect.MinY;
&#9;float w = Rect.MaxX - Rect.MinX;
&#9;float h = Rect.MaxY - Rect.MinY;

&#9;return fmt::format("xl {} yt {} w {} h {} picc {}", x, y, w, h, Color);
}</code></pre><p>In a very simple way here we are just translating our imq2_rect into <code>x, y, w, h</code> and use our new <code>picc </code>command along with the color value <code>0-255</code> for how bright it should be. </p><pre><code>// Inside my CustomUI command
imq2_rect Layout = { 0, 0, 180, 16 };

imq2_rect R1 = CutLeft(&amp;Layout, 16);
imq2_rect R2 = CutLeft(&amp;Layout, 16);
imq2_rect R3 = CutLeft(&amp;Layout, 16);

imq2_rect R4 = CutRight(&amp;Layout, 16);
imq2_rect R5 = CutRight(&amp;Layout, 16);

std::string UIString = "";
UIString += FormatRect(Layout, 0);
UIString += " ";
UIString += FormatRect(R1, 25);
UIString += " ";
UIString += FormatRect(R2, 50);
UIString += " ";
UIString += FormatRect(R3, 75);
UIString += " ";
UIString += FormatRect(R4, 100);
UIString += " ";
UIString += FormatRect(R5, 125);

gi.WriteByte(svc_layout);
gi.WriteString(UIString.c_str());
gi.unicast(ent, true);</code></pre><p>Putting this all together we can create a very simple layout to test our changes. Here I am creating an initial layout rectangle and cut three 16 pixels wide rectangles from it&#8217;s left side and then I cut two more from it&#8217;s right side. Then I call <code>FormatRect </code>on all of the rectangles using different brightness for them. When running the game this would produce this black bar with small grey rectangles on top of it, 3 from the left and 2 from the right.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UEP-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UEP-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 424w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 848w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 1272w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UEP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png" width="767" height="95" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:95,&quot;width&quot;:767,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31954,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UEP-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 424w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 848w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 1272w, https://substackcdn.com/image/fetch/$s_!UEP-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01629b0f-def8-41e9-b16e-3914b526fdb4_767x95.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">First test of the UI library</figcaption></figure></div><p>After this initial confirmation that the idea works I could now proceed with implementing more stuff. I borrowed the idea of having a definition of generic UI elements from the Brainroll source code. From a mod&#8217;s perspective I don&#8217;t believe that you have a single definition for buttons across mods hence I instead focused more on having a good interface for defining your own elements. Just like how Brainroll does I use a mega struct containing all the data you would need for a UI element and dictate how the element is rendered and behaving using a set of flags.</p><pre><code>typedef uint64_t imq2_element_flags;
enum
{
    Element_Flag_Clickable         = (1&lt;&lt;0),
    Element_Flag_DrawBackground    = (1&lt;&lt;1),
    Element_Flag_DrawPic           = (1&lt;&lt;2),
    Element_Flag_DrawText          = (1&lt;&lt;3),
    Element_Flag_DrawBackgroundPic = (1&lt;&lt;4),
};

struct imq2_ui_element
{
    uint64_t Index;
    bool Initialized;

    imq2_rect Rectangle;
    const char *String;
    imq2_element_flags Flags;
    int ColorValue;
    const char *PicName;
};

struct imq2
{
    imq2_ui_element Elements[256];
    uint64_t ElementCount;
};

void IMQ2ElementCreate(imq2 *UI, imq2_element_flags Flags, const char *String, const char *PicName, imq2_rect Rectangle)
{
    imq2_ui_element *Element = UI-&gt;Elements + UI-&gt;ElementCount++;
    Element-&gt;Index = UI-&gt;ElementCount - 1;
    Element-&gt;Initialized = true;
    Element-&gt;Rectangle = Rectangle;
    Element-&gt;String = String;
    Element-&gt;Flags = Flags;
    Element-&gt;ColorValue = 0;
    Element-&gt;PicName = PicName;
}</code></pre><p>I decided to use a very simple storage model and ignore the handling of navigation between elements for now and went with a simple array. Every new element gets defined with a rectangle, an optional string for displaying, an optional string for an image name and finally a set of flags. Here we use bitwise flags to define our element and just pass them into the ElementCrate function in order to create a new element. This is very powerfull and gives us a lot of customization, for example if I want to create a clickable element with a background and a text I would pass the flags: <code>(Element_Flag_Clickable | Element_Flag_DrawBackground | Element_Flag_DrawText)</code>. Combining this I took my previous inspiration from vampire survivors and decided to build a custom upgrades screen that could exist in a roguelike mod. For this I defined a function <code>IMQ2UpgradeSelectionButton </code>that combines several of these UIElements in order to create a more advanced element.</p><pre><code>void IMQ2UpgradeSelectionButton(imq2 *UI, imq2_rect_cut Layout, float Value, const char *Label, const char *Pic, const char *Text)
{
    imq2_rect Rectangle = RectCutCut(Layout, Value);

    IMQ2ElementCreate(UI, (Element_Flag_Clickable | Element_Flag_DrawBackgroundPic), NULL, "backtile", Rectangle);
    
    imq2_rect LabelRectangle = RectCutCut(RectCut(&amp;Rectangle, Cut_Side_Top), 30);
    IMQ2ElementCreate(UI, (Element_Flag_DrawText), Label, NULL, LabelRectangle);

    imq2_rect PicRectangle = RectCutCut(RectCut(&amp;Rectangle, Cut_Side_Top), 50);
    IMQ2ElementCreate(UI, (Element_Flag_DrawPic), NULL, Pic, PicRectangle);

    imq2_rect TextRectangle = RectCutCut(RectCut(&amp;Rectangle, Cut_Side_Top), 60);
    IMQ2ElementCreate(UI, (Element_Flag_DrawText), Text, NULL, TextRectangle);
}</code></pre><p>At first we cut out our target rectangle from the given layout, RectCutCut is just a helper function that takes a value and an enum for the direction to cut in and calls one of the previously introduced cutting functions. Using this I render a background over the entire rectangle, I also make the background clickable because I want any highlighting that I imlement to highlight the entire rectangle. From the Rectangle I then cut out its top part to use for  label, cut from the top again to create an image element and finally a third time for a description text. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f4IM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f4IM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 424w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 848w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 1272w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f4IM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2354752,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!f4IM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 424w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 848w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 1272w, https://substackcdn.com/image/fetch/$s_!f4IM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F019e3295-9d1d-4350-b99f-bbe82a1be0d5_2558x1438.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Three <code>IMQ2UpgradeSelectionButton in a row</code></figcaption></figure></div><div><hr></div><p>At this point I had basically proven that the library works to build stuff and anything beyond this point is basically just refinement and making it more ready for common use. One of the things that bothered me a bit was placement of things on the screen, specifically centering things. If you recall the different tokens in the layout language for positioning there was only tokens for left, right and virtual for the X axis and top, bollow and virtual for the Y axis. I added my own tokens <code>xc</code> and <code>yc </code>for &#8220;X Center&#8221; and &#8220;Y Center&#8221; respectively. After this I had to allow developers decide how to specify which alignment their elements have. Something I really enjoy when building UI systems is make them like a sort of state machine where you push some state into the UI system and pop it when its no longer being in use, this also reduces function arguments as many things can just be on their own stacks. </p><pre><code>enum class imq2_horizontal_align
{
    Left,    // xl: MinX is absolute left edge
    Right,   // xr: MinX is offset from right edge
    Center   // xc: MinX is offset from center
};

void IMQ2PushHorizontalAlignment(imq2 *UI, imq2_horizontal_align Alignment);
void IMQ2PopHorizontalAlignment(imq2 *UI);
imq2_horizontal_align IMQ2PeekHorizontalAlignment(imq2 *UI);</code></pre><p>I often quickly forget how C++&#8217;s namespaces work when I work in other codebases and quickly stumbled upon name clashes when I used my normal enum conventions, luckily you can make an enum into an enum class which is some C++ feature that is supposed to make the enums safer by not allowing their values to implicitly convert to ints alongside with not &#8220;exporting their enumerators to the surrounding scope&#8221; which is what I want. I also later added stacks for colors and tested it out by aligning and cutting rectangles from different sides.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N93C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N93C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!N93C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!N93C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!N93C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N93C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2403721,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N93C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!N93C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!N93C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!N93C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22052050-5074-48c8-8cef-049f0ccf5047_2559x1439.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Testbed for using different alignments</figcaption></figure></div><p>At this point the system was definitely competent enough to use and build some really cool stuff. I created a new UI element which was a speedometer, it tracks your speed as you move around in the world which is intereting in a game like Quake where you can gain a lot of acceleration. This new UI element didn&#8217;t work properly with the new layouting system and it resulted in the progress-bar that displays the speed to be relative to the center because that&#8217;s where I wanted my speedometer to be aligned to and that caused the progress-bar to grow from the inside out rather than grow from the left to the right which I wanted to fix. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Tppg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Tppg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Tppg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:3825246,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Tppg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!Tppg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff1caf373-22ed-44ae-bc08-fcbd6fcdfb80_2559x1439.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Speedometer element with green bar missaligned.</figcaption></figure></div><p>As clever as I am I decided to add a relation system between UI elements where you can specify one element as the other&#8217;s parent which would alter the alignment to use the parent as anchor rather than the screen. To implement this turned out to be quite simple, in order to mark an element as a parent element I would add the token <code>par</code> infront of it and let the client put the specified x, y, w, h to be put on different stacks. I also doubled the positional tokens to include one of each but for relative positioning (<code>rxl</code>, <code>rxr</code>, <code>rxc</code>, <code>ryt</code>, <code>ryb</code>, <code>ryc</code>) which would just mean that instead of using the screen&#8217;s normal offset we use the last pushed parent&#8217;s values. In order to test this I created a small example where I created a big rectangle and then added 9 smaller rectangles inside it which all uses the different combinations of relative positioning in each axis.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lBht!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lBht!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!lBht!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!lBht!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!lBht!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lBht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2058599,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!lBht!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 424w, https://substackcdn.com/image/fetch/$s_!lBht!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 848w, https://substackcdn.com/image/fetch/$s_!lBht!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 1272w, https://substackcdn.com/image/fetch/$s_!lBht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c705859-2c90-4513-b444-c8e84a997f9e_2559x1439.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Demo of relative positioning.</figcaption></figure></div><div><hr></div><p>At this point I was like 9 days in of creating the library and I felt like I had learned a ton about the Quake 2 source code and it was a real awesome experience, just to finish things off I wanted to create some sort of demo for an UI replacement which I could later show to some Quake modders that maybe spark some ideas for both for me and for them. Since I already had a progress bar that I used for the speedometer I decided to replace the health bar with a progress bar, I also added a bar for XP points for an imaginary level system. Then I added some buff icons at the top of the screen to simulate powerups and finalized it with the upgrade selection I created earlier which I improved to use the new alignment system.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CE4S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CE4S!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 424w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 848w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 1272w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CE4S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png" width="1456" height="818" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:818,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2605470,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.oskarmendel.me/i/158425121?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CE4S!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 424w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 848w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 1272w, https://substackcdn.com/image/fetch/$s_!CE4S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cdce6a5-073b-4110-b713-f90411034a0a_2558x1437.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Final UI demonstration</figcaption></figure></div><p>As I sit here and reflect about the project I am very happy that I decided to go through with it in the way I did. I have never ever seen the point in making a mod because it is just that, a mod. I thought of it as me writing software that is captive within the boundaries of this other encapsulating software, but it is more than that. It is an artform in the same sense people try to squeeze out the very last bit of performance from a machine or trying to make the smallest possible executable or any other form of development that has hard constraints on what is possible. In this project I didn&#8217;t have the luxury of a renderer or my code even being client-side, I had to rely on the already existing framework and build upon it to achieve my goals and for this system to even be in place in order for me to do that is very impressive. </p><p>The developers of Quake, with not a lot of extra work, gave the community enough tools to create an amazing set of experiences that talented people have embraced. There&#8217;s something for every creative person in Quake, modders can craft maps, improve  or replace assets, compose music, design models, invent game modes, and much more. For me, diving into game development this way has been a breath of fresh air. It&#8217;s reignited my passion for programming, sparking new inspiration and motivation to explore different topics, collaborate with great people, and try new things moving forward.</p><p> Looking back at it I realise that this Quake 2 mod means more than just adding something new to an old game. It&#8217;s about rediscovering what I love about programming in the first place. The constant problem solving, the creativity of creation and the joy of seeing it all come to life. It has reminded me that there is something very unique and satisfying about working inside a sandbox build by others and then make it your own. I am grateful to the Quake community for keeping this spirit alive. </p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">This website is reader-supported. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Recovering from a crippled mind]]></title><description><![CDATA[Explaining some troubles I've had and how I now will get back to work]]></description><link>https://www.oskarmendel.me/p/recovering-from-a-crippled-mind</link><guid isPermaLink="false">https://www.oskarmendel.me/p/recovering-from-a-crippled-mind</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sun, 26 Jan 2025 09:56:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/afd7ade1-4266-43c8-99fb-67e773947865_1024x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Before we begin I want to excuse myself for the long silence, I have not released anything new in over 5 months and been completely silent on all my socials. The main reason is that the burning passion for programming has somewhat faded. Don&#8217;t get me wrong I still really love programming but the constant mood and drive for writing code is not there anymore. I have given some thought to why it could be that I have those feelings. Part of it is probably lasting effects from burnout after the release of Brainroll but another big reason is that my day job which is my main contact with programming has changed pretty drastically from being something I have close to full control over to something I have next to zero control over. </p><h1>Developer control and current situation</h1><p>For me, how much control I have over my work is also how fun, interesting and rewarding it will feel. When I use the word control I really mean every sense of the word. I want to be able to decide what tools I use, how I design the system, how I solve the problem at hand. At my previous job we talked a lot about responsibility, every developer had responsibility over their own work which is a very fair, I get to chose basically everything about my projects as long as I know that I&#8217;ll be the one maintaining it. This freedom was rarely abused as me or other developers wouldn&#8217;t make choices that hinders a future colleague to help or take over the project. </p><p>That situation in contrast to how it is now is extremely different. I struggle to think of a single thing in my job I have full control over. I can&#8217;t chose my own tools as the project is locked into using a certain set of tools. I do not get to be apart of bigger architectural decisions as this is limited to the role of &#8220;architects&#8221; and not developers like myself. I do not get to chose how I write my code as a large set of coding style standards and programming patterns are enforced as well as architecture is already laid out for exactly everything so all I&#8217;m doing is just puzzling the small pieces together with existing built libraries. Basically &#8220;Get X and Y from the database, do this calculations on some of the data and return Z in a file in the format Foo.&#8221;.</p><p>On top of this there is an extreme abuse of processes for every single thing, I cannot edit a single line of code without it being connected to a ticket which in turn has to be managed by having a set of parameters specified such as which release its going to be apart of and which sprint or project its connected to and what status it has. Then of course this has to be connected with my commits and pull request by keeping track of an ID from Jira that you just have to keep track of. This is not the biggest issue ever but it does get tiring when there are times you just want to roll up your arms and get stuff done, which is very rare as just identifying a improvement in the system and fixing it yourself is not really a thing, you need to run it by someone higher up.</p><p>For you who know me I do currently still work with the programming languge COBOL which is known for being cumbersome and overall hated around the web. COBOL itself as a language I wouldn&#8217;t say I have that much troubles with, its just a very different type of programming compared to what most people are used to today. A few examples are that every variable is a global, you specify variables in length and there is nothing dynamic. That means if you have a list you need to know how many elements you will have in this list, in newer versions of COBOL this is solved but I havn&#8217;t worked with those. Most of the &#8220;advanced&#8221; made up concepts of classes and data structures are not really present in COBOL which is probably why it is easier to grasp for beginners.</p><p>The work I&#8217;m doing that I described earlier about getting data from the database and then returning it as a file also applies for COBOL programs, the difference between COBOL and C# is that the C# version is between 200-800 lines long which COBOL is between 2000-10000 lines long. On top of that the COBOL version is obscured a lot which makes any form of maintenance and development take an unfathomable amount of time longer. This code is extremely difficult if not impossible to port for someone that is unfamiliar with the project and the domain, when I as a developer read the code I can follow the data around and get a hunch about what is going on but understanding why it is going on is incredibly difficult. </p><p>Even with a very obscure and hard to understand codebase due to its many domain specific problems there is most certainly space for a good developer to find improvements. With the COBOL I work everyday there are tons of  things that I could refactor or move over to C# that would make the job easier and the product more robust.</p><p>However this type of internal improvements are also stopped because of fears of things breaking or taking too much time from developing new features. This is my main gripe with working in this environment. Instead of evolving and improving stuff I am placed into the maintenance mode just keeping the wheels turning. The good old quote &#8220;Don&#8217;t live with broken windows&#8221; is kinda applicable here because me and other developers are  opening our editors everyday seeing problems which we don&#8217;t fix and in the meantime we are probably creating new problems while maintaining what we currently have. Working as a consultant in a smaller company there was a lot of room to fix stuff that you wanted to fix and bake it into a task but now in a bigger company there are more rules enforced on the contents of every pull request making this impossible.</p><h2>Micromanagement and stress</h2><p>Every organization is driven by profits, we are working because everyone wants to make more and more money. Before I started working at my current job I thought moving from a small company to a bigger one also would mean that there are more resources for just making improvements and being excellent but its not. I have seen countless of more investment in making not only the code and software excellent at my previous position but also big investments in the people working there. Recently I have experienced what &#8220;stress&#8221; looks like in a large organization and it was an interesting experience to say the least. </p><p>Lets reflect a bit on how we dealt with stress at my old job. I would be working and noticing some issue with hiting my deadline, I escalate my findings to my project manager and a discussion is started, about what I need in order to hit my deadline. It can be aid from another developer, some tools or whatever. But sometimes there is just no way hiting the deadline is possible and if this happened my project manager would escalate this to my boss along with what or how much more time we would need. He would deal with the customer and in the meantime we just continue working untill we know more, maybe it is okay to cut some feature or make some other compromise and we woud simply just solve the problem together as a collective. A very important part here is that when I first escalate this problem there is zero blame on me, it is a mutual understanding that we probably didn&#8217;t have all the information at hand when making the estimate and the estimate has now changed, it&#8217;s not my fault its just reality.</p><p>A scenario from my current position would be that I can be working on something that doesn&#8217;t have a deadline and I&#8217;m told that I have time to learn the domain, take my time to get familiar with the project. Later the deadline would be set, something reasonable that is not given much thought to. The next week suddenly everything can change, the deadline is pushed closer in time so now maybe I&#8217;m not so certain it will be able to be finished in time so I will escalate this problem. Solution here is to add more experienced developers to the project because according to the managers they can do my work faster. A new problem arrise where there is not enough tasks for all developers and I am not instead of writing code myself looking at a screenshare of a &#8220;better&#8221; developer working so that I can learn while watching him write the code that I should have been writing. Also while escalating problems like this there is an underlying tone to the response that I recieve that for some reason it is my fault, not that the circumstances changed. </p><p>Why it goes down like this is impossible to figure out because there is some hierarchy of bosses and managers that I don&#8217;t even comprehend. I don&#8217;t know and I don&#8217;t care who made these weird and bad decisions I just care about my toes being stepped on. Sometimes there is good things to take away by watching someone else figure out a solution to a problem but not letting me work is just hindering me from learning to work in the project. It is just adding one more reason to the pile of reasons to not enjoy my craft. In the two different scenarios I bring up the one is a positive experience as I get to be a productive developer, the stress is a sort of positive stress where I just get to work and do my stuff, my team helps me get the things I need in order to do my job while the other scenario I would call it a negative form of stress causing fear to even speak up because when I state a problem my tools were taken away from me and I am suddenly punished for trying to do my job.</p><h1>How I coped and how I will recover</h1><p>Ever since I was young I have dived into games when I have felt down or had something really hard or tough to think about. This time one of my true passions and the main way I have had to consider myself a productive human being has been turned into shit. This is why I have basically not been programming outside of my job for like 5+ months. The feeling when I close down work is just not that I want to look at code anymore. This has been an extremely hard thing for me and I didn&#8217;t really know how to deal with it and so I have turned mainly to games once again. I have spent an inhuman amount of time playing games the last year and while I kind of hate myself for it I also think it was necessary to come to terms with my situation and slowly figure a way out of the slump.</p><p>The difference in my video game habits during this year is that instead of playing games to have fun I have tried to get really good at them. I tried getting good at League of Legends, Dota 2, World of Warcraft, Counter-Strike, Street-Fighter. I played these games and did reach a rank in each and everyone of them that I would say is above average. The interesting thing about these games is that the gameplay changes a lot when you progress in skill, the better you get at these games the more optimized your gameplay has to be, this is often taken to the point of my brain not being able to recognize that I am having fun but instead I&#8217;m constantly thinking of every small detail of my gameplay in order to not make a mistake. </p><p>Compare  this high level gaming to programming. As a beginner programmer I don&#8217;t think I ever had fun but as I got better and now when I am able to make my programs do whatever I want I can have a lot of fun with it and at the highest level I have ever programmed (Brainroll) was actually also the time I had the most fun. Bugs are not discouraging or when a new feature doesn&#8217;t work as intended because you can work around it or just try something new, its my imagination that is the limit. In hindsight its quite annoying it took so much time to realize.</p><p>In my last post I wrote about inviting my personality and other hobbies into my work just in order to increase the fun and re-reading it I kind of inspired myself to get going with that idea again. I have a lot of cool stuff that I still want to create and I do feel the itch of making stuff all the time. I do have a new game on its way which I am close to being ready of putting out a demo version for. I am also experimenting with the idea of an expansion to Brainroll which I want to explore. </p><p>For this to ever happen I have had to remove as much of the negative experience from programming possible to try boost my mood to get some stuff going after work and the way I have been doing that is to perform a lot of documenting and automation. I have documented a lot of the small parts that makes the day to day tasks a lot less mentally taxing for me to perform because I can just go through a step by step list of things I need to do besides the actual code to write. For the tasks that I do perform frequently I have created scripts to perform for me. This has helped a lot because I would say like 70% of my job is not to write code but setting up and preparing environments to be able to write the code. Last but not least the management and social part I just have to suck up and play the game in order to get by there is most likely no other way around it for now. Bringing back control to me over my work as a developer will be very difficult as I think I would have to really dive into the domain and the corporate life in the same way I have done with gamedev in order to succeed, I am very uncertain if I want to do that. In one aspect it might be smart because it offers a stable financial life but in the other hand I fear losing the creative and productive part of me forever. </p><p>Anyway, these last few paragraphs maybe got a bit incoherent but I really just want to get going, I feel motivated and I think 2025 is going to be REALLY good! I am going to try my best to publish something cool within the upcoming weeks.</p><div><hr></div><p>Did you know that you get access to the Maraton source code if you become a paid subscriber to my website? If you&#8217;re interested in learning or to be a part of the development then don&#8217;t hesitate and click the button below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Revving Up: Exploring Life Beyond Game Development]]></title><description><![CDATA[Sharing thoughts and lessons learned from pursuing my passions in life]]></description><link>https://www.oskarmendel.me/p/revving-up-exploring-life-beyond</link><guid isPermaLink="false">https://www.oskarmendel.me/p/revving-up-exploring-life-beyond</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sun, 09 Jun 2024 13:26:15 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/88b11d97-c46e-41c2-b4cc-f44670e62ee8_2293x899.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;14b2e2fb-0070-4510-b1de-370d889321cb&quot;,&quot;duration&quot;:null}"></div><p>Above is the video version of this post. The video is not that good of a camera angle so it&#8217;s not that interesting to watch and the audio is a bit distracting but I will try to increase the production quality for future posts. Unfortunately I got a flat recording this video so I couldn&#8217;t do a second take. Hope you enjoy!</p><div><hr></div><p>My first indie game was released about 10 months ago. You would imagine that I&#8217;d be halfway on a new project or cooking up something big by now but that is unfortunately not the case. Instead of producing I have been working a lot on myself, part of it was burnout that made me want to step away from the computerscreen for a while and another part was just real life stuff that was overdue and had to be dealt with. </p><p>The two years~ I spent working on Brainroll was very special and very fruitful, but also very damaging, something I didn&#8217;t know at the time. I worked 8 hours per day as a programming consultant only to work on the game for about 2 hours~ in the evenings. However my dreams were very big, in those two hours I tried to fit not only gamedev but also stuff like creating my own build systems, keeping up with emails and different gamedev and programming discords, post stuff to youtube and twitter. If someone has followed my content between platforms over the year it is basically no continuity just for the reason that I tried to do everything at once. </p><p>This is is already an easy recipe for burnout but the more damaging part which is easy to forget is that during this period I thought about gamedev ALL THE TIME. I could be working with my consulting stuff while suddenly some important gamedev thought appeared which made me have to take a break and note some stuff down or I could be doing some random chore at home on autopilot only to plan what I will do with my 2 hours later that day. It went so far that I was on autopilot even talking to my parents on the phone or when I was hanging out with my girlfriend. This is the truuly damaging behaviour not only for myself because I basically couldn&#8217;t enjoy any single moment in my life but more importantly for the people who are close to me. I don&#8217;t think I have been fair to my closest people living as this gamedev robot for this time.</p><h2>The post release</h2><p>Before the game was released I already had soft plans of taking a break from programming in my spare time post release which is exactly what I did. Somewhere between the the development and release of the game I switched jobs from working in consulting to working on an older salary system. The former workplace being a very relaxed startup type environment while the latter being very formal and corporate. This change was partly motivated by me wanting to experiencing working on a very big product everyday rather than working on very small products everyday, for some reason I thought there was a bunch of things to learn managing a very large codebase like this. </p><p>The transition between jobs was not painless for me, I had to go from a very free environment to a more restricted environment which no matter how hard I try I cannot really fully get used to. Biggest systems turns out to involve a lot of fear due to the larger amount of complexity, this makes it harder to push through changes that are not directly connected to revenue. Due to the pushback when making bigger refactors it is easy to fall into the maintenance mode developer situation where you are just performing the smallest possible code patch to make the program function as specification, over and over again. After listening to a <a href="https://www.youtube.com/watch?v=nL8GWU9M8LY">video</a> where Jonathan Blow explains the importance of having a serious and challenging job I got into a bit of a slump comparing it to the maintenance mode situation I have fallen into. </p><p>How can I ever expect to be achieve greatness by just performing maintenance patches in an old system? How will I ever be recognized and valued?</p><p>Asking myself these questions woke something up in me, I thought why would I care about being recognized? But then I started to piece things together, everything I have done in my life up til now has been to prove something, not to myself but to others. In retrospect I&#8217;ve caught myself in injecting my own achievements into everything things which are made for fun like playing video games were not for fun but only to try to be the best. For some reason I&#8217;ve thinking it matters to anyone that I&#8217;m good. All of this while when actually getting genuine praise it has not really had any effect on me, it doesn&#8217;t make me happy to be recognized.</p><p>Bringing this back to Brainroll, building a game in my own engine sounded like a very good idea to get recognized for my skill, for sure people would respect me by doing this and I would become great. When releasing the game I did actually get people impressed by the achievement but I didn&#8217;t really impress myself. For sure I was happy to finish it and I was very happy when I saw players finish it but I didn&#8217;t really feel fulfilled. The game was not good enough to allow me to feel fulfilled but would I have been fulfilled if it was a big hit? I don&#8217;t think it would. </p><p>Wrapping all of this up I think I have been in gamedev for the wrong reasons. I have not been doing anything for myself but for everyone else which is a poor way to live. A better way to live and actually bring value to people is by sharing something that only I can share, that is when a true connection can be formed and recognition can be achieved for the right reasons and not for an artificial one which I have previously pursued. With this said moving forward I want to embrace being me, I want to personalize everything and do everything in my way and not do this for success but do this because I genuinely enjoy it.</p><h2>Revving up, living in the moment</h2><p>One of my newly found interests are motorcycles, they are amazing for many reasons. Not only is the experience riding them one of the most enjoyable there is but there is also a beauty in how they work and how they&#8217;re built. Motorcycles just like bicycles works because of <a href="https://en.wikipedia.org/wiki/Bicycle_and_motorcycle_dynamics">physics</a>, I won&#8217;t pretend to understand it all but when you feel the physics working with you it&#8217;s a good feeling. </p><p>I try to draw many parallels between my bike and programming because I think that if my bike was a piece of software it would be close to perfect. Out of the box a new motorcycle is designed to just be able to run, bugs like engine failure can happen but its pretty rare. However it is most likely not the optimal bike for you hence you want to make some modifications, the manafacturer knows about this so they made it easy for you to swap out the things you&#8217;d like to change in your garage. A programmer with their software have can do the exact same by allowing modding support in their game for example. I personally found this very inspiring and I want to take my development in this direction, making my software close to perfect out of the box but possible to personalize for the ones who want.</p><p>In addition to finding inspiration in motorcycles in my work I also want to integrate it more with my work, not only to make things more fun for me by combining two of my hobbies but also to possibly give ideas or inspire others to try their own thing. I will hence try to make my content not purely about programming but rather an invitation to my life by sharing who I am and who I want to be. </p><div><hr></div><p>Did you know that you get access to the Maraton source code if you become a paid subscriber to my website? If you&#8217;re interested in learning or to be a part of the development then don&#8217;t hesitate and click the button below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Brainroll Postmortem Part 5: Assets, Serialization & Next Steps]]></title><description><![CDATA[How serialization and assets are handled within Brainroll as well as some notes on what I plan on doing in the future.]]></description><link>https://www.oskarmendel.me/p/brainroll-postmortem-part-5-assets</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-postmortem-part-5-assets</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Thu, 21 Mar 2024 18:25:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!zxYR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I have been in somewhat of a transition period in my life hence my focus has not been on writing about Brainroll or dealing with anything programming related. Now when I feel like I&#8217;ve landed more in what I want to do and where I want to go in life I thought its time to wrap up the postmortem posts. Since in the previous post I talked about the key parts of Brainroll and in my opinion the most interesting parts when it comes to tech I will wrap this up by talking about how I dealt with Levels, assets &amp; save files before finally wrapping up with talking about what I want to do next.</p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Nullsson is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Asset system</h2><p>The older versions of Brainroll were way friendlier when it comes to for example levels. It had a built-in level editor that would save levels into specific level files, in my mind I thought it would be cool if users would create levels and share with eachother like Mario Maker or something like that.  That being very early in development I didn&#8217;t really have a plan for dealing with for example changes in the engine or levels, sure I can load up each level and make changes to them but what if I change key parts of how levels are loaded or structured? I noticed problems with this early which made me create some sort of versioning system for the level files as well as a way for the level editor to convert the level files to the newest version. </p><p>After some time I had my first user use the level editor, to them it was not only confusing as how to use the editor but it was hell for me to take care of building the system that would keep the levels up to date when changes happened to the game. I had thousands of lines of code to deal with conversions between formats and versions and different edge-cases and after some time I just decided that this system has to go. Brainroll didn&#8217;t ship with a level editor and instead I just converted each level to the most bare-bones simple string representation I could think of and hard-coded them into the game.</p><pre><code>struct level_definition
{
    u32 Width;
    u32 Height;

    u32 MinimumNumberOfMoves;

    char *Name;
    char *Level;
    
    level_rope_connections RopeConnections;
};

/*
 * W - Wall
 * . - Floor
 * 1 - Slide Player
 * S - Sticky Roll
 * P - Passable Solid
 * p - Passable Hollow
 * G - Goal 
 * D - Door
 * R - Pressure Plate
 * r - Rope (Put infront of tile to be connected to, Example: rW or rS)
 *   - Space is ignored when parsing
 * # - Snow (Slime)
 */

STN_INTERNAL level_definition
Levels[] = 
{
    { // 38.
        13, 14,
        53,
        "Ascend",
        "WWWWWWWWWWWWW"
        "WWW.......WWW"
        "W.....W.....W"
        "W...........W"
        "W....SSR....W"
        "W....WWW....W"
        "WW...WGW...WW"
        "W....WDW....W"
        "W...r...r...W"
        "W..r.....r.WW"
        "WWS.......S.W"
        "W...........W"
        "W1....S.....W"
        "WWWWWWWWWWWWW",
        {
            4,
            { { 4, 8 }, { 5, 7 }, { 3, 9 } },
            { { 3, 9 }, { 4, 8 }, { 2, 10 } },

            { { 8, 8 }, { 7, 7 }, { 9, 9 } },
            { { 9, 9 }, { 8, 8 }, { 10, 10 } },
        }
    },
};</code></pre><p>This piece of code shows an example level with the translation of what each character translates to in the world. These level definitions are used as a blue print to how a level should look in its initial state. In the structure I specify a width and height for the level followed by the least &#8220;number of moves&#8221; to complete the level. Then you give your level a name and specify the level itself. Optionally if the level uses ropes  you specify how many ropes there are followed by their connections in tiles, each rope has a position and two tiles they are connected. Ropes can be connected to another rope to create a longer rope or a wall or something else to be attached to a object.</p><p>This system sure is booring and removes the original modding support I had planned but it worked perfectly to just wrap the game up and get to the finish line. However I will probably not do this again and if I work on another title with many levels I will deffinately spend more time building a robust system around handling them.</p><p> If we move on to talk about assets such as sprites and sound they are stored in pack files which I just randomly named to .mton files which stand for Maraton (the name of my engine). These files are very simple and what I&#8217;ve done is basically create a small program which I call packer, packer takes a list of files and just  reads some data that my engine needs before copy pasting the file contents after eachother into one large file. </p><p>The structures used by packer and the engine looks like the following:</p><pre><code>struct maraton_ff_texture
{
    int32_t Width;
    int32_t Height;
    void *Pixels;
};

struct maraton_ff_sound
{
    int32_t Channels;
    int32_t SampleRate;
    uint32_t SampleCount;
    int16_t *Samples;
};

struct maraton_ff_asset
{
    uint64_t DataOffset;
    uint64_t DataSize;
    
    union
    {
        maraton_ff_texture Texture;
        maraton_ff_sound Sound;
    };
};</code></pre><p>So basically packer produces a file with a list of <code>maraton_ff_asset</code>s which we on startup of Brainroll just read from disk into memory. If these files were larger than what they are we could improve this by making the files searchable through some sort of lookup table within the file format but I knew Brainroll wouldn&#8217;t have such requirements so I just settled with this. Packer uses external libraries to load the files and get their data, it uses stb_vorbis for loading .ogg files and stb_image for .png files.</p><p>Loading it within the game is as simple as opening the file, and for each <code>maraton_ff_asset</code> we just read the next DataSize number of bytes and then we have everything we need to use it within the game after we cast it to either a sound or texture asset depending on what we&#8217;re loading.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zxYR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zxYR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 424w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 848w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 1272w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zxYR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png" width="1456" height="470" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1864e67b-3717-4942-8100-858361be9717_3840x1240.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:470,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4557038,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zxYR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 424w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 848w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 1272w, https://substackcdn.com/image/fetch/$s_!zxYR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1864e67b-3717-4942-8100-858361be9717_3840x1240.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h2>Serialization</h2><p>When it came time to remember the players progress I did think of my problems handling versioning with the old levels and hence I wanted to actually implement some form of serialization. My save games very and are very simple, the only information I really need to keep track of is what levels the user has completed and how many moves they completed each level in. I simply use two arrays of integers to store these values.</p><pre><code>struct save_game_state
{
    u32 LevelCompleted[SAVE_GAME_FILE_NUMBER_OF_LEVELS];
    u32 NumberOfMoves[SAVE_GAME_FILE_NUMBER_OF_LEVELS];
};</code></pre><p>The real magic is when these arrays needs to grow. I found a really simple and <a href="https://handmade.network/p/29/swedish-cubes-for-unity/blog/p/2723-how_media_molecule_does_serialization">interesting technique</a> that was used by Media Molecule for Little big planet. </p><p>The serializer presented as &#8220;LBP Serializer&#8221; uses a single function to perform both serialization and deserialization. When performing serialization of a structure we do it for each field within it. This means that we only have to prepare serialization functions for the base types that we want to use and then we can go ahead with easily serializing any structure we want. </p><p>The serialization or deserialization of a base type is very simple, we either are writing to a buffer or we are reading from that buffer, to do that we simply need to keep track of where in the buffer we are reading or writing to which we can do by storing a cursor or index into our buffer.</p><pre><code>struct lbp_serializer
{
    u32 DataVersion;
    b32 IsWriting;
    
    u32 Counter;

    u8 *Buffer;
    u32 BufferCursor;
};

STN_INTERNAL void
Serialize(lbp_serializer *LBPSerializer, uint32_t *Datum)
{
     
    if (LBPSerializer-&gt;IsWriting)
    {
        uint32_t *Pointer = (uint32_t *)(LBPSerializer-&gt;Buffer + LBPSerializer-&gt;BufferCursor);
        *Pointer = *Datum;    
    }
    else
    {
        *Datum = *(uint32_t *)(LBPSerializer-&gt;Buffer + LBPSerializer-&gt;BufferCursor);
    }
    LBPSerializer-&gt;BufferCursor += sizeof(uint32_t);
}

#define ADD(_FieldAdded, _FieldName)                    \
    if (LBPSerializer-&gt;DataVersion &gt;= (_FieldAdded))    \
    {                                                   \
        Serialize(LBPSerializer, &amp;(Datum-&gt;_FieldName)); \
    }

#define REM(_FieldAdded, _FieldRemoved, _Type, _FieldName, _DefaultValue) \
    _Type _FieldName = (_DefaultValue);                                   \
    if (LBPSerializer-&gt;DataVersion &gt;= (_FieldAdded) &amp;&amp;                    \
        LBPSerializer-&gt;DataVersion &lt; (_FieldRemoved))                     \
    {                                                                     \
        Serialize(LBPSerializer, &amp;(_FieldName));                          \
    }

STN_INTERNAL void
Serialize(lbp_serializer *LBPSerializer, save_game_state *Datum)
{
    // SAVE_GAME_SERIALIZATION_VERSION_INITIAL
    {        
        for (u32 Index = 0;
             Index &lt;= SaveGameGetNumberOfLevelsForVersion(SAVE_GAME_SERIALIZATION_VERSION_INITIAL);
             ++Index)
        {
            ADD(SAVE_GAME_SERIALIZATION_VERSION_INITIAL, LevelCompleted[Index]);
        }

        for (u32 Index = 0;
             Index &lt;= SaveGameGetNumberOfLevelsForVersion(SAVE_GAME_SERIALIZATION_VERSION_INITIAL);
             ++Index)
        {
            ADD(SAVE_GAME_SERIALIZATION_VERSION_INITIAL, NumberOfMoves[Index]);
        }
    }
}</code></pre><p>A set of helper macros are created for Adding and Removing from our serialized data, within the function where we serailize our main structure we can specify which version we&#8217;ve added something to the file or removed something from the file. This makes sure that if we&#8217;re reading a file that is for example older than when we&#8217;ve added something it will simply skip it, and in the case something was removed we will check when it was added and when it was removed and only serialize it if its present, this information is passed to the remove macro.</p><p>This creates a serialization system with very few lines of codes that is also very robust and can withstand a lot of changes to the codebase which is perfect. I will most likely try using this again. I have not tried it with very big set of data yet but I can imagine it would work pretty well with that also.</p><h2>Moving on</h2><p>Wrapping things up I want to talk a bit about the future, both for Brainroll and myself. I have since I started with Brainroll had the aspirations of releasing the game to multiple platforms, I wanted to do this at the time of release but life got inbetween things and my engine doesn&#8217;t have support for anything else than Windows. I have since the release of Brainroll made a lot of patches to my engine Maraton and I am currently in the process of releasing version 2 of it. This is a version where I&#8217;ve re-done a lot of the APIs making creating new games or applications as well as porting to several platforms way easier. I will not promise anything but I really want to have Brainroll running on atleast linux when I&#8217;m done with the project.</p><p>Other than the engine specific stuff I have always loved achievements in games. I didn&#8217;t get achievements prepared before the release of Brainroll as I got fully burned out at the end but I am deffinately coming back to it and adding them.</p><p> Future content? I did prepare additional mechanics and content for the game but I am unsure if I am going to proceed with adding them into the game. I have built up some sort of love/hate relationship with Brainroll over the past year making me hesitate spending more time creating levels for it but at the same time it might be a good idea to see it as a bit of a redemption arc for myself where I can fix some of the mistakes I never enjoyed with it. We&#8217;ll see!</p><p>I have a very nice urge right now to learn and experiment so while all of this is happening I think I will try to re-ignite my passion for programming by spending more time with making prototypes and learning new things. In addition to this I want to invite everyone who is reading this into development process a bit more by creating video content as well. It&#8217;s going to be pretty cool!</p><div><hr></div><p>Did you know that you get access to the Maraton source code if you become a paid subscriber to my website? If you&#8217;re interested in learning or to be a part of the development then don&#8217;t hesitate and click the button below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Brainroll Postmortem Part 4: Movement, Ropes & Undo]]></title><description><![CDATA[Brainroll is a sokoban-style puzzle game where the main mechanic is that you slide on ice.]]></description><link>https://www.oskarmendel.me/p/brainroll-postmortem-part-4-movement</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-postmortem-part-4-movement</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Thu, 18 Jan 2024 08:31:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Vhai!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Brainroll is a sokoban-style puzzle game where the main mechanic is that you slide on ice. Traditionally in Sokoban games, each level is presented as a grid layout where each cell comprises either a floor or a wall. Within this grid, the floor cells serve as potential placements for boxes, while certain designated cells act as storage locations for these boxes.</p><p>The player, represented as a character or object, has the ability to move in any of the four cardinal directions: Up, Down, Left, and Right. This movement occurs within the confines of the grid and is permitted on any unobstructed floor cell. Moreover, the player can push boxes horizontally or vertically as long as the direction they wish to move the box also leads to a floor cell.</p><p>The primary objective of the game is to strategically maneuver each box onto its respective storage location. By pushing and arranging the boxes into these predefined spots, the player successfully completes the level. The challenge lies in navigating the obstacles, planning efficient movements, and avoiding getting boxes stuck in corners or against walls, ultimately requiring logical thinking and problem-solving skills to solve each level.</p><p>Brainroll introduces some mechanics that deviate from the classic Sokoban gameplay. First of all, the grid set of cells in the grid is modified by replacing traditional floor cells with two distinct terrain types: ice and snow cells.</p><p>The ice cells introduce an element of sliding movement mechanics. When the player character traverses an ice cell, they continue sliding uncontrollably in the same direction until they encounter an obstacle, such as a wall. This mechanic adds an additional layer of challenge and strategic planning, requiring players to anticipate the sliding movements while navigating the level.</p><p>Conversely, the snow cells in Brainroll adhere to the familiar Sokoban mechanics. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Vhai!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Vhai!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 424w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 848w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 1272w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Vhai!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png" width="836" height="465" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:465,&quot;width&quot;:836,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:480628,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Vhai!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 424w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 848w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 1272w, https://substackcdn.com/image/fetch/$s_!Vhai!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6e662b9-8a5a-4f18-947c-0a2781bc738b_836x465.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Furthermore, Brainroll diverges from the traditional Sokoban objective of maneuvering boxes into storage cells. Instead, Brainroll simplifies the win condition by only requiring the player to navigate the character into a specific goal within each level. However, the player still needs to move Brainroll&#8217;s version of boxes in order to reach that goal.</p><p>By modifying these fundamental rules while preserving the essence of Sokoban-like puzzle-solving, Brainroll introduces a unique blend of strategic thinking, spatial reasoning, and adaptability, challenging players to navigate through sliding ice cells and traditional snow cells to achieve their goal points</p><h3>Movement implementation</h3><p>Since Brainroll is a deterministic puzzle game, the most of the game&#8217;s code is related to the movement rules of the game and thats why I thought that might be the most interesting to dive into. A very important decision of Brainrolls entire system was to make it deterministic, Wikipedia has a very nice explanation of what this means in computer science:</p><blockquote><p>A deterministic model of computation, for example a deterministic Turing machine, is a model of computation such that the successive states of the machine and the operations to be performed are completely determined by the preceding state.</p></blockquote><p>This means is that with a given world state within Brainroll, a move will always yield the same result. This might seem very obvious for a puzzle game but there are puzzle games that has involved timing or physics based puzzles which I do not consider being deterministic, reason is because in order to get a deterministic result from a physics simulation you would need the exact same input at the exact same given time which is very hard if not impossible to reproduce. </p><p>In order to achieve this with Brainroll I decided to separate simulation with the game&#8217;s logic. I thought of it as separating what the player sees visually and what the player does physically through inputs. This can be seen by looking how my world is structured.</p><pre><code>struct world
{
    entity *Entities;
    u32 EntityCount;   
    
    entity_handle *Static;
    entity_handle *Dynamic;
    
    u32 Width;
    u32 Height;
};</code></pre><p>Each world has a set of entities, everything on the grid in Brainroll is an entity. The entities array is allocated upon level load and it contains every entity in the level in the order that it was read from the level. Next we have two arrays of type <code>entity_handle</code> which is basically a set of references into the Entities array. These represents the internal world state and the reason it is split up into two is to solve the problem of having entities stacked on top of eachover, for example the player (dynamic) standing on top of a floor (static) cell.</p><p>The arrays static and dynamic are created of the size of <code>Width * Height</code> in order to be able to represent each cell in the level through a reference. Each <code>entity </code>keeps track of its own position in the world while the game&#8217;s internals only cares about the Static and Dynamic handles, if an entity is moved the references will only change places within the Dynamic array which allows the game to actually move stuff around while the visuals not being forced to change. While this obviously works I wouldn&#8217;t recommend anyone doing it this way because we basically have two sources of truth here and there has been bugs in the past where the two states has been out of sync which is just an annoying thing to have happen.</p><p>How I tell stuff to move around is something I call the &#8220;event system&#8221;. Every entity has a <code>event_queue</code> inside them containing a list of tasks this entity has to perform. En event is structured in the following way:</p><pre><code>enum event_type
{
    EVENT_TYPE_MOVE,
    EVENT_TYPE_WALL_COLLISION,
};

struct move
{
    vector2i Start;
    vector2i Destination;

    move_direction MoveDirection;
};

struct wall_collision
{
    vector2i Position;
    move_direction MoveDirection;
};


struct event
{
    event_type Type;
    entity *Entity;

    union
    {
        move  Move;
        wall_collision WallCollision;
        // NOTE(Oskar): There are more events here but I removed it to keep the snippet shorter.
    };
};
</code></pre><p>So when an event is created it assigned a type as well as a target entity that &#8220;owns&#8221; this <code>event</code>. For example when a new move event has been created we specify which entity it is that is moving as well as setting the start and destination cell for the move. </p><p>Each frame the game checks if there are events to plays back and if there are, the events are simulated. For example if an entity has a move event strapped on it we will simulate it moving towards the move&#8217;s destination cell until it arrives. The same goes for every other type of event in the game, if we take a look at the <code>wall_collision </code>event which is basically just a cosmetic event that tells the game to play a small particle effect upon collisions it checks the <code>Position </code>of the collision and the <code>MoveDirection </code>which decides in which direction the particles should fly.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kNIp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kNIp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 424w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 848w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 1272w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kNIp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png" width="838" height="469" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1092340b-aecb-49c7-87b8-570f92037852_838x469.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:469,&quot;width&quot;:838,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:306537,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kNIp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 424w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 848w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 1272w, https://substackcdn.com/image/fetch/$s_!kNIp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1092340b-aecb-49c7-87b8-570f92037852_838x469.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Moving on to looking at how a single move is made we have the actual brains of the program. I have removed the code related to the ropes because it literally makes the move function hundreds of lines longer. In retrospect when removing the rope logic this code actually isn&#8217;t that bad. </p><pre><code>STN_INTERNAL void
PerformMove(entity *Entity, event_queue *EventQueue, vector2i CurrentTile, 
            move_direction MoveDirection, world *World)
{
    Assert(Entity-&gt;Type == ENTITY_TYPE_BRAINROLL);

    if (Entity-&gt;Brainroll.HasBeenAmended)
    {
        return;
    }

    b32 NextTileIsOutOfBounds = false;
    vector2i MovementVector = MoveDirectionToVector(MoveDirection);
    vector2i Destination = Vector2Add(CurrentTile, MovementVector);

    if (MoveFromCurrentTileIsBlocked(World, CurrentTile))
    {
        EventQueuePushWallCollision(EventQueue, Entity, CurrentTile, MoveDirection);
        return;
    }

    NextTileIsOutOfBounds = WorldTileIsOufOfBounds(World, Destination);

    entity *NextTile   = WorldGetStaticEntityAt(World, Destination);
    entity *NextObject = WorldGetDynamicEntityAt(World, Destination);
    while (NextTile   == NULL &amp;&amp;
           NextObject == NULL &amp;&amp;
           NextTileIsOutOfBounds == false)
    {
        Destination = Vector2Add(Destination, MovementVector);
        
        NextTileIsOutOfBounds = WorldTileIsOufOfBounds(World, Destination);
        if (NextTileIsOutOfBounds)
        {
            break;
        }

        NextTile   = WorldGetStaticEntityAt(World, Destination);
        NextObject = WorldGetDynamicEntityAt(World, Destination);
    }

    if (NextTileIsOutOfBounds)
    {
        EventQueuePushMove(EventQueue, Entity, CurrentTile, Destination, MoveDirection);
        EventQueuePushOutOfBounds(EventQueue, Entity);
    }
    else
    {
        if (NextObject)
        {
            MoveCheckDynamicCollision(Entity, EventQueue, CurrentTile, Destination, MoveDirection, World, NextObject);
        }
        else if (NextTile)
        {
            MoveCheckStaticCollision(Entity, EventQueue, CurrentTile, Destination, MoveDirection, World, NextTile);
        }
        else
        {
            if (Destination.X != 0 || Destination.Y != 0)
            {
                EventQueuePushMove(EventQueue, Entity, CurrentTile, Destination, MoveDirection);
                WorldSwapTiles(World, CurrentTile, Destination);
            }
        }
    }
}
</code></pre><p>Starting from the top we look at which direction we&#8217;re going and we build up a destination based of that. Since floor cells are made up of ice in Brainroll we simply don&#8217;t look ahead a single cell but instead we continue looking ahead once cell at the time untill we find another entity to collide with. This can either be a static entity such as a wall or a snow cell or a dynamic entity such as a brain block (I will talk about those soon). </p><p>When looking ahead we are looking at entities, the reason for this is that upon a collision, the event system needs to know which entity the event belongs. The actual lookup is done by looking at the <code>entity_handles</code> because they are the current single source of truth for the game logic. </p><pre><code>STN_INTERNAL entity *
WorldGetStaticEntityAt(world *World, vector2i Position)
{
    entity *Result = 0;
    
    entity_handle Handle = World-&gt;Static[Position.Y * World-&gt;Width + Position.X]; 
    Result = EntityFromHandle(Handle, World-&gt;Entities, World-&gt;EntityCount);
    
    return (Result);
}</code></pre><p>Retrieving the dynamic entities works in the exact same way. Upon a collision we have two functions <code>MoveCheckStaticCollision </code>and<code> MoveCheckDynamicCollision </code>which handles logic specific to coliding with some entities. It can be for example the <code>wall_collision</code> event upon coliding with a wall or an activation event for a pressure plate. Then we finally have what is actually performing the move itself which is the <code>WorldSwapTiles</code> function which is just really simple way of swapping the positions of two entity handles, since static entities are static we never move them, which make the swap function very simple.</p><pre><code>STN_INTERNAL void
WorldSwapTiles(world *World, vector2i Position1, vector2i Position2)
{
    entity_handle A = World-&gt;Dynamic[Position1.Y * World-&gt;Width + Position1.X];
    entity_handle B = World-&gt;Dynamic[Position2.Y * World-&gt;Width + Position2.X];
    
    World-&gt;Dynamic[Position1.Y * World-&gt;Width + Position1.X] = B;
    World-&gt;Dynamic[Position2.Y * World-&gt;Width + Position2.X] = A;
}</code></pre><p>There is one final detail about the movement code before moving on and that is the case when moving connected entities. Brainroll has also removed the concept of sokoban boxes in favor of brains that can stick to the player, when the player collides with a brain block it is connected to you and will mimic your every move. Brain blocks can also be connected to other brain blocks and through this you can form big chains of brains that moves together.</p><p>The way this is solved programatically is actually right now a simple hack. Before we move the player we check if he has anything connected to him. If he does we loop through the entire level and check each cell to see if it contains a connected brain or not. If it does we move it before we move the player. The movement code for the brains is the exact same codepath as for the player.</p><pre><code>STN_INTERNAL void
PerformGroupMove(event_queue *EventQueue, move_direction MoveDirection, 
                 world *World, entity *Player)
{
    Assert(Player-&gt;Type == ENTITY_TYPE_BRAINROLL);
    
    if (Player-&gt;Brainroll.HasConnectedBrainroll == false)
    {
        vector2i PlayerTile = Vector2InitI32(Player-&gt;TileX, Player-&gt;TileY);
        PerformMove(Player, EventQueue, PlayerTile, MoveDirection, World);
    }
    else
    {
        switch(MoveDirection)
        {
            case MOVE_DIRECTION_UP:
            {
                for (u32 TileY = 0; TileY &lt; World-&gt;Height; ++TileY)
                {
                    for (u32 TileX = 0; TileX &lt; World-&gt;Width; ++TileX)
                    {
                        vector2i Tile = Vector2InitI32(TileX, TileY);
                        PerformMoveIfBrainrollOnTile(EventQueue, MoveDirection, World, Player, Tile);
                    }
                }
            } break;
            case MOVE_DIRECTION_DOWN:
            {
                for (i32 TileY = World-&gt;Height - 1; TileY &gt;= 0; --TileY)
                {
                    for (u32 TileX = 0; TileX &lt; World-&gt;Width; ++TileX)
                    {
                        vector2i Tile = Vector2InitI32(TileX, TileY);
                        PerformMoveIfBrainrollOnTile(EventQueue, MoveDirection, World, Player, Tile);
                    }
                }
            } break;
            case MOVE_DIRECTION_LEFT:
            {
                for (u32 TileX = 0; TileX &lt; World-&gt;Width; ++TileX)
                {
                    for (u32 TileY = 0; TileY &lt; World-&gt;Height; ++TileY)
                    {
                        vector2i Tile = Vector2InitI32(TileX, TileY);
                        PerformMoveIfBrainrollOnTile(EventQueue, MoveDirection, World, Player, Tile);
                    }
                }
            } break;
            case MOVE_DIRECTION_RIGHT:
            {
                for (i32 TileX = World-&gt;Width - 1; TileX &gt;= 0; --TileX)
                {
                    for (u32 TileY = 0; TileY &lt; World-&gt;Height; ++TileY)
                    {
                        vector2i Tile = Vector2InitI32(TileX, TileY);
                        PerformMoveIfBrainrollOnTile(EventQueue, MoveDirection, World, Player, Tile);
                    }
                }
            } break;
        }
    }
}</code></pre><p>I think I just wrote this one pass and then just kept it throghout the entire development of the game. Deffinately a point of improvement.</p><h3>Ropes (Chain blocks)</h3><p>The most unique mechanic in Brainroll is the chain blocks which is internally refered to as &#8220;Ropes&#8221;. The idea of the ropes is to introduce a mechanic that limits the movement of entities in the game by having it be connected to a fixed position only allowing it to be moved a fixed number of cells away from it. A shorter and perhaps more accurate explanation that I heard from a player was &#8220;foldable walls&#8221;. This mechanic was interesting not only within the game but also interestin to implement as it did prove to not be trivial.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!zZoB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!zZoB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 424w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 848w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 1272w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!zZoB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png" width="697" height="672" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:672,&quot;width&quot;:697,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83130,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!zZoB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 424w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 848w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 1272w, https://substackcdn.com/image/fetch/$s_!zZoB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5de87b8d-fc39-42de-a6e4-609fea1ee363_697x672.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Example rope level where Brain is connected to pillar in the middle</figcaption></figure></div><p>I did mention that this does make the movement code quite a bit longer (mainly because I never bothered to clean it up) so I will only talk about the relevant snippets of code moving forwards. If you&#8217;re interested in the entire PerformMove function with the included rope code I&#8217;ve created a <a href="https://gist.github.com/brokenprogrammer/66f94a741422fdaa67ab7ff64ab7eba3">gist</a> for you.</p><p>The first most obvious change is that now before we start to actually move the target entity we perform a pre-check if it has a rope connected to it. If it does we need to make sure that the rope can follow if the entity moves in the specified move_direction. This is basically a single step of the previously displayed while loop over destinations. I did this because the PerformMove function starts with the Destination being one step towards the moving direction and in case of a collision it takes one step in the opposite direction to correct it. With ropes this becomes a problem in case the Destination is valid for the target entity but invalid from the ropes perspective. It is ugly and makes the function hard to grasp and should be refactored sometime in the future.</p><pre><code>if (Entity-&gt;HasRopeConnection)
{
    if (EntityIsFirstInRope(Entity, World))
    {
        RopeBuildOrder = ROPE_LIST_BUILD_ORDER_FORWARDS;
    }
    
    rope_list RopeList = RopeListCreate(Entity, World, &amp;Arena, RopeBuildOrder);
    if (!RopeInAdjacentTile(RopeList.Next, World, Destination))
    {
        rope_transaction_list *TransactionList = RopeCreateTransactionList(&amp;Arena);
        if (!RopeTryMove(RopeList.Next, World, &amp;Arena, MoveDirection, EventQueue, Entity, TransactionList))
        {
            Destination = Vector2Minus(Destination, MovementVector); 
            EventQueuePushWallCollision(EventQueue, Entity, Destination, MoveDirection);
        }
        else
        {
            // TODO(Oskar): Carefull cause we are not checking if Destination is occupied!!

            entity *NextTile   = WorldGetStaticEntityAt(World, Destination);
            entity *NextObject = WorldGetDynamicEntityAt(World, Destination);
            if ((NextTile == NULL &amp;&amp; NextObject == NULL) ||
                (NextObject != NULL &amp;&amp; NextObject-&gt;Type == ENTITY_TYPE_ROPE &amp;&amp; RopeListContainsEntity(RopeList.Next, World, NextObject)) ||
                (NextObject == NULL &amp;&amp; NextTile != NULL &amp;&amp; NextTile-&gt;Type == ENTITY_TYPE_PRESSURE_PLATE) ||
                (NextObject == NULL &amp;&amp; NextTile != NULL &amp;&amp; NextTile-&gt;Type == ENTITY_TYPE_SLIME))
            {
                if (IsMovingTowardsRope(MoveDirection, CurrentTile, RopeList.Next-&gt;Tile))
                {
                    RopeTransactionsCommit(TransactionList, World, EventQueue);

                    MoveManagePressurePlateOnMove(EventQueue, Entity, World, CurrentTile, Destination);
                    EventQueuePushMove(EventQueue, Entity, CurrentTile, Destination, MoveDirection);
                    WorldSwapTiles(World, CurrentTile, Destination);
                    if (NextTile != NULL &amp;&amp; NextTile-&gt;Type == ENTITY_TYPE_PRESSURE_PLATE)
                    {
                        EventQueuePushEnterPressurePlate(EventQueue, Entity, NextTile);
                    }
                        
                    CurrentTile = Destination;
                    Destination = Vector2Add(Destination, MovementVector);
                    DidAlterDestination = true;
                }
                else
                {
                    MoveManagePressurePlateOnMove(EventQueue, Entity, World, CurrentTile, Destination);
                    EventQueuePushMove(EventQueue, Entity, CurrentTile, Destination, MoveDirection);
                    WorldSwapTiles(World, CurrentTile, Destination);
                    if (NextTile != NULL &amp;&amp; NextTile-&gt;Type == ENTITY_TYPE_PRESSURE_PLATE)
                    {
                        EventQueuePushEnterPressurePlate(EventQueue, Entity, NextTile);
                    }

                    CurrentTile = Destination;
                    Destination = Vector2Add(Destination, MovementVector);

                    RopeTransactionsCommit(TransactionList, World, EventQueue);
                    DidAlterDestination = true;
                }    
            }
        }
    }
}</code></pre><p>Focusing on this snippet will give us most of the information about how the rope logic works. If we are dealing with a rope I build up a temporary linked list, the reason I dont cache these is because I had an idea in mind that you should be able to break the chains but I never got around to that. Once our <code>rope_list</code> has been created we check if there is a rope tile adjacent to the destination we&#8217;re moving to, we do this because if there is then we don&#8217;t have to move the rope. </p><p>If there is none we know that we will want to move the rope or that the entity that is currently being moved will be blocked or stopped from moving. We start with creating a rope_transaction_list to keep track of all the moves that entities within our rope has to make and then we attempt to move the rope through the <code>RopeTryMove</code> function. The function then goes through the entire lsit of rope entities with an almost exact same process as any other entity and builds up the transaction list with movement events that the entire rope needs to do in order for the original entities move to be valid. </p><p>Then we depending on if the entity is moving towards the rope or not we just submit the movement events in different orders to not make them clash. Even though this system can see a lot of improvement it was actually refactored a lot, the old implementation was way different to the point that it was unmaintainable. It started with me trying A* pathfinding for the rope to just find the best path to take upon a move and then I opted for my own algorithm where I build up lists of all the possible solutions and tried to pick the best move but in the end this solution to just treat the individual rope entities as normal entities and pass them through a similar movement process as any other entity was the best solution.</p><h3>Undo implementation</h3><p>I wont talk too much about how I solved undo in Brainroll because it&#8217;s just a really big hack. I don&#8217;t know why but I decided not to implement undo but changed my mind just a week or two before the release and I didn&#8217;t spend any time being clever about this and instead just built the absolutely simplest thing I could think of. Since the solution is pretty weird and I don&#8217;t know if someone did anything similar I decided to write something short about it in this post.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O5UW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O5UW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O5UW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2493111,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O5UW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!O5UW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa58b9ef7-2405-46d6-948f-5b53276c66b9_1920x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So the entire undo system is just a linked list of world states, what I do upon every move is that i take a complete memory copy of the <code>world</code> struct that we saw earlier in this post and just compress it to save space. When the player undo a move I just replace the world state with the last one in our linked list. It is super simple and it works really well except for it taking up more memory than it has to.</p><pre><code>STN_INTERNAL compressed_world
_UndoStateCreateWorldCopy(undo_state *State, const world &amp;WorldState)
{
    compressed_world Result = {};

    Result.Width = WorldState.Width;
    Result.Height = WorldState.Height;
    Result.NumberOfMoves = WorldState.NumberOfMoves;
    Result.EntityCount = WorldState.EntityCount;

    u32 Size = Result.Width * Result.Height;

    Result.Static   = (entity_handle *)MemoryArenaAllocateAndZero(&amp;State-&gt;MemoryArena, Size * sizeof(entity_handle), 1);
    Result.Dynamic  = (entity_handle *)MemoryArenaAllocateAndZero(&amp;State-&gt;MemoryArena, Size * sizeof(entity_handle), 1);
    
    // NOTE(Oskar): Compress entity buffer and store it along with its size within the undo_world.
    {
        u32 EntityBufferSize = Size * sizeof(entity);
        entity *EntityBuffer = (entity *)MemoryArenaAllocateAndZero(State-&gt;TemporaryArena, EntityBufferSize);
        MemoryCopy(EntityBuffer, WorldState.Entities, EntityBufferSize);

        const int MaxCompressionBound = LZ4_compressBound(EntityBufferSize);
        char *CompressedBuffer = (char *)MemoryArenaAllocate(State-&gt;TemporaryArena, MaxCompressionBound);
        Result.CompressedEntitiesSize = LZ4_compress_default((const char *)EntityBuffer, CompressedBuffer, EntityBufferSize, MaxCompressionBound);

        Result.CompressedEntities = MemoryArenaAllocateAndZero(&amp;State-&gt;MemoryArena, Result.CompressedEntitiesSize, 1);
        MemoryCopy(Result.CompressedEntities, CompressedBuffer, Result.CompressedEntitiesSize);

        MemoryArenaFreeBytes(State-&gt;TemporaryArena, EntityBufferSize); // CompressedBuffer
        MemoryArenaFreeBytes(State-&gt;TemporaryArena, EntityBufferSize); // EntityBuffer
    }

    _UndoStateCopyEntityReferences(State, WorldState, &amp;Result);

    return (Result);
}</code></pre><p>For this I used a library called LZ4 which is a fast compression library together with virtual memory arenas in Windows. This way I can allocate an absurdly big arena which only grows as more memory is needed. Of course none of this would ever be needed for a proper undo system but this is something I added just before the release of the game and I found it to be the quickest solution.</p><h2>Thoughts</h2><p>Through the entire journey of creating this game I&#8217;ve learned a lot about many things. When I was younger and played around with creating games I often barely even got things to render to the screen as I often got stuck trying to figure out the best way of doing every single thing possible. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!K3Z1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!K3Z1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!K3Z1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2232793,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!K3Z1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!K3Z1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb7ddf147-e4bb-4915-8538-9d4d35fb78f2_1920x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Starting with creating a new game I would have to open a new window, first thing I did was to research the most optimal and complete way to open windows. Doing this for every single thing will really get you nowhere. Some of the code I show in this post is far from perfect or optimal but it helped me reach the goal by working on average 2 hours per day after my normal job which I think is very valuable, more valuable than being stuck with a unfinished game that has the best movement or rope code.</p><p>If i go back to the Brainroll codebase now to try improve it I would deffinately try to simplify the rope related code, maybe have it perform less checks or try make it go through the same codepath as the other entities (PerformMove instead of RopeTryMove). I also noticed a lot of dead code while writing this that can be removed. </p><p>I don&#8217;t know if it is because of me having spent so much time on the codebase but I feel like the logic in Brainroll is very simple. Many times I wonder if I have had a slow development time creating this game. I have a hard time judging if I spent a lot of time writing code for easy problems. I briefly mentioned how the rope mechanic went through over 3 iterations with completely different approaches, why did I even do the 2 first ones? How come I didn&#8217;t start with the simplest one instead of starting with the hardest one? I think there are many times during the development that I went through something similar of me creating something that is way too complex and having to cut it or me just implementing something that I don&#8217;t end up using. </p><p>Part of the fun of programming in your own projects is to have the ouportunity to go through those journeys with code but when working against the clock I wonder if it somehow should be limited with better planning or dicipline. </p><div><hr></div><p>Did you know that you get access to the Maraton source code if you become a paid subscriber to my website? If you&#8217;re interested in learning or to be a part of the development then don&#8217;t hesitate and click the button below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Brainroll Postmortem Part 3: Engine]]></title><description><![CDATA[It is finally time to talk a bit about the thing I feel most comfortable with, the tech that powers Brainroll.]]></description><link>https://www.oskarmendel.me/p/brainroll-postmortem-part-3-engine</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-postmortem-part-3-engine</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Mon, 27 Nov 2023 10:50:43 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!VKrv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is finally time to talk a bit about the thing I feel most comfortable with, the tech that powers Brainroll. I plan on touching a bit on the stack, engine, architecture and how I solved some of the game logic and mechanics of the game. I also want to talk about some things that the good, bad and what I will think about for my next projects. As I was preparing these posts I noticed how it quickly grew in length which made me decide to split it into even more parts. For this first part we will only focus on my engine. Since it is a lot of stuff to cover if I were to go into every detail of it I decided to just write about the things I think is interesting or that I didn&#8217;t see that many others write about.</p><h1>Engine</h1><p>Brainroll is built within my own &#8220;engine&#8221; called Maraton. I put engine in quotation marks because as of late I have been viewing my engine as more of a framework as it doesn&#8217;t encapsulate any behind the scenes logic or systems and is right now only controllable through code just like any other game framework. But the ambition is to turn it to a full engine some day. It is built on things I&#8217;ve learned while being a part of the <a href="https://handmade.network/">Handmade Network</a> which I found through <a href="https://www.youtube.com/@MollyRocket">Casey Muratori&#8217;s Handmade Hero</a>. Since then I&#8217;ve also been very inspired by <a href="https://www.rfleury.com/">Ryan Fleury</a> which has explained very interesting ways of solving certain problems we&#8217;ll talk about later. </p><h2>Engine &amp; Architecture</h2><p>Maraton is built in a C style of C++ where I use a very slim subset of C++ features. The reason behind this is mainly that compile times are drastically reduced by ignoring the C++ standard library and features such as templates, in addition I have found C style code is also many times easier to debug than C++ heavy code. I personally also work better with a more limited set of syntax and language features as I am a person that tend to dive into a lot of rabbit holes when there are too many ways of solving the same problem.</p><p>As a reference the C++ features that I use are mainly:</p><ul><li><p>const references - Indication that value passed to function cannot be invalid and the function does not mutate this value.</p></li><li><p>Lambdas - Used in a very few places to make big functions easier to read by having helper functions spatially nearby.</p></li><li><p> templated struct - I use this at once place in the engine to implement a <a href="http://the-witness.net/news/2012/11/scopeexit-in-c11/">Defer macro</a>.</p></li><li><p>C++ style casts - Just something I experimented with but decided not to bother with. Still might find some of them in there.</p></li></ul><p>I have previously introduced the structure of the engine in an <a href="https://www.oskarmendel.me/p/maraton-initial-release">older post</a> which is now a bit outdated. Since then changed the structure a bit in preparation of the Brainroll release as well as my future titles.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mbj5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mbj5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 424w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 848w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 1272w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mbj5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png" width="381" height="201" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:201,&quot;width&quot;:381,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:12356,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Mbj5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 424w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 848w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 1272w, https://substackcdn.com/image/fetch/$s_!Mbj5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6d361ec6-7de9-4267-bada-2ec98fa2f821_381x201.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Engine submodule overview</figcaption></figure></div><p>The idea of this diagram is that its layered and your game is built on top of the stack while the different submodules depend on the modules underneath them, for example the 2D rendering system depends on the OpenGL abstraction layer as well as the platform layer which in its turn depends on the Win32 layer. This has worked great so far but I am still in the process of changing this entire structure into be even more modular because in the future I want them to be even more loosely coupled with eachother. I want every single subsystem to be its own self-contained module, it may depend on another module but you as a user of the engine should be able to use it standalone as well.</p><p>The engine does the essentials for me in order to get to work relatively fast. Here is an overview of what the engine does &#8220;behind the scenes&#8221; during a frame disregarding any of the rendering, font and ui modules.</p><ol><li><p>Update timers.</p></li><li><p>Check for input (Mouse, Keyboard, Controller &amp; Touch).</p></li><li><p>Update window size.</p></li><li><p>Prepare audio buffers.</p></li><li><p>Call into game update code.</p></li><li><p>Check if any settings was changed and update them if needed (VSYNC, Fullscreen).</p></li><li><p>Write to audio buffers with sound from the game.</p></li><li><p>Hot-reload game code if possible.</p></li><li><p>Refresh screen.</p></li><li><p>Update timers and sleep if needed to hit target FPS.</p></li></ol><p>As you can see its not really anything fancy but it does the important stuff that is required between game projects. I have actually very rarely had to modify this if ever. There was one project where I built a game requiring multiple windows and that game used two of these update loops, that was probably the only time I really had to make a &#8220;big&#8221; change to this system and all I did was basically to run this update loop twice.</p><h3>Hot reloading</h3><p>As a user of the engine you will first and foremost see the project divided into two parts, the game specific code and then the platform specific code, there is a barrier between these two and a platform layer that ties them together. The way this works is that the platform specific code includes the entrypoint for the application, this is compiled into an executable while the game code is built into a .DLL that is loaded by the platform specific code in runtime. It is mainly organized this way to allow for hot-reloadable code. This means that I can re-compile the game while its running and have it reload the DLL with the new version in runtime which allows for faster iterations. </p><p>The way this works is quite simple, all you need to do is make the platform specific code be in charge of the memory that drives the game, you allocate the entire game&#8217;s state ahead of time inside the platform specific code and then passes it down to the DLL for it to use. Then every frame inside the platform specific code you can have it compare timestamps on the DLL to see if there was a new version available to use. </p><pre><code><code>STN_INTERNAL b32
Win32GameLoad(win32_game *Game)
{
    b32 Result = false;

    WIN32_FILE_ATTRIBUTE_DATA Ignored;
    if (!GetFileAttributesEx(GlobalLockFullPath, GetFileExInfoStandard, &amp;Ignored))
    {
        if (CopyFile(GameDLLPath, GameAppDLLPath, FALSE) == 0)
        {
            Win32FatalError("Failed to load Game.", 
                            "CopyFile failed between \"%s\" &amp; \"%s\"", 
                            GameDLLPath, TempGameDLLPath);
        }

        Game-&gt;LastDLLWriteTime = Win32GetLastWriteTime(GameDLLPath);        
        Game-&gt;DLL = LoadLibraryA(TempGameDLLPath);

        if (Game-&gt;DLL == NULL)
        {
            Win32FatalError("Failed to load Game.", 
                            "LoadLibraryA failed for \"%s\"", TempGameDLLPath);
        }

        if (!Game-&gt;DLL)
        {
            Result = false;
            return (Result);
        }
    
        // NOTE(Oskar): Store function pointers to public functions in the DLL
        // I've removed some of the functions I have to make the code shorter.
        Game-&gt;Update         = (GameUpdateCallback *)GetProcAddress(Game-&gt;DLL, "Update");

        Result = true;

        // NOTE(Oskar): If any of your functions failed to load
        if (!Game-&gt;Update)
        {
            Game-&gt;Update         = GameUpdateStub;
            Result = false;
        }
    }
    
    return (Result);
}</code></code></pre><p>How this code snippet works is basically that we don&#8217;t use the game&#8217;s dll because that would lock the file which wouldn&#8217;t allow the compiler to delete it in order to create the freshly compiled dll. So what we do is that we copy our game.dll into a new file called temp_game.dll and load that file into the engine. Then we can write another small snippet to compare the write times with the game.dll on disk and if it is newer we call this function to reload.</p><p>There is a slight problem that you may encounter, that is that msvc won&#8217;t allow you to overwrite the .pdb files when applying this approach. There is a small hack that I learned from Handmade Hero where we can get around this issue in our build script:</p><ol><li><p>Delete all previouly generated .pdb files.</p></li><li><p>Create a temporary file to indicate that we&#8217;re in the process of building (<code>GlobalLockFullPath</code> in the snippet above<code>)</code></p></li><li><p>Build again but generate something random or unique as part of the .pdb file.</p></li><li><p>Delete the temporary file you created before.</p></li></ol><p>I use batch scripts when building my engine on Windows and this is how it can look when writing this in batch:</p><pre><code>if not exist build mkdir build
pushd build

del *.pdb &gt; NUL 2&gt; NUL
echo WAITING FOR PDB &gt; lock.tmp

start /b /wait "" "cl.exe"  %build_options% %compile_flags% ../src/game/maraton.cpp -Fmgame.map /LD /link -PDB:%dll_name%_%random%.pdb %common_link_flags% /out:%dll_name%.dll

start /b /wait "" "cl.exe"  %build_options% %compile_flags% ../src/engine/win32/win32_maraton.cpp maraton.res -Fmwin32_maraton.map /link %platform_link_flags% /out:%application_name%.exe

del lock.tmp

popd</code></pre><p>Batch uses <code>%indentifier%</code> as variable names and<code> %random%</code> is some sort of builtin variable that gives a random number. You can see in this snippet how we add the following flag to msvc <code>-PDB:%dll_name%_%random%.pdb</code> .</p><p>This system may be a bit cumbersome at first as a new user because you don&#8217;t have the game&#8217;s memory available at your fingertips. You have to ask the platform for more memory if you were to need it. If you don&#8217;t and just allocate your memory from the DLL it will be invalid upon a reload. I am a bit lucky because Brainroll is a game where I could easily always know how much memory the game needs so It has a fixed pool allocated on startup and that pool is just re-used for everything during the entire runtime of the game, there is however an exception to this that I will get into later. Worth to note that this only applies if you are using hot reloading, if you were to not use it then you are free to allocate memory wherever you want. </p><p>Over time I have come to dislike this barrier and hence I am going to remove the hot-reloading functionality from my engine as a part of the project making it more modular. That is until I figure out how to solve this problem in a good way. Overall I did have very good use of this while developing Brainroll, especially while making levels as the earlier versions of Brainroll required a recompile to alter the levels this was a very good way of making changes while playing the game at the same time. </p><h3>UI &amp; Font</h3><p>The UI subsystem in Maraton I have <a href="https://www.oskarmendel.me/p/ui-subsystem-iteration">covered before</a>, if you want a better explanation and deep dive into the system that I implemented it is available on <a href="https://www.rfleury.com/p/ui-series-table-of-contents">Ryan Fleury&#8217;s website</a>. That is why I will put more focus on how I solved the font related stuff, because I have not talked about it before. I decided to not use a library and implement my own functionality for baking a font atlas. In the past I relied upon the <code>stb_truetype.h</code> library which works really well but I experienced some issues when scaling my text for different sizes, more specifically very small text which made me just want to roll my own system. </p><p>I went with using DirectWrite as a backend, I made this decision based on that most of the other stuff I use is Windows specific (WASAPI, XInput, etc) in my engine and I feel a bit familiar with how they structure their APIs. DirectWrite is only used to rasterize the font into a texture, during the game&#8217;s runtime the font will be rendered like any other texture through my own rendering pipeline. The reason that we even have to use a library to do this for us is that font rendering is <a href="https://faultlore.com/blah/text-hates-you/">extremely complicated</a> to get right. Ignoring the fact that rendering glyphs for other languages such as Arabic is completely different to English we also have things such as anti-aliasing to worry about and I really didn&#8217;t feel like diving into that. </p><p>DirectWrite comes with its own subpixel rendering technique called ClearType which I was pretty excited to make use of in my engine. ClearType can help making the text a bit smoother by  combining an sub-pixel rendering technique with how the individual RGB leds in a LCD screen works to make the text look improved.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VKrv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VKrv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 424w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 848w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 1272w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VKrv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png" width="532" height="371.984375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:716,&quot;width&quot;:1024,&quot;resizeWidth&quot;:532,&quot;bytes&quot;:47488,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!VKrv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 424w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 848w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 1272w, https://substackcdn.com/image/fetch/$s_!VKrv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9c7df5f-0379-4450-9ca0-fdb35758c565_1024x716.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">a) Text rendered without ClearType. b) Text rendered with ClearType. Image from <a href="https://commons.wikimedia.org/wiki/File:ClearTypePixels.svg">Wikipedia</a> licensed under <a href="https://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0</a></figcaption></figure></div><p>The way I want to represent a Font within the engine is very simple, what I need to know is some information about each glyph, such as its dimensions and then I want to write each glyph into one big texture so that I can batch draw text through instanced rendering later on. To do this I also need to store the UV coordinates for each glyph to know where in the texture atlas the glyph is located. We also add a small offset because there needs to be some space between each glyph so that they don&#8217;t bleed into eachother. The Font also has some important values to keep track of such as Ascent, Descent &amp; Linegap which allows you to calculate the height of text if you are going to write several lines of text on the screen. </p><pre><code>struct glyph_metrics
{
    f32 OffsetX;
    f32 OffsetY;
    f32 Advance;
    f32 XYW;
    f32 XYH;
    f32 UVX;
    f32 UVY;
};

struct font
{
    glyph_metrics *Metrics;
    int32_t GlyphCount;

    f32 Ascent;
    f32 Descent;
    f32 LineGap;
    f32 PointSize;

    opengl_texture Texture;

    void *Face;
};</code></pre><p>The steps of actually rendering a full font into an atlas is not completely trivial. There are a lot of setup before we can start using DirectWrite to actually render our glyphs for us. First we need a factory that is used to create all our DirectWrite specific structures. Using that factory we need to tell it which font file we want to read and using that we can then create a font face which is the in-memory representation of a renderable font. This font face is something that we will keep around during the lifetime of the font in order to look up the indices into our glyph metrics for specific glyphs. This can be removed if we implement some sort of cache but I have not got that far yet. </p><pre><code>HRESULT Error = 0;

IDWriteFactory *Factory = 0;
Error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&amp;Factory);

IDWriteFontFile *FontFile = 0;
Error = Factory-&gt;CreateFontFileReference(FontPath, 0, &amp;FontFile);

IDWriteFontFace *Face = 0;
Error = Factory-&gt;CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &amp;FontFile, 0, DWRITE_FONT_SIMULATIONS_NONE, &amp;Face);</code></pre><p>Unfortunately DirectWrite doesn&#8217;t clean itself up after use so we need to actually call each of these objects Release method. This is one of the places where I found the defer macro that I refered to earlier be of good use. You may also have noticed that I&#8217;ve included an Error variable in the snippet, this is also something that we need to check. I did not include error handling nor cleanup in the code here just to save space.</p><p>Moving on we need to specify some parameters to tell DirectWrite how it should render our glyphs. I just decided to use the defaults that is set in the Windows control panel. Then we need a method of rendering glyphs into a bitmap I decided to use GDI, I didn&#8217;t explore the alternatives here so the decision is arbitrary. GDI is used by creating a GDIInterop which acts almost like the factory that we created earlier, we will only use it in order to create a bitmap render target. </p><pre><code>FLOAT Gamma = 1.0f;
IDWriteRenderingParams *DefaultRenderingParams = 0;
Error = Factory-&gt;CreateRenderingParams(&amp;DefaultRenderingParams);

IDWriteRenderingParams *RenderingParams = 0;
Error = Factory-&gt;CreateCustomRenderingParams(Gamma,
                                                DefaultRenderingParams-&gt;GetEnhancedContrast(),
                                                DefaultRenderingParams-&gt;GetClearTypeLevel(),
                                                DefaultRenderingParams-&gt;GetPixelGeometry(),
                                                DWRITE_RENDERING_MODE_NATURAL,
                                                &amp;RenderingParams);

IDWriteGdiInterop *DWriteGDIInterop = 0;
Error = Factory-&gt;GetGdiInterop(&amp;DWriteGDIInterop);</code></pre><p>We can now retrieve some metrics regarding the font to know how big bitmap we are going to have to allocate. We will draw each glyph to the bitmap and then copy it over to our bigger texture atlas. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hDRg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hDRg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hDRg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg" width="1266" height="713" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:713,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:117953,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!hDRg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 424w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 848w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!hDRg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03c9b1ba-0c24-406a-ab4f-25f189c12b06_1266x713.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Baked font atlas. Notice how much space is wasted (topic of future post).</figcaption></figure></div><p>DirectWrite uses <a href="https://learn.microsoft.com/en-us/windows/win32/gdi/device-vs--design-units">design units</a> in order to make it portable between differently scaled devices. This means that we will have to convert the design units into pixel units when rasterizing the font. I&#8217;ve created a small dictionary for the keywords that is used. Most of this is from <a href="https://learn.microsoft.com/en-us/windows/win32/learnwin32/dpi-and-device-independent-pixels">here</a>.</p><ul><li><p>Design Unit - Abstract unit independent of screen or text size and varies in resolution between fonts. </p></li><li><p>Em - Unit that scales relative to the visual size of the text. </p></li><li><p>Point - Fixed unit of physical length. This is 1 / 72 Inch </p></li><li><p>Design Unit / Em - The scale of a font's design unit. This exists within the Font Metrics. </p></li><li><p>Point / Em - The point size of text. For example 12pt text is it 12 Point / Em. </p></li><li><p>Inch / Point - Always 1 / 72 </p></li><li><p>Pixel / Inch - Also known as DPI. Default is 96 if your application is not DPI aware. </p></li></ul><pre><code>f32 PixelPerEM = PointSize * (1.0f / 72.0f) * DPI;
f32 PixelPerDesignUnit = PixelPerEM/((f32)FontMetrics.designUnitsPerEm);

i32 RasterTargetWidth  = (i32)(Padding * ((f32)FontMetrics.capHeight)*PixelPerDesignUnit);
i32 RasterTargetHeight = (i32)(Padding * ((f32)FontMetrics.capHeight)*PixelPerDesignUnit);
f32 RasterTargetX      = (f32)(RasterTargetWidth  / 2);
f32 RasterTargetY      = (f32)(RasterTargetHeight / 2);

IDWriteBitmapRenderTarget *RenderTarget = 0;
Error = DWriteGDIInterop-&gt;CreateBitmapRenderTarget(0, RasterTargetWidth, RasterTargetHeight, &amp;RenderTarget);

// GDI based HDC that allows us to make GDI calls to render.
HDC DC = RenderTarget-&gt;GetMemoryDC();</code></pre><p>Since we will write several glyphs to the same bitmaps we need to clear it per iteration, for this we have a small helper function that clears the render target that we&#8217;ve created.</p><pre><code>void
_DirectWriteClearDC(HDC DC, COLORREF Color, uint32_t L, uint32_t T, uint32_t R, uint32_t B)
{
    HGDIOBJ Original = SelectObject(DC, GetStockObject(DC_PEN));
    SetDCPenColor(DC, Color);
    SelectObject(DC, GetStockObject(DC_BRUSH));
    SetDCBrushColor(DC, Color);
    Rectangle(DC, L, T, R, B);
    SelectObject(DC, Original);
}</code></pre><p>Next all we need to do is to go over each glyph, render it to our bitmap and then copy it over to our texture atlas while also filing in our <code>glyph_metrics</code>. </p><pre><code>Font.GlyphCount = Face-&gt;GetGlyphCount();
u32 Column = 0;
u32 Row    = 0;
for (u16 GlyphIndex = 0; GlyphIndex &lt; Font.GlyphCount; ++GlyphIndex)
{
    // NOTE(Oskar): Render glyph into RenderTarget
    DWRITE_GLYPH_RUN GlyphRun = {};
    GlyphRun.fontFace = Face;
    GlyphRun.fontEmSize = PixelPerEM;
    GlyphRun.glyphCount = 1;
    GlyphRun.glyphIndices = &amp;GlyphIndex;
    RECT BoundingBox = {0};
    Error = RenderTarget-&gt;DrawGlyphRun(RasterTargetX, RasterTargetY,
                                        DWRITE_MEASURING_MODE_NATURAL, 
                                        &amp;GlyphRun, RenderingParams,
                                        RGB(255, 255, 255), &amp;BoundingBox);

    DWRITE_GLYPH_METRICS GlyphMetrics = {};
    Error = Face-&gt;GetDesignGlyphMetrics(&amp;GlyphIndex, 1, &amp;GlyphMetrics, false);

    i32 TextureWidth  = BoundingBox.right - BoundingBox.left;
    i32 TextureHeight = BoundingBox.bottom - BoundingBox.top;

    // Fill in Font.Metrics[GlyphIndex] for this glyph.

    HBITMAP Bitmap = (HBITMAP)GetCurrentObject(DC, OBJ_BITMAP);
    DIBSECTION DIB = {};
    GetObject(Bitmap, sizeof(DIB), &amp;DIB);

    // Copy bytes over to Atlas here.

    Column++;
    if ((Column * GlyphSize) &gt;= AtlasWidth)
    {
        Column = 0;
        Row++;
    }

    _DirectWriteClearDC(DC, Win32DirectWriteBackColor, 0, 0, RasterTargetWidth, RasterTargetHeight);
}</code></pre><p>The font metrics is really simple to fill in, you just specify the dimension of the texture and its UV coordinates into the atlas. In order to copy the pixels from the bitmap into your atlas its as simple as looping over every x and y coordinate in the bitmap. In order to access the pixels from the DIBSECTION you can use <code>DIB.dsBm.bmBits</code>. I have not included the exact code used in Brainroll because I am going to write a separate post about how my texture atlas is allocated. </p><p>This implementation have really helped me to make text look really crisp, especially when scaling the font way down. In the future I plan on implementing more backends here in order to allow the user of the engine decide to use DirectWrite or FreeType just like you might want to use D3D over OpenGL. </p><h3>Audio, Renderer, Memory, Crash reporter, misc</h3><p>The remaining systems of the engine are in my opinion not that interesting to go over. How I&#8217;ve implemented audio is very basic, it doesn&#8217;t do anything fancy all it does is mix audio samples in a buffer and sends it to WASAPI, I didn&#8217;t even bother with putting it in a separate thread yet. Same is basically true for the rendering system. I might be biased as almost everyone I know uses some sort of pushbuffer for their rendering system which is something I first learned from Handmade Hero.  </p><p>The idea is to have an immediate API but defer the actual rendering to the end of the frame. This is done by pushing the task to render something into a buffer. At the end of a frame you can in a very simple way loop over all your render tasks and the ones that are performing the same task can be instanced. For example if I render the same texture many times after eachother I can render them all in just one draw call which is very efficient. The same works for the renderers internal state, I can push a task for it to set what OpenGL framebuffer to activate or what blending modes to toggle on or off and then it will do everything it has to do at the end of the frame.  </p><p>Since I created mine then there has been open source implementations released of the same approach, one is <a href="https://github.com/Wassimulator/SimplyRend/blob/master/simplyrend.h/">SimplyRend</a> made by Wassimulator who is the developer of <a href="https://store.steampowered.com/app/1978850/AVRacer/">AV-Racer</a>. This library shares many of the ideas I have in my system.</p><p>I am a paranoid person, I think every developer about push the release button for their project is. What happens when someone starts the game on their machine, does it even start? I question myself things like that all the time. Luckily one of the developers of <a href="https://store.steampowered.com/app/1663410/Happenlance/">Happenlance</a>, Phillip Trudeau wrote a great post about how he uses discord webhooks as his crash reporter. I implemented this system and it has worked like a charm, the simple idea is that if the game crashes we save a crash dump and send it to a private discord channel where I can download and debug it. </p><h2>Thoughts</h2><p>Moving back to the post mortem perspective of the post I believe that using my own engine has its up and downsides. First of all the positives are that I am in control of my own tech, this has always been a goal of mine and it really improves my experience as a developer. In order to have this luxury I did sacrafice development time, I am confident that games can be faster developed using a pre-built engine. I believe if my goal was to get my company started and just focus on pushing products I would be much better of using pre-existing tech and having my engine as a side project, it would allow me to focus on building a great game instead of spending a lot of time programming things that might not matter but at the same time I don&#8217;t want to do that as it would remove the fun for me. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nkU4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nkU4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 424w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 848w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 1272w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nkU4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png" width="834" height="467" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:467,&quot;width&quot;:834,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:348633,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nkU4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 424w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 848w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 1272w, https://substackcdn.com/image/fetch/$s_!nkU4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3fbfa4af-94ca-4bd0-8a2e-303657ce724e_834x467.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Second Brainroll illustration sketch</figcaption></figure></div><p>Another downside is that there is noone to ask, the documentation is as existing as I personally made it. If something is working I have to solve it myself. While cumbersome this is also very valuable for learning experience, I think that I have become much more hardened as a developer because of this experience. Ever since I started this project my life as a professional developer has improved by a lot, I have managed to get a well paying role that I wouldn&#8217;t have gotten without the knowledge I&#8217;ve gathered from this project. In the future I will continue to work on my own engine but I will be open to take it in a direction where it does more work for you in order for me to be more effective when working on a new product. This I think will be even more valuable when bringing in other people into the project.</p><p>For this to work I think I need to be more strict with how I allocate my own time. I need to separate engine and game in order to not get stuck working too much on my engine and also make progress on the game projects. I consider myself a programmer-first, it is what I am best at and most comfortable with hence I have the tendency to stick my head under the sand and just tunnel vision on programming problems. Working on Brainroll has helped me a lot with this problem however, I feel like building a game from start to finish really gave me some important insights in what it really takes to get to the finish line.</p><div><hr></div><p>Did you know that you get access to the Maraton source code if you become a paid subscriber to my website? If you&#8217;re interested in learning or to be a part of the development then don&#8217;t hesitate and click the button below!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Brainroll Postmortem Part 2: Art & Assets]]></title><description><![CDATA[When I was young I was never good at a lot of different things.]]></description><link>https://www.oskarmendel.me/p/brainroll-postmortem-part-2-art-and</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-postmortem-part-2-art-and</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Fri, 27 Oct 2023 19:19:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!fnQP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When I was young I was never good at a lot of different things. When it was time to choose what direction I wanted to take my life I decided to focus on one thing and try to become the best I can be at that one thing. Since I had already dabbled a lot with programming and felt like it is something I feel like I can do all day I decided to go for it. I still kinda live by that philosophy to do one thing and do it great. This makes making games in my perspective really hard because I kind of limit myself to not doing some of the most important parts of games, basically everything the player will see and hear. In this post I will go over how I solved this for Brainroll. </p><h1>Art</h1><p>Right from the start of this project I knew I didn&#8217;t want to spend time doing graphics. The reason for this is what I previously mentioned and also that I knew that taking the time off developing the game to learn how to draw and make music would just take forever, especially if I want it to meet a certain standard. So instead, I wanted to use this time to further develop my engine as well as Brainroll itself.</p><p>For the first version that you saw a screenshot of in the first post I did actually draw the placeholder assets for. Since it doesn&#8217;t really matter what assets you use here it is a quick way of getting a prototype going. Moving forwards when I wanted to send the out my first test versions both to early access users and friends I had to upgrade the assets and here I got help from my wonderful girlfriend who drew the first versions of the character textures and the main tile set that I used almost until the final version of the game. For the assets that she didn&#8217;t have time to draw and for all the sound and music I used placeholders that I found in free art packs from itch.io.</p><p>The music that I originally found on itch.io I really liked. Many players felt like it didn&#8217;t fit the game that well because they sounded sad which I can agree with, originally the game was supposed to be a sad game but this is an idea that I scrapped half-way through development but I stuck with the music because I still felt like it fit the game well and wasn&#8217;t distracting for the player. Combining this with the tight budget and that I do not want to spend time making my own assets for this game it was a good enough fit for me however it is definitely a point that could be improved. Perhaps a good decision would be to have someone that has more knowledge in music than me to have a listen and give me some ideas.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!fnQP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!fnQP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 424w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 848w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 1272w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!fnQP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png" width="1456" height="351" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:351,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59411,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!fnQP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 424w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 848w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 1272w, https://substackcdn.com/image/fetch/$s_!fnQP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F496bb4e0-a790-4347-8d31-1c7d1ca51d1d_1560x376.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a><figcaption class="image-caption">Brainroll mockup</figcaption></figure></div><p>There are so many great artists on itch.io releasing so much stuff for free that I am confident that you can as a programmer today finish full games relying only on free or very cheap art. I however didn&#8217;t want to move away from my original idea that we have a brain that slides on ice and for something that specific it is really hard or even impossible to find a complete asset pack to use and mixing art from different sources just doesn&#8217;t turns out well in my opinion. My solution for this was to actually hire an artist to draw a complete asset pack for me to use in the game. This proved to be a very difficult task, especially on a very tight budget.</p><h2>Hiring an artist</h2><p>To hire an artist was a completely new area for me, I have never hired someone to do work for me in any way similar to this before and I have a very limited experience working with other people where I am the client. This made the whole endeavor hard for me because I didn&#8217;t know where to find someone, how much is a reasonable price to pay and how to properly give instructions for them to save time, reduce costs and more importantly me to get what I want. </p><p>I knew beforehand that I wanted the art to be of pixel art style, in hindsight I shouldn&#8217;t have limited myself to this but I think as a programmer with no real insight into art in general I just defaulted into thinking that pixel art is the standard for this type of indie game. Turns out good pixel art is actually really hard to make and there is just loads of pixel artists that doesn&#8217;t meet the bar, this also means that the ones that do usually does have a price tag that can get uncomfortable for a new solo developer. </p><p>I started out with gathering all the pixel artists that I liked from different subreddits such as:</p><ul><li><p><a href="https://www.reddit.com/r/gameDevClassifieds/">/r/gameDevClassifieds/</a>   </p></li><li><p><a href="https://www.reddit.com/r/HungryArtists/">/r/HungryArtists/</a></p></li><li><p><a href="https://www.reddit.com/r/PixelArt/">/r/PixelArt/</a></p></li><li><p><a href="https://www.reddit.com/r/commissions/">/r/commissions/</a></p></li><li><p><a href="https://www.reddit.com/r/gameDevJobs/">/r/gameDevJobs/</a></p></li></ul><p><strong>NOTE</strong>: This is not a comprehensive list and only some of the ones from the top of my mind.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Zx-f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Zx-f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 424w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 848w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 1272w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Zx-f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png" width="374" height="344.4180790960452" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fe417c30-9932-4e87-af03-ad2c817efd36_885x815.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:815,&quot;width&quot;:885,&quot;resizeWidth&quot;:374,&quot;bytes&quot;:431677,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!Zx-f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 424w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 848w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 1272w, https://substackcdn.com/image/fetch/$s_!Zx-f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffe417c30-9932-4e87-af03-ad2c817efd36_885x815.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Study of prop shapes</figcaption></figure></div><p>Something to point out is that this is by no means an easy process and really should be treated with the same care if not more than the rest of the tasks you do when building your own game. The process of vetting people you&#8217;re about to hire is really difficult and I could easily have seen myself having to pay for something that I wouldn&#8217;t end up using in the end if I wasn&#8217;t carefull with explaining what I was after both with words and images. </p><p>First thing I did was to just reach out and start a dialogue, see how well of a match I was with the artist. Some of the people that you will talk with are more interested in running a business than making friends and you just have to respect that. If you&#8217;re into very brief communication and willing to trust the other person to deliver what you want then go ahead. I personally don&#8217;t work that way especially when I am very new to a subject so I found artists that have the time to work with me, learn about the project and potentially give their own ideas and input to what I was doing.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!F6dB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!F6dB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 424w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 848w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 1272w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!F6dB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png" width="358" height="386.1941391941392" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:589,&quot;width&quot;:546,&quot;resizeWidth&quot;:358,&quot;bytes&quot;:135496,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!F6dB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 424w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 848w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 1272w, https://substackcdn.com/image/fetch/$s_!F6dB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b73d5b4-9476-4c86-9ebc-8faaef9140cb_546x589.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">First Brainroll character concept </figcaption></figure></div><p>Once I found someone that I felt was a good match and I was willing to work with I created a &#8220;design document&#8221; where I listed a pretty formal specification of the game&#8217;s concept, rules and game elements. In this document I wrote the explanations with text and images to to try really show what the idea of Brainroll really was. I later sent this document together with a playable demo of the game to the artist. </p><p>I don&#8217;t know if it is normal for artists to be interested in trying the actual game out but I personally consider it a very good plus because it not only shows that they are interested but it will help them figure out what you&#8217;re after and potentially suggest new stuff that you didn&#8217;t think about. In addition it is also a new playtester which maybe can give you some comments on the gameplay. </p><p>The artist that I ended up hiring did playtest the game and did give me some very valuable player feedback but also artistic feedback. For example encouraging me to not stick so hard to the brain and ice idea because it is pretty hard to make the ice look good. This feedback went to deaf ears because I knew that Brainroll was about a brain on ice. I can&#8217;t say for sure if I in hindsight think this was ignorance on my part. I think I still stand by my original idea and think that your artist should honor that but its not wrong for them to point out, maybe it makes you see things in a different way.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DSaX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DSaX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 424w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 848w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 1272w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DSaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png" width="365" height="343.4609250398724" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:590,&quot;width&quot;:627,&quot;resizeWidth&quot;:365,&quot;bytes&quot;:114520,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!DSaX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 424w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 848w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 1272w, https://substackcdn.com/image/fetch/$s_!DSaX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1cbb54ff-7fb3-4e5b-a172-d7473db7f696_627x590.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Second Brainroll character concept</figcaption></figure></div><p>Moving forwards we agreed for the artist to create a small mockup for a set price to show the idea that he had in mind. I think this is a pretty good starting point because you can get more information wether this artist is a good match and if you two think alike or if you&#8217;re too far apart.</p><p>I really liked what he put together, it looked simpler and easier to see what is what compared to the previous version where the ice and walls looked a bit too similar which confused players. I would not have been able to draw anything like that in a reasonable amount of time so I believe it was a good investment.</p><h2>Steam capsule</h2><p>The second thing that I knew that I would have to hire someone to do was the Steam capsule art. I don&#8217;t know why it often is refered to as &#8220;capsule&#8221; but it is basically all the illustrations used around the Steam store to show your game. A great resource for this has been Chris Zukowski&#8217;s website: <a href="https://howtomarketagame.com/">https://howtomarketagame.com/</a></p><p>It basically contains everything you initially need to know about marketing, much of the content is free as well in the form of blog posts which you can find just by spending some time browsing his site. But another great thing he offers are courses, one of which is the <a href="https://www.progamemarketing.com/p/howtomakeasteampage">How to market a steam page</a> which goes through all the steps of what you need to do when putting your game up on steam.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UkMw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UkMw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 424w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 848w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 1272w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UkMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png" width="310" height="393.0855855855856" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:563,&quot;width&quot;:444,&quot;resizeWidth&quot;:310,&quot;bytes&quot;:113621,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!UkMw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 424w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 848w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 1272w, https://substackcdn.com/image/fetch/$s_!UkMw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac6ca6cf-7d37-4f9a-8f6a-f9155cc5a079_444x563.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Third Brainroll character concept</figcaption></figure></div><p>One of the important parts he mentions is to definitely hire someone to do the illustration art for your Steam page. The reason for this is that the people who makes these types of illustrations just knows so much more about little details that you don&#8217;t even think about as a normal developer. They can pick an illustration apart thinking about placement, fonts, colors and other details that is specific for your genre that can make a huge difference in terms of visibility for your game. Also the way I reasoned about it is that it is the first image that potential customers sees which makes it important to have it be of good quality to draw peoples eyes to it. </p><p>Reddit can be really good for finding these types of artists but the best site I found was <a href="https://www.artstation.com/">https://www.artstation.com/</a> which is more oriented towards illustrations which is basically what you want. I opted to not restrict myself to an artist that have done Steam capsules previously and instead just went with someone I thought did great illustrations. </p><p>When talking to this person I didn&#8217;t send them a game design document but instead created a mood board as well as a couple of images of other illustrations on Steam that I found to be really good. This is something that I learned from the <a href="https://howtomarketagame.com/">howtomarketagame</a> website. I also sent him the entire folder that Steam gives you with PhotoShop files that includes measurements for the different assets, I didn&#8217;t exactly know which ones is which but he managed to figure that stuff out for me which was very appreciated. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wFtz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wFtz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 424w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 848w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 1272w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wFtz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png" width="835" height="470" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:470,&quot;width&quot;:835,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:368662,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!wFtz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 424w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 848w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 1272w, https://substackcdn.com/image/fetch/$s_!wFtz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf1e419a-62cf-4238-9e28-b73fe7bcb696_835x470.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">First Brainroll illustration sketch</figcaption></figure></div><p>I got to follow his work very closely with weekly updates on his progress and it was a really interesting experience on my part because what to me originally was a few hours in photoshop turned out to be a full on investigation with brainstorming ideas and exploring different styles to find the best way of expressing the idea. Another really amazing feeling was to see my game in a completely different light. To see my brain character in different shapes, forms and situations. I can say that this really helped me push through the final part of releasing the game because it really made the game look fun, almost like the illustrations followed me into the game when playing it and gave breath to a lot of more imagination.</p><h1>Some stuff I learned</h1><p>I think that I did make the right decision to pay people to make the assets for my game. I think currently the visuals of the game looks as good as it possibly can on this tight budget. I still today don&#8217;t have any interest in drawing my own assets or making music, maybe if I did the game would be cheaper and look and sound closer to what I originally had planned but still, I rather place my focus on getting better as a game designer and programmer. This might change in the future though depending on what the new project I decide upon is. </p><p>The way I ended up working with other people I think worked pretty well and I will most likely continue trying to be very personal and have a close relationship with the people I work with in the future. This time I only worked with people that didn&#8217;t touch &#8220;my area&#8221; of the project, it would be interesting to sometime bring on another developer into the project to learn how it is to work with more developers.</p><p>I should spent time investigating different art styles. There are tons of different styles to go for and working with indie games you are interesting in sticking out and trying new things. I think limiting myslef to pixel art was a bad decision, not because pixel art is bad in any way but rather that locking yourself to a single thing is bad when making games. The some goes for audio,  In hindsight I should have listened earlier to people giving me hints about the music and sound of the game. I think this was almost the same problem as with the pixel art that I locked myself to one thing not even allowing myself to try anything different. </p><div><hr></div><p>If you like what you&#8217;ve read, please consider subscribing. Thank you for reading.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p>I have also released my game Brainroll on Steam, be sure to check it out!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://store.steampowered.com/app/2396530/Brainroll/&quot;,&quot;text&quot;:&quot;Brainroll on Steam!&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://store.steampowered.com/app/2396530/Brainroll/"><span>Brainroll on Steam!</span></a></p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Brainroll Postmortem Part 1: History, Planning & Design]]></title><description><![CDATA[Maybe it is too soon to make a proper postmortem but I still feel like there are enough thoughts and lessons learned in process of making this game that I can write this post.]]></description><link>https://www.oskarmendel.me/p/brainroll-postmortem-part-1-history</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-postmortem-part-1-history</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sun, 01 Oct 2023 15:22:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5570ba57-edbc-4900-9f2c-a8439aca58f2_546x589.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Maybe it is too soon to make a proper postmortem but I still feel like there are enough thoughts and lessons learned in process of making this game that I can write this post. I will go over a brief history of Brainroll&#8217;s development history and the challenges I faced both technically and mentally, some reflections on the release and some improvements I could have made in the process as well as what I will do different in the future.</p><p>I have not read any other postmortems before so I don&#8217;t know if there is any specific format to adhere to, I will just try to talk about things that I found interesting to point out. I will release these posts in several parts starting with just how I got started with the project, then with how I planned and executed the project and finally some notes on the design of the game.</p><h1>History</h1><p>I want to start out with a very brief history of how I got started making my own game in my own engine. Ever since I got into software development I did it for my love of games, from the first line of code I wrote I&#8217;ve always had games in mind. I first got in contact with programming when I was about 13 years old when my father gave me the idea that I could make games instead of just spending my time playing them. He only have heard about C++ previously and told me that it&#8217;s what people use to make games, after some trial and error I managed to go out on the web and download Visual Studio and follow some guides to print out &#8220;C++ Rocks!&#8221; in the terminal. </p><p>Over the following years I really deep dived into programming making some small game prototypes. I never finished any of them but I was playing around and started to kind of get an idea of how to make my way around code. Just the fact that I could read and understand the basics of code made all my education a real breeze to get through.</p><p>During my spare time in university I was introduced to <a href="https://www.youtube.com/watch?v=Ee3EtYb8d1o">handmade hero</a> which is a YouTube series on how to create a game completely from scratch. I consider this a key point in my life because this series fed me what no other course or literature ever could, it explained everything, opened every single black box in programming and honestly by just following along with the 20 first episodes something just clicked for me and then I was able to write my own programs completely from scratch without the analysis paralysis that comes with many modern software development practices. Following along in the series I was inspired to write my own game engine, which I did. I iterated on this game engine a lot and at a certain point I felt like I could make a real game in it. It started with me trying to create a <a href="https://www.oskarmendel.me/p/bringing-dreams-to-reality">pinball game</a> which I eventually scrapped because the scope got to big so I decided to make something way simpler as my first game in order to be able to follow through and finish the entire project.</p><p>So the main goal for the project was to finish it, but other than that I set up some other goals that I had:</p><ul><li><p>Focus on simplicity and manage the scope to not let it grow to big and become overwhelming. The main goal is to actually finish the project.</p></li><li><p>Don&#8217;t be scared of learning things or trying new stuff out.</p></li><li><p>I wanted control over the project. This means I wanted to use my own tech so that the game will be able to live on my terms and not stop working due to some software update for example.</p></li><li><p> All non-game specific code should be re-usable for my next project. Graphics, UI System, Audio, etc.</p></li><li><p>Real production-grade assets to make it look like a good quality indie game. </p></li><li><p>A very tight budget on total maximum of 1000$ to spend on assets, tools or whatever else I need to finish the project.</p></li></ul><h1>Project planning &amp; structure</h1><p>Before moving onto details about the actual game I want to start with how I usually go about planning and structuring my projects. This is by no means a guide or me telling you what works and what doesn&#8217;t but just a walkthrough of how I personally get things done. It is also important to know that this was a solo project from start to finish (except for graphics) so I didn&#8217;t have to manage tasks for other people, instead just focus on myself and the project. Some of the things I mention here goes hand in hand with how I approached game design for Brainroll but I will go over that in a separate section. </p><h2>The initial planning document</h2><p>Before starting to work on the project I tried to dive deeper to flesh out the idea and brainstorm basically anything surrounding it. For a game project I usually start with the idea itself, it can be a section where I immerse myself a bit by making up a small story but more importantly I try to note down the rules of the game. For Brainroll the rules was the different tiles that the game would support and how they would interact with each other. This doesn&#8217;t have to be very serious because the rules will most likely change this over time but it is just to have some sort of guideline so I would know in which direction I am heading. It is also a good way to get the first couple of tasks going which helps if you&#8217;re programming because you get an idea of what you need to setup before you can get the most basic of the basic rules going.</p><p>I also try to investigate other games in a similar genre to take inspiration and perhaps note things that these games have in common. </p><p>Within this document I also touch a bit on the technical and art side of things where I note down ideas for graphical styles and what types of technical challenges that I predict that I will face along the way. This just like the game rules doesn&#8217;t have to be set in stone but can help because I can make a very brief plan very far ahead of time of how I want to deal with sounds or textures for example. If I know I want a graphical style that requires high resolution assets then I can have that in mind when programming. A concrete example of this is that I knew that I wanted to have pixel art graphics so I was planning in this document to use a sprite sheet or texture atlas, for convenience I noted down that I wanted to build a system where I only need to load one asset from disk and then I can re-use this several times when rendering things to the screen.</p><p>The mistake I did when it comes to this document is that over the course of the project I rarely refered back to my original plan. I basically created this plan and then started working on the project, the initial goals and ideas kind of faded away over time and as I came up with new ideas for the project. This is something I need to keep more track of in the future, perhaps instead of just making an initial plan it can become a more living document but deffinately not thrown away like it was this time. </p><h2>Task management </h2><p>During any project I like to keep a living list of tasks, ideas or questions that needs answers. I don&#8217;t have any good task management software that I like, I&#8217;ve tried using task boards like trello but what has worked best for me is to keep all of the things I need to do close to the software itself. For me this has been GitHub issues, it happens to be GitHub just because that is what I used for this project but other git solutions tend to have something similar. </p><p>Anyway, why issues in Github? First of all it is dead simple, all you really have is markdown text and labels and thats it. But second is that it lives together with the code. This means that I can control my list of tasks by just using git like I do with my everyday use. How I structure this is that I have a set of labels that I use to identify what each task is about and what priority it has. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PL5L!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PL5L!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 424w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 848w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 1272w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PL5L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png" width="949" height="284" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:284,&quot;width&quot;:949,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60272,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PL5L!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 424w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 848w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 1272w, https://substackcdn.com/image/fetch/$s_!PL5L!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6380dbec-9c52-4eab-b887-4ba0964c77a1_949x284.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This allow me to filter things depending on what is important or what mood I am in. Maybe sometimes I am not in the mood of programming than I can solve the issues marked as questions or documentation.</p><p>To make this work I let documents live inside my game&#8217;s repository alongside with the code and then when I work on a new task I simply create a new branch with the issue number as well as some identifier. For example if I were to implement achievements in Brainroll I would open a new branch called <code>317-achievements</code> where I work on this task untill it is finished, when it is I just create a new pull request into the master branch where I can code review my own code as well as link the issue to the pull request. </p><p>This may sound like a lot of overhead but in most cases it is just creating a pull request and typing &#8220;resolves #317&#8221; and Github just links the issue to the pull request automatically as well as closes the issue when the pull request is merged. I can agree that this gets a bit noisy but with this I am only using a single system for my code and tasks which I think is a big win. The markdown inside Github issues is also very flexible where you can paste images or links to make them very descriptive.</p><h2>Daily lists</h2><p>The final but probably most important part of planning and executing the project is my daily lists that I&#8217;ve kept almost everyday for the entire span of the project lifetime. This is actually something that I&#8217;ve done long before I started programming and it has been the only way for me to be productive and stay on track for long periods of time. After the release of Brainroll I have abandoned this list for a couple of weeks and I can say I have not been that productive since.</p><p>So my daily lists are just like what it sounds like, a list that I build up once per day. It consists of around 5 points, enough for me to be able to do them all in a single day. It is important that these tasks are pretty small because I don&#8217;t want to be overwhelmed and it is more important that I complete all the tasks on the list rather than getting a lot of work done because we don&#8217;t want to burn out. </p><p>I just flipped a few pages in my notebook and here is an example for one of my days:</p><ol><li><p>Setup NAS</p></li><li><p>Backup assets and videos to NAS.</p></li><li><p>Create 2-3 levels.</p></li><li><p>Remove pointer tool in editor.</p></li><li><p>Make release script save .pdb files. </p></li></ol><p>These are not that difficult to do and can easily be done in maybe 1-2 hours of focused work, perhaps even less. Important however is to keep it short and sweet, after I complete all the 5 points I stop working and do other things instead. </p><p>For me it has also been very important that these lists are written with pen and paper because for some reason I have a much easier time to remember what I&#8217;ve written down if I physically do it with a pen instead of writing it on my phone or something. This helps me stay focused because my daily tasks are fresh in my head during the entire day. Once I finish a task I draw a line over it, this is also important because I physically mark one task as finished and I can see how my list grows smaller. I usually prepare this list before I go to bed the day before so I also have the chance to get ideas before falling asleep on how to solve them, thinking about it now maybe that is why I have trouble falling asleep.</p><p>While this approach is great for getting things done everyday it is also the best approach for scope creep. When making your new list it is super easy to add items that you don&#8217;t really need to do and I did end up doing this a lot. I think working like this is something I really need to do in order to stay productive but I need to develop a way where the daily tasks are decided based on something else, for example if I were to make the initial planning document a more living document I could build my daily list based on that together with the technical list of tasks in Github. Having the freedom to set up my workday based on how I feel was very important and if I am to keep making games I need to be able to continue doing this but at the same time it needs to be structured.</p><h2>Splitting the project into parts</h2><p>So I didn&#8217;t create a big list of things to do at day one of the project, instead the list of things to complete was built over time. How I did this was that I just started with the known things I needed to do, then with this I planned my releases in sprints basically. Each time I complete a set of tasks I would end up with a shippable working (but not complete) version of the game. </p><p>At first when I hade my initial plan completed I started with creating the smallest possible prototype that showcase the game&#8217;s main idea. After this most of the decisions of what to do and how to do them was take ad-hoc. Part of the reasoning behind this was that I was unsure of what I needed to do next because I didn&#8217;t plan too far ahead instead I did mostly keep my list of tasks, my daily plan and just the initial document I made. So when planning a new sprint what I did was an evaluation of where the game was currently and in which direction I wanted to take it next. In addition to this many decisions was also based on what I felt like working on.</p><p>In retrospect I would probably not want to do it this way in the future. I don&#8217;t think the tech suffered a lot from this but the game&#8217;s design and how long time it took to arrive at the currently released version of the game did take a big hit from this. Let&#8217;s take the initial prototype as an example, doing this there is no clear defitiniton of when the prototype is completed. This problem persistend for every single version of the game. I think this might be valuable at the last few sprints of a project where the project is only being adjusted based on player feedback but not like how I did it for the entire lifespan of the project.</p><p>For my next project I think I will try to keep what I used as an initial plan document for this game to be something more alive, if I change game direction I need to verify what other parts of the game has to change moving forward and plan accordingly, something I did not do with Brainroll. I want to have a clean plan ahead all the time, It is not super important for me that I am the most optimal everyday I work but that I make meaninful progress towards my intended goals and don&#8217;t allow myself to guess or make ad-hoc or project altering decisions on a daily basis.  </p><h1>Design</h1><p>Shifting focus into how the design of the game took place I started with a simple idea that I wanted to make a game that was grid-based. Since one of the goals was to keep things simple I wanted to build off a core which I was very familiar with in programming which was to keep a simple array of tiles for the game state and mutate it when the player moves.</p><p>Looking around for ideas I found a game called <a href="https://orionsoft.itch.io/yopaz-icestar">Yopaz Icestar</a> I don&#8217;t recall why this particular game caught my eye but after buying and playing it I thought that this is a simple enough game for me to make so I started to basically just copy ideas from this game and make my own version of it. Building the base game was very simple and along with the mechanics I wanted I was able to finish the core in about one month. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qqR-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qqR-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 424w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 848w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 1272w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qqR-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png" width="1029" height="690" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:690,&quot;width&quot;:1029,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:63942,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qqR-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 424w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 848w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 1272w, https://substackcdn.com/image/fetch/$s_!qqR-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbec17729-ab34-4a2b-8949-29ed7a61045a_1029x690.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">First version of Brainroll</figcaption></figure></div><p>After sending this version of the game to some friends I got the feedback that the game was good and fun. When I personally played the game I didn&#8217;t have that much fun though and this prompted me to send a copy to someone outside of my friend circle and this proved to be very valuable because I got very different feedback compared to my group of friends. Ignoring all the technical feedback I will talk about some of the interesting game design feedback I received and my thoughts about it.</p><p>One of the main comments that I received from not only one person was that the levels were very trial and error and its not really a puzzle to solve as you can just pretty much brute force most levels. The basis of this feedback was that in the original game there was never any softlocks or game state that the player had to worry about. Every move you made was technically progression in the level and due to my choice of having a main mechanic where the player slides on ice the moves became more about trying to visually predict where you would end up in order to solve the level rather than think about what your next move should be. Combine this with close to no game state inside a level, the game idea was very and not interactive enough for a game.  </p><p>This triggered me to reconstruct the entire game idea to make the game more into a Sokoban puzzle game but on ice which it is today. A big takeaway that I learned from from this was that I actually had an almost finished game but I then took that finished game and turned it into a completely different type of game. Maybe that&#8217;s normal in game development but I still feel like the old version could have had its own place somewhere as a standalone title, maybe as a free demo or something.</p><p>The puzzle version of Brainroll that I ended up with I think is better in almost every way than the old version but I don&#8217;t really like to compare them because the goal of the games were completely different. This is something important that I will take with me when developing future projects that don&#8217;t throw away stuff when you have to pivot between game ideas because the old stuff you had may have a place somewhere else, since games are so difficult and expensive to make you really should try to salvage every small piece you create.</p><p>When designing the puzzles in Brainroll I had a lot of trouble because it turns out that puzzles are really hard to make, especially if you want to make good puzzles. I did outline in <a href="https://www.oskarmendel.me/i/114835666/level-design-language">another post</a> how I went about creating the puzzles in Brainroll but I will take some time and expand further upon it. </p><p>Before I started making a single puzzle in Brainroll I wrote down all the mechanics on a piece of paper, then I alongside every mechanic wrote down how they relate to eachother. What I mean by this is for example: </p><p>The brain blocks in Brainroll has the mechanic of sticking to you and other brain blocks, what happens is that out of this one mechanic you have several gameplay mechanics that gets invented:</p><ul><li><p>You can have the player <strong>arrange</strong> brain blocks in specific patterns.</p></li><li><p>You can have the player <strong>position</strong> a brain block at specific tiles.</p></li><li><p>Brain blocks can be used as a movable wall that you bring with you acting as an extra <strong>padding</strong>.</p></li><li><p>Finally if you use them as padding they offer the player the opportunity to <strong>sling</strong> themselves around a corner.</p></li></ul><p>I&#8217;ve highlighted some words in bold. This became my level designing language. When creating a new level I tried to think of of a small story or phrase explaining what the goal is for the player, so for example I could have written down &#8220;I want the player to <strong>arrange </strong>blocks in the shape of a minus sign and then use them as <strong>padding</strong> to <strong>sling</strong> around a corner&#8221;. Then I would start making a level where you have to do those things in order to solve it. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tLiw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tLiw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 424w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 848w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 1272w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tLiw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png" width="624" height="623.0850439882698" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:681,&quot;width&quot;:682,&quot;resizeWidth&quot;:624,&quot;bytes&quot;:578355,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tLiw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 424w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 848w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 1272w, https://substackcdn.com/image/fetch/$s_!tLiw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbe69fc-dd06-4eb4-9e4d-4560a2fe3612_682x681.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Second version of Brainroll</figcaption></figure></div><p>I can&#8217;t talk much about how effective this strategy was because I am so new in puzzle design I simply don&#8217;t know a better way, I just did something that made sense to me. Something I can vouch for is that this technique is very effective when you&#8217;re very short on time because it is makes it so simple to note down ideas for new levels during the day when you don&#8217;t really have time to work on the game. Then when you&#8217;re sitting down and making new levels it is not so important that every single level turns out great since you&#8217;ll build up such a big backlog of ideas you can just throw it away and try the next one.</p><p>The final game when every level was complete ended up being pretty mixed in terms of puzzle quality, some levels were good and some bad. I think like with all games, some people enjoyed it and some people hated it. I have trouble judging the game myself so I just listen to the small control group of players I tried to appeal to and most of them enjoyed and this I found to be a great strategy for feedback when testing the game, find testers who loves the type of games that you&#8217;re making. Something I have not analyzed that much myself is how much the old version of the game relates to the negative feelings I have towards some of the puzzles in the final game.</p><p>Something I&#8217;ve learned is that puzzle games are incredibly difficult to make. I think that if I want to become good at making puzzle games I need to spend more time playing other puzzle games, not only digital ones but paper ones as well. I have not been doing anything like that and I believe it plays a big part in how many of the puzzles in Brainroll turned out. For my next project I will deffinately spend more time studying what I need to study to appeal more to the audience of the genre I&#8217;m building a game for.</p><div><hr></div><p>If you like what you&#8217;ve read, please consider subscribing. Thank you for reading.<br>I have also released my game Brainroll on Steam, be sure to check it out!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://store.steampowered.com/app/2396530/Brainroll/&quot;,&quot;text&quot;:&quot;Brainroll on Steam!&quot;,&quot;action&quot;:null,&quot;class&quot;:&quot;button-wrapper&quot;}" data-component-name="ButtonCreateButton"><a class="button primary button-wrapper" href="https://store.steampowered.com/app/2396530/Brainroll/"><span>Brainroll on Steam!</span></a></p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Bringing Dreams to Reality]]></title><description><![CDATA[Thoughts about finishing an indie game.]]></description><link>https://www.oskarmendel.me/p/bringing-dreams-to-reality</link><guid isPermaLink="false">https://www.oskarmendel.me/p/bringing-dreams-to-reality</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Tue, 06 Jun 2023 16:18:46 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5c440735-a719-4f1a-857b-4bf9f20f4a4f_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>For me and many other programmers, games have been an entry point into the field of software engineering. When I was younger I spent most of my time consumed by different games, often daydreaming about the working with games just to be able to spend more time immersed in their virtual worlds. Now, over a decade later, I am proud to be able to say that I have turned what was only daydreams into my reality. </p><p>What is the longest time you have creatively worked on one single thing? </p><p>Leonardo da Vinci took four years to complete the Mona Lisa. Beethoven spent four composing his fifth symphony. Nowadays, games much like movies, also require several years to finish. I mention this to emphasize that producing creative works takes time, I will save a controversial post about games and art for another time. </p><p>The truth is that all forms of creative work demand a tremendous amount of effort to complete, and the quality of the finished work depends on the depth of the mental effort that is invested into the work. The fact is that creative work itself is genuinely difficult, especially when it comes to games, which combine several art forms into one. The challenge is something that many developers overlook as they daydream about the games they want to create. A lot of the problems arise from you not being able to decide when to be creative and when you&#8217;re not. Creativity is not something you can brute force, it is something you need to cultivate by exposing yourself to things that sparks your imagination.<br><br>When I was younger,  I used to enjoy drawing monsters, much like a daydreaming game developer. However, my hastly scribbled monsters rarely resembled the vivid creatures of my in my imagination. This happened because I never committed to completing them. I abstracted away all the effort required - various pencil strokes, precise details, shading, decorations as well as the monster&#8217;s story, name and goals. Consequently, my monsters never progressed beyond mere scribbles, so they stayed unfinished forever.</p><p>Unfortunately, most indie game projects meet the same fate as my unfinished monsters - they remain prototypes or forgotten side projects. Thankfully, today there are now tools available that can help you reach a completed stage faster. Game engines offer ready-made templates that can provide a foundation from day one. AI tools can assist with coding, generate images and even create sound or music. You can argue that these tools theoretically reduce the time needed to finish a game project - and they do to some extent, but they only solve the problem of reaching the finish line. They don&#8217;t automatically guarantee high-quality work, this means that just because tools can put together the pieces for you, you will not be finished in a shorter total timespan. Instead, you are sacrificing control over your project in order to get more velocity, but this can only be taken advantage of if you know these tools very well or if your AI generates the images or sound you want in a reasonable manner of time. </p><p>Once again, most indie games are never finished. Between the ones who are, there are very few that are hit games that will earn enough revenue to allow the developer to quit their job or immediately self-fund the next title. </p><p>There is a lot of talk about finishing a game or completing it as a project but the definition of what a finished game project is is quite vague. It can mean that you just shipped the product to Steam, or it can mean that your original idea is functional. There are no rules requiring you to have a main menu, or be able to adjust settings like sound or controls but yet players expect them, missing them will instantly give players the feeling that the game is not serious. There are of course exceptions to this but you get the idea.</p><p>To me, as a complete beginner with no games under my belt, finishing a game means first setting a certain bar for your game, then pushing it to that bar. With Brainroll being my first title, I didn&#8217;t know where the bar was at first. I just knew in which direction it was, mostly because of inspiration I got from looking at other games, this gave me a headstart in understanding what systems should be in place. The closer Brainroll got to the bar, the easier it was for both me and the people to tested it to know what was missing. This is the type of iterative process that I used throughout the entire development.</p><h1>How I got to the finish line</h1><p>When I started working on Brainroll I already knew that I wanted to make games, I also knew that I wanted to make it my own by being in charge of the code and design aspects. I had a good understanding of how much time I realistically can expect to pour into the development. Additionally, I wanted to develop the game publicly and offer an early access version to those interested in supporting it. This early access version was a paid edition of the game that received regular updates for every minor and patch version.</p><p>I also had a firm understanding of the project scope, as I had recently abandoned my first game - a 2D pinball game. I had planned on making it a pinball-based roguelike where you would travel between different planets and kill different types of aliens using your pinball spaceship. I even approached a local tattoo artist and asked him if he was accepting commissions in order to draw artwork for the game. However, after a year, I realized the scope was entirely unrealistic for my first game, given my skill level at the time. Consequently, I chose to develop something simpler.</p><p>The first important aspect of starting the second project was offering a paid early access version. It may have seemed weird for an unknown indie developer to do, but my intention was to find motivation in delivering a product to someone I didn&#8217;t know that had actually made an investment in the game. I wanted to be someone who could be trusted and relied upon, that I wouldn&#8217;t accept payment and then abandon the project. </p><p>This decision turned out to be one of the best decisions I made. After a couple of months and interactions with other indie game developers, someone kindly purchased a copy to show support, thereby holding me accountable for delivering on my promise. I am not the only indie developer that has found help in these small motivational factors, I know other developers that helped push themselves by things like live streaming their development, keeping a journal, and even taking help from family and friends to push them to finish what they started.</p><p>The second crucial part was having a realistic understanding of my capabilities and the scope of the game from the beginning. Interestingly after implementing the core idea, I received feedback from playtesters who felt that the gameplay lacked engagement. This prompted me to re-evaluate my progress and consider investing more time and effort to improve the game&#8217;s quality. Once again, the early backers of the project proved to be a great source of motivation for this decision as I wanted to make their money well spent. </p><p>I just mentioned that I did focus on quality towards the end of development, it&#8217;s important to clarify that although I made some refinements to the game&#8217;s levels I didn&#8217;t re-implement the game to achieve this, all the mechanics were already there and I did not do a complete re-write, instead, I just adjusted parts of the game. </p><p>Even with this effort the game still falls short of being in the top 5% of indie games. Creating the best puzzle game on Steam was never my goal. My aim was to become a game developer and &#8220;have skin in the game&#8221;. The reason I never aspired to create the most unique and outstanding experience was that, without ever having completed and released a game before, I couldn&#8217;t fully understand or estimate the effort required to reach the finish line. Without that knowledge, how would I be able to talk and explain what is needed for me to complete it to potential investors, family, and friends? I estimated Brainroll to take approximately one year to complete, but it ended up taking two.</p><p>Why it took so much longer than I anticipated was that I never fully realized that coding the game was just one part of the process. There were numerous other tasks that I hadn&#8217;t anticipated, such as:</p><ol><li><p>Managing accounting for the itch.io payments.</p></li><li><p>Dealing with feedback.</p></li><li><p>Resolving hardware-specific bugs.</p></li><li><p>Ensuring compatibility across different Windows versions.</p></li><li><p>Communicating and commissioning artwork.</p></li><li><p>Establishing an efficient debugging process for issues beyond my control.</p></li><li><p>Robustness of a UI system.</p></li><li><p>Addressing performance and optimizations.</p></li><li><p>Implementing settings and save files with versioning in case of updates.</p></li><li><p>Steam integration.</p></li><li><p>Rewriting rendering code just to make the art that I commissioned work.</p></li><li><p>Audio sliders need to be logarithmic instead of linear.</p></li><li><p>Adapting the game to different aspect ratios.</p></li><li><p>Effects to make the game look more interesting.</p></li><li><p>Implementing compression and keep both memory and disk footprint small.</p></li><li><p>Having a lot of days where I just don&#8217;t feel like working.</p></li><li><p>Mental fatigue to the point of me not being able to think straight or have normal conversations.</p></li></ol><p>These examples provide an idea of the additional tasks that unexpectedly arose during the game&#8217;s development. In my next project, this list will change with new unexpected things. Throughout the entire project, it was crucial to maintain a balance and prevent burnout, especially considering my full-time job as a programmer, which occupied eight hours of my day before I could even start working on the game.</p><h1>But what about quality?</h1><p>What is video game quality? If we ignore the absolute lowest bar a completed game should have. Some ideas may pop into your head such as story, graphics, or size. I personally think that the gameplay is what will determine the quality of the game, how well the problems with the idea have been ironed out, and how much thought has been spent deciding what the player will be doing, feeling, and thinking while playing the game.</p><p>I believe that game design is a skill like many others that improves with time and practice. I would never claim that the game design in Brainroll is exceptional or even good, I can however confidently say that both me and my game have come a long way since it first started. I see it as a journey of growth and development, where you gradually will form your own style and preferences. I think, however, it is important to not get fixated on perfection from the start. I would never finish Brainroll if I wouldn&#8217;t accept anything less than perfect. Instead, I&#8217;m going to take what I&#8217;ve learned and apply it to everything I do moving forwards.</p><p>Moving forward I plan on attempting to grow my design skill by having smaller side projects just for the sole purpose of practicing my game design skills. I am going to dedicate a few hours now and then to creating a board game or experimenting with small game prototypes. Why I didn&#8217;t make a great leap and do it for this project was that it is of my opinion that in order to successfully apply your skill to increase quality, there needs to be a product to apply it to. If your monsters will stay forever unfinished then you cannot reap the benefits from the quality increase of a great design.</p><h1>How you will reach the finish line</h1><p>If you want to release a game of your own, it&#8217;s crucial to first spend some time planning your route to the goal. Determine how much time you can allocate to the project each month and estimate the amount of work you will be able to accomplish within that timeframe. This is something that I recommend you do throughout the entire lifespan of the project, estimate each task and then measure how long it actually took. This will allow you to build up a better skill with planning but also help you stay on track because it will be more apparent if your time is spent in the right or wrong place.    </p><p>Once your expectations are adjusted and your route is planned you can select the appropriate tools that fit the scope of your project. Especially if it&#8217;s your first game then lower the scope even further. I recommend reading this article by Chris Zukowski titled &#8220;<a href="https://howtomarketagame.com/2021/01/04/happy-2021-you-are-going-to-finish-a-game-this-year/">Happy 2021: You are going to finish a game this year</a>&#8221; for more guidance and motivation.</p><p>Surround yourself with people who will support, motivate, and assist you. This point is critical for success. Personally, I couldn't have completed Brainroll without the support of my girlfriend, as well as the encouragement from my wonderful friends and fellow indie developers who wanted nothing but the best for me. This point is essential for success. Do not allow others to bring you down. Stay true to your vision and goals. If you encounter individuals who try to detract, provide toxic advice, or simply aren't interested in your growth, it's important to distance yourself from them. Don't let their negativity drag you down. Remember, those who behave this way are projecting their own insecurities and cynicism onto you. Spending time with them will hinder your productivity and hinder you from achieving your dreams.</p><p>Listen to feedback, but don&#8217;t blindly accept every suggestion. Throughout the development of Brainroll, I had the privilege of receiving help from excellent testers and developers who provided very valuable feedback. I carefully considered and took action on much of the input I received on the game. However, there were also instances where I chose to not follow certain suggestions. Remember that you are in charge of your project, in the end, it will be your name associated with the final product. Design the game that you believe in. Some feedback I received sat in my task management system for months before I decided whether to take action on it. </p><p>Avoid getting sidetracked. Side projects are fun and can provide a well-needed breath of fresh air, but it also takes away time from your main project, further delaying its release. If you need to take a break from your project, set clear limits on the amount of time you&#8217;ll spend on the side project before returning to your main focus. Alternatively, consider side projects that your game will directly benefit from. For example, updating your renderer or developing tools that help you create new levels.</p><p>Don&#8217;t get stuck in marketing, of course, you are creating a product that you want to earn money from so that you can continue your endeavors but if you are just starting out then your focus needs to be on actually learning about finishing your game. Once again Chris has a great article with the title &#8220;<a href="https://howtomarketagame.com/2019/10/21/the-stairstep-approach-to-indie-game-marketing/">The stairstep approach to indie game marketing</a>&#8221;. If you are just starting out then you will be on level 0 in his article and according to it doesn&#8217;t need to focus on making the world know who you are, save that for your 2nd or 3rd game. Remember that your new games will generate traffic to your older games as well.</p><p>My view on the matter is that just like finishing a game, building your career in indie games is not a race but a marathon. &#8220;Rome was not built in a day&#8221; kind of thing. </p><div><hr></div><p>If you like what you&#8217;ve read, please consider subscribing. Thank you for reading.<br>I am also releasing Brainroll on Steam, be sure to check it out!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://store.steampowered.com/app/2396530/Brainroll/&quot;,&quot;text&quot;:&quot;Brainroll on Steam!&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://store.steampowered.com/app/2396530/Brainroll/"><span>Brainroll on Steam!</span></a></p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Breaking through limitations]]></title><description><![CDATA[How I decided to give back control to the player in a limited playing-field.]]></description><link>https://www.oskarmendel.me/p/breaking-through-limitations</link><guid isPermaLink="false">https://www.oskarmendel.me/p/breaking-through-limitations</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sat, 15 Apr 2023 07:23:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The latest release of Brainroll, version 0.7.0 introduced the new chain mechanic which is a completely new and unique idea that creates some interesting levels that have players interested. As a developer, I am always excited to share my thoughts about the game and its design. I will provide a dive into the mechanic and how it came to be as well as what&#8217;s in store for the new version 0.8.0 as well as some exciting news for the future of Brainroll. Take a seat and grab your coffee and let&#8217;s explore.</p><h1>Increasing the number of states</h1><p>During the development of Brainroll I have always been aware of the very limiting movement that the ice-sliding mechanic brings to the table. If we for example look at the first level in Brainroll:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Nullsson is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3Hkg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3Hkg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 424w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 848w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 1272w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3Hkg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png" width="1266" height="486" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:486,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:644619,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3Hkg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 424w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 848w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 1272w, https://substackcdn.com/image/fetch/$s_!3Hkg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe61191e5-c8a8-4a5b-a9ad-5de559f5656a_1266x486.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It wouldn&#8217;t be difficult for you to find all the possible states the player can end up in within this level. We know the movement is already limited by the player only stopping upon a collision and while the travel distance may be far the number of unique tiles the player can stand on becomes quite small. The rule for the movement is that the player can only stand on tiles adjacent to walls. This rule is even further reduced by the prerequisite that you need to already stand in a position where this wall is in line of sight. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SbSM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SbSM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 424w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 848w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 1272w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SbSM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png" width="1266" height="486" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e0255257-57d3-4867-975a-05752ff91656_1266x486.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:486,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:626060,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SbSM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 424w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 848w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 1272w, https://substackcdn.com/image/fetch/$s_!SbSM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe0255257-57d3-4867-975a-05752ff91656_1266x486.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most of the states you see are even artificially created based on the positioning of the player as well as the first block that is obstructing the player&#8217;s path. Without them, we will lose 6 tiles the player can ever stand on. Even by simply moving the player&#8217;s spawn location to the top right corner we are removing those 6 states. Even with levels later on this problem is persistent. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!AQJ2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!AQJ2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 424w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 848w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 1272w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!AQJ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png" width="681" height="681" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:681,&quot;width&quot;:681,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:529633,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!AQJ2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 424w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 848w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 1272w, https://substackcdn.com/image/fetch/$s_!AQJ2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80ad3a78-a1dd-4393-892f-99bdbb127833_681x681.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So a problem that we quickly notice is that even though we increase the size of the levels as well as the complexity the player is very limited in what possible actions they can perform. The states can as we saw be artificially increased by positioning and adding additional walls but there is a fine line to walk between creating interesting and creative levels and creating cluttered and over-complex levels which most likely have artificial complexity embedded in them in the form of meaningless moves.</p><p>The sticky brains were one of the mechanics that I implemented in order to increase the number of states the player can end up in as well as allow the player to have some effect on the environment. This was a big success as many players have given feedback that they really do like the changes and that the state of the game today is much better than what it was a year ago. </p><p>This problem is not something new, however, I always had some awareness of the problems this type of movement would bring. In Brainroll, a game where movement is everything it is crucial for it to make sense and also mean something to the player. As I was progressing with creating new levels I found it harder and harder to keep the levels engaging as well as find the right balance between fun, interesting, and a reasonable difficulty level. I wanted to find an alternative, something that can help give the players more choice of the environment, a way the player can make the solution their own. This is how I figured out the chain mechanic.</p><p>A funny detail is that the original idea was sketched upon a napkin at the dinner table while I was visiting my in-laws, unfortunately, I didn&#8217;t keep the original sketch but it looked something like the following illustration.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LRny!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LRny!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 424w, https://substackcdn.com/image/fetch/$s_!LRny!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 848w, https://substackcdn.com/image/fetch/$s_!LRny!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 1272w, https://substackcdn.com/image/fetch/$s_!LRny!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LRny!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png" width="218" height="365.74407582938386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:708,&quot;width&quot;:422,&quot;resizeWidth&quot;:218,&quot;bytes&quot;:340281,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LRny!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 424w, https://substackcdn.com/image/fetch/$s_!LRny!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 848w, https://substackcdn.com/image/fetch/$s_!LRny!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 1272w, https://substackcdn.com/image/fetch/$s_!LRny!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2407b6dd-834c-4afd-9bbd-a3a301afec0e_422x708.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The filled rectangle represents a wall while the outlined rectangles represent possible states the player can end up in. </p><p>While very simple the big revelation was for me that if I tie one of the sticky brains to a position I can alter the states that the sticky brain can end up in which in turn can alter the states the player can end up in. In addition to this, it has the same effect on the game that you notice with the differently sized logs within <a href="https://www.monsterexpedition.com/">A Monster's Expedition</a> because we can alter the number of tiles long a chain should be. Plus if we want to we can explore connecting the chain to something else than just one sticky brain and one wall. I&#8217;ve actually created a level like this where I instead connect two sticky brains which creates an interesting scenario for the player. </p><p>I don&#8217;t think I&#8217;ve mentioned it before but I really enjoy the part of game design where you take something simple and push it to its limits. That is how I feel like I can be creative and explore new solutions to problems. I really feel like I did that with this mechanic because it is relatively unique and it does fit the game pretty well.</p><p>Implementing this however, meant a lot of complexities. The underlying architecture of the game didn&#8217;t really support dynamic objects other than the player, this may seem confusing as the sticky brains move but in reality, they are also players that you simply don&#8217;t have control over (this maybe give you a hint of something I might do in the future). So what I had to do was rip the world logic apart in order to include not only dynamic entities but also entities that move very differently compared to the straight line the player and sticky brain move in.</p><p>My first approach was to implement the chain through A* pathfinding where the start position was a wall the tile being connected and the end position the tile the connected entity would attempt to move to. This worked okay for a while but ended up not being the correct approach to implement a chain like this because I wanted the chain to be able to wrap around walls.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9o6t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9o6t!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 424w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 848w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 1272w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9o6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png" width="227" height="305.74254742547424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6abea21b-4f2e-43c0-948b-117399952e20_369x497.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:497,&quot;width&quot;:369,&quot;resizeWidth&quot;:227,&quot;bytes&quot;:297296,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9o6t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 424w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 848w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 1272w, https://substackcdn.com/image/fetch/$s_!9o6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6abea21b-4f2e-43c0-948b-117399952e20_369x497.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1zSJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1zSJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 424w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 848w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 1272w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1zSJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png" width="229" height="307.1951219512195" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/81558104-4203-4997-9586-949b4126f8ec_369x495.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:495,&quot;width&quot;:369,&quot;resizeWidth&quot;:229,&quot;bytes&quot;:296734,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1zSJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 424w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 848w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 1272w, https://substackcdn.com/image/fetch/$s_!1zSJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81558104-4203-4997-9586-949b4126f8ec_369x495.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The A* implementation was difficult to work with when adding these types of restrictions so I instead opted to just create my own algorithm for the movement of the chain. In very simple terms the pseudo code would look something like:</p><pre><code>b32 Succes = TryMoveToNewTile(Sticky, TargetTile);
if (!Success)
{
    // Try move rope to an adjacent tile to TargetTile
    for (RopeNode : Rope) 
    {
        vector2 TileA = TargetTile;
        vector2 TileB = RopeNode-&gt;Tile;
        for (AdjacentTileA : TilesInAllDirections)
        {
             for (AdjacentTileB : TilesInAllDirections)
             {
                  if (IsSameTile(AdjacentTileA, AdjacentTileB)
                  {
                      // This is a potentail move as both the destination and
                      // rope is adjacent to this tile.
                      // Check if next rope node also needs to be moved and
                      // recursively repeat process if it does.
                  }
             }
        }
    }
}</code></pre><p>You can imagine in your head the sticky brain attempting to move to a location and then attempting to pull the next chain node in line one tile towards it and then the chain nodes recursively repeat this process.</p><p>Today this works quite well, there are still some artifacts that can happen with some level designs but the overall mechanic and logic are deterministic and based on feedback more importantly also interesting.</p><p>As I learn more about creating games I want to restrict myself more and more from calling Brainroll a puzzle game but if I were to call the level&#8217;s puzzles then based on my personal judgment this mechanic doesn&#8217;t by itself make the game feel like a chore or uninteresting. There are still elegant puzzles that can be created with this mechanic, I however have not been able to find them yet as I was burnt out of creativity shortly after I finished implementing this. I play on resuming this task as the final step in the development process and only focus on levels once the game is in a &#8220;ready&#8221; state.</p><h1> Version 0.8.0</h1><p>The new version is going to be released right after I&#8217;ve finished composing this post. Inside this update, there have not been that many changes that will be immediately visible to the user but still important changes I had to push out in order for the game to be in a more ready state.</p><p>Internally I&#8217;ve referred to this version as the UI version as I&#8217;ve attempted to fit everything that has to do with the UI into this update. I will briefly go over the changes I made and then also talk a bit about the changes I didn&#8217;t make and will postpone to a later date.</p><p>First of all a lot of small annoying bugs and artifacts were fixed. For example, when navigating with the keyboard, the hover effect that appears was initially popping out of the top left corner, this has been fixed to not confuse anyone anymore. In addition to this something that many users have mentioned that needs fixing is the text scaling within the interface. Many of the testers have given feedback that the Brainroll title is the same size as the buttons of the interface and is sometimes confused for a button instead of just text. This has been fixed by my adding a bigger scale to the font of titles within the interface.</p><p>In doing this I also tackled a much bigger issue which is the main reason for this version taking longer than it had to. I made both my engine and Brainroll completely DPI aware. What this means is that text will be scaled based on the user&#8217;s settings within Windows which is an accessibility feature that many people including myself use. While doing this I also abstracted the entire font system of the game engine into some reusable components in the preparation for allowing programmers to choose which font backend they want to apply to their application. Right now I only support DirectWrite but in the future, I want to be able to use FreeType for example.</p><p>Just as my entire UI system I was also once again inspired by <a href="https://www.rfleury.com/">Ryan Fleury</a> with my font system. As he has in the past displayed something that he calls &#8220;Atlas Allocator&#8221; whose responsibility is to allocate glyphs onto a texture atlas. While doing this I optimized the size of the textures that the applications need to allocate in order to fit all the glyphs by quite a lot if you look at the following before and after images.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4GaG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4GaG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 424w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 848w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 1272w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4GaG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png" width="1266" height="713" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/be9d5595-375e-40a1-807b-378b9a755aee_1266x713.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:713,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:163746,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4GaG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 424w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 848w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 1272w, https://substackcdn.com/image/fetch/$s_!4GaG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbe9d5595-375e-40a1-807b-378b9a755aee_1266x713.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0Bp2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0Bp2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 424w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 848w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 1272w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0Bp2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png" width="1266" height="713" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4962c82-e014-4fd7-b867-023122a27dba_1266x713.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:713,&quot;width&quot;:1266,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:140596,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0Bp2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 424w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 848w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 1272w, https://substackcdn.com/image/fetch/$s_!0Bp2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4962c82-e014-4fd7-b867-023122a27dba_1266x713.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This was an investment into myself and my engine as I will re-use the same type of abstraction idea when I later add support for more rendering backends to the engine.</p><p> Next up I also centered the UI and removed the background assets of the main menu. This was mainly because with the new bigger title font the text often was hard to see because of the snowy mountain textures around the screen, I like them so I instead just centered the text to get rid of this problem. </p><p>The last UI thing I did was some preparations for the next version which will include a move counter and score your performance based on the number of moves you use to complete a level. I will award the player up to three different trophies based on how &#8220;good&#8221; they complete the level.</p><p>A lot of the background music was removed and instead of playing one track per level I now loop the background music and divide the different tracks over several levels. This is a much needed improvement that actually allows you to hear many of the full songs which I know many people will appreciate.</p><p>Lastly I also fixed a bug with save and settings files that didn&#8217;t allow players to put the game within their Program Files directory. That directory requires administrator priviledges to write to which the game most likely doesn&#8217;t have when it is being ran and hence it would crash or not be able to produce files within the same directory as the executable. To fix this the game now uses the Save Games directory that Windows manages.</p><h1>Moving on</h1><p>As for the future I am proud to announce that this version will be the last early-access version of Brainroll before the full release. Right after this version is published I am going to merge in the new assets into the master branch and continue with the last set of features that needs to be fixed and once that is done all I am going to do is to look over the levels once more and focus on adding some new interesting levels.</p><p>I just yesterday of writing this post became accepted as a steam partner and have Brainroll prepared as a game on steam. I will make a new announcement once the steam page itself is publicly available for you who want to wishlist the game.</p><p>If you have any feedback I am very interested in hearing it, you can get back to me here on substack or any of my other socials. I really hope that you enjoy this version of Brainroll!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Nullsson is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Brainroll feedback]]></title><description><![CDATA[This devlog is a bit late as I did ship version 0.6.1 of Brainroll about 3 weeks ago.]]></description><link>https://www.oskarmendel.me/p/brainroll-feedback</link><guid isPermaLink="false">https://www.oskarmendel.me/p/brainroll-feedback</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Tue, 14 Mar 2023 19:34:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/212f47c2-b45b-41d8-b06f-ff90afa6b01e_960x480.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This devlog is a bit late as I did ship version 0.6.1 of Brainroll about 3 weeks ago. Version 0.7.0 is dropping any day now which contains a bunch of bugfixes as well as some more levels. More importantly, I want to talk a bit about why I have not posted anything about Brainroll for the last couple of weeks including writing this devlog.</p><p>When releasing 0.6.0 I implemented a new unique mechanic and created a bunch of new levels using them. At this point, I think I was at about 30 levels in the game which I think is a fair amount. So instead of just shipping it as usual I decided to ask some people to playtest it. When doing this I wanted the opinion of people that had no investment in Brainroll or ties to me to get pure feedback that is not biased. I was looking for the same kind of feedback I would get when the game is eventually released on Steam.</p><p>I have contacted a total of six people, about half are game developers I wanted feedback from because I look up to them and the other half are puzzle game enjoyers that I picked because of their passion for puzzle games. Out of the six people, 5 people played the game so far and 4 of them enjoyed it and/or appreciated the ideas in the game. I want to point out however that when I say that the majority of the testers enjoyed it or appreciated it I don&#8217;t mean for it to sound like I&#8217;ve constructed a masterpiece rather that these people didn&#8217;t have any expectations of my game and just enjoyed it for what it is. With that said I want to go over some of the feedback and my thoughts about it. Note that I will only go over puzzle related feedback as bugs and other ideas are getting fixed soon!</p><p>If you want to get an early access copy of brainroll to support my work then it is currently available on itch.io using the link below.</p><p><a href="https://nullsson.itch.io/brainroll">Brainroll</a></p><h2>Difficulty</h2><p>Right now there is a gap in difficulty between earlier levels and when the new mechanics are introduced. Players not only felt like there was a big jump in difficulty but also that earlier levels had more depth than the advanced ones.</p><p>My thoughts about this are that it is a &#8220;mistake&#8221; on my part. The earlier levels in the game have gone through a lot of iterations while the last say 15-20 levels have been made pretty quickly not only as a way for me to experiment with the game but also to learn. Not all levels currently have a core idea behind them as many of the earlier levels do and this is something that I have taken very seriously. This is also why some players commented that earlier levels felt more difficult to solve than later ones.</p><p>My original plan was to release the game on Steam with about 50 levels, this number can increase if I find more fun stuff to add but if not I will stop when I reach 50 levels and instead of making new ones I will focus on iterating over the ones I already have. When 0.7.0 is released I will be at about 38 levels and maybe after 0.8.0, I will have reached that number of 50. I plan to try to keep a very nice velocity moving forward to get to this point as fast as possible so that I can have enough time to iterate (this is the closest to a hint at a release date you get).</p><h2>Random keys</h2><p>It has not been uncommon for players to feel like some levels are &#8220;random&#8221; or like they can just brute-force the level by spamming enough moves to finally reach the goal. The &#8220;problem&#8221; that some experienced of puzzles having multiple solutions also falls into this category of feedback.</p><p>This is something I am not that worried about as it has been a part of the core design of the game even since the earlier &#8220;maze&#8221; version of the game. My answer to the feedback of players that feel this way is that it is true, you&#8217;re right you can probably reach the goal eventually by spamming keys. If this is how you want to play the game then that is fine by me but the original game idea was for the player to find the most optimal path or solution to the puzzles. Maybe this is my fault for saying that it is a puzzle game or even comparing it to sokoban but I feel like that is fine, I am giving the player enough freedom to play the game that the player enjoys.</p><p>To go even further into this you can solve sudokus by using a sudoku solver online but will you be satisfied? Will you feel like you beat the puzzle? Most likely not, however sometimes to learn it is not bad to look at the solution, just as I many times learned new programming concepts I looked at already written code by others and I feel like if you play Brainroll and stumble on the solution then you&#8217;ll just learn what you had to do to win and can iterate on your solution.</p><p>This will of course become more clear as I implement some sort of state that keeps track of your move count. This however is not yet implemented in the game and is something that I will have to add very soon. So far I plan on doing some sort of 3-star system where you get more stars the fewer moves you make and make.</p><h2>Coffin mindset</h2><p>I was not originally going to touch on this but I changed my mind. Soon after the release of 0.6.1, I got into a bit of a depressive state due to some negative feedback about the game.</p><p>To be more specific the suggestion I got was to stop working on Brainroll and start working on a new game. I have been reflecting on this a lot and realized that I will categorize it as poisonous advice. What I have come to terms with is that no player or other developer will ever see Brainroll from my perspective, it is a snapshot of my ability as a game developer and game designer at this point. For what it is I think that Brainroll still encapsulates my ideas pretty well.</p><p>The game design of Brainroll is something that has evolved naturally and that is why I think that it is still a game that is worth shipping. What I mean by natural evolution is that the original concept for the game was the ice caves from the early pokemon games where you step on the ice and slide until you hit a rock and just have to get past a bunch of obstacles to beat the level. I took this concept and added a bunch of new mechanics, teleporters, bombs, stars, and more to make it a more interactive game. However this idea was scrapped in favor of a more stateful game, I brought it to more of a puzzle game with the current versions and I do believe that it is a better game.</p><p>However, something important that I realized in hindsight is that this is a new game, it&#8217;s not even the same as the original Brainroll. Technically I could have wrapped up the old &#8220;maze version&#8221; which had almost 100 levels at the end and just shipped it as some sort of demo or even a finished game on itch. The reason why I didn&#8217;t realize this at the time is that I drifted away from my original visions of not only what game I want to make but what it means to be a game developer to me. I want to make games that I enjoy using my ideas.</p><p>I have historically been very insecure about this and probably still am, especially when I am going to take the real step and release my game on Steam but I have learned that I need to follow my vision and not have other ideas imposed upon me. I will from now on practice simply not listening to feedback that is simply anti-improvement feedback and I will keep true to my vision of making my dreams come true.</p><p>Even if Brainroll is released and imperfect I will make it as good as I possibly can with the knowledge I have now and after release make a retrospective on the whole project and bring everything I&#8217;ve learned into the next game.</p><h2>Version 0.6.1</h2><p>As for the changelog for this version as I mentioned earlier it contains mainly a new mechanic as well as some new levels. The new rope mechanic is something unique I have not found in any similar games where the sticky brainrolls can now be connected to another object by a rope that limits its movement. The idea behind this was to allow for an even more dynamic environment that the player can mold during gameplay.</p><p>With that said here is the full changelog for this version:</p><h3>Added</h3><ul><li><p>Rope mechanic.</p></li><li><p>Rope levels.</p></li><li><p>Level select menu.</p></li></ul><h3>Updated</h3><ul><li><p>Improvements to introductionary levels.</p></li><li><p>Issue with UI not working after selecting level from level select.</p></li><li><p>Particles not being cleared upon reset.</p></li></ul><h2>Moving forwards</h2><p>Update 0.7.0 is around the corner which contains a bunch of bugfixes as well as new levels. After this release, I am going to perform a new pass over the interface of the game.</p><p>This last month I have also commissioned new art for the game, I have opted out of releasing this new art within the coming new version to give the Steam release some extra spice which I hope people will appreciate!</p><p>If you have any feedback I am very interested in hearing it, you can get back to me through any of my socials. Hope you enjoy this version of the game!</p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Curious heart & digital mind]]></title><description><![CDATA[The first few years of my career]]></description><link>https://www.oskarmendel.me/p/curious-heart-and-digital-mind</link><guid isPermaLink="false">https://www.oskarmendel.me/p/curious-heart-and-digital-mind</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Fri, 03 Mar 2023 19:31:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!jPOd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em><strong>&#8220;Accelerate the world&#8217;s digitalization by creating the next generation of digital tools that simplify and improve people&#8217;s quality of life&#8221;</strong></em></p><p>This quote is the driving force and inspiration behind all the work done at the company I&#8217;ve been working at for the last 3.5 years of my life. As It is time for me to embark on a new adventure I want to take some time to reminisce about my time spent there and go over parts of my journey of experiences and valuable lessons learned.</p><p>After finishing my bachelor&#8217;s thesis I still had some lingering courses that I didn&#8217;t finish so I was originally planning to take an extra semester to wrap things up but somewhere in the back of my head, I was anxious about looking for my first job. At this point in my life, I had very little professional experience and the thought of leaving the comfort of the university and wandering into the unknown was daunting.</p><p>To ease my mind I started looking for job opportunities in my city just to see if anyone was interested in hiring me. My inbox was empty for weeks. I started growing very desperate and started questioning myself about what the reason could be. I had still not yet realized that it was still summertime and most people were still on vacation.</p><p>Luckily it didn&#8217;t last for long, I pushed on for one or two weeks with my courses and then I started to a lot of responses pouring in. Excited I responded to the first one and shortly after booked my first interview.</p><p>At my interview at Zenta, I was greeted by some cool people, this was the first time that I met people that shared the same genuine interest and passion for technology as me. During my studies, there were a few classmates but the majority couldn&#8217;t care less about the software which I still find quite odd as I was studying computer science. I was taken on a tour around the office and I was in awe as I was given a short introduction to not only the software projects but the hardware projects they were working on, one thing I remember that stood out was a pair of car doors that had their windows modified with a film that when supplied with electricity could physically change the transparency of the window. In addition to this, there was a vibration speaker installed at the bottom of the glass which used the glass surface as a speaker.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jPOd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jPOd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 424w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 848w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 1272w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jPOd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png" width="392" height="522.536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1333,&quot;width&quot;:1000,&quot;resizeWidth&quot;:392,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Car Doors&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Car Doors" title="Car Doors" srcset="https://substackcdn.com/image/fetch/$s_!jPOd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 424w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 848w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 1272w, https://substackcdn.com/image/fetch/$s_!jPOd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F81a239c8-f0bb-4e99-9541-d49fc731ff52_1000x1333.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To me this was science fiction, I didn&#8217;t even know you could write code and build this type of thing. I was taught UML diagrams and design patterns and here they are building these crazy futuristic projects. I knew right there and then after learning about the things they were doing that It is no use for me to stay and finish the courses I had left and instead I needed to get a job here. The interview process went smooth as everyone working there was super interested in technology as I were and I think they felt my excitement so they hired me shortly after.</p><h2>Other people&#8217;s code</h2><p>Something that you do not learn while studying is to read and more importantly understand other people&#8217;s code. At university most software was developed completely by you but as soon as I stepped into the working shoes that was no longer the case, now most of the code I saw during the day-to-day was written by someone else. Since I did software consulting that meant I could sometimes work on 3-4 projects at once which all had their problem domains which resulted in very different technical problems and codebases.</p><p>In all projects, it would also potentially be different team members which meant you&#8217;d have to read and understand different coding styles. This is another very valuable part of reading code, this is great that you can learn new techniques and approaches as everyone kind of finds their way of how they like to solve problems, this allows you to pick and learn the good parts and use them for yourself.</p><p>Even though it is required many times and a very useful skill it is not that many developers practice reading others&#8217; code as much as they should. It is very easy to get too comfortable as a software engineer and tunnel vision on your next task instead of getting a better picture of the entire project.</p><p>Something that weighs extra in is to <strong>understand</strong> what someone else wrote, this is by far the hardest part of reading others&#8217; code as it may often depend on domain knowledge that you may not possess at this time. I think this is a problem that most developers will find in their career several times as most developers including myself are pretty bad at writing documentation or useful comments to prevent information loss when different developers have to touch the code. This is however something I have gotten a lot better at over the years, I usually have the habit of at least including comments with links or explanations when I use some solution that is not obvious, an example of this I had to do in my game engine Maraton where I load different versions on a library depending on if you&#8217;re using Address Sanitizer or not:<code>
</code></p><pre><code><code>STN_INTERNAL void
Win32LoadXInput(void)
{
    // NOTE(Oskar): Loading different XInput versions here depending on flag because of a bug in ASAN which clashes with xinput1_4.dll.
    // more info can be found here: 
    // https://developercommunity.visualstudio.com/t/AddressSanitizer:-Execution-hangs-at-glB/1533793?entry=myfeedback&amp;space=62
#if BUILD_USE_ASAN
    HMODULE XInputLibrary = LoadLibraryA("xinput1_4.dll");
    if (!XInputLibrary)
    {
        XInputLibrary = LoadLibraryA("xinput9_1_0.dll");
    }
#else
    HMODULE XInputLibrary = LoadLibraryA("xinput9_1_0.dll");
#endif
    if(!XInputLibrary)
    {
        XInputLibrary = LoadLibraryA("xinput1_3.dll");
    }
    
    if (XInputLibrary)
    {
        XInputGetState = (x_input_get_state *)GetProcAddress(XInputLibrary, "XInputGetState");
        if (!XInputGetState) { XInputGetState = XInputGetStateStub; }

        XInputSetState = (x_input_set_state *)GetProcAddress(XInputLibrary, "XInputSetState");
        if (!XInputSetState) { XInputSetState = XInputSetStateStub; }
    }
}
</code></code></pre><p>Here I explain why I have to use macros to separate version loading because of a bug I&#8217;ve reported to Microsoft about Address Sanitizer. Not the most obvious example but it shows the idea of what I think is sane information transfer as now another developer will have the information gap of &#8220;why&#8221; actually explained to them which allows them to take better decisions when managing this piece of code. Comments like this in addition to good straight-forward solutions to a problem is something I refer to as <strong>setting up your colleagues for success</strong> and it is something I attempt to practice as much as I can.</p><p>When it comes to how I read other&#8217;s code there are three rules I apply basically every time without fail and they are:</p><ol><li><p>High-level overview / scan.</p></li><li><p>Read line by line.</p></li><li><p>Ask questions.</p></li></ol><p>Since I cannot show any code from work let&#8217;s attempt to apply these three rules on one of my public GitHub repositories <a href="https://github.com/brokenprogrammer/mnotify">https://github.com/brokenprogrammer/mnotify</a>. This codebase is not very big but the basic principles are the same when I apply them to bigger codebases.</p><h3>High-level scan</h3><p>We first visit the page and perform a high-level scan of the repository and the code. We start with reading the description within the README and find the following important key information: &#8220;MNotify will sit in the background&#8221; which indicates that we are dealing with some background job or task and &#8220;IMAP protocol&#8221; which directly gives us the information that IMAP is a protocol which we most likely can find the specification for if we perform a quick google search.</p><p>Moving on we continue our scan by browsing into the src folder and inside it we find a set of files, some of them are source code and some of them are resources that someone forgot to move to the correct folder.</p><p>We see some files that has the format <code>imap_*.c</code> and <code>imap_*.h</code> and theese files my brain will instantly pattern-match to the keyword I learned earlier and I can ignore them for now. In addition there is <code>WindowsToast.h</code>, <code>tls.c</code> <code>tokenizer.c</code> and <code>mnotify.c</code> and <code>mnotify.h</code>.</p><p>Common convention for C or C++ projects are that the starting point of the application will be located in a file that is called main or something that has the project&#8217;s name in it and in our case we do have <code>mnotify.c</code> which sounds like a possible candidate.</p><p>I personally already know what a Toast in Windows is, TLS, and what a Tokenizer is so I can make assumptions about the contents of these files but if I didn&#8217;t know I would depending on the size of the project look at all those unknown files first if the project is of a small to medium size and if not I would try to find the application starting point and move on to step 2 of our process.</p><h3>Line by line</h3><p>We enter the <code>mnotify.c</code> file and inside it look for the main function. Searching for &#8220;main&#8221; would take us to a function called <code>WinMain</code> which happens to be the startingpoint for <code>Win32</code> based applications. From this point on we will read the entire main function line by line in order to collect information.</p><p>By doing it we can create a small list of things that happens in sequence as our application boot up. Do note however that many applications will not be as straightforward and include a lot of indirection in different forms as part of object orientation which results in you having to navigate through a tree of classes and methods. The same idea still applies even though it will be a bit harder in that case. <strong>A perfect tool to aid you in this process is the debugger</strong>, this tool will help you navigate the code line by line allowing you to also inspect the state of the application as it is running which can give you an even more clear insight into how it works.</p><p>Doing this on the main function in MNotify would give us a list that looks something like:</p><ol><li><p>Check if another copy of MNotify is running and if there is one we terminate this instance of the application.</p></li><li><p>Initialize &#8220;WindowsToast&#8221;.</p></li><li><p>Load icon resources.</p></li><li><p>Load the application configuration.</p></li><li><p>Create a new blank logfile.</p></li><li><p>Create a new Window.</p></li><li><p>Create a new thread and tell it to run the <code>ImapBackgroundThread</code> function.</p></li><li><p>An infinite loop reading Windows messages.</p></li></ol><p>This list gives us a very good understanding of what the main function does and from here you would perform this method recursively towards the goal you are interested in, for example if the goal was to made additions to the IMAP client we would continue reading the <code>ImapBackgroundThread</code> which would take us to move IMAP related functions that we also read and eventually we would get to the point where we can be confident of this codebase. This also opens many black boxes within a project and help you learn a great deal.</p><p>I want to point out that in some projects this exact approach of starting from the main function will not be possible, for example, if you are working in some framework that abstracts all of the internal initialization for you, for example, Unity then you will have to skip that part and be more direct to the parts you want to read and understand and work backward from there. This also applies to a codebase that is too big for you to reach your goal from the main function, start from a known location or perhaps even your goal and work yourself recursively backward.</p><p>However, I will also mention that in this case, it is still a good habit for you as a developer to try to understand this framework or tool&#8217;s internals in the best way possible for you to be able to take better decisions later on.</p><h3>Ask questions</h3><p>This point is perhaps very obvious but basically if you stumble on something that&#8217;s just like a brick wall and you are unable to get anywhere the best thing is to just ask. I most preferably ask the developer that wrote the code I am stuck on, I can find this out by using tools like for example <code>git blame</code> .</p><h2>Getting hard things done</h2><p>When I first joined Zenta I was quite arrogant, a trait I find in most people that just graduated. Even though I was about to start a new role at a new company programming in a language I had never touched before I still felt like I am the best. It wouldn&#8217;t take long however until I was brought back down to earth. As soon as I started working on my first real project I remember just looking at the list of tasks that were assigned to me and thinking &#8220;what the hell is this?&#8221;. I didn&#8217;t understand how to do any of them, I did go and ask questions but I didn&#8217;t want to feel humiliated by not understanding anything so many times I just sat and tried to understand what was going on. When this happened I had a great project manager that quickly identified the problem and already understood what I was going through, he sat down with me and went through my tasks and helped me divide them into smaller tasks that seemed more understandable and more importantly doable compared to the bigger ones. This was a game changer for me, even in my projects this is not something that I had ever practiced or done, I think back then one of the causes for my lack of productivity in personal projects had this same problem.</p><p>This application of divide and conquer on tasks is a real game changer and it can make what feels impossible feel possible instead. If you are put on a new codebase or a new project with new tech you have never heard of before. Well divide it into chunks, don&#8217;t attempt to understand the entire software in one go, instead understand what the different libraries are, that will let you know what they do and eventually, you&#8217;ll grasp why they were used and eventually the entire application.</p><p>In hindsight, I can also remember that this project manager that wasn&#8217;t a developer back then also gave me a lot of useful advice on how to conduct yourself at work as a developer. He also taught me that these tasks that I receive may be defined but not defined enough in a sense and in that case I should send them back and ask him to define them better for me to work on them. An important lesson from this was that the act of sending back tasks was not me disrespecting his work instead it is the opposite. The idea was to hold each other to a higher standard and not settle for sloppiness which I connected with. Other developers in my experience tend to take things very personally with the code that they&#8217;ve written and more so with their opinions. In the same way, I and my project manager held each other to higher standards I want the same relationship with my developer colleagues but I have met many developers that get very frustrated if they receive general advice or comments on their pull requests and even more so if it is requested for changes. I do not know why this is as I have always had the mindset that it is not my project and at the end of the day not my code anyway so I have no problems following company guidelines or &#8220;best practices&#8221;. I don&#8217;t have the best solution to these types of interactions, but for now, I have settled with applying the golden rule, which means to treat others as you wish to be treated. I want people to invest themselves in my pull requests and my code, I want to know if I made a mistake so I can learn, correct it and improve in my craft and therefore I will most likely continue doing it for others.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8HYa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8HYa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 424w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 848w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 1272w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8HYa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png" width="375" height="500.39619651347067" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:842,&quot;width&quot;:631,&quot;resizeWidth&quot;:375,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Hardware Project&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Hardware Project" title="Hardware Project" srcset="https://substackcdn.com/image/fetch/$s_!8HYa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 424w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 848w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 1272w, https://substackcdn.com/image/fetch/$s_!8HYa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe58a9538-18f9-4239-bbe0-fe517e917ef1_631x842.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Very soon after becoming more comfortable within my role at Zenta, I started becoming more and more interested in some very obscure and long-running bugs in some of the projects. Working as a consultant it is quite difficult to get the time you need to write a solution that you are satisfied with, especially when you are a very self-critical person like me. The reason behind this is kind of a sane one and that is that the customer or end-user will never see your code so it doesn&#8217;t matter that it is pretty. However, they do care if you are efficient when making new changes or additions and for that, you do need to not leave a mess behind you every time you finish a task. This makes it so you have to balance everything you do with what time you have. The same thing goes for bugs if the bug is not critical enough it may not seem worth it for the customer to fix this but I would argue that this is just a ticking bomb waiting to explode. Some bugs are not critical enough.. until they do become critical enough and I have had experiences with some bugs that have been very obfuscated over time resulting in a very different debugging experience compared to what you would have during the initial development. I have yet to learn a one fits all solution for every bug in the world but something important I have learned that resonates with me and that is to be fearless when facing these hard issues. What I mean with this is that I am going in ready to do almost anything to fix the issue, if it is an easy one then good, I&#8217;m out of there quickly but if its a hard and obscure bug I will not back down and I am ready to rip apart the entire application just to find out what is causing the issue and to fix it. Just the same way I believe we should work towards opening the black boxes of software I am a believer that we should bust the ghosts in our machines. While doing this however, I find it very important to not just solve it but spend some time and understand why it was a bug, why it happened, or what caused it. Doing this will make grow within the craft and make you feel empowered, you will be the lawyer that has never lost a case and not the one-hit wonder that got lucky one time. So far in my career, I have had a lot of success with this approach.</p><h2>Don&#8217;t take things personal</h2><p>I kind of touched on the subject earlier mentioning other developers getting defensive around any comment at all regarding code they&#8217;ve written. The real advice regarding this is to not take things personally. I am a very emotional person and I have a very hard time following this advice many times as but you really shouldn&#8217;t take things personally, it is really good to take responsibility and care about things deeply but it is not the same as taking it personally. People that take things personally at the workplace will get very worn out quickly and to prevent that you need to put up a sort of barrier but there are different kinds of barriers and different ways of taking things personally.</p><p>The example before with developers and their code. I handle this by wanting to improve and become better at what I do. Others see comments or advice as personal attacks, they will believe that you are not criticizing their code instead you are criticising them and attacking their skills as a programmer. In addition to this many developers has a very strong emotional attachment to their code which is okay but placing your final semi-colon before submitting your pull request is not the same as that piece of code being done and complete forever and ever. Code is not set in stone and changes a lot over time so don&#8217;t treat it as something complete. Even published books get corrected and are re-published under new editions and the same will happen to your code.</p><p>To treat this start with not seeing feedback as something negative, other developers are giving you a helping hand to potentially improve your code. Be polite and lead with a good example, if someone is disrespectful to you or taking things personally then take it up with your boss or the person directly. Remember that your code doesn&#8217;t reflect you instead maybe you can think that it reflects your knowledge at a point in time. I don&#8217;t know a developer that doesn&#8217;t go back to their old written code and thinks it&#8217;s stupid. I have many times used <code>git blame</code> only to find out I am the culprit.</p><p>But this was not the main thing I wanted to talk about, I have had the pleasure of also having direct contact with customers. I think it is very sane to let developers talk to customers directly, not only because they know the application and may understand problems or bugs that the customer may report better than for example a project manager would but also because it is good to go out of the code bubble and hear from people that are using what you&#8217;ve built in their day to day life. This might not be for anyone but at least in my opinion, it is a very good way for engineers to get insights into the real world.</p><p>Anyway, on to the more difficult subject. As development goes on, maybe something takes more time than expected and customers might not feel satisfied, because they of course have to pay more money than they thought. Or maybe they pay for the development and notice bugs in their production environment resulting in more developer hours to fix those bugs.</p><p>This should be fully understood, I think software engineering is the only field where we have the reality of still getting paid to fix stuff that is not working correctly. I usually make a funny little example comparing how you would react if you hired someone to renovate your bathroom and he comes back to you and says he is finished, but you later find out that there is no hot water in the bathroom, and upon contacting the guy who did the work you get a new estimate of how much it would cost to add hot water. I&#8217;m pretty sure you would be furious if this happened, so put yourself in one of your customer&#8217;s shoes. Maybe they hired you to branch out their business or they had a great software Idea that they strongly believe in and want to invest in. Working as a consultant I&#8217;ve seen most of the different customers and almost everyone who doesn&#8217;t have experience with software projects will very quickly become frustrated.</p><p>Some customers do take out this frustration on the developer that has direct contact with them and while you can tell them to go to hell your boss most likely won&#8217;t be too happy. I don&#8217;t know how true it is but I don&#8217;t feel like customers that have once become fed up or unsatisfied with a project can ever really be saved. What I mean with this is that you can talk with the customer, comfort them, or whatever but the trust they had for the team and its developers will most likely still be in shambles.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VWkj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VWkj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 424w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 848w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 1272w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VWkj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png" width="346" height="443.27169811320755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:679,&quot;width&quot;:530,&quot;resizeWidth&quot;:346,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Maraton&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Maraton" title="Maraton" srcset="https://substackcdn.com/image/fetch/$s_!VWkj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 424w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 848w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 1272w, https://substackcdn.com/image/fetch/$s_!VWkj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F648e3b2f-8b3c-4228-8e79-32ab7624a119_530x679.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Do continue talking to this person and keep it professional, you might already have taken a step back and accessed the situation and got some ideas on how to salvage the project, most often it just boils down to keeping the customer updated more frequently than before so they feel like they are more part of the development process but something I think is very important is that during this time if it ever gets nasty enough where you are personally attacked or insulted depending on how to sever it is I think you have an obligation to yourself to say something.</p><p>I have seen many developers feeling weakened by letting this happen to them, I know developers that have been sad over the weekend from things customers have said to them. This I think is just not okay, you work as a software engineer and not a punchbag. Either stand up to the customer and be honest or if that is difficult you contact your project manager or someone from the HR team and explain that you will not accept this. And if the workplace continuously puts you in pathological situations over and over then start looking for a new job.</p><p>But I want to wrap this back into the fact that you shouldn&#8217;t take things personally. Reading all of this I do not mean for you to be an impenetrable wall that no negative feedback can ever get to but a saner one, be aware of things around you from a new third-person view instead of looking at everything with black and white glasses. Sometimes maybe it is 100% your mistake that the customer is angry, and many times the more powerful move is to take responsibility for your mistakes however do not take things personally and do not let your work make you feel weak.</p><p>I hope this was an interesting read, if you enjoyed it please consider checking out my other socials!</p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[How I make puzzle games]]></title><description><![CDATA[During the last three weeks I have not spent too much time on just programming and instead addressed a topic of game development that I&#8217;ve just like many other more programmer-oriented game developers tend to postpone because it is quite difficult and needs serious deep mental work in order to turn out well.]]></description><link>https://www.oskarmendel.me/p/creating-good-puzzle-games</link><guid isPermaLink="false">https://www.oskarmendel.me/p/creating-good-puzzle-games</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Fri, 13 Jan 2023 19:28:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oybC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During the last three weeks I have not spent too much time on just programming and instead addressed a topic of game development that I&#8217;ve just like many other more programmer-oriented game developers tend to postpone because it is quite difficult and needs serious deep mental work in order to turn out well.</p><p>Like I mentioned within my previous devlog I have already discovered once that my initial design of the game had flaws and instead of throwing the project away I am determined to interate on it to try make it better. In order to do this I have to put my designer hat on and really think about what makes a puzzle game good.</p><p>In this writeup I want to outline some of the things I&#8217;ve learned while studying during these weeks. Some of the points will seem very fundamental but I include them anyway in order to emphasize how important they are, and while some of the points are simple they are still not followed in many games.</p><h2>Core mechanic</h2><p>Traditionally a puzzle game revolves around a core mechanic, the mechanic is often simple enough to be explained with a single sentence and while simple it should be fully explored it can then be enhanced by several other mechanics if needed.</p><p>In the puzzle platformer, Braid the player has the power to rewind time as its core mechanic. Upon progressing the game introduces a new mechanic where some items are &#8220;magical&#8221; and not affected by the player&#8217;s power. Here a new mechanic is used in a way that hinders the player from utilizing their main ability but also opens the game up for a whole new set of states which itself gives more possibilities for interesting puzzles.</p><p>In the game Portal, the player is in charge of a gun that can open up portals that the player can walk through. In this game, the developers decided to bring in more mechanics that complement their core mechanics, for example, items in the world can travel through the portals as well as allow the player to experiment with using the portal in new ways with the different items in the world.</p><p>I believe the most elegant mechanics are the ones that give you all their power right from the start but are not directly visible and instead slowly present themselves to you as you progress. A good example of what I mean here is how the game Stephen&#8217;s Sausage Roll does it, in other Sokoban-style games you often see several new mechanics introduced while you progress. Traditionally they start by pushing the box around the level but shortly after you are met with pressure plates, monsters, and many other different creations.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oybC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oybC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 424w, https://substackcdn.com/image/fetch/$s_!oybC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 848w, https://substackcdn.com/image/fetch/$s_!oybC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 1272w, https://substackcdn.com/image/fetch/$s_!oybC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oybC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png" width="1200" height="960" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:960,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Stephen's Sausage Roll&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Stephen's Sausage Roll" title="Stephen's Sausage Roll" srcset="https://substackcdn.com/image/fetch/$s_!oybC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 424w, https://substackcdn.com/image/fetch/$s_!oybC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 848w, https://substackcdn.com/image/fetch/$s_!oybC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 1272w, https://substackcdn.com/image/fetch/$s_!oybC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33ef8ac6-0794-45d6-99f9-25f178e8672a_1200x960.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While this is not bad it is something I want to avoid as it gives the player a discovery feeling while stumbling on new unknown elements instead of the revelation that the player feels when reaching a deeper understanding of how the game works. I do not think this is beyond reach within a game that contains many different mechanics but I do believe it is harder to get right and for Brainroll I want to attempt to limit the number of mechanics and focus on the game&#8217;s core.</p><h2>Linear and non-linear</h2><p>Something I have thought a lot about when investigating other games is the two different forms of puzzle games. Linear puzzles are what you can call Brainroll today, you spawn into a level and once you complete that level you will move into the next. Non-linear, on the other hand, is a game that presents the user with multiple ways of solving the puzzles, the start and end goal may still be static but the tools available to the user gives for enough freedom to solve the puzzle in many ways.</p><p>I want to expand on this idea further a bit and also include the games that don&#8217;t have a linear set of levels for the user to complete. For example, in open-world style puzzle games the user is free to wander around and solve puzzles in whatever order they want. This is an important observation because this type of design allows the designer to build the game in a way where exploration is also a key part of reaching the revelation I mentioned earlier.</p><p>A good example is Jonathan Blow&#8217;s game The Witness where once you complete the &#8220;tutorial&#8221; the first puzzle you stumble upon is designed to be too hard for you to initially solve and the idea is for the player to try it, fail, and then move on and find another area which contains a much simpler subset of the same puzzle that teaches the player how to solve the previous one.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Um9g!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Um9g!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Um9g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg" width="1200" height="628" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:628,&quot;width&quot;:1200,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Witness&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Witness" title="The Witness" srcset="https://substackcdn.com/image/fetch/$s_!Um9g!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 424w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 848w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!Um9g!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb8b9c1ee-5d43-445e-be7f-f048a27c1a82_1200x628.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>I don&#8217;t want to say that non-linear puzzle design is always better than linear but I do believe that if you do have a linear puzzle game such as Brainroll or another Sokoban-style game you can add a lot more depth into the game if you somehow connect the different levels. What I mean with this is for example if you utilize an overworld when the user navigates from level to level, this is a good opportunity for the overworld to also be a puzzle in interesting ways.</p><h2>No Tutorials</h2><p>Tutorials are a point of discussion, some games couldn&#8217;t be made without them, and many AAA games today wouldn&#8217;t survive without having tutorials upon tutorials for all their different systems and subsystems. It is not uncommon for these games to include skill trees, crafting, and perhaps different advanced attack combos. Having the players learn all of this would most likely take up so much of the core game that just learning how everything works would be the entire game, instead, it is just easier to strap short text prompts or messages on the screen with text that explains what you&#8217;re doing. One thing we all can agree on however is that text in games are almost always boring and just takes away the fun of playing the game. This is exactly what we want to strive for, letting the players just play the game. A quote often used is &#8220;A game with good game design explains itself&#8221; and this is also something I want to strive for in my games. The strategy that I apply when removing the need for tutorials is to incrementally introduce new things into the game, for example in Brainroll the first levels are just about movement, there are no real other mechanics to interact with and that allows the levels to be simple enough so that the players can just experiment their way to the goal. Allowing user freedom here is a great benefit to your game because the game will be more interesting if there is room for experimentation.</p><h2>Level design language</h2><p>Another advice I got and applied to my game design is that I try to think really hard about what I am asking the player to do in the level. For you who played the latest version of Brainroll there is a mechanic where a sleepy brain would stick to you on collision and you have to move them around in order to aid you reaching the goal. I created a set of levels and analyed them by writing down all the actions the user has to perform in order to complete them and by doing this I learned that the mechanic surounding the sleepy brain had another set of mechanics tied to it, by moving them around you had the oupportunity to <strong>arrange</strong> them in different patterns, by carefull <strong>positioning</strong> you can reach new places, they can be used as <strong>padding</strong> as if you were moving a wall around with you and finally you can <strong>sling</strong> them around corners.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T-G1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T-G1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 424w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 848w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 1272w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T-G1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png" width="699" height="902" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:902,&quot;width&quot;:699,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Design Language&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Design Language" title="Design Language" srcset="https://substackcdn.com/image/fetch/$s_!T-G1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 424w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 848w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 1272w, https://substackcdn.com/image/fetch/$s_!T-G1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc253551b-9f33-4b37-a30b-243ebf76e28e_699x902.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While all of these mechanics are overlapping they are still distinct enough to have a language to use when describing the designed levels. This allows me to go into the level design with concrete ideas of what I want the level to be about. Instead of experimentation (which also can be very lucrative) I can go into the design mode by specifying for example that I want the player to <strong>arrange</strong> the sleepy brains to form the letter L for them to trigger a pressure plate that opens the next door. This can of course be achieved without having terms to describe the different actions your player performs but I still believe there is a benefit in making it more formal.</p><h2>Don&#8217;t design backwards</h2><p>Designing levels I&#8217;ve come to believe that it is bad to design backward, what I mean with this is if you choose to design your levels by placing down a start position and an end goal and solely move the level forward by finding ways to block the path between the player and the goal in different ways. I am sure this can yield success sometimes but to further dive into the mechanics you&#8217;ve created and learn what they are about you have to try to start from the other end as well, before even creating the level start with an idea, this idea can be very simple like &#8220;I want to design a level that looks like a skull&#8221; or even better &#8220;I want the player to use a sleepy brain as padding to slide between two walls&#8221;. All of the best levels I&#8217;ve created have come from this way of making them. Why I believe the backward route is bad is also because when I&#8217;ve done it that way, later when playing the levels something has always felt a bit off. I later concluded that the reason behind this weird feeling has been the result of artificial complexity being introduced into the level. What I mean by this is that the puzzle I&#8217;ve created wasn&#8217;t a puzzle with an idea, it was just something preventing you from reaching the goal. This is the same problem that Brainroll had before when the game was oriented around mazes, which just wasn&#8217;t interesting or fun. So try to design forwards, start with a good or interesting idea and explore how your game&#8217;s mechanics fare against this idea. In some cases, it won&#8217;t work and then you&#8217;re at a crossroads where you have to decide as a game developer if you want to change the game to make it work or just accept that this idea won&#8217;t work in the game you&#8217;re making and move on.</p><p>I hope this was an interesting read, if you enjoyed it please consider checking out my other socials!</p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[UI Subsystem Iteration]]></title><description><![CDATA[The title already spoiled the subject as we&#8217;re going to dive into the UI system that has been implemented into my engine.]]></description><link>https://www.oskarmendel.me/p/ui-subsystem-iteration</link><guid isPermaLink="false">https://www.oskarmendel.me/p/ui-subsystem-iteration</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Fri, 02 Dec 2022 19:27:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9zIa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The title already spoiled the subject as we&#8217;re going to dive into the UI system that has been implemented into my engine. In this post I will outline some of the improvements made to the current ui system that I&#8217;ve implemented for Maraton.</p><h2>Graphical User Interface</h2><p>A User Interface or UI for short is our medium of interaction with the machine. One of the most important goals that designers of interfaces have is to not have their users think too much about the interface itself. What this means is not that we want mindless and lazy users but instead, we want our interface to be as intuitive as possible to not force users to have to reason about what things do or how to do things.</p><p>The first step to address this is to build your user interface with components that users will already make the correct assumption on how to use. These components can be:</p><ul><li><p>Buttons</p></li><li><p>Checkboxes</p></li><li><p>Radio buttons</p></li><li><p>Text fields</p></li><li><p>Sliders</p></li></ul><p>Why this matters at all are because we&#8217;re building our UI system from scratch so we have the opportunity to define the behavior of all of our components ourselves, if we&#8217;re not trying to explicitly try to redefine how the components should work it wouldn&#8217;t make much sense to make the behavior different to how they work anywhere else. The reason for this is because as mentioned above that our users would most likely make the incorrect assumption of what happens when they for example click a button which will result in the user losing time and most likely getting frustrated.</p><p>For a button, the set of rules of operation may be pretty simple but for a text field, it can become more complex as it would involve tons of new keybinds, and perhaps you would need to implement a cursor allowing the user to see where they are when they&#8217;re writing which itself has a different set of behavior. All of the standard components actually have analog counterparts that we&#8217;re taught since childhood, for example the keys on your keyboard which acts just like the buttons inside an elevator. Or the knobs on your stove or microwave to control the heat acts like a slider.</p><p>This brings us to our first constraint on our user interface system: &#8220;<strong>Do not defy the standard behavior of user interface components but be flexible enough to allow it.</strong>&#8221;</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9zIa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9zIa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9zIa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg" width="1024" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Example of a control panel showcasing an analog interface.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Example of a control panel showcasing an analog interface." title="Example of a control panel showcasing an analog interface." srcset="https://substackcdn.com/image/fetch/$s_!9zIa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9zIa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F78c6dc5c-4c07-40b4-a2f4-7352d13d1955_1024x768.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Example of a control panel showcasing an analog interface with buttons, togglers and sliders (Source: <a href="https://commons.wikimedia.org/wiki/File:Hanford_B_Reactor_-_Control_room_desk.jpg">Control Desk</a>).</p><h2>UI Modes</h2><p>Designing a UI system has many exciting aspects to it, there are generally two styles of implementations for UI systems and they have two camps of developer communities split between them.</p><h3>Retained mode</h3><p>The first style is known as &#8220;Retained mode UI&#8221; and I would like to say that this is the most common style as it is adopted by many popular UI frameworks. The word &#8220;Retained&#8221; comes from the idea that this type of system retains the state of the UI across frames, the main idea is that the library or system retains the data of the scene and objects used to build it. Using this style you build an API that hides details from the user about the internally used objects, rendering primitives, and interaction with the different widgets are often done in an indirect fashion where you tell the library what you want to update and rely on it taking care of it. Retained mode is often but not limited to being built using the object-oriented style of programming.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d7ST!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d7ST!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 424w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 848w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 1272w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d7ST!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png" width="512" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:512,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Abstract diagram of retained mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Abstract diagram of retained mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))" title="Abstract diagram of retained mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))" srcset="https://substackcdn.com/image/fetch/$s_!d7ST!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 424w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 848w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 1272w, https://substackcdn.com/image/fetch/$s_!d7ST!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7d50425-8e3c-47ab-9e88-4df17624ba57_512x190.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Abstract diagram of retained mode ui. (Source: <a href="https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode">https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode</a>)</p><p>Why this idea that is retained often is coupled with the object-oriented approach is that this model tends to be designed in a way where the users have to extend existing elements to build new constructs within the boundaries of the library. Hence this type of library is often designed in a way where the users are required to extend and inherit pre-defined behavior to create new elements that are not supported by default by the library. There are however no such limitations to this style, where extensibility in a procedural approach can be achieved with for example function pointers. If you are interested in diving further into this I recommend a tutorial that a developer that goes by nakst has put together that is available <a href="https://nakst.gitlab.io/tutorial/ui-part-1.html">here</a>. He also has a good library to learn from available on Github <a href="https://github.com/nakst/luigi">here</a>.</p><h3>Immediate mode</h3><p>The second style that has grown in popularity in more recent years is the &#8220;Immediate mode UI&#8221; style. This approach has lately been frequently adopted by game developers because of its simplicity and how easy it is to learn and get started with especially in an environment where rapid iterations are of interest. In an Immediate mode system, the code that defines for example a button is also the same code that handles the rendering and code that checks if it was interacted with or not. This results in an extremely simple API for developers to consume were creating a button in your interface may becomes as simple as an if statement, for example:<code>
</code></p><pre><code><code>if (Button("Click Me"))
{
    DoSomething();
}
</code></code></pre><p>If you do not want the button to be rendered in this case you simply do not call the Button function. This means that a lot of code that you write will be code that decides if the UI should be drawn or not but the simplicity of it all remains the same.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b7tl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b7tl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 424w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 848w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 1272w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b7tl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png" width="464" height="188" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/addb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:188,&quot;width&quot;:464,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Abstract diagram of immediate mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Abstract diagram of immediate mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))" title="Abstract diagram of immediate mode ui. (Source: [https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode](https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode))" srcset="https://substackcdn.com/image/fetch/$s_!b7tl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 424w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 848w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 1272w, https://substackcdn.com/image/fetch/$s_!b7tl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faddb6e8b-4df4-45f1-bf4c-51025326a8f8_464x188.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Abstract diagram of immediate mode ui. (Source: <a href="https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode">https://learn.microsoft.com/en-us/windows/win32/learnwin32/retained-mode-versus-immediate-mode</a>)</p><p>In this style of library, all the data required is within the client code and not inside a library. This means that more weight is put on the user of the library which also allows for more freedom. Because of the &#8220;immediate nature&#8221; of the API and the fact that the user holds the data, the types of libraries that implement this tend to not store any internal state about what to draw and not which results in the immediate type interfaces often being redrawn every single frame. While some may argue that this is a drawback because it will result in a slow application I wouldn&#8217;t bother that much because the UI would have to be very complex before refilling your vertex buffers are going to be the bottleneck in your application and you will most likely find that optimizing other areas of your application will give more results than rendering your interface. One real drawback that between this and a retained mode style approach is that the library itself cannot make assumptions about what is and is not visible on the screen which can be a limitation for some projects.</p><p>Due to the simplicity of the immediate type of approach the libraries that you use also tend to introduce less bloat into your application, this is even part of the slogan for the most popular immediate mode libraries &#8220;<a href="https://github.com/ocornut/imgui">Dear ImGui</a>&#8221;. At the end of the day though you can build very powerful systems with both approaches and I for one would be interested in sinking my teeth into retained-mode in the future.</p><p>I have since chosen to stick with the immediate mode style because of the beforementioned reasons and with this we also get two more constraints for our UI system: &#8220;<strong>Designed for developer&#8217;s ease of use</strong>&#8221; &amp; &#8220;<strong>Do not get in the way of development</strong>&#8221;.</p><h2>The previous UI system</h2><p>The old UI system for my game engine Maraton was an immediate mode-style implementation that had a few interesting concepts attached to it. This UI system was designed to work and not to be the most flexible system out there, but while working it was also designed to be flexible enough to be able to construct real applications. One of the most limiting factors of this system was that every widget was hard-wired to be a part of the system itself.</p><p>The code defining widgets in this system was built upon the idea of discriminated unions (also known as &#8220;tagged unions&#8221;), it was all built on code that looks like the following:<code>
</code></p><pre><code><code>enum ui_widget_type
{
    UI_WidgetType_Button,
    UI_WidgetType_Slider,
    ...
};

struct ui_widget
{
    ui_widget_type Type;

    struct
    {
        ...
    } button;

    struct
    {
        ...
    } slider;
};
</code></code></pre><p>While I love this structure of code it has serious issues when trying to build a flexible system because we are already declaring a button and a slider to be two separate things while in practice they are very similar and utilize many of the same properties. This design gave me many troubles with the engine in the past because every time I wanted to support something new like a button using a texture instead of text and then a toggler with the same thing then perhaps a scrollable list, it all ended up with me having to do quite a bit of work inside the internals for the engine and the library code gave me very little power when it comes to extending it or making small tweaks. This already breaks the first and third constraints that we set up for our UI system.</p><p>Another lacking part of the system was that it did not have any internal support for autolayouting, this means that the entire system was based on you keeping track of where everything is. There was a concept of rows and columns but how big they are and where they are located on the screen was part of the API and something the user had to keep track of, an example UI from my game Brainroll looked something like this:<code>
</code></p><pre><code><code>UIPushTextColor(UI, Vector4Init(1.0f, 1.0f, 1.0f, FadeInOutTransparency));
vector2 Size = Vector2Init(400, 80);
vector2 Position = Vector2Init(ScreenWidth / 8.0f, ScreenHeight / 2.0f - (Size.Y * 5) / 2.0f);
UIPushColumn(UI, Position, Size);
{
    UITitle(UI, "Brainroll");
    
    if (UIButton(UI, "Play"))
    {
        PlayUIClickSound();
        State-&gt;LevelSelectMenuOpen = true;
    }
    if (UIButton(UI, "Editor"))
    {
        PlayUIClickSound();
    }
    if (UIButton(UI, "Settings"))
    {
        PlayUIClickSound();
        State-&gt;SettingsMenuOpen = true;
    }
    if (UIButton(UI, "Quit"))
    {
        PlayUIClickSound();
        Quit = 1;
    }
}
UIPopColumn(UI);
UIPopTextColor(UI);
</code></code></pre><p>You can see on like 2 and 3 that I am calculating where this UI should be placed on the screen and what size it should be. For this specific case, it is not that fatal but it is still something that I do not like having to do while performing fast iterations on for example a game in this case. Thankfully in this example the buttons have their size calculated depending on the text size so they will be laid out next to eachother vertically in a column.</p><p>Last but not least the previous system had no notion at all of the keyboard navigation, it was designed like keyboard navigation wasn&#8217;t a thing and this caused me a big pain because I know how frustrating it is for a player of a 2D puzzle game that only plays with the keyboard to always have to grab the mouse because for some reason it doesn&#8217;t react to keyboard input in the menus. While performing Brainroll playtests this has been a feature that almost everyone has asked about.</p><p>Before moving on I still want to touch on some of the good parts of the system. In the above code example, you can see the pattern of Push and Pop being used to almost push a text color and also for the Column. This is a concept that I use in many places of Maraton, I was introduced to this concept by the name of <a href="https://www.youtube.com/watch?v=zbufcZ_JBbU&amp;feature=emb_title">&#8220;Push buffer&#8221;</a>. The idea here is that we have a set of internal stacks for different things such as colors, fonts, and positions that will modify all widgets until we pop them off the stack. Since it is a stack and if we push several things onto it, the system will then always peek at the stack and use the last thing pushed onto it when creating its elements. This is an idea that works very well and was transferred over to the new UI implementation.</p><p>Another very positive thing with the old system was that it had what I call a &#8220;backdoor&#8221; for things that I knew I never would implement as a standard widget, this was a way for you to through function pointers define your widgets, and this allowed you to create two callback functions one for what happens during an update and one to specify what happens during rendering of the widget. This was powerful enough for the cases where I needed to implement something new and wanted it to somewhat interface with the rest of the UI system.</p><h2>Harder, Better, Faster, Stronger</h2><p>I was sitting in a dark room hacking away in the night, in the middle of a short sprint developing a new renderer for Maraton in preparation for supporting multiple renderer backends. While taking a small break from reading discord messages I see something that catches my eye. Ryan Fleury who is a very talented developer that has inspired me multiple times and helped solve many issues and give plenty of good ideas applied to Maraton, released a new article on his substack explaining his new UI system and while reading it I got more and more captivated by it. <a href="https://www.rfleury.com/p/ui-part-1-the-interaction-medium">Within this article Ryan explains the UI system solving all of my issues</a> and I decided to scope-creep my current sprint and implement this in the engine before continuing work on my game Brainroll. I will not go into too much detail about the internals of this system and instead refer all the traffic to <a href="https://www.rfleury.com/">Ryan&#8217;s site</a> but I will however explain the parts of the system that solve my previously mentioned constraints.</p><h3>Flexible components &amp; developer freedom</h3><p>Instead of tagged unions, Ryan disconnects every widget from the idea of having a type and instead into adopting a set of properties. For example, a button can have the following properties:</p><ul><li><p>Background</p></li><li><p>Text</p></li><li><p>Clickable</p></li><li><p>Hover Animation</p></li><li><p>Click Animation</p></li></ul><p>Instead of building this as a button widget, we build an abstract widget type that can have all of these properties toggled on or off. I never thought about this myself but when laid out in front of me this is a very flexible and strong approach to managing the building blocks of the interface as once one property is added it can be used by any component. Representing this in code is very simple, instead of using the enum type inside your widget you use an integer type and define your enum as a set of bitflags allowing you to toggle any flag on or off. For example:<code>
</code></p><pre><code><code>typedef u32 ui_widgetflags;
enum
{
    UI_WidgetFlag_Background     = (1&lt;&lt;0),
    UI_WidgetFlag_Text           = (1&lt;&lt;1),
    UI_WidgetFlag_Clickable      = (1&lt;&lt;2),
    UI_WidgetFlag_HoverAnimation = (1&lt;&lt;3),
    UI_WidgetFlag_ClickAnimation = (1&lt;&lt;4),
};

struct ui_widget
{
  ui_widgetflags Flags;
};
</code></code></pre><p>The different flags don&#8217;t have to apply to every process step of the widget&#8217;s lifetime but instead, it can only be managed where it matters, for the button example above the flags <code>UI_WidgetFlag_Background</code> &amp; <code>UI_WidgetFlag_Text</code> will only be toched by the rendering code while the <code>UI_WidgetFlag_HoverAnimation</code> may be touched both by the code performing the animation and the rendering code. So every process step that the widget goes through can look at what flags the widget has activated and decide whether to perform part of the processing or not depending on its flags.</p><p>This type of system also streamlines the code that manages the user input because every widget can just be treated the same, it doesn&#8217;t matter if it&#8217;s a button or a slider we still record if it was clicked or not as well as where it was clicked. This becomes very dynamic because the core UI system will technically support almost any widget you want and you can accompany the core itself with a library of basic widgets.</p><p>Looking back at our first constraint, this is no longer an issue because with this system nothing is preventing you as a developer from defining any component with any behavior. You can also fairly easy with this system combine several widgets almost like legos to build new widgets in your system. As mentioned earlier for a more in-depth look at how this is done I will refer to <a href="https://www.rfleury.com/p/ui-part-4-the-widget-is-a-lie-node">Ryan&#8217;s articles</a>.</p><h3>Ease of use</h3><p>I mentioned earlier that I felt like I was missing a system that aids me with layouting because it would save me tons of time when going through iterations on a codebase. The system Ryan came up with also takes care of this for you. Remember earlier when I mentioned that most immediate mode systems re-build the entire UI every frame? What this system does is that while building a hierarchy of widgets it also caches their state as long as they are in use. The reason behind this is that when for example you are pushing a button into the current scene since the system is immediate you are only working with a partial scene and some assumptions that we make at this stage may change because we do not have access to the complete hierarchy at this point.</p><p>If we, however, separate the building of the hierarchy and rendering step we are free to inject an autolayouting step in between which allows us to work with the entire hierarchy and allows us to make advanced layouting decisions as a result.</p><p>The way this is achieved in this system is that the underlying data structure is constructed as an n-ary tree. Each widget has a parent, siblings, and children that can form a sort of hierarchy structure internally. If you google n-ary trees most examples do not keep references to siblings, an example I drew on paper may look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wsZP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wsZP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 424w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 848w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wsZP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg" width="1456" height="1941" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1941,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;IMG_0006.jpg&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="IMG_0006.jpg" title="IMG_0006.jpg" srcset="https://substackcdn.com/image/fetch/$s_!wsZP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 424w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 848w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!wsZP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc52142cd-8b0a-495f-905d-ccfbae5b7283_3024x4032.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In code this can be written like:<code>
</code></p><pre><code><code>struct ui_widget
{
    // tree links
    ui_widget *First;  // First Child
    ui_widget *Last;   // Last Child
    ui_widget *Next;   // Next Sibling
    ui_widget *Prev;   // Previous Sibling
    ui_widget *Parent; // Parent

    ...
};
</code></code></pre><p>So our widgets are cached in-between frames like it would in a &#8220;retained-mode&#8221; design we still utilize the features of an immediate mode API. Since at the time we perform layouting we have access to the entire hierarchy we can perform quite advanced layouting and can be just as sophisticated as any algorithm applied to a retained-mode hierarchy.</p><p>The layouting itself is boiled down into something that we refer to as &#8220;Semantic sizes&#8221;. This is a way for us to assign each widget a way how it should be managed by the autolayouting system. In my implementation I have the following semantic sizes:</p><ul><li><p>Pixels - A &#8220;hard&#8221; set size specified in pixels</p></li><li><p>TextContent - Size is set to be the text of the widget in the used font&#8217;s size.</p></li><li><p>PercentOfParent - The widget is a specified percent of the parent&#8217;s size.</p></li><li><p>ChildrenSum - The widget&#8217;s size is a sum of the size of its children.</p></li><li><p>BiggestChild - The size is set to be the same size as the biggest child widget.</p></li></ul><p>Each semantic size has one of the types above as well as a value and a strictness. The value is not always used but for example, for pixels, it would be the pixel amount and the percent for the PercentOfParent. The strictness is the interesting part, it is a way for us to predetermine how much of the widget&#8217;s size in percent it is willing to give up in case it doesn&#8217;t fit within the layout.</p><p>Each widget adopts two semantic sizes, one for each axis. This is extremely powerful because it allows us to make advanced decisions where the layouts can differ on X and Y, for example, we can have a container that is hard set to 250 pixels in height but it will always be 50% in width.</p><p>The autolayouting algorithm itself traverses our tree of widgets once per axis in 5 different steps:</p><ol><li><p>Calculate standalone sizes. Theese are the widgets with semantic sizes that are not dependant on any other widgets in the hierarchy(<code>Pixels</code> &amp; <code>TextContent</code>). This can be done in either post or pre-order traversal of the tree.</p></li><li><p>Calculate the &#8220;upwards-dependent&#8221; sizes. This are the widgets with semantic sizes that depends on their parent (<code>PercentOfParent</code>). This should be done in pre-order traversal.</p></li><li><p>Calculate the &#8220;downwards-dependent&#8221; sizes. This is the widgets whos semantic size depends on their children (<code>ChildrenSum</code> &amp; <code>BiggestChild</code>).</p></li><li><p>Solve size violations. At this step we traverse the entire hierarchy and verify that no widgets extends past their parent&#8217;s boundaries. Exceptions are made for parents that do allow overflow (usefull for cases where you want to implement scrolling). In case of a violation it will cut off part of the child&#8217;s size depending on the strictness defined within it&#8217;s semantic size. This should be done as a pre-order traversal step.</p></li><li><p>Compute the final screen coordinates and the relative positions between every widget. This step produces the rectangle that will be used for rendering and user input. This should be done in pre-order traversal.</p></li></ol><p>This covers most cases while also being very simple to extend. As a user of this API, it will save me a lot of time as I do not have to worry about the placement of individual elements as the auto layout together with the specified semantic sizes can be utilized to take care of the thinking for me.</p><p>Another very important thing is that this type of implementation makes keyboard input quite trivial. I solved this by building a quadtree of directional links during the layouting step, this allows me to based on each widget&#8217;s position within the tree build another tree with the default behavior of which widget you would be taken to on directional key navigation. The way this is done is by looking at the spatial distance between the widgets on the screen in all 4 directions and selecting the closest widget (that allows user interaction) and then building up a tree of links to widgets in the real hierarchy. This also allows the user to set their shortcuts to different parts of the UI.</p><p>This solves the remaining constraints we had on this UI system. And the resulting system is a system that:</p><ul><li><p>Allows the developer to utilize a more granular type of building blocks that allow them to define a wide variety of UI components by thinking of properties instead of hard specified components.</p></li><li><p>The system is designed with ease of use in mind. The API is small and consistent and everything follows the same push / pop pattern. As the system is still utilizing stacks internally it is trivial for new users to get a grasp of the basics and modify and style parts of the UI with limited knowledge.</p></li><li><p>Compared to the old system this approach doesn&#8217;t get in my way as much as I can worry about the content and postpone the exact layout as the algorithm will take care of it for me. I do not have to worry about new widget properties as everything I have now is for sure enough to complete my next game and as a bonus the navigation is solved and requries zero developer maintenance to be supported.</p></li></ul><h2>Result</h2><p>In the end I have a <em>more</em> robust and production ready UI system inside Maraton than before. I wouldn&#8217;t claim that this is the silver bullet that solves all my UI problems for the rest of my career but it for sure is an iteration in the right direction. Here is a sample screenshot of one of the UI examples that is shipped with the engine.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vXz1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vXz1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 424w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 848w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 1272w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vXz1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png" width="946" height="533" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:533,&quot;width&quot;:946,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!vXz1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 424w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 848w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 1272w, https://substackcdn.com/image/fetch/$s_!vXz1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33e03f76-1a87-45c1-a4bf-3f3584afa138_946x533.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Here is a visual representation of the autolayouting algorithm at work:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!he19!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!he19!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 424w, https://substackcdn.com/image/fetch/$s_!he19!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 848w, https://substackcdn.com/image/fetch/$s_!he19!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 1272w, https://substackcdn.com/image/fetch/$s_!he19!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!he19!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png" width="946" height="533" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:533,&quot;width&quot;:946,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!he19!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 424w, https://substackcdn.com/image/fetch/$s_!he19!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 848w, https://substackcdn.com/image/fetch/$s_!he19!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 1272w, https://substackcdn.com/image/fetch/$s_!he19!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ae5a8e3-85c7-4809-887e-008bef0c16c1_946x533.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>While this took far too long for me to get in place I am very happy that I went on this journey because I got to learn an enourmous amount of new things. This is the work I wanted to get done before allowing others to take part of the Maraton project and it is now finished and now it is time for me to finish my game Brainroll.</p><p>If you are interested in supporting my projects, me becoming an indie game developer or just want access to my games as well as the Maraton game engine it is available to my paid subscribers.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p>You can also follow my work on theese other places:</p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[Maraton Initial Release]]></title><description><![CDATA[It is finally time for me to step out of the shadows and make my work public for the programming enthusiasts out there.]]></description><link>https://www.oskarmendel.me/p/maraton-initial-release</link><guid isPermaLink="false">https://www.oskarmendel.me/p/maraton-initial-release</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sat, 19 Nov 2022 19:23:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!nsCE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It is finally time for me to step out of the shadows and make my work public for the programming enthusiasts out there. Today I have finally merged one of the biggest PRs I&#8217;ve ever made to my engine and it is now in what I consider a useable state.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nsCE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nsCE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 424w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 848w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nsCE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png" width="474" height="379.2" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1280,&quot;resizeWidth&quot;:474,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;FullLogo.png&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="FullLogo.png" title="FullLogo.png" srcset="https://substackcdn.com/image/fetch/$s_!nsCE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 424w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 848w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 1272w, https://substackcdn.com/image/fetch/$s_!nsCE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50d6ea13-f114-4b64-925c-b81b1bc72b5a_1280x1024.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What is going on?</h2><p>For the ones who have not followed me since before, welcome! This is a post that I produced in addition to my first <a href="https://www.youtube.com/watch?v=A7qFR60jFcU">devlog</a>.</p><p>I am an aspiring indie game developer, for my indie games, I am developing my general-purpose game engine from scratch without any libraries. While working on my games and the engine I released it to a close set of friends and all of them gave me very positive feedback (in addition to a big bag full of bugs to fix) and mentioned that they felt very productive in it because of how easy it is to get started and make fast iterations on your project.</p><p>I always knew I sometimes wanted to release the engine but I didn&#8217;t know in what way or capacity but decided before I release it there are a couple of things I wanted to polish. This was mainly the UI system and the built-in renderer.</p><p>So what I&#8217;ve done is rewrite the renderer as well as the UI system for the engine to allow users to be more modular and not be as restrictive. The new renderer doesn&#8217;t force users to rely on it and users can use the OpenGL wrapper that comes with the project to write their things. Same with the UI system that I have prepared a more in-depth and lengthy post about.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EFaI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EFaI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 424w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 848w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 1272w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EFaI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png" width="1091" height="72" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:72,&quot;width&quot;:1091,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!EFaI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 424w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 848w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 1272w, https://substackcdn.com/image/fetch/$s_!EFaI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed83b7ed-9a12-4a13-8c79-3754a5c1c561_1091x72.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><h2>Why not unity?</h2><p>My main answer to this question is that I want to learn, I want to open the black box and not rely on any &#8220;magic&#8221; instead I want to understand every single part of how a game engine works and with this engine, I want to encourage everyone else to also not be afraid and learn it just like me.</p><p>The engine is built in a low-level language C++ but utilizes a more C-like coding style for simplicity. This makes the codebase very lightweight and built in a way where you are not limited to modifying the engine for specialized games. This can be very powerful, while this is also possible in a game engine like unreal it has a much steeper curve while in Maraton you can modify the engine very quickly once you&#8217;ve grasped the basics.</p><p>I&#8217;m sure you can talk to other developers using their custom engine and they will say the same regarding productivity. They know every single part of the engine like the back of their hands and it does show when you&#8217;re going to produce and ship something. Maraton is a great oupportunity for developers that want to use their own engine to have a good base or starting point. There is nothing wrong with using Maraton for learning or as a stepping stone to start making your own specialized engine.</p><h2>Quick overview</h2><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4d3n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4d3n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 424w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 848w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 1272w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4d3n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png" width="401" height="211" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c59e3777-d802-4cc0-876a-99d355786890_401x211.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:211,&quot;width&quot;:401,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;maratondiagram.png&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="maratondiagram.png" title="maratondiagram.png" srcset="https://substackcdn.com/image/fetch/$s_!4d3n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 424w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 848w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 1272w, https://substackcdn.com/image/fetch/$s_!4d3n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc59e3777-d802-4cc0-876a-99d355786890_401x211.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Above is a simplified diagram of the different submodules available in this initial release. I didn&#8217;t make it very fine-grained to keep it simple. In most cases, the APIs that you will consume is for the UI, Renderer, Debug, and Audio. The way these modules are documented is in their respective header files where forward declarations have been placed. I hope that for now, the function signatures will be enough to get a grasp of what the functions do but knowing that sometimes it is not enough I have also prepared a set of examples. I am also in the process of preparing some tutorials for working in the engine as well.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EXgK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EXgK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 424w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 848w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 1272w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EXgK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png" width="778" height="745" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:745,&quot;width&quot;:778,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!EXgK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 424w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 848w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 1272w, https://substackcdn.com/image/fetch/$s_!EXgK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F90c72f2b-514d-42ab-80a0-89c4921f36a7_778x745.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The examples itself is a list of example projects that links to source files that show in code how to produce what is shown in the screenshots. This is something I am going to build over time, my hope is also for other users to engage with the community and share their examples.</p><p>If you are uncertain of what you can produce with this engine I have examples published on <a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">youtube</a> for two games built in the engine.</p><p>While this may seem simple I am also producing a full game called <a href="https://nullsson.itch.io/brainroll">Brainroll</a> using this engine that will be complete so the possibilities are not limiting instead very open for tinkerers to build what they want.</p><p>Moving forward I will have a different format of the release posts that is more focused on the changelogs. Full set of changes for the earlier versions also ship with each release of the engine.</p><h2>Closing notes</h2><p>My goal with this release and moving forwards is to build a community of like-minded people interested in developing games. I have created this engine as a base for people wanting to get into development and also created a <a href="https://discord.gg/F2ZQVX4Vax">discord</a> server where I encourage people to post their questions and help each other learn and become better at what we do.</p><p>Moving forward I will also post new devlogs not only for the engine but also for my indie game. On my website, I will also produce more posts explaining the engine and valuable information on how to build interesting things.</p><p>If you are interested in supporting my projects, me becoming an indie game developer or just want access to my games as well as the Maraton game engine it is available to my paid subscribers.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.oskarmendel.me/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.oskarmendel.me/subscribe?"><span>Subscribe now</span></a></p><p><br>You can also follow my work on theese other places:</p><p>&#128187;<a href="https://github.com/brokenprogrammer">GITHUB</a> &#128038;<a href="https://twitter.com/nullssondev">TWITTER</a> &#128488;<a href="https://discord.gg/F2ZQVX4Vax">DISCORD</a> &#128249;<a href="https://www.youtube.com/channel/UCQiugYROtgcVZa4Y3PScojg">YOUTUBE</a></p>]]></content:encoded></item><item><title><![CDATA[How to hack a .NET Core game]]></title><description><![CDATA[During my vacation, I spent some time with an MMORPG that I had not played before.]]></description><link>https://www.oskarmendel.me/p/how-to-hack-a-net-core-game</link><guid isPermaLink="false">https://www.oskarmendel.me/p/how-to-hack-a-net-core-game</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Mon, 15 Aug 2022 18:20:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Q1Vu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>During my vacation, I spent some time with an MMORPG that I had not played before. I was enjoying playing the game but when I had to get back to work I realized that I won&#8217;t have time to play this game If I also want to make progress on my stuff like my game engine and my game.</p><p>The idea in my head was &#8220;What if I could write a program to make the gameplay itself?&#8221;. I played around a bit with this idea and like with many pictures like this it piqued my interest just enough for me to investigate what that kind of program would look like as well as if I have the skills to pull it off, so I rolled up my sleeves and got to work.</p><p><strong>Disclaimer</strong>: In this post I will not explain how to hack any specific application or game, I will just mention some of the approaches that people who make cheats and bots use so that we can learn about them. No game was harmed in the process of making this.</p><p><strong>Disclaimer</strong>: I do not promise that this is 100% bulletproof and works for everything. I am sure there are countermeasures to this that would require you to be even more clever. I have however not investigated how to protect yourself from this and will save that for another article. So if you care about this then consider the title a clickbait.</p><p>With that said let&#8217;s get into what I came up with!</p><h2>Background</h2><p>Some background information before starting this. Before doing this small project I had no previous experience with hacking or reverse engineering anything so what I did was just blindly test different stuff that I learned along the way.</p><p>The target game I had in mind was built using .NET Core using FNA and was an open-source game. The reason I mention this is because many of the things I tried just simply won&#8217;t work because of the technology the game was built with which we will get more into.</p><p>Another thing before we start that perhaps is good to know is that there are two common approaches to design your hack that I am aware of, it can either be external or internal where external hints at a standalone application using the platform APIs to manipulate another process on the machine where internal injects itself into the process and attacks it from within.</p><p>This article will go through the steps I went through to investigate and eventually land on a solution. I will not go overly in-depth into any of the techniques, if something specific needs to be clarified however just tell me and I will try to answer to the best of my knowledge.</p><h2>Attempt 1: Memory hacking</h2><p>So the first thing I tried which I also knew about before because it is not only used for reverse engineering was to manipulate the game&#8217;s memory. If you have ever been curious about cheats and perhaps googled around a bit I&#8217;m sure many of you know a program called &#8220;Cheat Engine&#8221;.</p><p>Many people are under the impression that this is a tool for people who want to make cheats but don&#8217;t know how or just want to create small scripts. This is not true, Cheat Engine is a very valuable tool used for memory scanning and debugging. It is capable of scanning the memory of running processes on your computer as well as debugging the disassembled source code of the executable of your choice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q1Vu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 424w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 848w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png" width="1041" height="851" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8d107f5-fd29-48e1-8003-892512935256_1041x851.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:851,&quot;width&quot;:1041,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 424w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 848w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 1272w, https://substackcdn.com/image/fetch/$s_!Q1Vu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8d107f5-fd29-48e1-8003-892512935256_1041x851.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The most simple way to manipulate a game using this piece of software is to open your target process within Cheat Engine followed by searching for a known value within the application. A common example is if you currently have 100 health within your game you would scan for the value 100 within Cheat Engine, this would bring up thousands upon thousands of results of every address in memory that contains the value 100. To narrow it down Cheat Engine allows you to create a new scan for each variable that changed, so for example if you take some damage within the game and now have 70 health you would perform a new scan for all values that were 100 but are now 70.</p><p>This is a process you do over and over again until you have a manageable amount of addresses left. What you then can do is to select the addresses and manipulate them, for example manually changing the value to 200, if you found the correct health variable within your game your health would now be 200 because you set that address in memory to that value.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hUw8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hUw8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 424w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 848w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 1272w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hUw8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png" width="1041" height="850" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:850,&quot;width&quot;:1041,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!hUw8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 424w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 848w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 1272w, https://substackcdn.com/image/fetch/$s_!hUw8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffa06ed01-51c1-4409-bd05-cce31c76936b_1041x850.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let us say that you managed to find the correct address and change it to 200. Well, now you&#8217;ve manipulated and &#8220;hacked&#8221; the game. But what happens if you restart the game? There is no guarantee that they will get the same address in memory upon restart. This is where another technique called pointer scanning comes into play.</p><p>Cheat Engine comes with a feature that allows you to save all pointers into what is called a pointer map, this is a file with a snapshot of the game&#8217;s memory at that specific given time.</p><p>You can then scan that pointer map for a specific pointer and compare it between different runs of the game. What this essentially allows you to do is to find static offsets within the memory where things will always be located at.</p><p>I will not go into the details on how to perform this but in the end, you will find a pointer that looks something like this within the user interface of Cheat Engine:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!EWVE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!EWVE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 424w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 848w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 1272w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!EWVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png" width="790" height="459" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/482ce320-1446-48d6-962e-d920febdbe53_790x459.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:459,&quot;width&quot;:790,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!EWVE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 424w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 848w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 1272w, https://substackcdn.com/image/fetch/$s_!EWVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F482ce320-1446-48d6-962e-d920febdbe53_790x459.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>So this is a chain of pointers that ends up with a value of 1000 within the loaded application. The interesting part that the pointer scan can do for us is that it scanned through a chain of addresses that ends up giving us our desired value. With this available, we have a static offset inside our target program that will persist between runs and we can write a program to change this value for us so that we can change our health value whenever we want.</p><h3>How to access memory addresses through code</h3><p>So how we make use of this information we found in Cheat Engine is quite simple. All we have to do is to get the base address for the application we are targetting, in the example image above this is the address for the &#8220;cheatengine-x86_64-SSE4-AVX2.exe&#8221; string that is used because I opened the Cheat Engine process for this example.</p><p>So to find this address we can use the Windows API which has a handy function ready for us called <code>CreateToolhelp32Snapshot</code> which gives us a snapshot of a target process which includes its heaps, modules, and threads.</p><p>We can use this function to get the process id (PID) for our target process as well as all the modules within a given process. With this information, we can simply iterate over all the modules and once we find a module we care about take that module&#8217;s address and use that as a base address.</p><p>The PID can then be used with the function <code>OpenProcess</code> which gives us an open handle to the specified process. This allows us to use the functions <code>ReadProcessMemory</code> as well as <code>WriteProcessMemory</code>. The two functions mentioned last allow us to Read and Write to a process&#8217;s internal memory which allows us to manipulate the memory of the process.</p><pre><code><code>#include &lt;Windows.h&gt;
#include &lt;TlHelp32.h&gt;

DWORD GetProcessId(const wchar_t ProcessName)
{
    DWORD ProcessId = 0;
    HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, ProcessId);
    if (Snapshothandle != INVALID_HANDLE_VALUE)
    {
        PROCESSENTRY32 ProcessEntry = { 0 };
        ProcessEntry .dwSize = sizeof(ProcessEntry);
        if (Process32First(Snapshothandle, &amp;ProcessEntry))
        {
            do
            {
                if (!_wcsicmp(ModuleEntry.szModule, ProcessEntry))
                {
                    ProcessId = ProcessEntry.th32ProcessID;
                    break;
                }
            } while (Process32Next(Snapshothandle, &amp;ProcessEntry));
        }
    }
    CloseHandle(Snapshothandle);
    return ProcessId ;
}

uintptr_t GetModuleBaseAddress(DWORD ProcessId, const wchar_t ModuleName)
{
    uintptr_t ModuleBaseAddress = 0;
    HANDLE SnapshotHandle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, ProcessId);
    if (Snapshothandle != INVALID_HANDLE_VALUE)
    {
        MODULEENTRY32 ModuleEntry = { 0 };
        ModuleEntry.dwSize = sizeof(ModuleEntry);
        if (Module32First(Snapshothandle, &amp;ModuleEntry))
        {
            do
            {
                if (!_wcsicmp(ModuleEntry.szModule, ModuleName))
                {
                    ModuleBaseAddress = (uintptr_t)ModuleEntry.modBaseAddr;
                    break;
                }
            } while (Module32Next(Snapshothandle, &amp;ModuleEntry));
        }
    }
    CloseHandle(Snapshothandle);
    return ModuleBaseAddress;
}

int main()
{
    DWORD ProcessId = GetProcessId(L"MyGame.exe");
    uintptr_t ModuleBaseAddress = GetModuleBaseAddress(ProcessId, L"MyGame.exe");

    HANDLE ProcessHandle = 0;
    ProcesHandle = OpenProcess(PROCESS_ALL_ACCESS, NULL, ProcessId);

    uintptr_t PointerBaseAddress = ModuleBaseAddress + 0x00E036B0;

    uintptr_t TargetAddress = 0;
    int Offsets[] = { 0x148, 0xA8, 0x88, 0x48, 0x8, 0x58, 0x0 };
    int NumberOfOffsets = 7;

    for(int Index = 0; Index &lt; NumberOfOffsets; ++Index)
    {
        ReadProcessMemory(ProcessHandle, (BYTE *)TargetAddress, &amp;TargetAddress, sizeof(TargetAddress), 0);
        TargetAddress += Offsets[Index];
    }

    // TargetAddress now contains the address!

    int NewHealthValue = 5000;
    WriteProcessMemory(ProcessHandle, (BYTE *)TargetAddress, &amp;NewHealthValue, sizeof(NewHealthValue), 0);

    return 0;
}
</code></code></pre><h3>Why attempt #1 doesn&#8217;t work for .NET Core</h3><p>Unfortunately, even though it takes a lot of work to even get this far this method of reverse engineering does not work for a .NET Core process.</p><p>The reason is that the .NET compiler produces code that can run on the CLR (Common Language Runtime) which handles the stuff for you like garbage collection, runtime type checking, and reference checking.</p><p>Unlike languages like C/C++ which produces machine code that is not managed hence nothing is done for you in a sense.</p><p>So in practice, this means that even if we do this type of scanning we won&#8217;t get static offsets within the executable because the code that is executed is not part of it in a sense and is prone to change when the CLR performs just in time compilation for example.</p><p>So if you were to try this, most of the addresses you would find wouldn&#8217;t even be inside your executable but inside the coreclr.dll which will have different pointer offsets each time.</p><p>So, back to the drawing board.</p><h2>Attempt 2: Signature Scanning</h2><p>The next thing I investigated was if I were to do signature scanning on the application&#8217;s code to find a sequence of bytes that I have previously identified to mean something I know.</p><p>In the end, this will give the same result as attempt 1 but we use a different method to arrive at the final address. Instead of scanning for data itself, we are scanning for the code. At this point, I didn&#8217;t think too much that this will result in the same problems as attempt 1.</p><p>So Cheat Engine also contains a memory view that allows you to see the code part of the process, it can look something like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HrNn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HrNn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 424w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 848w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 1272w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HrNn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png" width="694" height="189" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:189,&quot;width&quot;:694,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!HrNn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 424w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 848w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 1272w, https://substackcdn.com/image/fetch/$s_!HrNn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe167576d-ef92-41df-988e-2cf6ffd742f6_694x189.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This view also allows you to set breakpoints and step around the application while it&#8217;s running. I used this to find the method for <code>GetPlayer</code> in the application I used. This requires you to understand some assembly as well as experiment a lot.</p><p>Once you find something interesting you can use the sequence of bytes in the second column of the image in as that code&#8217;s signature. There are many tools developed for Cheat Engine to help you with this. The tool I used allowed me to right-click an address and export a signature for it. That kind of signature looks something like this: <code>48 8d 64 24 ? 48 8b 45 ? 48 8d 4d</code>.</p><p>Something weird you may notice is that there are question marks within the generated sequence, these are wildcards that exist because while the address may be moved around in memory the actual code will stay the same hence we use wildcards to aid us with finding these offsets even though the address space is updated between runs.</p><p>Some implementations use Masks to help with parsing the string which means to accompany the pattern above with a string like <code>xxxx?xxx?xxx</code> .</p><p>So an example of how this would look within code would be very similar to the first implementation.</p><p>Important when doing this is that you only scan parts of memory that matter. What this means is that we can use the function <code>VirtualQuery</code> which will retrieve memory information of that memory region only yielding valid memory regions which will save us time.</p><p>Here it depends a bit if you are writing internal or external code, the example above was an external application that will modify the memory of the target application externally but below we are utilizing an internal application that is injected into the target as a DLL. The reason for this is that when utilizing DLL injection we are becoming a part of the target process meaning that we have access to its memory from within itself.<code>
</code></p><pre><code><code>char* _Scan(char* Pattern, char* Mask, char* Begin, intptr_t Size)
{
    intptr_t PatternLength = strlen(Mask);

    for (int I = 0; I &lt; Size; I++)
    {
        bool Found = true;
        for (int J = 0; J &lt; PatternLength; J++)
        {
            if (Mask[J] != '?' &amp;&amp; Pattern[J] != *(char*)((intptr_t)Begin + I + J))
            {
                Found = false;
                break;
            }
        }
        if (Found)
        {
            return (Begin + I);
        }
    }
    return 0;
}

char *Scan(char *Pattern, char *Mask, char *Begin, intptr_t Size)
{
    char *Match { 0 };
    MEMORY_BASIC_INFORMATION MBI = { 0 };

    for (char *Current = Begin; Current &lt; Begin + Size; Current += MBI.RegionSize)
    {
        if (!VirtualQuery(Current, &amp;MBI, sizeof(MBI)) || MBI.State != MEM_COMMIT || MBI.Protect == PAGE_NOACCESS) continue;
        Match = _Scan(Pattern, Mark, Current, MBI.RegionSize);

        if (Match != 0)
        {
            break;
        }
    }

    return Match;
}
</code></code></pre><h3>Why attempt #2 doesn&#8217;t work for .NET Core</h3><p>In addition to what&#8217;s mentioned about why attempt 1 didn&#8217;t work is that .NET running its applications on the CLR is that it doesn&#8217;t produce machine code. Instead, it produces a special type of bytecode known to the CLR called Intermediate Langue (IL). This is later run within the CLR which takes this IL code and translates it into native instructions that the CPU can understand, this process is known as Just In Time (JIT) Compilation.</p><p>This is just as it sounds, a compiler that takes the code and compiles it to machine code as it is executed the first time. There is, of course, more to this if you were to dig down deeper into the CLR but for our purposes, this means that our signatures may not look the same each run as they are compiled on the fly. This can produce insanely long signatures (it did in my case) which takes a long time to look for.</p><p>For example the main one I wanted to work on looked like this:</p><p><code>ff 15 ? ? ? ? 48 8b 80 ? ? ? ? 48 83 c4 ? c3 cc cc cc cc cc cc cc cc cc cc 56 48 83 ec ? 48 8b f1 ff 15 ? ? ? ? 48 8d 88 ? ? ? ? 48 8b d6 ff 15 ? ? ? ? 90 48 83 c4 ? 5e c3 cc cc cc cc cc cc cc cc cc cc cc 48 83 ec ? ff 15 ? ? ? ? 48 8b 80 ? ? ? ? 48 83 c4 ? c3 cc cc cc cc cc cc cc cc cc cc 48 83 ec</code></p><p>And unfortunately it wasn&#8217;t consistent between runs.</p><h2>Attempt 3: CLR Hosting</h2><p>At this point, I had spent a lot of hours investigating the subject and getting real frustrated as nothing I was throwing at the game worked. I started reading up about a concept called function hooking which is used to change the game&#8217;s instructions to make it call your function before or after executing the normal code or simply ignore the original altogether and just execute your code.</p><p>At this point I understood that I can&#8217;t manipulate the game&#8217;s memory through static memory offsets, I had to change my strategy a bit. I stumbled upon a concept called CLR Hosting.</p><p>What this technique means is that you boot up a CLR instance to get into a managed application space and execute your code there. If you were to do this from inside a DLL injected into an application this means that your code will now run alongside the already managed application and you will have access to everything like you would in a normal C# application.</p><p>My hypothesis here was that if I can execute managed code from here I would be able to use reflection in C# to modify the code in the target application. The question is how do you do this?</p><p>The irony in it all was that all along Microsoft already had a <a href="https://github.com/dotnet/runtime/blob/main/src/tests/Interop/ExecInDefAppDom/ExecInDefAppDomDll.cpp">code sample available</a> for how you would do this available among their interop tests.</p><p>The code is very simple and all it does is find the handle to the <code>coreclr.dll</code> which is the module that is running our code then get its CLR Host and execute the target function inside the target dll using the method <code>ExecuteInDefaultAppDomain</code> because we are a DLL injected into the target process the same and main AppDomain as the rest of the code that we&#8217;ve been wanting to manipulate.<code>
</code></p><pre><code><code>HMODULE CoreCLRModule;
CoreCLRModule = GetModuleHandleA("coreclr.dll");
if (!CoreCLRModule)
{
    printf("ERROR - CoreCLR.dll could not be found");
    return -1;
}

ICLRRuntimeHost2* RuntimeHost;
FnGetCLRRuntimeHost PFNGetCLRRuntimeHost =
    (FnGetCLRRuntimeHost)::GetProcAddress(CoreCLRModule, "GetCLRRuntimeHost");

if (!PFNGetCLRRuntimeHost)
{
    printf("ERROR - GetCLRRuntimeHost not found");
    return -1;
}

HRESULT hr = PFNGetCLRRuntimeHost(IID_ICLRRuntimeHost2, (IUnknown**)&amp;RuntimeHost);
if (FAILED(hr))
{
    printf("ERROR - Failed to get ICLRRuntimeHost2 instance");
    return -1;
}

hr = RuntimeHost-&gt;Start();
if (FAILED(hr))
{
    printf("ERROR - Failed to start the runtime");
    return -1;
}

DWORD ExitCode = -1;
hr = RuntimeHost-&gt;ExecuteInDefaultAppDomain(L"MyHack.dll", L"Namespace.Class", L"Method", L"Arguments", &amp;ExitCode);
if (FAILED(hr))
{
    printf("Assembly execution failed!");
    return -1;
}
</code></code></pre><p>To combine this with reflection, there are handy tools that play heavily upon the fact that .NET is compiled into IL code and can rebuild the C# code written in a very accurate way which you can inspect to find out more information. An example of such a program is ILSpy which I show an example screenshot of below alongside Visual Studio.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-pR7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-pR7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 424w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 848w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 1272w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-pR7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png" width="887" height="366" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/69964893-9aef-421e-a094-05a41a83a982_887x366.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:366,&quot;width&quot;:887,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!-pR7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 424w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 848w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 1272w, https://substackcdn.com/image/fetch/$s_!-pR7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69964893-9aef-421e-a094-05a41a83a982_887x366.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Final notes and future reading</h2><p>Once again I do not promote or condone cheating or hacking. I simply did this as an experiment and to learn more about it.</p><p>If you are interested in learning more about this type of stuff and perhaps want to get into it yourself I can recommend a book called &#8220;<strong>Game Hacking: Developing Autonomous Bots for Online Games&#8221;</strong> by Nick Cano. It goes more in-depth about the techniques that I mention here as well as covers more things you can do.</p><p>In addition to that, there are very good information and communities available for free on the internet if you just google.</p><p>I think this was a very fun experience and I like something that Nick Cano mention early in his book that goes something along the lines that within every game there is another game available in addition to the one in the title, the cat and mouse game of wits between game developers and hackers. I think this is a fun way to look at it, almost like there is more to a game than the features you are supposed to interact with, more content that you can explore and even make yourself. This gives me the happy thought that nothing is stopping you from using this kind of technique to patch parts of a game that you are unhappy with or you want to improve.</p>]]></content:encoded></item><item><title><![CDATA[Computer Memory Part 4]]></title><description><![CDATA[In this article, we will finally get our hands dirty by writing some code.]]></description><link>https://www.oskarmendel.me/p/computer-memory-part-4</link><guid isPermaLink="false">https://www.oskarmendel.me/p/computer-memory-part-4</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Wed, 15 Jun 2022 18:18:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!bTHe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this article, we will finally get our hands dirty by writing some code. We&#8217;ve now learned about how the CPU works and how it deals with memory so now it is time to discuss what programmers can do with this knowledge.</p><p>Programming in a way that utilizes the CPU cache efficiently is a whole subject on its own. I will for sure not be able to cover everything in this subject but hopefully, at least get you started.</p><h2>Data-oriented design</h2><p>This is a subject that is quite popular within the game development space where you focus on the data layout of your application. Some may think that the compiler is smart enough to do this for them but this is not the case as compilers lack the bigger picture it needs to optimize the data access and data flow of the application.</p><h2>Optimize for size</h2><p>One part of the data-oriented design is to optimize your data structures for size. Why this can be important because if your data structure is small then more of them can fit within a single cache line.</p><p>An example of how this may look in practice is pretty simple. The compiler has explicit alignment for primitive types and their structures.</p><p>This means for example that an <code>int</code> can never be on address 0x15 (unless you specifically tell it to have a different allignment) because it is aligned to 4 bytes so the only aligned memory addresses would be: 0, 4, 8, 0xC, 0x10, 0x14, 0x18 &#8230;</p><p>Putting that same int on any misaligned address would be undefined behavior and the code would potentially not work at all. With that in mind look at the following code snippet:</p><pre><code><code>struct example
{
  char  A;
  uint32_t B;
};
</code></code></pre><p>Since a char is 1 byte the 32-bit integer here would be misaligned if the compiler did not introduce padding between the two fields. To see what the compiler does behind the scenes there is a flag for MSVC that allows you to print out the layout of your data structure called <code>/d1reportSingleClassLayout</code> which if we use we get the following output:</p><pre><code><code>class example   size(8):
    +---
 0  | A
    | &lt;alignment member&gt; (size=3)
 4  | B
    +---
Compiler returned: 0
</code></code></pre><p>This padding can lead to the structure being larger than it needs to be based on in which order you place your fields in that struct. Let&#8217;s take another example:</p><pre><code><code>struct example
{
    uint32_t A;
    uint64_t B;
    uint32_t C;
};

/*
class example   size(24):
    +---
 0  | A
    | &lt;alignment member&gt; (size=4)
 8  | B
16  | C
    | &lt;alignment member&gt; (size=4)
    +---
Compiler returned: 0
*/
</code></code></pre><p>This would result in a structure of 24 bytes due to the padding being introduced between A and B as well as after C. However if we re-order our fields like this:<code>
</code></p><pre><code><code>struct example
{
    uint64_t B;
    uint32_t A;
    uint32_t C;
};

/*
class example size(16):
    +---
 0  | B
 8  | A
12  | C
    +---
Compiler returned: 0
*/
</code></code></pre><p>And completely get rid of the padding and get a smaller resulting data structure which in turn we can fit more of in the cache.</p><h2>Spatial locality</h2><p>Another common concept in data-oriented design is to start thinking about how your data is accessed. By now you should understand that processing data sequentially in memory is more efficient that having to jump around to different memory locations. A simple way to show this is when organizing data in terms of structures of arrays (SOA) versus arrays of structures (AOS). Data structure adopting this mindset looks like this:<code>
</code></p><pre><code><code>#define SIZE 16384

typedef struct entityaos_
{
    uint64_t Power;
    uint64_t Health;
    uint64_t Speed;
} entityAOS;

typedef struct entitysoa_
{
    uint64_t Power[SIZE];
    uint64_t Health[SIZE];
    uint64_t Speed[SIZE];
} entitySOA;
</code></code></pre><p>What happens here is that instead of allocating an array of entities we have a data structure that has all the different fields an entity can have in its arrays. The implications these two methods have on memory access are very different and can be easily spotted when visualized.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bTHe!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bTHe!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 424w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 848w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 1272w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bTHe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png" width="493" height="352" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/509029ba-5183-40b6-b289-9e4bee659977_493x352.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:352,&quot;width&quot;:493,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!bTHe!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 424w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 848w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 1272w, https://substackcdn.com/image/fetch/$s_!bTHe!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F509029ba-5183-40b6-b289-9e4bee659977_493x352.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now instead of processing each entity separately, we would instead process their fields. This allows us to deal with the specific field as a contiguous block of memory which helps us with fitting more of that property into the cache. I wrote a small piece of code to showcase this, here is two dummy functions for the two structs presented above which go over each entity and sum their power level.</p><pre><code><code>#define SIZE 16384

typedef struct entity1_
{
    uint64_t Power;
    uint64_t Health;
    uint64_t Speed;
} entityAOS;

typedef struct entity2_
{
    uint64_t Power[SIZE];
    uint64_t Health[SIZE];
    uint64_t Speed[SIZE];
} entitySOA;

void InitEntityAOS(entityAOS *E)
{
    E-&gt;Power = rand();
    E-&gt;Health = rand();
    E-&gt;Speed = rand();
}

void InitEntitySOA(entitySOA *E)
{
    for (int i = 0; i &lt; SIZE; ++i)
    {
        E-&gt;Power[i] = rand();
        E-&gt;Health[i] = rand();
        E-&gt;Speed[i] = rand();
    }
}

uint64_t SumEntitiesAOS(entityAOS *Entities, int Number)
{
    uint64_t Sum = 0;
    
    for (int i = 0; i &lt; Number; ++i)
    {
        Sum += Entities[i].Power;
    }
    
    return (Sum);
}

uint64_t SumEntitiesSOA(entitySOA *Entities, int Number)
{
    uint64_t Sum = 0;
    
    for (int i = 0; i &lt; Number; ++i)
    {
        Sum += Entities-&gt;Power[i];
    }
    
    return (Sum);
}
</code></code></pre><p>The result of benchmarking this can be seen in the following graph where the lower bar means faster. Here processing the SOA-based entities is about 3,5 times faster than processing them as AOS. A small disclaimer here is that entity is just an example and probably not something I would write like this in the real world as it would get tedious.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GVVU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GVVU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 424w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 848w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 1272w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GVVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png" width="919" height="456" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f6691564-e60b-46c1-bd58-f054089e1157_919x456.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:456,&quot;width&quot;:919,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Untitled&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Untitled" title="Untitled" srcset="https://substackcdn.com/image/fetch/$s_!GVVU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 424w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 848w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 1272w, https://substackcdn.com/image/fetch/$s_!GVVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff6691564-e60b-46c1-bd58-f054089e1157_919x456.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Further reading</h2><p>As I mentioned before data-oriented design is a very broad topic and requires more effort to go over than an article like this one. Therefore I strongly recommend looking into it a bit on your own. I found <a href="https://github.com/dbartolini/data-oriented-design">https://github.com/dbartolini/data-oriented-design</a> which has a list of resources related to data-oriented design. There is also a great <a href="https://www.youtube.com/watch?v=rX0ItVEVjHc">talk by Mike Acton on YouTube</a> which was the one that introduced me to the concept. A few years ago there was also a <a href="https://www.dataorienteddesign.com/dodbook/dodmain.html">book written by Richard Fabian called Data-oriented design</a> which I&#8217;ve heard good things about.</p><h2>Intrinsics</h2><p>Moving on we will be using something called intrinsics. What this is is a specific compiler function not part of any library but the compiler itself. These intrinsic functions can be thought of almost like an inline function that the compiler exposes to you when programming. We will specifically use intrinsics that are directly mapped to the x86 single instruction multiple data (SIMD) instruction set. SIMD is a way of operating on multiple data at once. For example in the previous section when we are adding all the power values for the entities instead of looping over each one we can loop over four at a time and add all of them together increasing the performance even further. However this type of optimization I would expect the compiler to try and do for us in this specific example due to its simplicity.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!viH_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!viH_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 424w, https://substackcdn.com/image/fetch/$s_!viH_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 848w, https://substackcdn.com/image/fetch/$s_!viH_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 1272w, https://substackcdn.com/image/fetch/$s_!viH_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!viH_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png" width="951" height="351" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/11676830-4554-45ad-bc7a-976409f01ee9_951x351.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:351,&quot;width&quot;:951,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Figure from H. Jeong, S. Kim, W. Lee, and S.-H. Myung, &#8220;Performance of sse and avx instruction sets,&#8221; arXiv preprint arXiv:1211.0820, 2012.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Figure from H. Jeong, S. Kim, W. Lee, and S.-H. Myung, &#8220;Performance of sse and avx instruction sets,&#8221; arXiv preprint arXiv:1211.0820, 2012." title="Figure from H. Jeong, S. Kim, W. Lee, and S.-H. Myung, &#8220;Performance of sse and avx instruction sets,&#8221; arXiv preprint arXiv:1211.0820, 2012." srcset="https://substackcdn.com/image/fetch/$s_!viH_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 424w, https://substackcdn.com/image/fetch/$s_!viH_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 848w, https://substackcdn.com/image/fetch/$s_!viH_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 1272w, https://substackcdn.com/image/fetch/$s_!viH_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F11676830-4554-45ad-bc7a-976409f01ee9_951x351.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Figure from H. Jeong, S. Kim, W. Lee, and S.-H. Myung, &#8220;Performance of sse and avx instruction sets,&#8221; arXiv preprint arXiv:1211.0820, 2012.</p><p>There are two main SIMD instruction sets available called SSE (Streaming SIMD Extensions) and AVX (Advanced Vector Extensions) which offer 128-bit XMM registers and 256-bit YMM registers respectively.</p><p>Initially, SSE offered a set of 70 instructions that number was later increased with the releases of SSE2, SSE3, SSSE3, and SSE4. The XMM registers are displayed in the figure above and as you can see this instruction set allows for operating on four values at once since each register is 128-bit (16 bytes).</p><p>The AVX instruction set uses the YMM registers which can fit double the size of an XMM register which means that eight values can be stored in one register which is 256-bit (32 bytes) in size. In addition to its size, the AVX instruction set also provides the option for three input arguments which allows functions with the format of c=a+b instead of the normally used format a=a+b which SSE is restricted to.</p><p>Important to note when using SIMD instructions is to keep track of what throughput the instructions used have and how that affects your application. An example is the AVX instruction &#8220;VDIVPD&#8221; whose latency is twice as high as the alternative SSE instruction &#8220;DIVPD&#8221;. Using the wrong instructions can have a negative impact and become a performance bottleneck.</p><p>A great resource for available intrinsics is available on <a href="https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=6954,6952">Intel&#8217;s intrinsic guide</a>. I will probably make a separate series on SIMD so I won&#8217;t touch more on this subject here but if you are confused by the _mm instructions in the following sections do know that they are intrinsic functions and their documentation is available in the intel&#8217;s guide.</p><h2>Bypassing the cache</h2><p>Sometimes we are working with data that will not be immediately used or with large data structures and because each store operation will read a full cache line first and then modify that cached data if we have a large enough structure that means that the earlier parts of it will be evicted from the cache as we process the later parts of the structure and this makes caching the writes ineffective.</p><p>To solve this we have something called non-temporal write operations. If you remember that temporal here refers to re-use within a relatively small time duration and since the operation is non-temporal there is no reason to cache it. These operations do not read a cache line and then modify it, instead, they write new content into memory directly.</p><p>Following are a couple of code snippets taken from Ulrich Drepper&#8217;s paper which perfectly display how to do this. In the first one we can see the available intrinsics to perform the non-temporal writes:</p><pre><code><code>#include &lt;emmintrin.h&gt;
void _mm_stream_si32(int *p, int a);
void _mm_stream_si128(int *p, __m128i a);
void _mm_stream_pd(double *p, __m128d a);
#include &lt;xmmintrin.h&gt;
void _mm_stream_pi(__m64 *p, __m64 a);
void _mm_stream_ps(float *p, __m128 a);
#include &lt;ammintrin.h&gt;
void _mm_stream_sd(double *p, __m128d a);
void _mm_stream_ss(float *p, __m128 a);
</code></code></pre><p>The next snippet shows how to use these intrinsics in order to initialize a matrix which can be useful when doing math or game development:<code>
</code></p><pre><code><code>#include &lt;emmintrin.h&gt;
void setbytes(char *p, int c)
{
    __m128i i = _mm_set_epi8(c, c, c, c,
                             c, c, c, c,
                             c, c, c, c,
                             c, c, c, c);
    _mm_stream_si128((__m128i *)&amp;p[0], i);
    _mm_stream_si128((__m128i *)&amp;p[16], i);
    _mm_stream_si128((__m128i *)&amp;p[32], i);
    _mm_stream_si128((__m128i *)&amp;p[48], i);
}
</code></code></pre><p>This is also how the <code>memset</code> function within the C standard runtime deals with writing large blocks of memory. There is a single instruction available <code>_mm_stream_load_si128</code>for loading with the same non-temporal memory hint which means that you do have the option of loading memory without risking cache eviction.</p><h2>Final notes</h2><p>Using what we&#8217;ve learned so far in the series we should have all the tools available to understand how we can optimize our application&#8217;s cache usage. While the series has not presented every single optimization technique I think it has successfully laid the foundation for thinking about memory.</p><p>Moving on from here involves a lot of reading and experimentation, every application is different and hence needs different types of optimizations. Unfortunately for these types of things, there is no universal solution but to think about your data and how it is moving around in your application.</p><h2>Resources</h2><ul><li><p><a href="https://en.wikipedia.org/wiki/Data-oriented_design">https://en.wikipedia.org/wiki/Data-oriented_design</a></p></li><li><p><a href="https://www.dataorienteddesign.com/dodbook/dodmain.html">https://www.dataorienteddesign.com/dodbook/dodmain.html</a></p></li><li></li></ul><div id="youtube2-rX0ItVEVjHc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;rX0ItVEVjHc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/rX0ItVEVjHc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><ul><li><p><a href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">https://people.freebsd.org/~lstewart/articles/cpumemory.pdf</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Intrinsic_function">https://en.wikipedia.org/wiki/Intrinsic_function</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">https://en.wikipedia.org/wiki/Single_instruction,_multiple_data</a></p></li><li><p><a href="https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=6954,6952">https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#ig_expand=6954,6952</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Computer Memory Part 3]]></title><description><![CDATA[Last article we learned about CPU caches and its different levels, we also learned about cache lines, how they are stored and retrieved and last but not least we also touched upon multiprocessing describing the MESI protocol briefly.]]></description><link>https://www.oskarmendel.me/p/computer-memory-part-3</link><guid isPermaLink="false">https://www.oskarmendel.me/p/computer-memory-part-3</guid><dc:creator><![CDATA[Oskar Mendel]]></dc:creator><pubDate>Sat, 28 May 2022 18:15:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!h1Uy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last article we learned about CPU caches and its different levels, we also learned about cache lines, how they are stored and retrieved and last but not least we also touched upon multiprocessing describing the MESI protocol briefly.</p><p>In this article we will dive into virtual memory as well as non-uniform memory access. I suspect this to be the last article before we start to actually discuss code.</p><h2>Virtual Memory</h2><p>The virtual memory subsystem is part of the processor and can provide a virtual address space to each process, this kind of makes each process think it&#8217;s alone within the system. The main storage from the process perspective becomes one contiguous address space or collection of contiguous segments of memory.</p><p>The part of the CPU that implements the virtual address space is the Memory Management Unit (MMU). The operating system has to manage the virtual address spaces and assign them with real memory, the CPU has the capabilities here to automatically translate virtual addresses into physical addresses. A very beneficial part of this is that the main memory can be extended further than the real main memory&#8217;s capacity by utilizing disk storage.</p><p>The way the CPU looks up a physical address is by walking a set of hierarchically organized directories.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!h1Uy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!h1Uy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 424w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 848w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 1272w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!h1Uy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png" width="828" height="315" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:315,&quot;width&quot;:828,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38990,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!h1Uy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 424w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 848w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 1272w, https://substackcdn.com/image/fetch/$s_!h1Uy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9a31313-54f1-4bcb-bbfb-7c7afcfce4b8_828x315.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Taken from Drepper, Ulrich. &#8220;What Every Programmer Should Know About Memory.&#8221; (2007).</em></p><p>The above figure displays how a 64-bit address maps into the different directories. First, the top bits of the address are removed and used as a lookup inside the root directory which is the L4 Directory in this image.</p><p>When inside the L4 directory we will determine if it&#8217;s a valid address if it&#8217;s not we will stop because of a page fault while if it&#8217;s valid we will use those bits to lookup which L3 directory we are going to look at. The process is then repeated by using the Level 3 Index in the L3 directory. Once the process has been repeated the L1 directory will find out where the memory is in physical memory, the remaining bits of the virtual address are then used as an offset within the resulting page.</p><p>With all the caching we&#8217;ve gone through earlier that is great we don&#8217;t have to read memory that often but if we would have to perform four reads instead of one because of looking up physical memory addresses that would be very inefficient.</p><h3>TLB cache</h3><p>To solve that we have another cache called Translation Look-Aside Buffer (TLB). These TLBs are multi-level caches for the virtual address translations. On my machine which has a Haswell-based Intel CPU, there are two levels of TLBs, an L1 and L2. The L1 has 64 4KB page entries, 32 2MB, and 4 1GB page entries. What is stored inside one of these cache entries is the page for this specific translation, this means that for each entry there are 4096 physical bytes available which mean that there is a total buffer of 256KB that can be cached and looked up instantly.</p><p>The 32 2MB pages and the single 1GB page are something often referred to as &#8220;Huge pages&#8221; and are somewhat tedious to set up as they need for example administrator privileges on Windows and have the risk of failing if you have more of it than available memory. But it can be quite worthwhile as you do get a big buffer of memory which lets you prevent TLB misses.</p><p>With all this said the TLB is not something thought of much as it does get flushed upon context switches.</p><h3>In practice</h3><p>This gives quite some power to the developer to do some very interesting things. When allocating memory through the virtual memory system the memory is not actually allocated physically but instead just reserved so that no other code can use that memory space until we start operating on the allocated pages.</p><p>Most commonly in ArrayList implementations, the array size is first set to a default value like <a href="https://github.com/microsoft/referencesource/blob/master/mscorlib/system/collections/arraylist.cs#L52">4</a>, then once the capacity is met the underlying array is simply reallocated into the double size.</p><p>Using virtual memory we don&#8217;t have to go through this hassle but instead, we can allocate a <em>very</em> big array from the start, let&#8217;s say for example 1 million objects.</p><p>This can be done using <a href="https://docs.microsoft.com/sv-se/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc?redirectedfrom=MSDN">VirtualAlloc</a> on Windows or <a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap</a> on Linux.</p><h2>NUMA</h2><p>Non-uniform memory access is just something we will briefly mention but not talk about too much.</p><p>NUMA comes into play when you are dealing with multi-chip solutions where you have more than one physical CPU. This is very common for servers or high-end machines.</p><p>The communication between these CPUs is expensive and they have their own set of RAM plugged directly into them. The RAM plugged in to all of those CPUs does become the RAM of the computer so it is possible for one CPU to access the RAM plugged into the other but this is of course slower. So non-uniform in this case means that memory can&#8217;t be accessed in a uniform way access memory in the system as it depends on where it is plugged in on the chip and where your application is running.</p><p>NUMA can improve the performance over single shared memory on a factor based on the number of processors available.</p><h2>References</h2><ul><li><p><a href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">https://people.freebsd.org/~lstewart/articles/cpumemory.pdf</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Virtual_memory">https://en.wikipedia.org/wiki/Virtual_memory</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Translation_lookaside_buffer">https://en.wikipedia.org/wiki/Translation_lookaside_buffer</a></p></li><li><p><a href="https://en.wikichip.org/wiki/intel/microarchitectures/haswell_(client)">https://en.wikichip.org/wiki/intel/microarchitectures/haswell_(client)</a></p></li><li><p><a href="https://en.wikipedia.org/wiki/Non-uniform_memory_access">https://en.wikipedia.org/wiki/Non-uniform_memory_access</a></p></li><li><p><a href="https://ourmachinery.com/post/virtual-memory-tricks/">https://ourmachinery.com/post/virtual-memory-tricks/</a></p></li></ul>]]></content:encoded></item></channel></rss>