<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://www.sheehanahmed.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.sheehanahmed.com/" rel="alternate" type="text/html" /><updated>2026-02-22T20:20:29-08:00</updated><id>https://www.sheehanahmed.com/feed.xml</id><title type="html">Sheehan Ahmed</title><subtitle>Tools programmer, graphics programmer, gameplay programmer.</subtitle><author><name>Sheehan Ahmed</name></author><entry><title type="html">Video games aren’t cool anymore (and Japan can’t save us) [DRAFT]</title><link href="https://www.sheehanahmed.com/video-games-arent-cool-japan-cant-save-us/" rel="alternate" type="text/html" title="Video games aren’t cool anymore (and Japan can’t save us) [DRAFT]" /><published>2025-03-28T11:00:00-07:00</published><updated>2025-03-28T11:00:00-07:00</updated><id>https://www.sheehanahmed.com/video-games-arent-cool-japan-cant-save-us</id><content type="html" xml:base="https://www.sheehanahmed.com/video-games-arent-cool-japan-cant-save-us/"><![CDATA[<h2 id="what-is-coolness">What is ‘coolness’?</h2>

<p>“Go skydiving!” Hideaki Itsuno exclaims at <a href="https://youtu.be/CWi5gjxdh4o?feature=shared">his 2019 GDC talk</a> detailing the combat design of <em>Devil May Cry 5</em>. Not as a silly joke to complement the important, technical parts of his talk. No, ‘Go skydiving’ is his premier piece of design advice to a room packed to the brim with aspiring game designers.</p>

<p><img src="/assets/images/posts/07_itsuno.jpg" alt="" class="align-center" width="50%" /></p>

<p>Itsuno wraps many exciting activities under the emblem of ‘skydiving’: travel, attending sports games and concerts, venturing into the unknown. The ultimate goal is to build a well of emotional experiences that can’t be replicated with verbal or written description. This well, although abstract and ethereal, becomes the game designers’ greatest asset for creating raw emotional experiences. I didn’t quite internalize it the first time I heard it. I was there to hear about the implementation details of a complex action game, after all. But in the years since, ‘go skydiving’ has become my guiding light through an age of artistic drought.</p>

<p>The room for a <em>Devil May Cry 5</em> design talk <em>was</em> packed to the brim, of course, because developers across the globe are obsessed with Japanese games. Partially because of their historic dominance, but primarily because of how ‘cool’ they are. Video games in general, at least right now, aren’t ‘cool.’ More people play them but they don’t command the cultural attention as an artistic medium they once did. How am I defining ‘coolness?’ Beyond raw popularity or recognizability, ‘coolness’ represents cultural allure and taste-making value. A work of art is ‘cool’ when it’s treading new ground, attention-grabbing, and confident in its own direction regardless of external validation. To create ‘cool’ art requires a tasteful amount of experimentation and mainstream inaccessibility, two things that are gigantic no-no’s in the modern business world. High budgets and long development periods enforce sticking to tried-and-true trends instead of the raw artistic experimentation of gaming’s heyday, the golden era of the sixth console generation (1998–2009).</p>

<p>Japanese games from that era like <em>Final Fantasy VII</em>, <em>Smash Bros Melee</em>, <em>Silent Hill</em>, <em>Resident Evil</em> and <em>Metal Gear Solid</em> still command fierce fanbases who cannot help but be magnetized to their cross-generational appeal. Since then, we’ve been experiencing the entropy of video game coolness with the onslaught of remakes, remasters, and IP crossovers that reference that era without creating anything new to challenge or evolve it. Epic Games’ <em>Fortnite</em> recently added Raiden and Solid Snake from the <em>Metal Gear</em> franchise despite the last mainline entry, <em>MGS:V</em>, being a decade old.</p>

<p><img src="/assets/images/posts/07_raiden.png" alt="" class="align-center" width="75%" /></p>

<p class="text-center"><em>Raiden Concept art by Yoji Shinkawa / in-game model in MGS2 (2001) / in-game model in Fortnite (2024)</em></p>

<p>It’s easy to see why they did this; <em>Metal Gear</em> characters and art-style command attention and coolness even amongst audiences who are completely unfamiliar with the franchise. Solid Snake is an easier sell than a character like Viktor Reznov from the objectively much more popular <em>Call of Duty</em> franchise because <em>Call of Duty</em> isn’t ‘cool’ in the way <em>Metal Gear</em> is. Nevertheless, we will eventually run out of ‘cool’ characters from the era in which they were allowed to be made, and the event horizon of our coolness heat death is rapidly approaching.</p>

<h2 id="japans-cool-dominance">Japan’s ‘cool’ dominance</h2>

<p>Every time a modern game of high artistic merit does release (and it’s usually from Japan), it has now re-upped itself as a permanent context that will be copied or compared against for the next decade. The current ‘cool kids on the block’ are undoubtedly FromSoftware of <em>Elden Ring</em> fame, and for how much I love them, it’s regrettable to see every modern action franchise develop in their Erdtree’s shadow. Take Game Science’s <em>Black Myth Wukong</em>, a game developed and advertised as a shining beacon of Chinese mythology. <a href="https://web.archive.org/web/20240823150009/https://www.jiemian.com/article/11578722.html">Wukong aims to depict the J<em>ourney to the West</em> myth and <em>Wuxia</em> (武俠)</a>, a uniquely Chinese form of theatrical martial combat found in Chinese cinema, most famously <em>Crouching Tiger Hidden Dragon</em> (Ang Lee, 2000). I was excited to see them adapt the spatial combat style to video game form.</p>

<p class="text-center"><em>Sekiro: Shadows Die Twice (2019, FromSoftware)</em></p>

<p><img src="/assets/images/posts/07_sekiro.gif" alt="" class="align-center" width="50%" />
<img src="/assets/images/posts/07_wukong.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Black Myth: Wukong (2024, Game Science)</em></p>

<p>Unfortunately, <em>Wukong</em> follows the FromSoftware mold to a tee, specifically their samurai action game <em>Sekiro: Shadows Die Twice</em>. <em>Sekiro</em>’s deflection-based gameplay evokes the defensive precision and discipline of a shinobi warrior, marking its gameplay as distinctly Japanese-themed. Tragically, <em>Wukong</em>’s minimal variations can’t avoid feeling like a Japanese samurai game with a superficially Chinese coat of paint.</p>

<p><img src="/assets/images/posts/07_michelle.gif" alt="" class="align-center" width="50%" />
<img src="/assets/images/posts/07_ninja-gaiden.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Elegantly mixing verticality, wallrunning and melee combat, the Ninja Gaiden games are perfect Michelle Yeoh simulators.</em></p>

<p>Taking inspiration from spatially rich games like <em>Ninja Gaiden II</em> or <em>Bayonetta</em> would have better fit the Wuxia theme.  Watching any gameplay from those two titles feels more in line with what you might see in a fight scene from <em>Crouching Tiger</em> than anything in <em>Sekiro</em>, and by extension, <em>Wukong</em>. The cruel irony here, though, is that both <em>Ninja Gaiden</em> and <em>Bayonetta</em> franchises are Japanese-produced. Even in my criticism of a Chinese studio to make their game more distinctly Chinese, I use Japanese products as reference points. Japan is <a href="https://youtu.be/62tIvfP9A2w?feature=shared">John Coltrane on Giant Steps</a>, switching through keys at breakneck speed, and the rest of us, from Shanghai to San Francisco, are Tommy Flanagan trying desperately to catch up.</p>

<p><img src="/assets/images/posts/07_shadows.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Despite literally taking place in Japan, Assassin’s Creed: Shadows repeats the stagnant open world formula with zero learnings from Japan’s innovations in the genre.</em></p>

<p>Don’t feel too bad for China, because Japan is even beating us at our own game too (and I’m not talking about Shohei). The Open World genre, starting with Rockstar’s <em>Grand Theft Auto III</em> and popularized most famously by Bethesda’s <em>Elder Scrolls V: Skyrim</em>, quintessentially represents the American spirit. It’s a game genre about exploring a new world and conquering it, piece by piece; what could be more ‘American Cowboy’ than that? Despite this, the genre-defining open world games as of now are all Japanese. Nintendo’s <em>Breath of the Wild</em> and FromSoft’s <em>Elden Ring</em> are credited for revitalizing the genre with bold direction and experimental design that recontextualizes how exploration can be encouraged. Just this year, Nintendo’s open world <em>Xenoblade Chronicles X</em> edges out Ubisoft’s open world <em>Assassin’s Creed: Shadows</em> with the former scoring an 88 on MetaCritic and the latter scoring an 81. Even in the game genre that is most American by principle, it is ironically Japan treading new ground as to what is possible with the medium of video games.</p>

<p><img src="/assets/images/posts/07_kena.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>“[Kena: Bridge of Spirits] uses Japanese iconography… presumably [only] because it looks cool, because due to missing worldbuilding, none of it is referenced anywhere.” <a href="https://www.eurogamer.net/kena-bridge-of-spirits-review-gorgeous-looking-yet-wholly-unoriginal">(Malindy Hetfeld, Eurogamer)</a></em></p>

<p>The solution is decidedly not to make our games more ‘Japanese’ in an aesthetic or superficial sense. While the world has many lessons to internalize from Japanese design, I feel we are too comfortable in aping their aesthetics without ample narrative justification. Yes, yes — I grew up with <em>Naruto</em> and Ghibli films too — and Japan themselves have spent billions of dollars to hold their throne as the premier cultural exporter of the world (<a href="https://en.wikipedia.org/wiki/Cool_Japan">‘Cool Japan’ Project</a>). However, when projects from outside Japan release with all their aesthetic trappings — Shinto imagery and anime-inspired character designs — but none of their core principles or explicit call-outs to Japanese culture, it can’t help but feel a bit embarrassing. If nothing else, it’s certainly uncool; clawing desperately for the exotic appeal of its inspirations without the confidence and focused vision to back it up.</p>

<h2 id="what-makes-japan-so-cool-anyway">What makes Japan so cool, anyway?</h2>

<p>Does Japan have a secret sauce? Some umami ‘coolness’ that we can’t access without starting a new studio in Tokyo ourselves? Well, no, I don’t think so. To be fully honest, even Japan isn’t as cool as Japan anymore. Hayao Miyazaki of Studio Ghibli famously lamented on the state of the Japanese animation industry. His “anime was a mistake” quote, although arguably mistranslated and bereft of context, still resurfaces to poke fun at less-than-impressive seasonal shows.</p>

<p><img src="/assets/images/posts/07_anime-mistake.gif" alt="" class="align-center" width="50%" /></p>

<p><a href="https://soranews24.com/2014/01/30/ghiblis-hayao-miyazaki-says-the-anime-industrys-problem-is-that-its-full-of-anime-fans/">The full quote</a> points to an educated frustration with the state of his industry: “You see, whether you can draw like this or not,” Miyazaki explains as he sketches a character, “depends on if you can say to yourself, ‘Oh, yeah, girls like this exist in real life.’ If you don’t spend time watching real people, you can’t do this, because you’ve never seen it… Almost all [Japanese] animation is produced with hardly any basis taken from observing real people, you know. It’s produced by humans who can’t stand looking at other humans. And that’s why the industry is full of <strong>otaku</strong>!”</p>

<blockquote>
  <p><strong>Otaku (Japanese term):</strong></p>

  <p>A young person who is obsessed with computers or particular aspects of popular culture to the detriment of their social skills. <a href="https://www.oed.com/dictionary/otaku_n?tl=true">(Oxford Dictionary)</a></p>
</blockquote>

<p>A similarly unfortunate fate has befallen the games industry, both in terms of consumer-base and production. The population of people making and playing games are overrun with a disinterest in life and culture outside of digital worlds. I can imagine a statement similar to Hayao Miyazaki’s coming from the lips of Hidetaka Miyazaki, director at FromSoftware, <a href="https://www.knowll.com/e/92/complete-list-of-influences-on-souls-games">who credits much of his inspirations to novels, manga, and architecture studies</a> outside of just video games. Miyazaki was famously inspired by getting his car stuck during a particularly gnarly snowstorm for the unique co-op features of the <em>Souls</em> franchise. Meanwhile, every new ‘Soulslike’ game made in Elden Ring’s image feels starved of any other cultural nutrition than the video games themselves. <strong>Video games aren’t cool, they haven’t been cool in a long time, and it feels like we all wordlessly expect that by following in Japan’s footsteps they’ll become cool again.</strong> I’m here to tell you that no, that won’t happen. Not without understanding that life experience and a wide cultural diet is what’s necessary for creating good, cool art.</p>

<h2 id="indie-as-a-response">Indie as a response</h2>

