Game Engineer @ Monomi Park
Programmer for A Hat in Time: Vanessa's Curse
@ Lady_Jazzrabbit on twitter
Profile Picture by Lumivos


So the other day, I saw this post from @easrng about a way to embed iframes in posts, so I thought it might be fun to try it out with one of my old games, and maybe do a short writeup on it. So this is my game, Tap Shot!
(Warning: Does play audio kind of loud, but you can press the sound and music buttons to stop it. Only tested on Chrome desktop)

This can also be found here on my website or on the google play store here.
Tap Shot is basically a single button side-scrolling arcade shooter with a modern mobile-friendly design. You control a spaceship that will both ascend and shoot every time you tap/click on the screen or press space. Protect your ship by tapping to avoid asteroids, enemy ships and wormholes. Shoot down the obstacles in your way and collect gems to gain the highest score that you can!
Tap shot was made a few years ago in under 48 hours as part of 2 game jams that happened to be running concurrently, which were the Miz Jam and the Kenney Jam 2020. The goal of both of those was to make a game using this specific premade art pack from kenney.nl It was mainly designed for Android, but I also set it up to run on windows and web browsers as well.
I basically made this game because I like the control/feel of flappy bird, but kind of don't actually the game itself very much, so I thought it would be fun to do my own twist on it. I thought combining it with a simple arcade shoot-em-up would have a lot of synergy in the gameplay. I replaced the score system, so it now tracks points based on collecting gems and shooting rocks, instead of tracking how far you got, which I'm not sure was the best idea, because people got a bit confused about that.
The obstacles are randomly generated, and come in waves, so they actually get harder as your score goes up. Although, I was pretty limited on time, so once your score gets over around 100, it will stop scaling, and just do the same wave forever. Although, I did also add a secret that gets unlocked once you get over 100 that does change the gameplay a bit. :)
I do think there is a lot of things I would do differently if I remade it today, but I'm pretty happy with how it turned out for only 48 hours of work! Having to work with a premade art kit was nice, because it meant I didn't have to worry about art, but added an extra challenge, since it made me try to think of a way to use the art in a creative way. I definitely want to try to do more game jams in the future, and would heavily recommend game jams as a place to get some experience for people interested in game development. 🙂


Corncycle
@Corncycle

Hello! I've been seeing more and more posts using a trick that I've seen called "width-hacking", so I thought it could be nice to have a post describing it in depth for reference, as well as to introduce it to people who don't know about it yet!

This post is aimed at people who know at least the basics of css, but don't really know how to make interactive posts with cohost's restrictions in particular.

This post has unfortunately reached "blog post" length, so you might have to settle in if you're planning to read through the whole thing. Alternatively, you could skim through this post over the course of about one minute and play around with the demos. That's probably what I would do.


In this post I'll build up from one simple but clever technique to show you how to make the following demo, which emulates 2d grid movement:

content

The goal of this post is not only to show you "how to make grid movement in cohost", but also to give a demonstration of how width-hacking works. Grid movement happens to be an illustrative example of width-hacking, but you can do so, so much more with it.

With width-hacking, you'll find that something like the demonstration above can be even be written by hand (indeed, this demo was handwritten), so imagine how powerful this technique can be if we generate HTML programatically! Let's get into it!


Consider the following basic markup structure:

<div style="width: 300px"> <span>I'm span1!</span> <span>I'm span2!</span> </div>
I'm span1! I'm span2!

Sibling relationships of HTML elements like the one between span1 and span2 are common, but it is difficult to make these two elements "communicate" with the tools we have in cohost's toolbox.

If we put more words inside of span1 to increase its width, span2 will move to the right to accomodate the new dimensions of span1, but that's about the extent of what we can do to make span1 influence span2. Even worse, there are probably very few things we can do to span2 that will cause span1 to change because it comes after span1.

