Make Your Game Fair With i-Frames

Franks laboratory| 00:13:50|May 22, 2026
Chapters5
The chapter explains invincibility frames (I-frames) as brief, post-hit windows where the player cannot take damage, used to prevent stun-locks and to balance gameplay. It also covers visual cues and the idea of adding power-ups during invincibility, and previews implementing I-frames fully with a flicker effect and a game-over flow.

Learn how Franks Laboratory implements invincibility frames with a 2-second timer, flickering visuals, and a complete game over flow.

Summary

Franks Laboratory walks through adding i-frames (invincibility frames) to a 2D game, showing how to grant brief immunity after taking damage and how to visually communicate it with a flicker effect. The tutorial sets a 2-second invincibility duration in the player data, initializes an invincible flag, and uses a timer updated every frame to reset vulnerability. The render method uses a semi-transparent pulsing effect to indicate invincibility, explaining how to use Math.sign, Math.abs, and a controlled amplitude for a smooth blink. The video then builds out the game-over flow: when health reaches zero, the game emits a player died event, triggers a game over screen, hides the HUD, and plays a game over sound. Franks demonstrates integrating this logic across multiple modules—player.js, collision manager.js, and game.js—so damage only applies when not invincible, and death properly halts further collision checks that frame. The code checkpoints, UI updates, and audio cues are tied together to deliver a cohesive, testable starter kit for future games. Finally, the session emphasizes extending the system with additional visuals (shields, blinking) and outlines how to add a game over panel to the existing UI framework.

Key Takeaways

  • Invincibility is implemented with a boolean flag (this.invincible) and a timer (this.invincibilitytimer) set to 2 seconds in player data, ensuring no damage is applied during the window.
  • Damage is conditionally applied in takeDamage: if already invincible, the method returns false; otherwise health is reduced and invincibility starts.
  • A per-frame update uses delta time to decrement the invincibility timer and resets the invincible flag when it hits zero.
  • Visual feedback uses render with globalAlpha adjustments to render the player at 20% opacity, plus a sinusoidal-based pulsing effect for a blinking look.
  • The game-over flow is wired through events: on player death, a game over sound plays, the game state switches to GAME OVER, the HUD hides, and a game over panel is shown.
  • The UI and audio systems are designed to work with events (player damaged, player died, game start, etc.) and are adaptable for future games.
  • You can extend the invincibility visuals with a shield or a stronger blink by tweaking opacity ranges (e.g., 0.2 to 0.8) and amplitude.

Who Is This For?

Essential viewing for game developers implementing damage immunity and game-over flows in 2D titles. It’s particularly helpful for folks building modular starter kits or learning how to coordinate player state, collision, UI, and audio.

Notable Quotes

"In 2D games, I-frames, invincibility frames, are short windows of time after taking damage where the player cannot be harmed."
Defines what i-frames are and why they’re useful.
"Once your code base is fully set up, you can also add fun animations, maybe something like this, or just something very simple like this."
Mentions adding visual feedback for invincibility.
"When the player takes damage, we first check if the player is currently invincible. If it is, we return false and we do not apply the damage."
Shows the core guard clause preventing damage during i-frames.
"Inside the render player method, if the player's invincible flag is true, we take this.ctx, the drawing context, and set global alpha to 0.2."
Demonstrates how visual feedback is implemented.
"The game over panel will appear, the game over sound plays, and this.gameover sets the game state to game over."
Illustrates the end-game sequence and UI changes.

Questions This Video Answers

  • How do invincibility frames affect game balance in 2D action games?
  • What are practical ways to visually indicate invulnerability besides blinking?
  • How can I wire up a game over screen with sound and UI panels in a modular game kit?
  • How does delta time help manage timers for effects like invincibility in a game loop?
  • What is circle collision detection and how is it used in player-enemy interactions?