<p>A common rebuttal is to associate the entirety of our cultural drought with the much maligned ‘AAA’, or popular high-budget video games produced at multi-billion dollar companies like Activision-Blizzard, Rockstar, or Ubisoft. For true, real artisanal experiences, one must seek out indie games! There are many strong contenders for ‘good video games’ in the indie space; games that tell interesting stories, dazzle with vibrant visuals and keep players coming back for refined, polished gameplay. Despite this, the indie space is stuck in a rut of constantly reinterpreting nostalgia without any reinvention to push it forward. We are currently in a 90’s nostalgia phase, which means roguelikes, platformers, and survival horror games are all in vogue. A Y2K nostalgia phase peeks around the corner with several indie action and adventure games currently in development. These projects revere their inspirations but fail to justify themselves evolutionarily. If you really love <em>Zelda</em> enough to make a whole new <em>Zelda</em> game of your own, matching everything up to but just short of IP infringement, why would I play yours instead of just playing <em>Zelda</em>?</p>

<p><img src="/assets/images/posts/07_pine.jpg" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>‘Central character overlooks grassy green expanse…’ describes nearly half of new adventure game announcement trailers in the years since Zelda: Breath of the Wild.</em></p>

<p>Indie falls into the same trap as AAA, stuck looking backward and lacking inspiration beyond the video games that spawned it. Between Steam’s 2-hour rule and the market of public opinion on Twitter, the indie game market highly optimizes for easily digestible mechanical or narrative gimmicks. This context for development and marketing does not foster the experimentation or artistic excellence found in the Y2K Japanese golden age, and I can’t say I’m looking forward to the troves of games made by other people who really really love that era but have nothing else to say about it. Even if indie games are good, even if they are great, I don’t believe they will stand the test of time in the same way that golden age games do. <strong>My 15-year old cousin just told me she’s getting a PS2 second-hand for <em>Silent Hill 2</em>. She was born almost a decade after that thing came out!</strong> Do we truly believe that anything released as of late has that same cultural pull?</p>

<blockquote>
  <p><strong>Randomly remembering that:</strong></p>

  <p>Classmates at my prestigious game art university said <em>Silent Hill 2</em> looked ‘old’ and ‘ugly’ and the ‘dialogue is trash,’ which makes me chuckle when thinking of my little cousin’s opposingly strong curiosity. No shade, we were 20 years old, all love &lt;3</p>
</blockquote>

<p><img src="/assets/images/posts/07_team-silent.png" alt="" class="align-center" width="75%" /></p>

<p>On the topic of <em>Silent Hill</em>, just take a look at their development team. Team Silent exudes the confidence and aura of a historied group of artistic collaborators. <a href="https://youtu.be/uDYPLdu2FQk?si=lZavRGfOawrB3vlA">From the development documentaries</a>, it’s easy to observe that their office is filled with books on art, history, classic literature, architecture, and philosophy. You can imagine they’d take smoke breaks to discuss topics that have nothing to do with video games at all, yet effortlessly and unambiguously filter into the trilogy of masterpieces they were about to create. Not to mention they dress well too! This is the atmosphere in a studio required to create a timeless work of art, beyond any technical expertise at programming or design or whatever else.</p>

<h2 id="thats-just-how-they-do-it-or-japanese-mysticism">“That’s just how they do it,” or Japanese mysticism</h2>

<p><img src="/assets/images/posts/07_capcom-designer.png" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Designer at Capcom working on Marvel vs. Capcom: Infinite</em></p>

<p>I’m sure you may have come to the following conclusion: “this writer is tragically butthurt that he wasn’t born in 1980s Tokyo so he could have burgeoned into the Japanese golden age during his 20s.” I’m sure I gave off that impression, but to be honest, no. As much as I cherish and respect the golden age and the numerous classics that era of Japan has given us, my dream is for the rest of the world to catch up. I’m tired of Japan being the only voice of reason in the medium I love so dearly. Much of the conversation around Japan seems to align their stellar products with an unreplicable Japanese-ness, and I could not disagree more. It’s a niche fact that acclaimed Japanese film director Akira Kurosawa named Bengali-Indian director Satyajit Ray, notably his film Pather Panchali, <a href="https://www.indiewire.com/news/general-news/akira-kurosawa-said-watching-a-satyajit-ray-film-is-like-seeing-the-sun-or-moon-187504/">as one of his premier inspirations</a>:</p>

<blockquote>
  <p>“The quiet but deep observation, understanding and love of the human race, which are characteristic of all his films, have impressed me greatly. I feel that he is a ‘giant’ of the movie industry. Not to have seen the cinema of Ray means existing in the world without seeing the sun or the moon. I can never forget the excitement in my mind after seeing [Pather Panchali]. It is the kind of cinema that flows with the serenity and nobility of a big river. People are born, live out their lives, and then accept their deaths. Without the least effort and without any sudden jerks, Ray paints his picture, but its effect on the audience is to stir up deep passions. How does he achieve this?”
—Akira Kurosawa</p>
</blockquote>

<p class="text-center"><em>Pather Panchali (1955, Satyajit Ray)</em></p>

<p><img src="/assets/images/posts/07_pather-panchali.gif" alt="" class="align-center" width="50%" />
<img src="/assets/images/posts/07_yojimbo.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Yojimbo (1961, Akira Kurosawa)</em></p>

<p>Just reading that passage alone may enlighten you as to the magic of Kurosawa’s own work: Japanese cinema classics like <em>Rashomon</em>, <em>Seven Samurai</em>, and <em>Yojimbo</em>. These works are talked about as if they spawned from nothing else but uniquely Japanese sensibilities for serenity, gracefulness, and technical craftsmanship, when in reality even legends crafted with a nod toward their own (in this case Bengali) heroes. Kurosawa’s work was lightning-fierce, and we still feel his influence into a medium he was never alive to observe. Sucker Punch Studios’ <em>Ghost of Tsushima</em> shipped with a ‘Kurosawa Mode’ for their samurai game to pay homage to the legendary director, and Japanese titles like <em>Sekiro</em>, <em>Rise of Ronin</em>, and <em>Onimusha</em> are undoubtedly developed with his films pinned tightly to the wall.</p>

<p>I would describe my own Japanese heroes, ranging from Shinji Mikami to Fumito Ueda to many more, with the same reverence that Kurosawa paid to Ray. Never having played <em>Dark Souls</em> or <em>Resident Evil 4</em>, games I hold dearly to my heart, may be as debilitating to me personally as never having seen the sun, moon, or a beautiful eclipse. Now, as a person of Bengali heritage, I feel some responsibility to put my culture back on the map and close the loop that was started nearly a century ago. I’m excited to produce works that are inspired by the Japanese masters but indelibly marked by my own cultural upbringing. Let’s not kid ourselves into believing that artistic inspiration and technical craftsmanship are ‘just how the Japanese do it’ as an excuse for our own shortcomings. Many of the legendary creators of that era — Kamiya, Mikami, Ueda, Kojima, Itsuno, Inaba — are now heading new studios working on completely new IP’s or revitalizing classic ones. As an avid fan, I’m more than excited for their work, but eager to see the rest of the world rise to the competitive challenge.</p>

<h2 id="how-to-be-cool-101">How to be Cool 101</h2>

<p>Well, how can we achieve that? Like any other early-in-career, I work a technical industry job, absorbing as much knowledge and experience as possible. I dream of starting a studio one day, aware of how many others have the same dream and how hard I’ll have to work to truly achieve it. Even beyond my own ambitions, however, I dream of a world where all together we aspire to create artisanal experiences that reach beyond games as meaningless toys for wasting time. I dream of another golden age, but this time at a global scale instead of centralized to three companies in Tokyo. I dream of video games respected as artworks on the level of film, literature, and music, and the creators of those video games revered as the great artists of our generation. I dream of making a video game that, a decade after its release, a culturally curious young girl like my little cousin will fight tooth-and-nail over Ebay bids and retro stores in order to get her hands on. Is there any better feeling than sinking your teeth into a video game like that?</p>

<p>This dream, however, requires more than just video games. In fact, it requires the exact opposite. It requires creators who are cultured, historied, filled to the brim with ravenous hunger for life and death and everything in between. It requires creators who are sharply dressed and have a crazy look in their eyes. It requires creators who speak several languages and have seen many corners of the world. It requires creators who watch the sun and moon, sitting patiently for beautiful sunsets and mesmerizing eclipses. It requires creators who have read the classics, and the contemporaries, and some shitty reality television too. It requires creators who have gone skydiving, felt the wind cut and stab at their cheeks and smiled ear-to-ear the entire fall.</p>

<p><img src="/assets/images/posts/07_botw.gif" alt="" class="align-center" width="50%" /></p>

<p>If you work in the games industry, or aspire to, or love video games and art in general in any capacity at all. Please, please, please. I beg you. Go skydiving. More than ever, art needs to be interacted with by people who have gone skydiving. In front of the screen, yes, and behind it too.</p>

<blockquote>
  <p><strong>For Legal Reasons:</strong></p>

  <p><em>‘Go skydiving’ is defined as an allegory for reading, studying, travelling, meeting new and different kinds of people, interacting with foreign cultures, learning a new language, trying new hobbies, exercise/working out, becoming a sports fan, switching up your fashion, and/or going to music shows. I am not responsible for skydiving (or otherwise) related injuries.</em></p>
</blockquote>

<h2 id="ps-on-ai-art-not-sure-about-keeping-this-part">P.S: On AI Art (not sure about keeping this part)</h2>

<p>Maybe you’ve gotten to the end of this blog and are still wondering what the big deal is about ‘coolness.’ So what if art nowadays is less ‘cool’? Does it need to be ‘cool’? Isn’t ‘coolness’ arbitrarily exclusive and immature? I’d like to point you to last week’s Ghibli AI Art fiasco.</p>

<p><img src="/assets/images/posts/07_ai-ghibli.png" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Popular meme images converted to Ghibli-style with ChatGPT.</em></p>

<p>With ChatGPT’s image generation feature, troves and troves of users converted every kind of image to AI-generated artworks in Ghibli’s famous house style. Hayao Miyazaki has been outspoken about his distaste for AI-generated artwork, and it’s clear to see why. A studio that has dedicated its entire artistic portfolio to capturing an impressionistic portrait of life has been sullied against their will for corporate, anti-artistic greed.</p>

<p>There’s a lot of talk on the pro-AI side of ‘democratizing art,’ and making art accessible for everyone. So let me clearly state, in no uncertain terms: art shouldn’t be democratized like this. You shouldn’t get to make art that looks like the art cool, interesting, skilled people make if you yourself aren’t cool, interesting or skilled. Art has already been democratized in the sense that making great art is accessible to everyone of all races, creeds, and economic statuses. There has been widely celebrated art created by the homeless, the disabled, and the most ostracized minority members of society. The only limiting reagent is whether you have something valuable to say; a limiting reagent that has proved debilitating for a certain crowd. I live in San Francisco, and it’s clear to see that the Bay is filled with individuals of privileged socio-economic backgrounds who can buy any experience money can buy and yet are permanently stuck reaching toward the uniqueness or individual confidence that others easily exude. These are the people prompting and peddling AI art; unromantic, culturally unrefined ignoramuses who wouldn’t know ‘cool’ if David Lynch came down from the smoke room in Heaven and handed it to them on a silver platter.</p>

<p>AI Art is only going to get better at doing what we were kind of already doing before it hit the market; copying, and copying, and copying. What were funny pitfalls months ago — fingers, text, continuity — have been overcome swiftly by hardworking, highly paid and hopelessly uncool AI engineers. The only thing AI Art can’t ever be is ‘cool.’ As a machine, it can only move all of its creation towards a middling median, appealing to all but loved by none. So you, the human artist reading this. You need to be cooler than the techbro down the street. Don’t worry, you have quite the head start. And, after that — you need to make cool art. Art that is somewhat inaccessible, foreign, or hard to grasp. Art that is magnetic and interesting without needing to bend over backwards for broad appeal. You need to make the art that cool people will gate-keep away from the lames who use AI art for no other reason than “they just wouldn’t get it.”</p>

<p>And you, non-artists who have gotten this far. Poptimisim is over, okay? Start making fun of your friends who only watch mainstream things again. Yes, you are a better, cooler human being because your Spotify end-of-year has more than just artists with 10M+ monthlies. Our society depends on a healthy amount of gatekeeping and snobbiness, and the pendulum has swung too far the other way. This healthy pretentiousness is the coal that fuels our society to both make and appreciate the kinds of art that AI could never make.</p>

<p>Godspeed! Go live life and make masterpieces that only seasoned, interesting, <em>cool</em> people can craft!</p>