The main tools we have besides "elements pushing each other around" that cause interesting interactions are <details> tags and css' calc() function. I'm not going explain the basics of <details> tags so you should read the mdn web docs page on them if you're unfamiliar because we'll be relying on them heavily later. To see how to use calc(), I've changed the style of span1 in the following example:

<div style="width: 300px"> <span style="display: inline-block; width: calc(50% + 20px)">I'm span1!</span> <span>I'm span2!</span> </div>
I'm span1! I'm span2!

We need to set the "display" of span1 to "inline-block" because the "width" property is ignored for elements with "display: inline", which <span>s have by default. Then, we set the width of span1 to calc(50% + 20px). Here, 50% refers to the width of span1's parent container, which is the <div> with a width of 300px. All together, this means that the width of span1 becomes 0.5 * 300px + 20px which ends up being 170px.

This example may not seem particularly exciting, but calc() completely blows the door open for us by allowing us to have two <details> tags interact with each other, as well as other elements, even if neither of them contains the other. Let's see how with a more complicated example.


Because inline styling of elements becomes unwieldy very quickly, I am going to use css class syntax for readability in the rest of these examples. Please recognize, however, that everything is implemented with inline styling.

As an example, I may write something like the following in a demonstration:

<div class="someClassName">I'm a div!</div>
.someClassName { width: 100px; height: 100px; margin-left: 100px; margin-top: 100px; }

But if you were to put this markup in a chost, it would need to be implemented as follows:

<div style="width: 100px; height: 100px; margin-left: 100px; margin-top: 100px">I'm a div!</div>