i-framesinvincibility frames2D game developmentcollision managementcircle collisionhealth bargame over screenevent-driven architecturerendering and opacityBlitting and canvas drawing
Full Transcript
In 2D games, I-frames, invincibility frames, are short windows of time after taking damage where the player cannot be harmed. Developers use them to prevent players from being stun locked by rapid overlapping hits. They're also useful balancing tool, giving the player a brief moment to recover and reposition before the next hit lands. In many games, I-frames are paired with visual feedback like blinking or a flashing transparency effect, so the player always knows when they're invulnerable. Once your code base is fully set up, you can also add fun animations, maybe something like this, or just something very simple like this. And when you have invincibility in your game, you can even build on it further, adding power-ups or pickups that grant the player immunity for a set period of time, things like that. In this class, we'll fully implement I-frames in our game, including a flickering opacity animation while the player is invincible. And since our player can now take damage, we'll also implement a full game over loop. The source code for the project at this stage is available to download in the resources section below. I called it game starter kit part 18. Use it if you hit a bug and want to compare your code to mine, or just save it as a checkpoint you can always come back to. I'll be providing source code checkpoints throughout this project to make sure everyone can follow along and nobody gets left behind. By the end, we'll all have a well-structured, optimized starter kit we can use to build many different games. I will define invincibility duration inside player data. It will probably be shorter later, but for now, I'll set it to 2 seconds so we can properly test it. We export player data and import it here. So, inside the player class constructor, I create a property called this.invincibilityduration and set it to the value we just defined in player data. I'll also define this.invincible flag and initially I set it to false. And we will need invincibility timer like this. If you want to make more games, click the like to let me know. And the whole trick to this will be here inside take damage method. When player takes damage, we first check if the player is currently invincible. If it is, we return false and we do not apply the damage. Otherwise, if the player is not invincible, the rest of this code will run where we actually reduce player's health by the incoming damage amount and then after that hit, player will become invincible for a brief moment, which will prevent the player from taking more damage immediately after because of the code on line 51. And we set invincibility timer to the value of invincibility duration, which we set to 2 seconds in the data. So, that's it. When the player takes damage, one of two things happens. If the player is already invincible, nothing happens and this method just returns false. If the player is not currently invincible, we reduce the player's health, set them to invincible and the method returns true. As long as this.invincible is set to true, this code will not run again. So, the player will not be taking damage. When we reset the player, we set this.invincible back to false and this.invincibilitytimer to zero. Inside the update method, which runs every frame, we check if this.invincible is true. If it is, we know the timer was set, in our case to 2 seconds, and we reduce it over time using delta time, the actual time passing between frames. Once this.invincibilitytimer drops to zero or below, we set this.invincible back false and clamp the timer to zero. Now, we want to make it visually clear that the player is invincible using the render system. Inside the render player method, if the player's invincible flag is true, we take this.ctx, the drawing context, and set global alpha to 0.2. This makes the player semi-transparent, 20% opacity. After rendering the player, we set global alpha back to one to make sure anything drawn after this is not affected. Now, if I play, when the player hits an enemy, they become invincible and semi-transparent for 2 seconds. If we hit another enemy while invincible, the enemy will still get destroyed, but the player will not take any damage. We'll tweak how enemy health works later. Right now, we're focused on the player. Let me try it again. I hit one enemy and immediately another one. And you can see the second enemy doesn't apply any damage because the player is still invincible from the first hit. So, our code is working. This is a very simple way to show invincibility. Instead, we could draw a shield around the player or do what many games do, make the player blink. For example, between 0.2 and 0.8 alpha. This line of code sets the base opacity to 0.2 and then we add a changing value on top of it. This part is a bit tricky to explain. Math.sign generates values along a sine wave. If we pass in a value that changes over time, like the invincibility timer, it produces a smooth oscillation. We multiply it to control how fast the blinking happens and then wrap it in math absolute, so the value is always positive. Finally, we multiply it by 0.6 to control the strength of the effect. Math.sign gives values between -1 and +1. Math.absolute turns the into a range between zero and one by converting negative values to positive ones. So, now we get a repeating pattern that goes from zero to one and back to zero over time, creating a smooth pulsing effect. The 0.6 controls the amplitude, meaning how strong the effect is, how far the opacity moves away from the base value. Without the 0.6, we would add the full zero to one range directly to the base value, meaning the opacity would swing between 0.2 and 1.2, which could make the effect too strong and even go beyond the normal alpha range. With the current values, the opacity endlessly moves back and forth between 0.2 and 0.8. The sign function continuously oscillates over time, so the effect repeats indefinitely as long as the player is invincible. If I change these values to 0.1 plus 0.8, I will get a range between 0.1 and 0.9 if you want more dramatic effect. And now you know how to handle invincibility frames in your games. So far, nothing happens when the player health reaches zero. So, let's go to constants.js. Inside the game state object, I will add a new game state called game over. Now, I go into collision manager.js where we handle player damage from enemy collisions. Inside player.js, I create a helper method called is dead. It simply returns a boolean expression. If the player's health is less than or equal to zero, it returns true. Otherwise, it returns false. As we check collisions between the player and enemies, we check if damage was applied. Notice that this comes directly from player take damage, which returns true if the player actually takes damage or false if the player is invincible. So, only if the player actually receives damage, we emit the player damaged event. After that, we check player is dead. If it returns true, we emit a player died event so that different systems across the code base can react to it. After we emit the player died event, we return from this function. This stops the rest of the collision checks for this frame because once the player is dead, there is no reason to keep checking collisions with other enemies. It also prevents us from triggering additional damage or death logic multiple times in the same update. Now, I go to game.js. Up here, where we register handlers for player-related events using the on method, I register a few listeners for player died. I make sure I defined player died inside my events object. When this event fires, we react by emitting a sound event, which makes the audio manager play the game over sound. I already have the game over MP3 file inside the audio folder and it's included in audio data. So, everything is set up correctly. So, when the player died event is emitted, we play a sound and call this.gameover. This method doesn't exist yet, so I'll define it below. It sets the game state to game state game over, which maps to game over. Then, we use the UI manager to hide the HUD so the timer and health bar disappear. And we call show panel to display the game over screen. The only issue is that we haven't created that panel yet, so let's go to index.html and add it. The game over screen will be a div with an ID of game over menu and we'll give it the shared UI panel class so it works with our existing styles and show hide logic. No extra work needed. Inside, I'll add a title and two buttons, play again button and quit from game over button. Now, back in UI manager, let's organize things a bit. At the top, where we store UI panel references, I'll add game over menu and point it to the new panel using get element by ID. In the button section, I'll add references for play again button element and quit from game over button element. Then, inside setup event listeners, when we click play again button, we emit the game start event. In game.js, we already registered a listener for that event, which calls this.startgame. For the quit button, we emit game return to menu and we already have a handler for that as well. I'll also add both buttons to the hover array, so they trigger the button hover sound on mouse enter. Finally, I include game over menu element inside hide all panels, so it gets hidden together with the rest of the UI panels. Now, let's test it. I'll play and collide with enemies until the player runs out of health. And the game over panel appears. [music] The game over sound plays as well. Everything is working. So, to recap how [music] the logic works and how everything works together, inside game.js, the game loop runs continuously, usually around 60 times per second. Each frame, it calls the update method, passing in delta time and the list of active enemies. The update method then passes that data to collision manager update. Inside collision manager, this method calls check player versus enemies, passing in the player and the active enemies. This method loops over all active enemies and checks each one against the player using our custom collision system, where we implemented circle versus circle collision detection. In the future, we could add more collision types here, but for now, circular collision is all we need. If a collision is detected, we call player take damage. This method will either return false if the player is invincible, or it will reduce the player's health, start the invincibility timer, and return true. If the player actually receives damage, we emit a player damaged event. Then we check if player is dead. If the player is dead, we emit a player died event and stop checking collisions with any remaining enemies for this frame. When the player is damaged, we play the player hurt sound and update the health bar. When the player dies, we play the game over sound and call this.gameover. This method sets the game state to game over, hides the HUD, for now that's the timer and health bar, and shows the game over panel. Every time we emit a sound event, we're telling the audio manager to play a sound by name. The play method resets the audio and plays it from the beginning. Whenever the player is damaged, we also call UI manager update health bar. Inside of that method, we calculate the ratio between the player's current health and max health, and we store it as a value between zero and one. That value is then used to update the health percentage variable, which drives the visual animation of the health bar. If you want more episodes in this series, you can let me know by leaving a like. And you can also drop a comment. Do you prefer pixel art games like this one, or maybe hand-drawn style like this, or something completely different? I make these episodes based on your feedback, so let me know. I read all the comments.

Get daily recaps from
Franks laboratory

AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.