<p><em>Follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.</em></p>]]></content><author><name>Sheehan Ahmed</name></author><category term="life" /></entry><entry><title type="html">*shen mei*: for the love of the game</title><link href="https://www.sheehanahmed.com/shen-mei/" rel="alternate" type="text/html" title="*shen mei*: for the love of the game" /><published>2024-10-14T11:00:00-07:00</published><updated>2024-10-14T11:00:00-07:00</updated><id>https://www.sheehanahmed.com/shen-mei</id><content type="html" xml:base="https://www.sheehanahmed.com/shen-mei/"><![CDATA[<p>When visiting my Michael Kors fashion assistant friend in New York, I spent a couple hours in his apartment with a group of friends. The topics at the time: the struggle of finding housing, how the Ducks were doing (they were mostly Oregon graduates), and some lighthearted gossip. At one point, my friend trimmed the edges of an insole to fit a pair of tabis he had just secured from a sample sale. They were too big, he explained. I’m not sure how common this type of thing is, but judging by everyone else’s surprise at the sudden shoe surgery, I’d wager not very. “Fashion Assistant at Michael Kors” sounds like the type of job someone only gets with a discerning eye – the kind that sees potential in a shoe that most of us would simply pass over as a size too big.</p>

<p><img src="/assets/images/posts/06_tabis.png" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Tabis — Japanese design of shoe with a toe separator</em></p>

<p>Later, we celebrated our respective new careers that happened to land in our interests (I had started working at Niantic on Pokemon Go around the same time he got his job). What was intriguing was that neither of us could pinpoint what put us a cut above the applicant pool. The interviews went smoothly in a sterile way; no ‘knock their socks off’ moments for either of us. I proposed that maybe our interviewers saw something in us, a potential or promise that was invisible to our eyes. Like a pair of too-big tabis. The theory was vague and inconclusive, but it sounded satisfying enough.</p>

<p>Another of my college friends grew up in Beijing. We speak in English, but anytime she’d run into a word or concept she couldn’t find a satisfying stand-in for, she’d teach me a Chinese word and do her best to convey its meaning. One of these phrases was 审美 — shen mei. It refers to a discerning taste, a vision of quality or beauty that remains invisible to anyone without. It’s like a sixth sense built through hours of study, practice, and instinct.</p>

<p>Shen (审) means ‘examine’ and Mei (美) means ‘beautiful.’ I interpret their combination here as a form of divinely inspired beauty held entirely in the eye of the beholder or similarly discerning observers. A mysterious, alchemical sophistication present in wine tasters or vocal judges. My friend introduced this word to describe an artist classmate’s impeccable understanding of color theory. Many of our classmates at USC crafted visually impressive pieces, filled with detail and vibrancy — but her work could make a simple sketch look beautiful with choice colors that perfectly complemented each other. Good sense? Taste? No… a mere English word wouldn’t cut it here. I can’t help but think about shen mei often, and it’s become one of my favorite borrowed words.
Do I have shen mei? If I do… then in what, exactly? It’s hard to tell what’s visible to me but invisible to others. I’d like to think I have a certain instinct with game feel: the tactile nature of real-time games. The way the animation, sound, and movement all come together to give a game a special immersion that melts the fourth wall and spirits you away. Think of drifting in MarioKart and how your physical body leans with the motion, as if that might help you make the turn.</p>

<p><img src="https://i.makeagif.com/media/12-11-2018/b4rdak.gif" alt="" class="align-center" width="50%" /></p>

<p>Thanks to my jack-of-all-trades-master-of-none skill set in game development (and a little bit of instinctual shen mei), I have a knack for producing highly game-feeley projects. Among classmates and colleagues, I’ve become known for this. Whether it’s a jump attack or a special effect, people who playtest my games say “It has the Sheehan bounce to it!” This has given me a strong sense of artistic identity, one closely knit to video games. I have met much better artists, much better programmers, much better everything really… but few of them could deliver that game feel quite like I could.</p>

<p>I love movies, music, writing — and I hope to acquire a workmanlike expertise in each field — but my strongest shen mei will always be in the immersive feel of video games. I can press a button in a new release and tell you that the response needs to be fifteen frames shorter, the way a cook could tell a dish needs an extra pinch of salt. It’s exciting to cross paths with friends whose shen mei are in completely different fields, whether that be fashion, architectural design, or photography. Observing their ability to see things that I can’t is inspiring in a way I can’t quite explain.</p>

<p>There’s a new piece of Gen-Z slang I like: “for the love of the game.” It’s usually used to humorously poke fun at individuals who perform a certain activity purely for its own sake without any ulterior motive. For example, most people dress nicely to appear clean and attractive or portray elements of their personality and interests. I can attest that’s about as far as it goes for me. However, for my fashion assistant friend, it’s deeper than that. You can tell that he truly loves fashion for the love of the game. In much the way that Gen-Z usually does, slang tends to touch upon ancient philosophical truths with the casual goofiness of a grade-school sleepover.</p>

<p>Many of my orbital interests serve an underlying interest in games. I’d like to learn photography to frame better shots while programming the cameras in my games. I’d like to learn music and sound design to better score the important moments in my games. It’s only games themselves that, no pun intended, I love for the love of the game. With that perspective, it becomes clear to me why I hear perfectly clear sounding music when my sound designer friend raises his eyebrow and observes, “The mix isn’t great on this.” I can ask him what he means, and I learn. Maybe in the future, I’d be able to make the same observation. But he can hunt these nuggets of discernment down in the way a bloodhound can track prey for miles. It’s a means to an end for me; it’s the love of the game for him. The shen mei must come naturally after that.</p>

<p><img src="https://media0.giphy.com/media/H8QFfDgSCDscO39RBb/giphy-downsized.gif" alt="" class="align-center" width="50%" /></p>

<p class="text-center"><em>Inscryption: a 2021 indie game that went viral for pairing card roguelike with experimental metafiction.</em></p>

<p>You’d think I might say that shen mei only comes from loving a craft so deeply, and yet I’m not so sure. I think that’s the most common path to achieving shen mei, and most of the people I know who have it are deeply embedded into their craft, but it isn’t a hard rule. The next day, I was in Chicago visiting another friend, a barista who also runs an online sticker business on the side. She’s highly economical with her hobbies — music, games, art, fashion — and I can’t help but admire (and envy) her high degree of shen mei with everything she takes a passing interest in. I watched her play an hour of <em>Inscryption</em> and her keen eye picked up on so many details I had assumed only someone in the business of game development would notice. “You can tell this game is well-made,” she mentioned. <em>I can,</em> I thought. <em>I’m impressed and overjoyed you can, too.</em> My habit would be to intellectualize this somehow — find a working theory that transposes the intricacies of coffee-making to game design — but nothing ever is so simply capturable. Many avenues can exist to reach shen mei, even if loving the game is my personal favorite.</p>

<p>It’s poetic that I was introduced to the concept of shen mei by a friend who couldn’t find an English word to describe what she meant in our conversation. As English becomes the globally default language, there’s a certain amount of shen mei in discovering the cracks where its linguistic possibilities fall short.</p>

<p>I try to end my blogs with some kind of takeaway, but I find myself unable to. Whoever you are, reading this — there is beauty in your world that I can’t describe because I can’t see them the same way you do. There are takeaways out there that can’t be written down, in English or any other language. Ghosts of our ideological and artistic pasts, yearning for an observer to understand them. I hope you can see them and cherish your own shen mei, whatever it is that calls to you. Whatever it is that you love purely for the love of the game.</p>

<p><em>Follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.</em></p>]]></content><author><name>Sheehan Ahmed</name></author><category term="life" /></entry><entry><title type="html">Dijkstra’s Algorithm Pixel Shader for Tactics Movement</title><link href="https://www.sheehanahmed.com/dijkstra-algorithm-pixel-shader-for-tactics-movement/" rel="alternate" type="text/html" title="Dijkstra’s Algorithm Pixel Shader for Tactics Movement" /><published>2024-02-05T19:00:00-08:00</published><updated>2024-02-05T19:00:00-08:00</updated><id>https://www.sheehanahmed.com/dijkstra-algorithm-pixel-shader-for-tactics-movement</id><content type="html" xml:base="https://www.sheehanahmed.com/dijkstra-algorithm-pixel-shader-for-tactics-movement/"><![CDATA[<p>I’ve been playing Intelligent Systems’ <em>Fire Emblem</em> games recently. It’s hard not to appreciate how the tactical movement gives depth and immersion to a Game Map that’s visually just a handful of pixels compared to modern game landscapes like <em>Red Dead Redemption 2</em> or <em>Breath of the Wild</em>. Considering units’ positioning down to choices between adjacent grid cells gets a player intimately engaging with the game environment.</p>

<p><img src="/assets/images/posts/04_blazing-blade.gif" alt="Fire Emblem Gameplay" class="align-center" /></p>

<p>Notice that when the player selects a unit to move them, it displays a grid. This grid depicts grid cells the player can move to in blue, and grid cells they can attack in red. This display is integral to the strategy of <em>Fire Emblem</em>, as it gives the player a clear yet informative summary of their tactical options.</p>

<p>I wanted to recreate the effect myself, with an added optimization challenge; have the whole algorithm run on the GPU with a pixel shader.</p>

<blockquote>
  <p><strong>Why the GPU?</strong></p>

  <p>Speed is a concern, since moving units is a common command. The game freezing up every time you click on a unit to move is unacceptable. This also can’t be solved with multithreading instead, because the game needs to show where a unit can travel immediately upon clicking on it. In conclusion, our solution must run fast on the main thread using a pixel shader.</p>
</blockquote>

<h3 id="expected-behavior">Expected Behavior</h3>
<p>Firstly, let’s review the needs and expected behavior of the system:</p>
<ul>
  <li>The world is organized into grid cells. Each grid cell has an integer Movement Cost (1, 2, 4, or 8), or is a wall that cannot be moved upon or through.</li>
  <li>There are Units that live in individual grid cells, and can move to other grid cells. Units can only move up, down, left or right. “Diagonal” movements are counted as moving two grid cells.</li>
  <li>Each Unit has an integer Movement Range. This means that they may travel as far as the sum of the travelled grid cells’ Movement Costs is less than or equal to their Movement Range.</li>
  <li>A Unit will be able to move at least one grid cell away from their starting one, even if their Movement Range is less than the cost of that cell’s Movement Cost.</li>
  <li>Units additionally may Attack up to an integer Attack Range grid cells away from their ending position. Attack Range ignores Movement Cost and walls.</li>
</ul>

<h3 id="dijkstras-algorithm">Dijkstra’s Algorithm</h3>
<p>The needs and expected behavior denote two specific problems:</p>
<ul>
  <li>How can we find every accessible point given a starting cell and movement range?</li>
  <li>How can we trace this point back to the starting point?</li>
</ul>

<p>This is best done with Dijkstra’s Algorithm because it’s essentially built to solve this problem.</p>

<p><img src="https://upload.wikimedia.org/wikipedia/commons/2/23/Dijkstras_progress_animation.gif" alt="" class="align-center" /></p>

<p>Here is a brief paraphrased refresher on Dijkstra’s as it applies to our problem. This is for newcomers… or if your memory of sophomore-year algorithms class is getting hazy.</p>
<ol>
  <li>Start with every cell marked open, other than a start point cell, which is closed. We will “close” nodes as we visit them to keep track of where we’ve been.</li>
  <li>Assign to every cell a Travel Cost value. This defaults to infinity for every cell other than the starting cell, which is zero. This value represents the shortest possible path to reach this cell from the starting cell.</li>
  <li>For a current node, consider all open neighbors. If an open neighbor could be given a Travel Cost less than the one it currently has by virtue of travelling there from the current node, assign it the newer Travel Cost. Close the current node.</li>
  <li>Repeat step 4 for every open neighbor of the current cell in a breadth-first search. Stop when cells whose Travel Cost exceeds the Movement Range are reached.</li>
  <li>After this process, every “closed” cell can be travelled to, and each contains a Travel Cost defining how much of the Movement Range is expended to reach it.
(This is loosely paraphrased from the <a href="https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm">Wikipedia article</a> on Dijkstra’s algorithm)</li>
</ol>

<h3 id="running-an-algorithm-on-a-pixel-shader">Running an algorithm on a Pixel Shader</h3>

<p>Dijkstra’s algorithm is cool and all but… how do we run that on a pixel shader? The algorithm asks for maintaining a set of closed cells, and shaders can’t really keep consistent state like that. It also involves a Breadth-first search, which would be best done in a loop. As it’s currently defined, a pixel shader is incapable of adapting Dijkstra’s algorithm.</p>

<p>So, we’ll have to think outside the box… and flip the script.</p>