Hopefully you agree that the first way is more readable! Now, to begin making the demo I showed at the beginning, let's just focus on making 1-dimensional moment for now. We'll start really simple, placing only the following elements:

  • A div that I'll refer to as memory. This div serves as the "main screen" for the game, and we'll see why I call it memory later.
  • A div that I'll refer to as the hero. This div is a child of memory and has its background set to the eggbug image that the user will be able to move around.
  • An unstyled details tag. It has as children a <summary> tag positioned absolutely near the bottom of the screen (this is what the user clicks on to open the details element, so I'll call it button), as well as a <div> with its width set to 1px which I'll call "oneWide."
<div class="memory"> <div class="hero"></div> <details> <summary class="button"></summary> <div class="oneWide"></div> </details> </div>
.memory { display: inline-flex; position: relative; padding-right: 160px; padding-bottom: 120px; background: turquoise; } .hero { position: absolute; left: 0; top: 0; width: 40px; height: 40px; background: url('https://staging.cohostcdn.org/attachment/7eadd433-1902-4521-b1b8-e23738a8112c/eggbug.png') 0% 0% / 100% 100%; } .button { position: absolute; top: 70px; left: 90px; width: 20px; height: 20px; background: plum; } .oneWide { width:1px; }

This produces the following:

Most of this styling is just to position the elements and specify their dimensions, but I'll go over the styling that is a bit more subtle.

  • Setting "display: inline-flex" on memory essentially means that it will only be as wide and tall as it needs to be to fit its contents that are not absolutely positioned. Since hero is absolutely positioned and the <details> has width and height equal to 0 by default, this means that the width and height of memory are both 0.
  • "What do you mean memory has a width and height of 0? I clearly see memory right there. It's that gigantic turquoise rectangle."
    Here, width and height refer to the content of memory. Sometimes when I talk about the width and height of an element, I'll be referring to its contents, and sometimes I'll be referring to the container overall. I'll try to be unambiguous in my wording but it's something you will have to pay attention to. The turquoise rectangle you see is because memory has its "padding-right" set to 160px and its "padding-bottom" set to 120px. If you inspect memory with your browser's dev tools, you'll see that indeed it has a width of 0 and a height of 0.
    Here's what that looks like in Chrome's dev tools
  • The other important bit of styling is setting "position: relative" on memory and "position: absolute" on hero and button. Setting "position: relative" on memory basically says to its children, "Hey, if you have 'position: absolute', then if you set any of the properties 'top', 'left', 'bottom', or 'right', it will be relative to me!". Therefore when we set "left" and "top" in hero and button, these positions are set relative to the top left corner of memory.

Now I'm sure you've been clicking button incessantly in the demo above. If you look closely, you'll see that memory becomes 1 pixel wider when you open the details tag, and 1 pixel smaller when you close it. This is because of the oneWide element: when the details tag is open, its dimensions are as large as its non-absolute contents (this consists of only the oneWide element, so its dimensions become 1px by 0px). Then, memory stretches to be as big as it needs to be to accommodate these new contents, also becoming 1px by 0px. This is in addition to the padding we set earlier.

Now we can finally make something interesting happen; I am going to make exactly one change to the demo above, changing "left: 0" to "left: calc(40 * calc(100% - 160px))" for hero. I'll demystify that expression in a moment, but check out what happens when you click button now.

Let's break that down. By default, memory is a container that has an overall width of 160px. Thus, when we compute "calc(100% - 160px)", this reduces to 160px - 160px, which is just 0. Further multiplying by 40 still gives 0, so the "left" property of hero is 0 by default, just like before.

However, when the details tag is open, we know that this increases the overall width of memory to 161px. Now computing "calc(100% - 160px)" reduces to 161px - 160px, which is 1px. But there is still more computation to do: calc(40 * 1px) finally reduces to 40px, so when the details tag is open, the "left" of hero is set to 40px.

To summarize, setting "left: calc(40 * calc(100% - 160px))" on hero essentially makes hero say "I will move 40px to the right every time I see the width of memory increase by 1." I like to think of the width of the content of memory as storing a "variable" that we can refer to from other elements, hence the name "memory". Every time you see the expression "calc(100% - 160px)", you should really read this as "access the variable we have stored in memory."

This setup makes it incredibly easy to move hero multiple times: we just need to copy and paste our details tag repeatedly (and edit the positions of the summaries so that they aren't all in the same location). That's the only thing I've done to get to the following demo:

Here's the markup for it. The only styling that changed for the various buttons is their "left" property, just to make sure they're in distinct locations. Aside from that, everything is exactly the same as before.

<div class="memory"> <div class="hero"></div> <details> <summary class="button1"></summary> <div class="oneWide"></div> </details> <details> <summary class="button2"></summary> <div class="oneWide"></div> </details> <details> <summary class="button3"></summary> <div class="oneWide"></div> </details> </div>

Every time you open a details tag in the above demo, you are making the content of memory 1 pixel wider, and hero is piggybacking off of that and moving 40 pixels to the right.

We have almost achieved 1-dimensional movement. The main thing that is missing is the fact that we want the user to always click in one location (right on the dpad) to move right, and in another location (left on the dpad) to move left. Currently, the user needs to remember which details tags are open and which are closed and click accordingly in order to move left or right, which is unintuitive and unwieldy.

To solve this problem, let's return to the case where we only had a single button. At the beginning, we can place this button at the right side of the dpad, because clicking there will open the details tag and move the hero to the right. Then, to move left we want to click that same details tag, which should now be located at the left side of the dpad.

If you've followed up to this point, you should know exactly how this was implemented. Just like how we wanted hero to move 40px to the right every time we opened a details tag, we want button to move 40px to the left every time we open a details tag. Thus, the only change that was required (besides adding in an element with the dpad image) was changing "left" for button from "90px" to "calc(90px - 40 * calc(100% - 160px))".

Now we can add all of the other buttons back, making sure that they start to the right of the original button and move to the left by 40px every time a details tag is opened.

Something you may notice about this demo is that if you click the buttons out of order, the buttons on the dpad may no longer behave as desired, ie clicking right on the dpad may move the hero left instead of right (why?). It is imperative that the user is only able to click on buttons when they lie over the left or right of the dpad.

There is a simple solution to ensure this: simply place some giant blocker-divs covering everything except the dpad so that the player cannot click buttons unless they currently lie over the dpad. Here's what that might look like (in a final product we would make these invisible)

There are many aesthetic changes we should probably make at this point. A very noticeable annoying behavior of this demo is that the width of the "screen" visibly changes by 1 pixel every time we move the hero. We would like the screen to be a consistent 160px by 120px, so one way we can fix this is by wrapping the entire thing in a div with the following styling:

.wrapperDiv { width: 160px; height: 120px; overflow: hidden; font-size: 0; }

This results in a cleaner, less "shaky", view of the previous demo:

From here, there are a few smaller tweaks we can make for a more pleasant experience. First, I suggest setting "cursor" to "pointer" on all of our buttons, so that the user knows when they can click on the dpad. We should also make all buttons and blocker divs invisible (I do this by removing the "background" property from all of them, but you can also play with the "opacity" property, or even just set the background to a completely transparent color). Last, I like to put a simple backdrop div behind the controller area to distinguish it from the rest of the screen. With that, we end up with the following gizmo!

We are actually already very close to 2d movement, and you might see how we can make it already! We're effectively storing the x coordinate of hero in the width of memory, why not store the y coordinate of hero in the height of memory? Indeed, this is exactly what we do, but there is one small accommodation we must make.

If we naively try to add buttons which contribute to the height of memory, maybe in a way similar to the following example:

<div class="memory"> <div class="hero"></div> <details> <summary class="button1"></summary> <div class="oneWide"></div> </details> <details> <summary class="button2"></summary> <div class="oneWide"></div> </details> <details> <summary class="button3"></summary> <div class="oneWide"></div> </details> <details> <summary class="button4"></summary> <div class="oneTall"></div> </details> <details> <summary class="button5"></summary> <div class="oneTall"></div> </details> <details> <summary class="button6"></summary> <div class="oneTall"></div> </details> </div>
.oneTall { height: 1px; }

We won't get the intended behavior.

content

This is because memory has "flex-direction: row" by default, meaning it will line up its contents horizontally as they are added. If we line up a bunch of 1px tall elements horizontally upon opening our vertical buttons, this will at most make the content of memory 1px tall, when we would like it to vary up to 3px tall.

The solution is not to change "flex-direction" to "column" on memory, as this would break our horizontal buttons. Instead, we just need to wrap our vertical buttons in a <div> inside of memory with the following styling:

<div class="memory"> <div class="hero"></div> <details> <summary class="button1"></summary> <div class="oneWide"></div> </details> <details> <summary class="button2"></summary> <div class="oneWide"></div> </details> <details> <summary class="button3"></summary> <div class="oneWide"></div> </details> <div class="verticalButtonsContainer"> <details> <summary class="button4"></summary> <div class="oneTall"></div> </details> <details> <summary class="button5"></summary> <div class="oneTall"></div> </details> <details> <summary class="button6"></summary> <div class="oneTall"></div> </details> </div> </div>
.verticalButtonsContainer { display: inline-flex; flex-direction: column; }

This essentially places another element that acts like a vertical version of memory inside of memory, which allows our vertical buttons to properly stack up their heights inside of memory. After taking care to postion things correctly which is tedious but nothing new, we end up with the demo I showed you at the beginning of the post!

content

If you get the ball rolling with something like this and you don't hate your code yet, there's a ton of directions you can go. You can add "collision" with obstacles by having blocker-divs that move over the controls as the hero moves. You can put things besides oneWides or oneTalls inside of details tags to allow the user to "unlock" new things upon reaching certain locations. If you can think of a turn based mechanic, I imagine you can implement it with a clever enough approach. You can probably even implement real-time mechanics with clever uses of animation!

Finally, here is a link to the source HTML for the completed demo. It may be smaller than you think! I was surprised how compact it can be when I first started trying to make grid movement.


Here are some posts I've seen using width-hacking that show how you can use it to do much more than grid movement!

I learned about width-hacking by inspecting the source of this charming post.

I have no fucking idea what is going on in this mindblowing post, but I can't imagine you could make something like it without width hacking.

This is a fun port of Wordle, which includes a generator to make your own Wordles!

Here is another explanation of width-hacking, and how it was used to make a binary to decimal widget!

Now go make something awesome!



(Desktop only)
Enter
 
 
Enter
 
 
Enter
 
 
Enter
 
 
Enter
 
 
Enter
 
 

How to Play Similar to normal wordle. Try to guess the hidden word. Use the arrows to choose letters to form a 5 letter word and press enter to get hints about what the word is. Green letters mean there you have the correct letter in that position. Yellow letters mean that letter is somewhere in the word. You win when all 5 letters are green. You have 6 guesses.
Differences from normal Wordle
  • The word you guess doesn't have to be a real word. It just has to be any 5 letter combination. Although the solution is a real word.
  • Yellow letters will show if the letter exists anywhere in the word, even if that has already been marked green. So if a letter is there as both green and yellow, that doesn't mean it's in the word twice. (In this case, there are no doubles)
  • There is no "Hard mode" although you are free to try to enforce those rules upon yourself.
  • There is no win screen currently, so once you get all letters as green, that means you've won.
How it works I largely used the technique described in this post by @Corncycle and also kind of adapted the html for my title from that post. I also heavily referenced this post and this post by @blackle. The letter pickers increment the height every time one of the buttons is clicked, and then I use the height to determine which letter to show. It tracks which letter is currently selected, and if it's the right letter, it will display a green square, and if the word just contains the letter, it will display a yellow square. I made a JavaScript and React generator that generates everything to make it easier. Tbh, there's probably better ways to build stuff like this and optimize it, but I'm still kind of new to building stuff without something like JavaScript powering it, so this is still kind of just a proof of concept for now. It's pretty messy, but you can see the source for the generator here or generate your own on my personal website here.


Hello Cohost! My name's Jasmine, AKA Jazzrabbit. I'm currently exploring new social media sites, and I do like what I've seen so far here on Cohost, so I thought it might be fun to introduce myself, and share some of the things I've worked on. I'll try to be brief in this post, but maybe I'll give more details on some of my experiences in the future.
I'm a transgender game engineer and modder interested in solving cool engineering problems and making cool stuff. I don't know how much I'll post, but if I do, I'll probably talk about game development, games, modding, and maybe some personal life stuff occasionally as well. Here's some of the things I'm currently working on and some things I have worked on previously.

Slime Rancher 2

I am currently working as a Jr. Game Engineer at Monomi Park, where I'm helping develop the Early Access game, Slime Rancher 2. I only joined a few months ago, but I've already contributed dozens of bug fixes, and have started developing several new features as well. Learn more about the game at the official website:
https://www.slimerancher.com/ Slime Rancher 2 Banner Image

A Hat in Time: Vanessa's Curse

Previously, I worked as the lead engineer on Vanessa's Curse, which was the first official Creator DLC for the 3D indie platformer, A Hat in Time. I built most of the online gameplay on top of the game's existing online systems, and I coded a lot of the UIs and other gameplay systems as well. See it on Steam here: https://store.steampowered.com/app/1738980/A_Hat_in_Time__Vanessas_Curse/ A Hat in Time: Vanessa's Curse Banner Image

A Hat in Time Modding

In my free time, I also enjoy modding games. I've modded a few different games, but the one I've spent the most time on is A Hat in Time. I've created dozens of unique mods for this game, with a focus on new and unique gameplay features, and Quality of Life improvements. Check out some of my highlighted on the Steam workshop here!
https://steamcommunity.com/sharedfiles/filedetails/?id=2068630350 Jasmine's A Hat in Time Modding Showcase

Game Jams and Other Personal Gamedev Projects

Before doing much professional gamedev work, I also made a lot of smaller games as part of game jams, and also from college assignments and personal projects. A lot of these were made under pretty tight time constraints, so they may not be the most polished, but I'm still pretty proud of them either way. Check out my portfolio website for more details!
https://jasmine.games/#/