<p><img src="https://64.media.tumblr.com/e1135919ffae84a9a05a33d6aecbcb5b/b04f7415d0823b3d-86/s540x810/da6b1bdda1fba1e0479e38494b41409be72e18f5.gif" alt="" class="align-center" /></p>

<details open="">
  <summary> Bottoms-up Dijkstra's Algorithm </summary>

  <p>This is bottoms-up because open cells who notice a closed neighbor drive themselves, instead of closed cells finding open neighbors. This works great for a pixel shader where we need to run an identical instruction on every cell and can’t rely on holding list states.</p>

  <p>Pseudocode:</p>

  <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="p">(</span><span class="n">current</span> <span class="k">is</span> <span class="n">Wall</span> <span class="n">or</span> <span class="n">current</span> <span class="k">is</span> <span class="n">Closed</span> <span class="n">or</span> <span class="n">all</span> <span class="n">neighbors</span> <span class="n">are</span> <span class="n">Open</span><span class="p">)</span>
	<span class="k">return</span>

<span class="k">if</span><span class="p">(</span><span class="n">up_neighbor</span> <span class="k">is</span> <span class="n">closed</span> <span class="p">&amp;&amp;</span> <span class="n">up_neighbor</span><span class="p">.</span><span class="n">travelCost</span> <span class="k">is</span> <span class="n">least</span><span class="p">)</span>
	<span class="n">current</span><span class="p">.</span><span class="n">nearestNeighbor</span> <span class="p">=</span> <span class="n">up</span>
	<span class="n">current</span><span class="p">.</span><span class="n">travelScore</span> <span class="p">=</span> <span class="n">up_neighbor</span><span class="p">.</span><span class="n">travelScore</span> <span class="p">+</span> <span class="n">up_neighbor</span><span class="p">.</span><span class="n">cost</span>

<span class="cm">/* repeat the above if statement for down, left, right */</span>

<span class="k">if</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">travelScore</span> <span class="p">&lt;=</span> <span class="n">movementRange</span><span class="p">)</span>
	<span class="nf">close</span><span class="p">(</span><span class="n">current</span><span class="p">)</span>
</code></pre></div>  </div>

</details>

<h3 id="holding-dijkstra-state-in-an-8-bit-rgba-texture">Holding Dijkstra State in an 8-bit RGBA Texture</h3>

<p>We can’t rely on holding state externally to the texture. Instead, we will find clever ways to use all 4 channels in the 8-bit texture in order to store state.</p>

<p>The only offline information we need for a Map is the Movement Cost of each cell and whether or not it’s a wall. This will be stored in the Red channel, as follows:</p>

\[MC=
\begin{cases} 
  1 &amp; 0.0 \leq R &lt; 0.2 \\
  2 &amp; 0.2 \leq R &lt; 0.4 \\
  4 &amp; 0.4 \leq R &lt; 0.6 \\
  8 &amp; 0.6 \leq R &lt; 0.8 \\
  Wall &amp; 0.8 \leq R &lt; 1.0 \\
\end{cases}\]

<p>Which can be simplified to:</p>

\[MC=\begin{cases} 
  2^{ceil(5*R)-1} &amp; 0.0 \leq R &lt; 0.8 \\
  Wall &amp; 0.8 \leq R &lt; 1.0 \\
\end{cases}\]

<p>In a real game, this Red Channel will be populated by sampling the tile map of the game world. For the purposes of this demo, I’ll draw up a sample map in Photoshop instead.</p>

<p><img src="/assets/images/posts/04_testing-maze.png" alt="" class="align-center" /></p>

<p>Here is the test map I drew, stored in the Red channel but displayed in greyscale for clarity. It’ll work well for testing purposes, although I can’t say Shouzou Kaga would be proud of the design. As you can see, the absolute black and shades of grey define grid cells that may be travelled to and their Movement Cost, whereas the absolute white defines impassable walls.</p>

<p>With the Movement Cost stored in the Red Channel, that leaves the other three channels (GBA) to hold state.</p>

<p>G: During algorithm, will be used to mark open/closed cells. After algorithm completion, acts as a mask for entire travelable area.</p>

\[Mask=\begin{cases} 
  Open / Untravelable &amp; G = 0.0 \\
  Closed / Travelable &amp; G = 1.0 \\
\end{cases}\]

<p>B: Travel Cost.</p>

\[TC=B*255\]

<p>A: Points to nearest neighbor (lowest travel cost). When the player picks a point, this will be traced to move the Unit to that location.</p>

\[NN=\begin{cases} 
  Up &amp; 0.0 \leq A &lt; 0.2 \\
  Down &amp; 0.2 \leq A &lt; 0.4 \\
  Left &amp; 0.4 \leq A &lt; 0.6 \\
  Right &amp; 0.6 \leq A &lt; 0.8 \\
  N/A &amp; 0.8 \leq A &lt; 1.0 \\
\end{cases}\]

<p>Now that we know how to use the texture, we can define our pixel shader’s behavior. Instead of travelling top-down from the starting cell, the pixel shader will apply an iterative process to the entire image. This single iteration will use a plus-shaped kernel to convolve the image.</p>

<blockquote>
  <p><strong>What is a kernel?</strong></p>

  <p>A kernel describes a certain shape for sampling around the current pixel when running a pixel shader. Usually, this is a square with specified dimensions. For example, a simple box blur uses a 3x3 kernel to sample the 9 pixels surrounding a pixel and average them to achieve a blurring effect. In our case, we use a 3x3 plus-shaped kernel because Units can only move up, down, left and right, not diagonally.</p>
</blockquote>

<p><img src="/assets/images/posts/04_plus-kernel.png" alt="" class="align-center" /></p>

<p>Now that we know our kernel, lets write the pass.</p>

<details open="">
  <summary> Helper Functions </summary>

  <p>I started by implementing some simple helper functions that keep the code demystified.</p>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">float</span> <span class="nf">pack255</span><span class="p">(</span><span class="n">float</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">val</span> <span class="o">/</span> <span class="mi">255</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
<span class="n">float</span> <span class="nf">unpack255</span><span class="p">(</span><span class="n">float</span> <span class="n">val</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">val</span> <span class="o">*</span> <span class="mi">255</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
<span class="n">float</span> <span class="nf">getMoveCost</span><span class="p">(</span><span class="n">fixed4</span> <span class="n">col</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nb">max</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="nb">pow</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="nb">ceil</span><span class="p">(</span><span class="n">col</span><span class="p">.</span><span class="n">r</span> <span class="o">*</span> <span class="mi">4</span><span class="p">.</span><span class="mi">99</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">));</span> <span class="p">}</span>
<span class="n">bool</span> <span class="nf">isWall</span><span class="p">(</span><span class="n">fixed4</span> <span class="n">col</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">unpack255</span><span class="p">(</span><span class="n">col</span><span class="p">.</span><span class="n">r</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">200</span><span class="p">;</span> <span class="p">}</span>
<span class="n">bool</span> <span class="nf">isOpen</span><span class="p">(</span><span class="n">fixed4</span> <span class="n">col</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="n">col</span><span class="p">.</span><span class="n">g</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">.</span><span class="mo">01</span><span class="p">;</span> <span class="p">}</span>
<span class="kt">void</span> <span class="nf">close</span><span class="p">(</span><span class="k">inout</span> <span class="n">fixed4</span> <span class="n">col</span><span class="p">)</span> <span class="p">{</span> <span class="n">col</span><span class="p">.</span><span class="n">g</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div>  </div>

</details>

<details open="">
  <summary> Defining the Kernel </summary>

  <p>Then, in the fragment shader, I define the kernel by sampling the four points necessary: up, down, left and right.</p>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">float2</span> <span class="n">cell_size</span> <span class="o">=</span> <span class="n">_MainTex_TexelSize</span><span class="p">.</span><span class="n">xy</span><span class="p">;</span>  
<span class="n">fixed4</span> <span class="n">col</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span><span class="p">);</span>  
<span class="k">const</span> <span class="n">fixed4</span> <span class="n">col_u</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="nf">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>  
<span class="k">const</span> <span class="n">fixed4</span> <span class="n">col_d</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="nf">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>  
<span class="k">const</span> <span class="n">fixed4</span> <span class="n">col_l</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="nf">float2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>  
<span class="k">const</span> <span class="n">fixed4</span> <span class="n">col_r</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="nf">float2</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span> 
</code></pre></div>  </div>

</details>

<details open="">
  <summary> Guard Clause </summary>

  <p>We check for if the cell is closed, a wall, or if all neighbors are open.</p>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">isOpen</span><span class="p">(</span><span class="n">col</span><span class="p">)</span> <span class="o">||</span> <span class="n">isWall</span><span class="p">(</span><span class="n">col</span><span class="p">)</span> <span class="o">||</span> <span class="nb">max</span><span class="p">(</span><span class="nb">max</span><span class="p">(</span><span class="n">col_u</span><span class="p">.</span><span class="n">g</span><span class="p">,</span> <span class="n">col_d</span><span class="p">.</span><span class="n">g</span><span class="p">),</span> <span class="nb">max</span><span class="p">(</span><span class="n">col_l</span><span class="p">.</span><span class="n">g</span><span class="p">,</span> <span class="n">col_r</span><span class="p">.</span><span class="n">g</span><span class="p">))</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">.</span><span class="mo">01</span><span class="p">)</span>  
<span class="k">return</span> <span class="n">col</span><span class="p">;</span>
</code></pre></div>  </div>

</details>

<details open="">
  <summary> Reverse Dijkstra </summary>

  <p>Now, for the main operation: find the neighbor with the least Travel Cost, and update our own Travel Cost by adding the Movement Cost to it. Keep in mind that the B channel stores Travel Cost and the A channel stores Nearest Neighbor.</p>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">float</span> <span class="n">tc_u</span> <span class="o">=</span> <span class="n">unpack255</span><span class="p">(</span><span class="n">col_u</span><span class="p">.</span><span class="n">b</span><span class="p">);</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">tc_d</span> <span class="o">=</span> <span class="n">unpack255</span><span class="p">(</span><span class="n">col_d</span><span class="p">.</span><span class="n">b</span><span class="p">);</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">tc_l</span> <span class="o">=</span> <span class="n">unpack255</span><span class="p">(</span><span class="n">col_l</span><span class="p">.</span><span class="n">b</span><span class="p">);</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">tc_r</span> <span class="o">=</span> <span class="n">unpack255</span><span class="p">(</span><span class="n">col_r</span><span class="p">.</span><span class="n">b</span><span class="p">);</span>  

<span class="n">float</span> <span class="n">min_tc</span> <span class="o">=</span> <span class="mi">9999</span><span class="p">;</span> <span class="c1">// minimum travel cost  </span>
<span class="n">float</span> <span class="n">mc</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span> <span class="c1">// movement cost</span>

<span class="c1">// If closed and travel value &lt; minValue  </span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">isOpen</span><span class="p">(</span><span class="n">col_u</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">tc_u</span> <span class="o">&lt;</span> <span class="n">min_tc</span><span class="p">)</span>  
<span class="p">{</span>  
	<span class="n">min_tc</span> <span class="o">=</span> <span class="n">tc_u</span><span class="p">;</span>  
	<span class="n">mc</span> <span class="o">=</span> <span class="n">getMoveCost</span><span class="p">(</span><span class="n">col_u</span><span class="p">);</span>  
	<span class="n">col</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="p">;</span>  
<span class="p">}</span>  
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">isOpen</span><span class="p">(</span><span class="n">col_d</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">tc_d</span> <span class="o">&lt;</span> <span class="n">min_tc</span><span class="p">)</span>  
<span class="p">{</span>  
	<span class="n">min_tc</span> <span class="o">=</span> <span class="n">tc_d</span><span class="p">;</span>  
	<span class="n">mc</span> <span class="o">=</span> <span class="n">getMoveCost</span><span class="p">(</span><span class="n">col_d</span><span class="p">);</span>  
	<span class="n">col</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">3</span><span class="p">;</span>  
<span class="p">}</span>  
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">isOpen</span><span class="p">(</span><span class="n">col_l</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">tc_l</span> <span class="o">&lt;</span> <span class="n">min_tc</span><span class="p">)</span>  
<span class="p">{</span>  
	<span class="n">min_tc</span> <span class="o">=</span> <span class="n">tc_l</span><span class="p">;</span>  
	<span class="n">mc</span> <span class="o">=</span> <span class="n">getMoveCost</span><span class="p">(</span><span class="n">col_l</span><span class="p">);</span>  
	<span class="n">col</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>  
<span class="p">}</span>  
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">isOpen</span><span class="p">(</span><span class="n">col_r</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">tc_r</span> <span class="o">&lt;</span> <span class="n">min_tc</span><span class="p">)</span>  
<span class="p">{</span>  
	<span class="n">min_tc</span> <span class="o">=</span> <span class="n">tc_r</span><span class="p">;</span>  
	<span class="n">mc</span> <span class="o">=</span> <span class="n">getMoveCost</span><span class="p">(</span><span class="n">col_r</span><span class="p">);</span>  
	<span class="n">col</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">7</span><span class="p">;</span>  
<span class="p">}</span>

</code></pre></div>  </div>

</details>

<details open="">
  <summary> Finalize Pass </summary>

  <p>Finalize setting our own Travel Cost. Importantly, only close the cell if the Travel Cost is less than the Movement Range; this ensures that we can’t travel beyond the Movement Range’s scope.</p>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="n">float</span> <span class="n">tc</span> <span class="o">=</span> <span class="n">min_tc</span> <span class="o">+</span> <span class="n">mc</span><span class="p">;</span>  
<span class="n">col</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="n">pack255</span><span class="p">(</span><span class="n">tc</span><span class="p">);</span>  
<span class="k">if</span><span class="p">(</span><span class="n">tc</span> <span class="o">&lt;=</span> <span class="n">_MoveRange</span><span class="p">)</span>  
	<span class="n">close</span><span class="p">(</span><span class="n">col</span><span class="p">);</span>  
<span class="k">return</span> <span class="n">col</span><span class="p">;</span>
</code></pre></div>  </div>

</details>

<details open="">
  <summary> Iterations </summary>

  <p>This shader pass only counts for a single iteration, though. For the algorithm to work, we have to run the texture through this pass for a movement range number of iterations.</p>

  <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">movementRange</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>  
<span class="p">{</span>  
	<span class="n">Graphics</span><span class="p">.</span><span class="nf">Blit</span><span class="p">(</span><span class="n">mRenderTexture</span><span class="p">,</span> <span class="n">tempRT</span><span class="p">,</span> <span class="n">mAlgorithmMaterial</span><span class="p">);</span>  
	<span class="n">Graphics</span><span class="p">.</span><span class="nf">Blit</span><span class="p">(</span><span class="n">tempRT</span><span class="p">,</span> <span class="n">mRenderTexture</span><span class="p">);</span>  
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<hr />

<p>And… the result:</p>

<p><img src="/assets/images/posts/04_movement-vis.gif" alt="" class="align-center" /></p>

<p>Although the above gif is slowed down for clarity, the actual algorithm runs lightning fast. For expected use-cases of Movement Ranges under 32, I consistently get &lt;0.2ms of total runtime.</p>

<h3 id="attack-ranges">Attack Ranges</h3>

<p>We’re over the hard part, but still need the attack ranges. These are the little red squares that show how far a Unit can attack if they move to the very edge of their Movement Range.</p>

<p><img src="/assets/images/posts/04_blazing-blade.gif" alt="Fire Emblem Gameplay" class="align-center" /></p>

<p>Luckily, this is quite simple. Since the Attack Range ignores Movement Cost and walls, we don’t need to do any pathfinding of any sort. Instead, we will convolve the G channel of the Reverse Dijkstra result with an expansion formula, using the same plus-shaped kernel.</p>

<details open="">
  <summary> Expansion </summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">fixed4</span> <span class="n">col</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span><span class="p">);</span>  
<span class="k">const</span> <span class="kt">float2</span> <span class="n">cell_size</span> <span class="o">=</span> <span class="n">_MainTex_TexelSize</span><span class="p">.</span><span class="n">xy</span><span class="p">;</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">down</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="kt">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">)).</span><span class="n">g</span><span class="p">;</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">left</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="kt">float2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">)).</span><span class="n">g</span><span class="p">;</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">right</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="kt">float2</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">)).</span><span class="n">g</span><span class="p">;</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">up</span> <span class="o">=</span> <span class="nb">tex2D</span><span class="p">(</span><span class="n">_MainTex</span><span class="p">,</span> <span class="n">i</span><span class="p">.</span><span class="n">uv</span> <span class="o">+</span> <span class="n">cell_size</span> <span class="o">*</span> <span class="kt">float2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">)).</span><span class="n">g</span><span class="p">;</span>  
<span class="k">const</span> <span class="n">float</span> <span class="n">maximum</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="nb">max</span><span class="p">(</span><span class="n">down</span><span class="p">,</span> <span class="n">left</span><span class="p">),</span> <span class="nb">max</span><span class="p">(</span><span class="n">up</span><span class="p">,</span> <span class="n">right</span><span class="p">));</span>  
<span class="n">col</span><span class="p">.</span><span class="n">g</span> <span class="o">=</span> <span class="nb">max</span><span class="p">(</span><span class="n">col</span><span class="p">.</span><span class="n">g</span><span class="p">,</span> <span class="nb">min</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span> <span class="n">maximum</span><span class="p">));</span>  
<span class="k">return</span> <span class="n">col</span><span class="p">;</span>
</code></pre></div>  </div>

  <p>This will simply take any pixel with a value greater than 0.5 and set the neighboring pixels to 0.5. The effect will look like an expansion.</p>

</details>

<p><img src="/assets/images/posts/04_expansion.gif" alt="Expansion" class="align-center" /></p>

<p>Perfect! Now the effect is complete.</p>

<h3 id="conclusion">Conclusion</h3>

<p><img src="/assets/images/posts/04_final.gif" alt="Expansion" class="align-center" /></p>

<p>Here is the demonstration of the final effect.</p>

<p>In the future for my tactics side-project, I’ll add:</p>
<ul>
  <li>Red Channel auto-generation from world map contents</li>
  <li>Pretty in-game shader to display this grid to the player</li>
  <li>Arrow tracing player start to destination</li>
</ul>

<p>Thank you for reading my blog. If you were impressed and are an employer, consider hiring me as a Graphics Programmer (<a href="https://www.linkedin.com/in/sheehan-ahmed-94136b192/">My LinkedIn</a>).</p>

<p>Otherwise, follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.</p>

<hr />]]></content><author><name>Sheehan Ahmed</name></author><category term="HLSL" /></entry><entry><title type="html">*Dark Souls* is a cozy game, actually</title><link href="https://www.sheehanahmed.com/dark-souls-is-a-cozy-game/" rel="alternate" type="text/html" title="*Dark Souls* is a cozy game, actually" /><published>2023-07-27T19:35:00-07:00</published><updated>2023-07-27T19:35:00-07:00</updated><id>https://www.sheehanahmed.com/dark-souls-is-a-cozy-game</id><content type="html" xml:base="https://www.sheehanahmed.com/dark-souls-is-a-cozy-game/"><![CDATA[<p>At the level of critical oversaturation a game like <em>Dark Souls</em> has reached, you are legally required to have something actually new to say about it. Otherwise, FromSoft sends a real-life version of one of those Depths frog-things to your house. It’s true, I heard it from my friend on the playground!</p>

<p><img src="/assets/images/posts/03_depths-frog.png" alt="" class="align-center" /></p>

<p class="text-center"><em>If you know, you know…</em></p>

<p>Among the sea of endless debate about whether or not the franchise should have easy modes, ‘Dark Souls being a cozy game’ is a fresh take – most likely because of how absurd it sounds. To be clear, I’m not “<em>Die Hard</em> is a Christmas movie”-ing this. This isn’t a categorization purely for categorization’s sake, and there’s valuable takeaways for game developers about the nature of ‘cozy gaming’ that I can only reach after you’re with me on the “<em>Dark Souls</em> is one” thing.</p>

<p>The first trapping to let go of is the idea that a game’s visuals, aesthetics, or mechanics directly influence the categorization, because they don’t. It sounds ridiculous when laid out like this — how can the individual components of an artwork have no bearing on the genre? Well, we don’t treat genres of any other mediums like this. Movies aren’t categorized into genres by the content of their individual scenes. <em>Mad Max</em> and <em>The Batman</em> both have car chase scenes, but one is a dystopian action film while the other is a crime drama. These individual scenes can contribute to the genre, but only when considered in the context of the overall work.</p>

<p>So… what do we categorize by? Consider how people refer to genres: “I’m feeling sci-fi today.” People gravitate to certain genres by virtue of a shared set of emotions. Adventure films may look completely different from one another, but they all give you the feeling of a swashbuckling journey through uncharted territories. If emotions are colors, you can think of genres as paintings that share a palette, even if they depict wholly different subjects.</p>

<p>Regardless of other mediums, many would be skeptical that such a reading fits games, which are often defined entirely by their core mechanic. In reality, I find that designers talk about games like this much more often than games audiences do. It’s quite difficult to think of a single emotionally compelling game wholly defined by a singular mechanic. Games are a rich combination of emotive materials just like any other art-form, and the idea of a singularly defining “core” mechanic is outdated. We regularly observe games reuse many similar mechanics to their contemporaries without falling into the same genre. <em>Metal Gear</em>, <em>Fallout</em>, <em>Call of Duty</em> and <em>Bloodborne</em> all involve firing a gun but nobody would confuse any of these works as remotely similar.</p>

<p><img src="/assets/images/posts/03_you-are-a-cozy-game.png" alt="" class="align-center" width="75%" /></p>

<p class="text-center"><em>You’re a cozy game! No, you’re a cozy game!</em></p>

<p>I would be remiss to not mention the gendered element of the ‘cozy’ moniker, which is often associated with games made for a predominantly female audience. I think this topic is large enough for another discussion entirely, so I will link this great video essay by Graythorn about how games with similar mechanics are taken seriously or not based on superficial traits: <a href="https://youtu.be/FfkinrTljh8">The Worst Double Standard in Gaming</a></p>

<p>Where do we start then in defining what a “cozy” game is? Let’s begin with the word itself. Cozy isn’t exactly the same as comfortable; you are cozy when sitting by a campfire in a dark forest, or huddled up in your bed during the deep of a winter night. The word implies a sublime quality, a danger lurking just beyond where the speaker is. Cozy is a safe space in the midst of a dangerous world.</p>

<p><img src="/assets/images/posts/03_jp-box-art.png" alt="" class="align-center" width="75%" /></p>

<p>This is the original Japanese box artwork for <em>Dark Souls</em> before we Americans got that dreadful localized version. We see the player avatar — the Chosen Undead — sit beside a bonfire, which restore the players resources and act as brief moments of respite during the endless dungeons of the game world. After enduring whole sections of pure danger and despair, catching a glimpse of a bonfire’s red-orange flames fills the player with a feeling of incomparable safety and calm. Here lies the key emotive core of <em>Dark Souls</em>: chipping away at a desolate and scary world, one bonfire at a time. This is a game filled with dragons, wizards, horrific beasts and gorgeous 100-foot-tall goddesses! Nevertheless, the protagonist resting by a bonfire is the image FromSoft chose to represent <em>Dark Souls</em>.</p>

<p>Coziness.</p>

<p>But wait, you say, <em>Dark Souls</em> is mostly a tense action game with role-playing elements that is famous for brutal difficulty. The individual moments of sitting at bonfires don’t add up to more than a single percent of a playthrough. How can this possibly be a fair assessment of the game’s emotional goal?</p>

<p>I find the ‘mathematical’ approach to categorizing games completely ineffective, especially because audiences never talk about other mediums like this. Imagine if people identified <em>Transformers</em> as a more of a war movie than <em>Zero Dark Thirty</em> by the number of explosions. Great art spends most of its time setting up the big, hard-hitting moments that affect you emotionally. Again, a game’s genre is a product of the strongest emotions it makes you feel, not the mechanics or aesthetics it employs in the process.</p>

<p>Putting the final nail in the cozy coffin involves separating <em>Dark Souls</em> from the genre it’s traditionally associated with, Action. For how popularly it’s grouped in with action games, <em>Dark Souls</em> shares incredibly little with those contemporaries. The other standouts serve to empower the player with a unique moveset representative of the game theme. Dante from the <em>Devil May Cry</em> series utilizes a plethora of absurd weaponry to fulfill your fantasy of being a stylish demon hunter, while <em>Bayonetta</em>’s titular heroine makes you feel like an elegant dancer as she pirouettes from one racy move to the next. <em>Dark Souls</em>… has none of this. A typical playthrough involves stepping up and poking an enemy before backing off to safety, over and over until the job is done. It’s hardly engaging by itself, made all the more clear by FromSoft’s other game <em>Sekiro</em>, which has a dexterity focus and plays fundamentally differently than any of their other work.</p>

<p><img src="https://media.tenor.com/kVJsZ23iUmIAAAAd/dark-souls-matthewmatosis.gif" alt="" class="align-center" width="75%" /></p>

<p class="text-center"><em>“This right here is 90% of Dark Souls combat,” I say with love.</em></p>

<p>No, <em>Dark Souls</em> is not special because of the intricacies of the combat system, much to the demise of copycats who incorrectly identified this as the secret sauce. Slow animations and hard-hitting enemies are deliberate design choices, yes, but they are supporting elements rather than fundamental. Souls series director Hidetaka Miyazaki famously claimed he never prioritized creating a ‘difficult’ game. This sounded absurd at the time, but it makes more sense after playing the game and especially after playing other clearly inspired projects that miss the mark. <em>Dark Souls</em> doesn’t brutalize you as much as it wants you to succeed and feel the warmth of the next checkpoint. The fact that the multiplayer systems have a clear bias towards cooperative play rather than antagonistic PvP play bolsters this notion. The harshness of the world simply serves as the dark forest around that campfire.</p>

<p>We assume <em>Dark Souls</em> is a difficult game because we associate death to be a failure state, but the assumption that mechanics foster the same emotions in every game requires rethinking. Death in <em>Dark Souls</em> initially feels punishing because it takes away so much: the progress you’ve made in the level, and your souls, the de-facto points system of the game. Despite this, <em>Dark Souls</em> forces death upon a new player so frequently that they learn to stop fearing it, and can even see it as a tool. In my first playthrough, I often went on ‘scouting runs’ where I had zero souls in order to survey a location and gather information. Death acted as a safety net, as it would simply bring me back to the bonfire without punishment. The repeating content doesn’t hurt when most levels in the game are quite short anyway, having a span of maybe 3-5 minutes of in-game walking. Most if not all non-boss encounters can be skipped by a clever use of running and jumping, turning the game into a faux-platformer. <em>Dark Souls</em> doesn’t use death to punish you; rather, it gently teaches you that death is just another tool in your arsenal to conquer the content. Ask any player who has reached completion and they will no doubt tell you that the deaths eventually stopped hurting as much as they did in the introductory hours. As you expand your exploration of Lordran out from the home-base of Firelink Shrine, more and more of the map begins to feel safe, like building little campfires out from your first one. The game is expertly produced to leave just enough room between bonfires that each and every one feels like a treat to find. This brilliant emotional pacing is the true champion of <em>Dark Souls</em>’ design rather than the surface level mechanical rules of the combat engine.</p>

<p>In the mainstays of the cozy genre, we see the necessary darkness shine through and through. There’s a haunting quality to the quietness of Animal Crossing’s world. Your villagers in AC can leave anytime, giving you a short warning when they rest by a campfire, taking memories and gifts with them. Stardew Valley gets scary after night falls, and the entire narrative has an ever encroaching megastore that threatens to replace the local businesses. Losing a harvest in Stardew is arguably worse than ten deaths in <em>Dark Souls</em>. This darkness and loss is more than important – it is integral to the cozy feeling.</p>

<p>If you don’t want to hear it from me, hear it from Katie Chironis, senior designer at Riot Games:</p>

<blockquote class="twitter-tweet" data-theme="dark"><p lang="en" dir="ltr">in both series there is a lot of wistfulness, sadness, even anger from some characters and elements (Mr. Resetti! Grouchy villagers!) because these are the things that lend believability to the fantasy, which makes it more compelling. can&#39;t make cake without a little salt, right?</p>&mdash; Katie Chironis (@kchironis) <a href="https://twitter.com/kchironis/status/1673367613244850176?ref_src=twsrc%5Etfw">June 26, 2023</a></blockquote>
<script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<p>I watched the Wholesome Direct this year and it pained me to see what looked like several cozy-likes fall into the same trap that souls-likes did from the opposite direction. Where souls copycats ignore any sense of safety and home to create a cruel, brutal and ultimately forgettable combat simulator, cozy-likes fear any negativity in their perfect candy simulation as if it would infect the happiness. Both of these groups of developers could stand to more closely examine the games they so clearly love and feel the warmth of their tones brave against the cold of their moods. Sheer positivity without any counterbalance risks a cynicism greater than that of the apocalypse.</p>

<p>I don’t wish to cast judgment on artists creating what they want to create, but if your express goal is to make a “cozy” game, you have to be aware of what that word means and what makes games of the genre compelling. As a person who credits <em>Dark Souls</em> with helping my journey in overcoming mental illness, I think cozy games are some of the most important games to be making right now. In a world marching towards destruction on so many fronts, we need games that let people feel warmth. However, for this warmth to be genuine, for it to have any staying power in our hearts, it can’t exist without acknowledging the dark forest surrounding us first. Don’t be afraid to fill your dark forest; just don’t forget to give your players the tools to chop down the mighty trees and fuel their campfires.</p>

<p>Or, if you’ve gotten this far and want nothing more to do with cozy games, please try my favorite hardcore survival-horror strategy game, Pikmin.</p>

<p><img src="/assets/images/posts/03_pikmin.gif" alt="" class="align-center" /></p>

<p>Special thanks to  <a href="https://www.linkedin.com/in/bethanylai/">Bethany Lai</a>, <a href="https://www.westonb.dev/">Weston Bell-Geddes</a> &amp; <a href="https://www.jebbyzhang.com/">Jebby Zhang</a> for helping me edit this post.</p>

<p>Follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.</p>

<hr />]]></content><author><name>Sheehan Ahmed</name></author><category term="game-design" /><category term="narrative" /></entry><entry><title type="html">Recommended Naming Conventions</title><link href="https://www.sheehanahmed.com/recommended-naming-conventions/" rel="alternate" type="text/html" title="Recommended Naming Conventions" /><published>2023-07-26T19:35:00-07:00</published><updated>2023-07-26T19:35:00-07:00</updated><id>https://www.sheehanahmed.com/recommended-naming-conventions</id><content type="html" xml:base="https://www.sheehanahmed.com/recommended-naming-conventions/"><![CDATA[<h2 id="artwork--other-non-code-files">Artwork &amp; Other Non-Code Files</h2>

<h3 id="file-naming">File Naming</h3>

<p>The format for all filenames is: <code class="language-plaintext highlighter-rouge">PREFIX_[Group]_[Name]_SUFFIX</code></p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">PREFIX</code> defines the kind of asset.</li>
  <li><code class="language-plaintext highlighter-rouge">[Group]</code> defines which group in the project this asset belongs to. For example, if an environmental model, which level it appears in. Or, if an NPC model, whether it is an Enemy or a Player.</li>
  <li><code class="language-plaintext highlighter-rouge">[Name]</code> self explanatory. Name appropriately and descriptively.</li>
  <li><code class="language-plaintext highlighter-rouge">SUFFIX</code> for extra dilineation on the asset type.</li>
</ul>

<table>
  <thead>
    <tr>
      <th>Asset</th>
      <th>Prefix</th>
      <th>Suffix</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>(UNITY) Material</td>
      <td><code class="language-plaintext highlighter-rouge">MAT_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNREAL) Material</td>
      <td><code class="language-plaintext highlighter-rouge">MAT_</code></td>
      <td><code class="language-plaintext highlighter-rouge">Lit</code> or <code class="language-plaintext highlighter-rouge">Unlit</code></td>
    </tr>
    <tr>
      <td>(UNREAL) Material Instance</td>
      <td><code class="language-plaintext highlighter-rouge">MI_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Texture</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Texture (PBR Diffuse)</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td><code class="language-plaintext highlighter-rouge">_D</code></td>
    </tr>
    <tr>
      <td>Texture (PBR Normal)</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td><code class="language-plaintext highlighter-rouge">_N</code></td>
    </tr>
    <tr>
      <td>Texture (PBR Occlusion/Roughness/Metallic)</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td><code class="language-plaintext highlighter-rouge">_ORM</code></td>
    </tr>
    <tr>
      <td>Texture (PBR Emission)</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td><code class="language-plaintext highlighter-rouge">_E</code></td>
    </tr>
    <tr>
      <td>Texture (HDRi)</td>
      <td><code class="language-plaintext highlighter-rouge">TEX_</code></td>
      <td><code class="language-plaintext highlighter-rouge">_HDR</code></td>
    </tr>
    <tr>
      <td>Render Target / Render Texture</td>
      <td><code class="language-plaintext highlighter-rouge">RT_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Static Mesh</td>
      <td><code class="language-plaintext highlighter-rouge">SM_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Skeletal Mesh</td>
      <td><code class="language-plaintext highlighter-rouge">SK_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Post Process Profile</td>
      <td><code class="language-plaintext highlighter-rouge">PP_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Visual Effect</td>
      <td><code class="language-plaintext highlighter-rouge">VFX_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNREAL) Blueprint</td>
      <td><code class="language-plaintext highlighter-rouge">BP_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNITY) Prefab</td>
      <td><code class="language-plaintext highlighter-rouge">PF_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Config</td>
      <td><code class="language-plaintext highlighter-rouge">CONFIG_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Icon</td>
      <td><code class="language-plaintext highlighter-rouge">ICON_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNREAL) Map</td>
      <td><code class="language-plaintext highlighter-rouge">MAP_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNITY) Scene</td>
      <td><code class="language-plaintext highlighter-rouge">SCENE_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>(UNITY) Input System Controls</td>
      <td><code class="language-plaintext highlighter-rouge">CONTROLS_</code></td>
      <td> </td>
    </tr>
    <tr>
      <td>Shader (Lit)</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Lit</code></td>
    </tr>
    <tr>
      <td>Shader (Unlit)</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Unlit</code></td>
    </tr>
    <tr>
      <td>Shader Function (HLSL)</td>
      <td><code class="language-plaintext highlighter-rouge">HLSL_</code></td>
      <td> </td>
    </tr>
  </tbody>
</table>

<h2 id="code">Code</h2>

<h3 id="code-file-naming">Code File Naming</h3>

<table>
  <thead>
    <tr>
      <th>Class Catagory</th>
      <th>Prefix</th>
      <th>Suffix</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Standard</td>
      <td> </td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Thing.cs</code></td>
    </tr>
    <tr>
      <td>Abstract</td>
      <td><code class="language-plaintext highlighter-rouge">A</code></td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">AThing.cs</code></td>
    </tr>
    <tr>
      <td>Interface</td>
      <td><code class="language-plaintext highlighter-rouge">I</code></td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">IThing.cs</code></td>
    </tr>
    <tr>
      <td>Scriptable Object</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Data</code></td>
      <td><code class="language-plaintext highlighter-rouge">ThingData.cs</code></td>
    </tr>
    <tr>
      <td>Singleton Manager</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Manager</code></td>
      <td><code class="language-plaintext highlighter-rouge">ThingManager.cs</code></td>
    </tr>
    <tr>
      <td>Static Extensions</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Extensions</code></td>
      <td><code class="language-plaintext highlighter-rouge">Vector3Extensions.cs </code></td>
    </tr>
    <tr>
      <td>Asset Library</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Library</code></td>
      <td><code class="language-plaintext highlighter-rouge">ThingLibrary.cs</code></td>
    </tr>
    <tr>
      <td>Unit Tests</td>
      <td> </td>
      <td><code class="language-plaintext highlighter-rouge">Tests</code></td>
      <td><code class="language-plaintext highlighter-rouge">ThingTests.cs</code></td>
    </tr>
  </tbody>
</table>

<h3 id="code-naming">Code Naming</h3>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">enum</span> <span class="n">ExampleEnum</span>
<span class="p">{</span>
  <span class="n">ENUM_MEMBER_ONE</span><span class="p">,</span>
  <span class="n">ENUM_MEMBER_TWO</span><span class="p">,</span>
  <span class="n">ENUM_MEMBER_THREE</span>
<span class="p">}</span>

<span class="c1">// "A" prefix because abstract class</span>
<span class="k">public</span> <span class="k">abstract</span> <span class="k">class</span> <span class="nc">AExampleClass</span>
<span class="p">{</span>

  <span class="c1">// Arrange variables in this order: </span>
  <span class="c1">// Serialized &gt; Public Vars &gt; Public Properties &gt; Member Vars</span>

  <span class="p">[</span><span class="n">SerializeField</span><span class="p">]</span> <span class="kt">int</span> <span class="n">SerializedVariable</span><span class="p">;</span>
  <span class="k">public</span> <span class="kt">int</span> <span class="n">PublicVariable</span><span class="p">;</span>
  <span class="k">public</span> <span class="kt">int</span> <span class="n">Property</span> <span class="p">=&gt;</span> <span class="n">mMemberVariable</span><span class="p">;</span>
  <span class="kt">int</span> <span class="n">mMemberVariable</span><span class="p">;</span> <span class="c1">// Omit 'private' for private vars</span>
  <span class="kt">bool</span> <span class="n">mIsElectric</span><span class="p">;</span> <span class="c1">// Bools should be named in such a way that you can tell what true or false means implicity. IE: `mIsElectric` instead of `mElectric`</span>

  <span class="c1">// Arrange methods in this order:</span>
  <span class="c1">// Unity &gt; Protected &gt; Public &gt; Private</span>

  <span class="c1">// Unity functions should be private</span>
  <span class="k">void</span> <span class="nf">Update</span><span class="p">()</span>
  <span class="p">{</span>
    
  <span class="p">}</span>

  <span class="k">protected</span> <span class="k">virtual</span> <span class="k">void</span> <span class="nf">ExampleVirtualMethod</span><span class="p">()</span> <span class="p">{}</span>
  <span class="k">protected</span> <span class="k">abstract</span> <span class="k">void</span> <span class="nf">ExampleAbstractMethod</span><span class="p">();</span>

  <span class="k">public</span> <span class="kt">int</span> <span class="nf">GetValue</span><span class="p">(</span><span class="kt">int</span> <span class="n">parameter</span><span class="p">)</span>
  <span class="p">{</span>
    <span class="k">return</span> <span class="n">parameter</span> <span class="p">*</span> <span class="n">mMemberVariable</span><span class="p">;</span>
  <span class="p">}</span>

<span class="c1">// ----------------------------------------------------</span>
<span class="c1">// Run coroutines like this:</span>

  <span class="c1">// Reference to the running coroutine so starting again stops the old one.</span>
  <span class="n">IEnumerator</span> <span class="n">mDoTaskCoroutine</span><span class="p">;</span>

  <span class="c1">// Function to start coroutine</span>
  <span class="k">void</span> <span class="nf">DoTask</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="n">mDoTaskCoroutine</span> <span class="p">=</span> <span class="nf">CR_DoTask</span><span class="p">();</span>
    <span class="nf">StartCoroutine</span><span class="p">(</span><span class="n">mDoTaskCoroutine</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="c1">//Function of the coroutine itself, CR_ prefix</span>
  <span class="n">IEnumerator</span> <span class="nf">CR_DoTask</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">float</span> <span class="n">t</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">t</span> <span class="p">&lt;</span> <span class="m">1.0f</span><span class="p">;</span> <span class="n">t</span> <span class="p">+=</span> <span class="n">Time</span><span class="p">.</span><span class="n">deltaTime</span><span class="p">)</span>
    <span class="p">{</span>
      <span class="n">Debug</span><span class="p">.</span><span class="nf">Log</span><span class="p">(</span><span class="s">"The current time is "</span> <span class="n">Time</span><span class="p">.</span><span class="n">time</span><span class="p">);</span>
      <span class="k">yield</span> <span class="k">return</span> <span class="k">null</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>

<span class="c1">// ----------------------------------------------------</span>

<span class="p">}</span>

</code></pre></div></div>

<h2 id="folder-structure">Folder Structure</h2>

<p>Top-level folders are capitalized, all else lower are lowercase.</p>

<ul>
  <li>Art
    <ul>
      <li>example_category_static
        <ul>
          <li>material instance (UNREAL) / material (UNITY)</li>
          <li>model</li>
          <li>texture</li>
        </ul>
      </li>
      <li>example_category_skinned
        <ul>
          <li>animation</li>
          <li>avatar_masks</li>
          <li>material instance (UNREAL) / material (UNITY)</li>
          <li>model</li>
          <li>texture</li>
        </ul>
      </li>
      <li>…</li>
    </ul>
  </li>
  <li>Blueprints (UNREAL)</li>
  <li>Data</li>
  <li>Font</li>
  <li>Icon</li>
  <li>Maps (UNREAL) / Scenes (UNITY)</li>
  <li>MasterMaterials (UNREAL) / Shaders (UNITY)</li>
  <li>PostProcessProfiles</li>
  <li>Prefabs (UNITY)</li>
  <li>RenderTargets (UNREAL) / RenderTextures (UNITY)</li>
  <li>VisualEffects</li>
</ul>]]></content><author><name>Sheehan Ahmed</name></author><category term="Production" /></entry><entry><title type="html">Realtime Rain VFX Breakdown</title><link href="https://www.sheehanahmed.com/realtime-rain-vfx-breakdown/" rel="alternate" type="text/html" title="Realtime Rain VFX Breakdown" /><published>2023-07-09T19:35:00-07:00</published><updated>2023-07-09T19:35:00-07:00</updated><id>https://www.sheehanahmed.com/realtime-rain-vfx-breakdown</id><content type="html" xml:base="https://www.sheehanahmed.com/realtime-rain-vfx-breakdown/"><![CDATA[<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/S7RwDOuJ_ds" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<hr />

<p><em>Made with Unity, Blender, HLSL, ShaderGraph, VFX Graph</em></p>

<p>My friends in LA look at me like I’m crazy when I mention how much I love rainy overcast days. I can’t help it! I’m from the best place on Earth: the Pacific Northwest.</p>

<h3 id="gathering-reference">Gathering Reference</h3>

<p>Now usually if I wanted to take a close look at rain, I would just take a stroll outside, but Portland is having an unfortunately dry summer. I checked out some YouTube videos instead, like this one:</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/0en2q82Zaok" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<hr />

<p>There were some phenomena in the reference video that I chose to leave out, namely the bubbles and the base waves on the surface of the water. I wanted to shoot for a shallower puddle that still had the vibrancy of rain hitting a deep body of water. It’s not completely realistic, but then again, no great visual effect ever is!</p>

<p>My effect was made in Unity by layering several steps/passes:</p>

<ul>
  <li>Droplets (ortho camera depth pass -&gt; VFX graph spawn)</li>
  <li>Splashes (ortho camera depth pass -&gt; VFX graph spawn)</li>
  <li>Puddles (vertex color + height blend)</li>
  <li>Ripples (UV manipulation shader)</li>
  <li>Wash cascades (transparent mesh with scrolling shader)</li>
  <li>Mist (depth effect)</li>
</ul>

<h3 id="the-base-environment">The Base Environment</h3>

<p>The base environment is a simple blocky mesh that has areas of cover, elevated ground, and varied shapes (sphere and angled block). These represent the main environment problems I expected my rain to solve for. I wanted the splashes to follow the form of the environment and rain to ignore areas that should be covered and safe from rainfall.</p>

<p><img src="/assets/images/posts/02_base-environment.png" alt="BaseEnvironment" width="100%" /></p>

<h3 id="the-ortho-camera">The Ortho Camera</h3>

<p>Inspired by my friend Sichen Liu’s <a href="https://80.lv/articles/recreating-death-stranding-odradek-terrain-scanner-in-unity/">Odradek Scanner effect</a>, I created an orthographic camera facing down on the scene. I render out this camera’s depth and normals to a RenderTexture. These will come in handy later.</p>

<p><img src="/assets/images/posts/02_normal-depth.png" alt="NormalDepth" width="100%" /></p>

<h3 id="droplets">Droplets</h3>

<p>The droplets are as simple as can be: a LineRenderer in the VFX Graph that scales the lifetime of each line according to the depth of the ortho camera. This ensures that rain droplets don’t intersect the level and appear underneath cover.</p>

<p><img src="/assets/images/posts/02_raindrop-depth-sample-node.png" alt="Nodes" width="50%" /></p>

<p><img src="/assets/images/posts/02_raindrop-depth-sample-result.gif" alt="NormalDepth" width="50%" /></p>

<h3 id="splashes">Splashes</h3>

<p>Splashes are a spritesheet particle spawned along the ground with the particle-up facing the normal and the particle-forward facing the camera plane. During my process, I ran into issues with ledges. Splashes were spawning half-on a ledge and along the wall, which were immersion-breaking and ugly. To solve this, I run the orthographic camera’s depth through a Sobel filter, which essentially creates outlines around areas of depth contrast. Because the camera is facing downward, these areas of high contrast are my walls and ledges.</p>

<p><img src="/assets/images/posts/02_sobel.png" alt="Sobel" width="50%" /></p>

<p>By passing this mask into my VFX graph, I can make my splashes ignore walls. The outline has the added benefit of culling away a bit from the ledges, which solves the half-on-ledge problem as well.</p>

<p><img src="/assets/images/posts/02_splashes.gif" alt="Splashes" /></p>

<h3 id="puddles">Puddles</h3>

<p>The puddles and general wetness in the scene was painted on through vertex color and adjusted by sharpness and fill. This is also influenced by the heightmap of the underlying texture.</p>

<p><img src="/assets/images/posts/02_wetness.gif" alt="Wetness" /></p>

<h3 id="ripples">Ripples</h3>

<p>The typical approach for ripples on deep water is a voronoi-style pattern that creates overlapping circles. I wanted to make the ripples feel like they had more local impact, so I instead used a normal texture for the ripple.</p>

<p><img src="/assets/images/posts/02_ripple-texture.png" alt="Wetness" width="50%" /></p>

<p>In my shader, I sampled this texture with two layers of ripples. Having two layers on separate start times helps kill the grid-like look. I then simulate a ripple animation by scaling the UV of the ripples and fading the normal strength in and out with an ease curve based on the following function:</p>

<p><code class="language-plaintext highlighter-rouge">1.0 - pow(2.0 * input - 1.0, 2)</code></p>

<p>This function gives a nice ease to the ripple transparency that looks natural.</p>

<p><img src="/assets/images/posts/02_ripples-shader.png" alt="Ripple" /></p>

<p>I also use a population slider that basically compares a <code class="language-plaintext highlighter-rouge">floor(rand(uv))</code> sample against a threshold value to kill a certain percentage of ripples. A full set of ripples looks strangely uniform anyway, and this gives me control over how torrential I want the rainfall to look.</p>

<p><img src="/assets/images/posts/02_ripple-population.gif" alt="RipplePopulation" /></p>

<h3 id="wash-cascades">Wash Cascades</h3>

<p>I saw this effect on a pillar in Overwatch 2, and it felt like a nice, slightly stylized way to evoke water travelling down a lateral surface.</p>

<p><img src="/assets/images/posts/02_water-cascades-overwatch.gif" alt="OW2Cascades" /></p>

<p>I recreated it in a relatively simple way: generating a mesh at the side of every ledge that water would be washing down on and scrolling a voronoi + clouds texture along the Y of the UV.
Here is the effect with exaggurated visibility.</p>

<p><img src="/assets/images/posts/02_water-cascades.gif" alt="OW2Cascades" /></p>

<h3 id="mist">Mist</h3>

<p>Finally, the mist is scrolling noise with a depth mask in order to keep it closely hugging the ground.
Here is the effect with exaggurated visibility.</p>

<p><img src="/assets/images/posts/02_mist.gif" alt="Mist" /></p>

<h3 id="conclusion">Conclusion</h3>

<p>Thank you for reading my breakdown! This was a fun project that challenged me to use the information I had at hand in interesting ways. Making a tribute to my home state felt satisfying, and I feel well-equipped to use ortho-camera depth sampling techniques for various other effects.</p>

<p><img src="/assets/images/portfolio/swallow-falls-rain.gif" alt="FinalGif" /></p>

<p>Follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.</p>]]></content><author><name>Sheehan Ahmed</name></author><category term="C#" /><category term="HLSL" /></entry><entry><title type="html">Starting out with Flecs ECS in Monogame</title><link href="https://www.sheehanahmed.com/starting-out-flecs-monogame/" rel="alternate" type="text/html" title="Starting out with Flecs ECS in Monogame" /><published>2022-08-16T19:35:00-07:00</published><updated>2022-08-16T19:35:00-07:00</updated><id>https://www.sheehanahmed.com/starting-out-flecs-monogame</id><content type="html" xml:base="https://www.sheehanahmed.com/starting-out-flecs-monogame/"><![CDATA[<h2 id="leaving-the-unity-birdnest">Leaving the Unity birdnest</h2>
<p>I started game development when I was around ten using Clickteam Fusion to make simple platformers. After learning to code, I started using Unity, and have been a pretty loyal Unity dev ever since. All I’ve ever really known is coding within Unity’s paradigm of GameObjects and C# and whatnot. I figured it was time to try something new.</p>

<h3 id="what-to-pick">What to pick?</h3>
<p>To maximize the learning opportunity I wanted to use a lower-key framework, something that leaves most of the game loop to me.</p>

<p>I ended up settling with Monogame, as coming from Unity I’m most used to C# anyway. Monogame (prev. XNA) is very battle-tested: several high-profile indie games use it, like <a href="http://www.celestegame.com/"><em>Celeste</em></a> by <a href="https://exok.com/games.html">Extremely Ok Games</a>, <a href="https://terraria.org/"><em>Terraria</em></a> by <a href="https://re-logic.com/">Re-Logic</a> and <a href="https://www.stardewvalley.net/"><em>Stardew Valley</em></a> by <a href="https://twitter.com/ConcernedApe">Eric Barone</a>.</p>

<p><em>Other choices I researched and did not go with, but you might!</em></p>
<ul>
  <li><strong>Godot</strong>: Although I appreciate how much more it stays “out of your way” than other engines, it still feels too much for my current wants.</li>
  <li><strong>Bevy</strong>: Young but I am keeping an eye on this one!</li>
  <li><strong>SDL2/C++</strong>: I use C++ all the time at school anyway so wanted to do something else when I’m coding for fun.</li>
</ul>

<h3 id="first-date-with-monogame-a-little-nervous">First date with Monogame, a little nervous</h3>
<p>It seems like a pattern I have for any new engine/framework is… <strong>try to make Cave Story with it</strong>. It was the first thing I tried to do with Clickteam Fusion, Unity, and now Monogame.</p>

<p><img src="/assets/images/posts/01_my-first-mono-screenshot.png" alt="MyFirstMonoScreenshot" width="100%" /></p>

<p>Getting Quote on the screen with relatively basic platformer controls is at this point my personal <em>Hello World</em>. I have to admit, I was intimidated at first by having no software to go off other than good old VSCode, but after this point most of my anxieties disappeared. I love the freedom of having the framework give me just the bare essentials: an entry point, a <code class="language-plaintext highlighter-rouge">Tick()</code>, and a <code class="language-plaintext highlighter-rouge">Draw()</code>.</p>

<p>I was about to type out a familiar Actor.cs class… when the freedom really went to my head. After all, I’ve been doing the traditional pattern my whole life. While everyone else was going out and partying and hating OOP last year, I had to stay inside with my GameObjects and MonoBehaviours. It’s not a phase, Mom!</p>

<p><img src="/assets/images/posts/01_friendship-ended-with-actor.png" alt="FriendshipEnded" width="100%" /></p>

<p>Which brings us to…</p>

<h2 id="in-my-ecs-era">In my ECS Era</h2>

<p>I watched <a href="https://youtu.be/zrIY0eIyqmI">that one Overwatch GDC Talk about ECS</a> and was immediately sold. One of my absolute favorite aspects of game design is <strong>consistent systems.</strong> I love when every actor in a game, from the player to enemies to even random little objects, have to obey the same rules of the universe.</p>

<p>These personal core values of game design is basically a match made in heaven with ECS, a programming paradigm based on “declarations (there shall be this)” instead of “imperative statements (do this)” (<a href="https://ajmmertens.medium.com/why-vanilla-ecs-is-not-enough-d7ed4e3bebe5">Martens</a>). To use an example from <em>Breath of the Wild</em>: I want to tell my game that fire catches on wood, <strong>no matter the circumstance of fire catching wood</strong>, whether I am lighting an arrowhead on a bonfire or hurling magic fireballs at an enemy Bokoblin’s wooden shield.</p>

<h3 id="what-is-ecs-anyway">What is ECS, anyway?</h3>

<p>There are a lot of in-depth explanations for ECS online – one of the best ones is an Overwatch GDC talk linked above – so I’ll try to distill the essence of it briefly in three main points.</p>

<ul>
  <li>1: Your game consists of a list of <strong>Entities</strong>. These have no individual data/functionality but contain a list of <strong>Components</strong>.</li>
  <li>2: These <strong>Components</strong> are SIMPLY data-objects with zero functionality. Nothing more.</li>
</ul>

<p>So far, this may sound familiar to Unity or Unreal users, but the <em>zero functionality</em> is a key difference. No mutations, no logic, nothing. Imagine if MonoBehaviours or ActorComponents were only allowed to have public variables. All of your functional code is instead delegated over to…</p>

<ul>
  <li>3: Sets of <strong>Systems</strong> iterate over components that they care about and read/write accordingly.</li>
</ul>

<p>This is the crux of ECS software design: complete separation between data and logic. Entities only care about what components they have and components only care about what data they hold. It’s all up to systems to determine the flow of change in your software, and an ordered set of systems is massively easier to keep in check when compared to the typical OOP slew of side-effects. The “I want to damage my enemy, but sometimes they can block it, but sometimes I have armor-piercing”-type situations we’ve all been in suddenly feel a lot more manageable.</p>

<p>For my project I decided to go with <a href="https://github.com/flecs-hub/flecs-cs">a C# wrapper for Flecs</a>.</p>

<blockquote>
  <p>NOTE: Original Flecs is written in C/C++. The wrapper adapts C source code to be usable by C# projects.</p>
</blockquote>

<h3 id="paradigm-shift">Paradigm shift</h3>

<p>ECS sounds very cool at first, but once you start trying to create components and systems it can be a bit daunting of how to proceed appropriately. Researching the Flecs user manual helped me understand a lot of the unique aspects and challenges of ECS. I decided on a new Hello World: Have at least 5 digits of entities carrying this image of Pochita from <em>CHAINSAW MAN</em> bounce around in an enclosed square.</p>

<p><img src="/assets/images/posts/01_pochita.png" alt="Pochita" width="50%" /></p>

<p>The best way to start planning an ECS project is to boil down entities to as granular components as possible. My first hunch was that I would need a Transform component, but this is actually incorrect as per a <a href="https://flecs.docsforge.com/master/designing-with-flecs/#component-size">direct example from the Flecs manual.</a> A Transform is already not granular enough, because it could be boiled down further into Position, Rotation, and Scale. This atomic way of thinking about data allows systems to <em>only</em> bother the data that they need to, instead of having to manage potential side effects of entire OOP classes. I’m excited by the possibilities but recognize that this will take a lot of getting used to.</p>

<p>For my Pochita game, I figured I would need the aforementioned Position, Rotation, and Scale components, as well as two more components: Velocity and Sprite. The components themselves are kind of self-explanatory, especially since they literally do not contain anything other than their named value. For example, Position is just:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">struct</span> <span class="nc">C_Position</span> <span class="p">:</span> <span class="n">IComponent</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="n">Vector2</span> <span class="n">Position</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>That’s literally it. It’s very freeing that in terms of data, ECS is very WYSIWYG (What you see is what you get) about things. Data is granular and gives you exactly what you expect. Nothing less, nothing more.</p>

<p>My Sprite component ended up being more verbose, but mostly due to helper functions and some data I assume will never be used outside of the context of a sprite draw. If I need to refactor it into multiple components later, it should be easy enough to do so.</p>

<blockquote>
  <p>TIP: When using Flecs, REMEMBER that every component needs to be registered at the beginning of the program.</p>
</blockquote>

<p>Systems are defined as functions that can get subscribed into the Flecs ecosystem. They take an <code class="language-plaintext highlighter-rouge">Iterator</code> as a parameter and use their <code class="language-plaintext highlighter-rouge">Field&lt;T&gt;</code> function to generate several <code class="language-plaintext highlighter-rouge">Span&lt;T&gt;</code> that allow the developer to access specified components. Here is the system that applies velocity to position.</p>

<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">ApplyVelocityToPosition</span><span class="p">(</span><span class="n">Iterator</span> <span class="n">it</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">deltaTime</span> <span class="p">=</span> <span class="n">it</span><span class="p">.</span><span class="nf">DeltaSystemTime</span><span class="p">();</span>     <span class="c1">// Gather deltaTime. I contributed this method! :D</span>
    
    <span class="kt">var</span> <span class="n">posIter</span> <span class="p">=</span> <span class="n">it</span><span class="p">.</span><span class="n">Field</span><span class="p">&lt;</span><span class="n">C_Position</span><span class="p">&gt;(</span><span class="m">1</span><span class="p">);</span>      <span class="c1">// Initialize span for position</span>
    <span class="kt">var</span> <span class="n">velIter</span> <span class="p">=</span> <span class="n">it</span><span class="p">.</span><span class="n">Field</span><span class="p">&lt;</span><span class="n">C_Velocity</span><span class="p">&gt;(</span><span class="m">2</span><span class="p">);</span>      <span class="c1">// Initialize span for velocity</span>

    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">it</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>           <span class="c1">// Loop thru every entity in the game</span>
    <span class="p">{</span>
        <span class="k">ref</span> <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="k">ref</span> <span class="n">posIter</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>           <span class="c1">// Position component MUST be mutated so use 'ref' keyword</span>
        <span class="kt">var</span> <span class="n">vel</span> <span class="p">=</span> <span class="n">velIter</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>                   <span class="c1">// Velocity component is simply being read</span>

        <span class="n">pos</span><span class="p">.</span><span class="n">Position</span> <span class="p">+=</span> <span class="n">vel</span><span class="p">.</span><span class="n">Velocity</span> <span class="p">*</span> <span class="n">deltaTime</span><span class="p">;</span> <span class="c1">//Add velocity*dt to position</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As you can see, this loops through every entity to handle a system all at once. It’s not difficult to imagine the possibilities for how this simplifies things. You have access to every other entity that a particular system concerns, right in the business-logic code for that system!</p>

<p>Subscribing the function as a system to ECS looks like this:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">World</span><span class="p">.</span><span class="nf">RegisterSystem</span><span class="p">(</span><span class="n">S_Physics</span><span class="p">.</span><span class="n">ApplyVelocityToPosition</span><span class="p">,</span> <span class="n">EcsOnUpdate</span><span class="p">,</span> <span class="s">$"</span><span class="p">{</span><span class="k">typeof</span><span class="p">(</span><span class="n">C_Position</span><span class="p">)}</span><span class="s">, </span><span class="p">{</span><span class="k">typeof</span><span class="p">(</span><span class="n">C_Velocity</span><span class="p">)}</span><span class="s">"</span><span class="p">);</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">EcsOnUpdate</code> describes the phase this will participate in. the <code class="language-plaintext highlighter-rouge">$"{typeof(C_Position)}, {typeof(C_Velocity)}"</code> describes the query that will occur to search through entities to update. It specifies to only bother Entities with <code class="language-plaintext highlighter-rouge">C_Position</code> and <code class="language-plaintext highlighter-rouge">C_Velocity</code> components with this system.</p>

<p>This is pretty beautiful because this system will apply to any entity that has both a position and velocity without asking any other questions about them. Also, the data being structs means I have to explicitly state when variables are to be mutated or not, using the ref keyword.</p>

<p>The 4 total systems are: ApplyVelocityToPosition, BounceOffWall, RotateTowardVelocityDirection, and PendSpritesForDraw. All of them were pretty straightforward to write and do <em>exactly</em> what their name implies with zero side-effects.</p>

<p><img src="/assets/images/posts/01_thousand-pochitas.png" alt="Thousand Pochitas" /></p>

<h3 id="visualizing-ecs-data-with-dear-imgui">Visualizing ECS data with Dear IMGUI</h3>

<p>With ECS as data-driven as it is, I would like to have a way to visualize all of my entities with their components. I decided to start with a simple approach that could grow to be more complex later. For my visualization I went with the <a href="https://github.com/mellinoe/ImGui.NET">.NET Wrapper</a> for Dear IMGUI. <a href="https://flatredball.com/news/dear-imgui-integration/">This tutorial from FlatRedBall</a> was really helpful to figure out how to do this.</p>

<p><img src="/assets/images/posts/01_dear-imgui.png" alt="Dear IMGUI Integration" /></p>

<p>So far it’s just a simple setup with an entity browser that allows the user to dynamically change values at runtime. It doesn’t have any features for adding/removing components or entities as of now.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The final result (for now) is 10,000 Pochitas bouncing around a virtual box. I can move the camera around or change any of their positions/velocities as I wish.</p>

<div class="youtube-wrapper">
  <iframe src="https://www.youtube.com/embed/Jxl7S-t2Azc" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<h3 id="next-steps">Next steps</h3>

<p>So far all I have used Flecs ECS for is a simple physics simulation. I’m sure trying to make a character-based platformer video game like <em>Cave Story</em> will have its own set of challenges, like handling different kinds of queries, using tags, and component relationships. I’ll see how it goes and continue to publish my progress here.</p>

<p>For the IMGUI visualizer, there are a couple next steps I have in mind.</p>
<ul>
  <li><strong>Entity focuser</strong>. A toggle that targets the camera at a specific entity’s position.</li>
  <li><strong>Entity browser filter by component.</strong></li>
  <li><strong>Hierarchy based on Flecs relationships.</strong></li>
</ul>

<p>Thanks for reading! School starts in a couple days but I’ll try to continue this side project as well as I can.</p>

<p>Follow me <a href="https://twitter.com/wheatpenguin">@wheatpenguin</a> on Twitter.
Check out <a href="https://github.com/sahmed19/ProjectMono">this project repo on Github.</a></p>]]></content><author><name>Sheehan Ahmed</name></author><category term="Monogame" /><category term="C#" /><category term="ECS" /></entry></feed>