Tuesday, May 12, 2026

Stop Making Platformers First

A lot of indie games start as prototypes. You make something small, it feels fun, someone plays it, and suddenly the idea appears: maybe this could become a real game. That is not a bad thing. My first Steam game, Frogo Jump, also started like this. It was a fun prototype first, and later I turned it into a full commercial game.

But this article is not really about prototyping itself. It is about the moment after the prototype. The moment where you decide if this small idea should become a commercial release. In my opinion, this is where many beginner indie developers make the wrong decision, because they only ask if the prototype is fun. They do not ask if the game has a market.

Jump to section

Prototypes are easy. Commitment is hard.

Prototyping is one of the most fun parts of game development. You test a mechanic, you move a character around, you add a few objects, and suddenly something works. It feels alive. It feels like a game. This is why prototyping is so important. You learn fast, you test ideas, and you can find out if a mechanic feels good before you build a whole world around it.

But the prototype itself is not the hard part. The hard part is commitment. Most people do not fail because they cannot make a prototype. A lot of people can make prototypes. They fail because they do not know what to do after that. They do not know if the prototype is worth finishing, if the game has a market, or if the platform they are aiming for even wants this kind of game. And sometimes stopping is the right decision.

Not every prototype has to become a full game. Not every fun idea has to become a commercial product. Some prototypes are just learning projects. Some are experiments. Some are useful because they teach you something, not because they should be sold. I still think you should make prototypes. Make a lot of them. Even make platformers if you want to learn. Platformers are great for learning movement, physics, level design, collision, camera systems, and game feel. But learning projects and commercial games are not the same thing.

The platform is the market.

Before you commit to a commercial game, you need to understand the platform you are aiming for. A game is not just a game. A game on Steam is different from a game on mobile. A game on Nintendo Switch is different from a game on itch.io. A game on Epic Games is different from a game on Steam. The platform is not just the place where you upload the game. The platform is the market.

Different platforms have different players. They have different expectations, different buying habits, different genres that work better, and different ways of discovering games. If you make a commercial game, you need to know where that game belongs. You need to know who it is for. You need to know how people will find it. This is the part I did not understand enough when I made Frogo Jump. I thought more like a developer. I made something fun, and then I tried to release it. But I did not think enough about the platform, the audience, the genre, or the market before committing to it.

Stop making platformers for Steam.

If your goal is to make your first commercial game for Steam, then in my opinion, stop making platformers. I know this sounds harsh. I also made a platformer. Frogo Jump is a precision platformer. So I am not saying this from the outside. I made the same decision. But after releasing a platformer on Steam, I would not recommend it as a first commercial release.

Platformers are oversaturated. Puzzle games are also oversaturated. Every day, many games release on Steam, and so many of them are platformers or puzzle games. We do not need more. Even if your platformer is good, people still have to find it. And that is the problem. If there are too many similar games, being good is not enough. Your game can be fun and still disappear.

This is not just a feeling either. How To Market A Game has written a lot about genre choice on Steam, and one of the big lessons is that the genre you choose is already one of your biggest marketing decisions. In one article, Chris Zukowski points out that platformers are hard to market on Steam and that the platformers that do better often have another stronger genre attached to them, like roguelike or metroidvania. In another analysis, puzzle and platformer games are described as some of the least performant genres, while the number of puzzle games and platformers had increased. You can read more about that here: More evidence of which genres Steam shoppers love to play and here: What the hell happened in 2025?.

In my opinion, Steam players do not really like platformers that much. They like other types of games more. They buy other types of games more. They get more excited about other types of games. Of course, there are successful platformers. Of course, there are amazing platformers. But that does not mean platformers are a good first commercial choice for an unknown solo developer on Steam. The problem is not only quality. The problem is visibility.

I understand why so many beginner developers make platformers anyway. Platformers are easy to start. There are many tutorials for them. If you learn Unity, one of the first things you often build is a character that runs and jumps. You use the physics engine, you add collision, you place platforms, and suddenly you have something that feels like a game. Platformers are also familiar. Most game developers are gamers. Many of us grew up with platformers. We know how they work. We understand jumping, enemies, checkpoints, coins, levels, and bosses. So it feels natural.

Puzzle games have a similar problem. They look manageable. You can make them with simple graphics. You can focus on the mechanic. They seem like a good solo developer genre. But easy to start does not mean easy to sell. This is the trap. The genre that is easiest to prototype is not always the genre that makes the most sense commercially.

I am not saying platformers should never exist. I like platformers. I made one. You can still make one if that is what you really want to make. But then you need to think about the right platform. From what I have heard, platformers may work better on Nintendo Switch than on Steam. That makes sense to me. Nintendo has a different audience. Platformers fit better into that ecosystem.

But releasing on Nintendo is not as simple as uploading a game to a store. You need developer access and platform approval. Nintendo says that the Nintendo Developer Portal is for all Nintendo developers, regardless of size or experience, and that registering for the portal and downloading tools is free. But Nintendo also says that development hardware still has to be acquired and that more information about that is inside the portal. You can read Nintendo’s own FAQ here: Nintendo Developer Portal FAQ.

If you use Unity, like I do, there is another thing to think about. Unity says that for Nintendo Switch development, you need to register as a Nintendo developer, apply for the closed console platform, get the platform support add-on, test on platform hardware, optimize the game, go through the platform testing process, and release. Unity also says that you need an active Unity Pro subscription or a Preferred Platform license key from the platform holder to access these specific build modules. You can read Unity’s own page here: Unity for Nintendo Switch.

This is why I would not call Nintendo the easiest option for a solo developer like me. Maybe platformers fit better there, but you still have to deal with approval, tools, hardware, licensing, testing, certification, and the extra work of supporting a console platform. So yes, maybe platformers can work better somewhere else. But then you have to plan for that from the beginning. If you make a platformer and your only real plan is Steam, then I think you should seriously reconsider it.

If you make PC games, study Steam.

If you make PC games, Steam is the platform you have to understand. You can release on other platforms too. You should not ignore itch.io, Epic Games, GOG, or other stores. They can still be useful. They can bring extra sales, extra visibility, or another place where people can find your game. But if your game is a PC game, Steam is probably still the main target.

This means you have to study Steam. You have to understand how Steam pages work, how wishlists work, how tags work, how festivals work, how demos work, and what kind of games Steam players actually want. A useful website for this is How To Market A Game. The main lesson I take from that site is that marketing does not start after the game is finished. Marketing starts with the type of game you choose to make.

Steam is not magic. Steam can help you, but it cannot save a game that nobody wants to click on. It cannot magically make an oversaturated genre easy. It cannot fix a game that has no clear audience. Steam is a powerful platform, but you still need to bring the right game to it.

Make your Steam page early and collect wishlists.

If you know that your prototype is becoming a real commercial game, make your Steam page as early as possible. Do not wait until the game is almost finished. Do not wait until release month. Do not finish the whole game and then suddenly think about marketing. Make the page early.

You do not need the full game. You need enough to show what the game is. You need footage, screenshots, a good description, and a clear idea. As soon as you can show the core of the game, the Steam page should exist. Steam itself says that it is useful to put up a coming soon page once you are far enough to show screenshots and describe the core of your game. You can read more about this in the official Steamworks page about Steam marketing tools.

The reason this matters is wishlists. On Steam, wishlists are one of the most important numbers. A wishlist is not a sale. Not everyone who wishlists your game will buy it. Some people wait for discounts. Some people forget. Some people wishlist too many games and never buy most of them. But without wishlists, your launch is much weaker.

Wishlists give your release a starting point. Steam explains that players who wishlist your game can receive notifications when the game releases or when it has a qualifying discount. That means your launch is not just you posting into nothing. You have a group of people who already showed interest before release. You can read more about this in Steam’s official page about wishlists.

This is why you need to collect wishlists before launch. You need social media. You need a demo if possible. You need festivals. You need people to see the game before it comes out. If you launch with no wishlists, you are basically hoping that people randomly find the game on release day. That is not a strategy.

Use festivals to get more wishlists.

Festivals are one of the best ways to get real exposure and more wishlists. There are many game festivals during the year. Some are on Steam. Some are outside of Steam. Some are for specific genres, countries, themes, or communities. If your game fits a festival, you should try to apply.

When I look at the statistics for Frogo Jump, the biggest wishlist increase came from Games Made in Germany. It still was not enough to make the game successful, but it showed me that festivals can actually move the numbers. Festivals are useful because the audience is already looking for games. That is different from posting on social media, where most people are not actively searching for a new indie game to buy.

Steam Next Fest is probably the biggest festival target for many indie developers on Steam. But you can only participate in one Steam Next Fest with a game, so you need to choose the right one. Steam says titles may only participate in one Next Fest, the base game store page has to be public, and the game needs a publicly playable demo by the time Next Fest begins. You can read the official rules here: Steam Next Fest documentation.

In my opinion, you want to enter Steam Next Fest with as many wishlists as possible. How To Market A Game also recommends doing the last Steam Next Fest before launch, because visibility scales with the number of wishlists you have going into it, and because your demo should already be tested and polished before the event. You can read more here: Frequently asked questions about Steam Next Fest.

Steam Next Fest can be huge, but it is not a magic button. It works best when you already prepared the game, the Steam page, the demo, the screenshots, the trailer, and the marketing. Then the festival can multiply what is already there.

Also remember the cost of releasing.

Releasing a game also costs money. On Steam, you have to pay the Steam Direct fee. At the time of writing, Steam lists this as a 100 dollar fee for each new app you want to distribute. You can read the official Steamworks page about the Steam Direct fee.

And that is only one part. Steam also takes a cut from your revenue. After that, you still have taxes. You might also have asset costs, tool costs, music costs, localization costs, trailer costs, or other expenses. So when you sell a game, the full price is not the money you keep. This is another reason why the genre decision matters. If the game has a very low chance to sell, then even small costs become harder to justify.

When should you commit to a prototype?

This is the real question behind the article. When should a prototype become a commercial game? In my opinion, not just because it is fun. Fun is important, but it is not enough. You also need to know the platform, the audience, the genre, the competition, and the marketing path.

Before you commit, ask yourself who the game is for, where these players will find it, if the genre works on the platform you are aiming for, if the genre is oversaturated, if you can show the hook in screenshots or short videos, if the game can get wishlists, and if there are festivals where the game fits. These questions are not as fun as prototyping. But they are important.

What I learned from Frogo Jump.

I do not regret making Frogo Jump. It was my first released Steam game, and I learned a lot from it. I learned how to finish a game, how to release on Steam, how wishlists work, how festivals work, how hard marketing is, and how different a finished product is from a prototype. You can find the game here: Frogo Jump on Steam.

I also collect my games, browser tools, and coding experiments on Kami Media. If you want to see more of my projects, you can check out my project page here: Kami Media projects.

That experience was valuable. But if I could go back, I would not choose a platformer as my first commercial Steam game. I would still make the prototype. I would still use it to learn. I would still have fun with it. But I would think much harder before turning it into a commercial release.

That is the message of this article. Make prototypes. Learn from them. Have fun with them. But when you decide to make a commercial game, stop thinking only like a developer. Think about the market too.

Sources and useful links

Friday, May 1, 2026

PlayerPrefs vs JSON Saves in Unity

When I was building Frogo Jump, my first released Unity game, I used PlayerPrefs for things that I probably should not have used PlayerPrefs for.

At the time, it made sense to me. I was still learning Unity, and many beginner tutorials used PlayerPrefs whenever they needed to save something. So when I had a problem where I needed to remember a value, my beginner brain went: okay, PlayerPrefs saves things, so I will use PlayerPrefs.

It worked, but it was messy.

The bigger lesson I learned later is that not all game data is the same. Some data should only exist while the game is running. Some data should stay on the player's device as a setting. Some data is real save data and should be part of a proper save file.

Mixing all of that into PlayerPrefs can work for a while, but it makes the project harder to understand and harder to clean up later.

View Frogo Jump on Steam


The Frogo Jump problem

Frogo Jump has an overworld map. On that map, there are level dots. The player moves between those dots, and when the player enters one of them, the game loads the actual level scene.

The important part is what happens afterward.

If the player leaves the level and returns to the overworld, they should come back to the same level dot. Otherwise, the overworld would load again and the player could appear at the start of the map instead of the place they came from.

So the game needed to remember the last overworld position before loading the level.

As a beginner, I solved this with PlayerPrefs. I saved the position before entering the level, then loaded it again when the overworld scene came back.

Technically, this can work. But the problem is that this position was not really permanent save data. It was not a setting either. It was just temporary scene transition data.

I did not need to write that value permanently to the player's device. I only needed to carry it from one scene to another while the game was still running.


What PlayerPrefs are actually for

PlayerPrefs are useful, but the name already tells you the intended use: player preferences.

Unity describes PlayerPrefs as a class that stores player preferences between game sessions. It can store strings, floats, and integers. That makes it useful for small local values like volume settings, fullscreen mode, language choices, or other simple settings.

Settings are also usually device-specific. If I play a game on my desktop PC and set the music volume to 30%, I do not necessarily need that exact setting on another device. Maybe my laptop speakers are quieter, so I want a different volume there.

That is a good use case for PlayerPrefs. The value is small, local, and simple.

But game progress is different. Checkpoints, unlocked levels, inventories, collected items, quest states, and save slots are not just preferences. They are part of the player's progress. That kind of data should be handled more intentionally.

PlayerPrefs are also not always easy to inspect like a normal save file. Depending on the platform, Unity stores them in different places. On Windows, for example, PlayerPrefs are stored in the Windows Registry. On WebGL, Unity uses the browser's IndexedDB. That is fine for preferences, but it can become annoying when you accidentally use PlayerPrefs for the wrong kind of data and then need to reset or debug it.

Unity also notes that PlayerPrefs are stored locally without encryption, so they should not be used for sensitive data.

Source: Unity PlayerPrefs documentation.


The better solution for temporary scene data

For the Frogo Jump overworld position, I would not use PlayerPrefs today.

I would use a GameManager that stays alive between scenes. In Unity, this is commonly done with DontDestroyOnLoad. Unity's documentation explains that scene loading normally destroys current scene objects, but DontDestroyOnLoad can preserve a root object during scene loading.

That is useful for this kind of problem. The data does not need to be written permanently to the device. It only needs to stay available while the game moves from the overworld scene into a level scene and then back again.

Many projects use a singleton-style GameManager for this. The point is not that every project must use one giant manager for everything. The point is that one persistent object can hold small pieces of temporary state while scenes change.

A simple GameManager for this problem could look like this:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    public Vector3 LastOverworldPosition { get; private set; }
    public bool HasLastOverworldPosition { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void SetLastOverworldPosition(Vector3 position)
    {
        LastOverworldPosition = position;
        HasLastOverworldPosition = true;
    }

    public void ClearLastOverworldPosition()
    {
        HasLastOverworldPosition = false;
    }
}

Before loading a level from the overworld, the game could store the current position like this:

GameManager.Instance.SetLastOverworldPosition(player.transform.position);

When the overworld scene loads again, the player can be placed back at that position:

if (GameManager.Instance.HasLastOverworldPosition)
{
    player.transform.position = GameManager.Instance.LastOverworldPosition;
}

This is not a full save system. It is not supposed to be one. It is just a clean way to pass temporary data between scenes.

That distinction matters a lot.

Source: Unity DontDestroyOnLoad documentation.


Temporary data, settings, and real save data

The mistake I made was not simply “using PlayerPrefs.” The mistake was treating every kind of data as if it belonged in the same place.

The overworld position in Frogo Jump was temporary data. It only mattered because the player moved from the overworld scene into a level scene and then back again. It did not need to survive forever. It did not need to be part of a save slot. It just needed to survive a scene change.

Settings are different. A music volume setting should still be there after closing and reopening the game. It makes sense for that to be stored locally on the device.

Real save data is different again. If the player unlocks World 3, finishes Level 12, collects an item, or reaches a checkpoint, that is actual progress. That should be stored in a proper save system, not scattered across random PlayerPrefs keys.

The simple mental model is this:

PlayerPrefs are for local preferences. GameManagers are useful for temporary runtime data. JSON save files are better for structured game progress.

Once I understood that separation, save systems started to make much more sense.


Why JSON is better for real save data

If I were building Frogo Jump again, I would separate real progress into a structured save file.

A JSON save file is a common way to do that in Unity because it lets you define a structured data object and convert it into a text-based format. Unity's JsonUtility can convert objects to JSON and back again, as long as the data follows Unity's serialization rules.

Instead of having many separate PlayerPrefs keys, you can create a class that represents the save file.

using System;
using System.Collections.Generic;

[Serializable]
public class SaveData
{
    public string currentWorld;
    public int highestUnlockedLevel;
    public int coins;
    public List<string> collectedItems = new List<string>();
}

This is easier to understand than spreading the same data across many keys.

PlayerPrefs.SetInt("Coins", coins);
PlayerPrefs.SetInt("HighestUnlockedLevel", highestUnlockedLevel);
PlayerPrefs.SetString("CurrentWorld", currentWorld);

The PlayerPrefs version is not automatically wrong for a tiny prototype, but it becomes messy as the game grows. With a JSON save file, the save data has a shape. You can see what belongs to the save. You can create save slots. You can delete a save file. You can back it up. You can later connect it to a cloud save system if the game needs that.

That does not mean every game needs a giant save system. A small arcade game might only need a high score. But once a game has worlds, level progress, checkpoints, items, or unlocks, a proper save structure becomes much cleaner.

Sources: Unity JSON Serialization manual, Unity JsonUtility.ToJson documentation, and Unity JsonUtility.FromJson documentation.


What should be cloud saved?

Cloud saves are another reason why it helps to separate data early.

Steam Cloud is designed to store game files on Steam's servers so players can log into Steam and access their saved games from another computer. That makes it useful for real progress data, like save slots, unlocked levels, completed worlds, collected items, or other important player progress.

But that does not mean every local setting should be treated like cloud save data. A laptop and a desktop PC can need different resolution settings, graphics settings, or audio volume. Those are local preferences, so keeping them local often makes sense.

That is why I would not think of PlayerPrefs as “bad.” I would think of them as local preference storage. The problem starts when PlayerPrefs become the place where everything goes.

Source: Steamworks Documentation: Steam Cloud.


The rule I would use now

If I were building Frogo Jump again, I would separate the data like this.

Music volume, fullscreen mode, resolution, and language would go into PlayerPrefs because they are local settings.

The overworld position before entering a level would go into a singleton GameManager because it is temporary scene transition data.

Completed levels, unlocked worlds, collected items, coins, and checkpoint progress would go into a structured save file, probably JSON for a small Unity game.

If I later added cloud saves, I would sync the real save files, not every local preference.

This is the main lesson I took from the mistake: saving data is not just about storing a value somewhere. It is about understanding how long that value should exist and what kind of data it really is.


Final thoughts

PlayerPrefs are useful. I just would not use them as my whole save system anymore.

For a beginner Unity project, it is very tempting to put everything into PlayerPrefs because it is easy and many tutorials show it early. I understand why I did it in Frogo Jump. I needed a solution, and PlayerPrefs gave me one quickly.

But after finishing a real game, I can see the problem more clearly. The better approach is to separate temporary data, local settings, and permanent progress from the beginning.

That makes the project easier to debug, easier to reset, and easier to expand later.

Related

Frogo Jump: My first finished Unity game

View Frogo Jump on Steam

View all projects

Sources and further reading

Thursday, April 30, 2026

Building a Country Higher or Lower Game

I Built a Country Higher or Lower Game with JavaScript and Country Data

I recently built a small browser game called Country Higher or Lower. The idea is simple: the game shows two countries, one population is visible, and the player has to guess whether the other country has a higher or lower population.

This project is based on a similar idea to my Pokémon Higher or Lower game, but it feels different in practice. Pokémon stats are fun if you know Pokémon, but countries are much more general. Almost everyone understands the concept immediately. You see two countries, compare them, and make a guess.

That is why I think this version works well as a small browser game. It is clean, fast to play, and easy to understand. The player does not need to read a long explanation before starting. The game can be played directly in the browser, and it also works on mobile.

You can play the game here: Country Higher or Lower Game

The source code is available here: Country Higher or Lower on GitHub

The basic idea

The game shows two countries. One country has its population shown, while the other country has its population hidden. The player chooses higher or lower. If the answer is correct, the score increases and the game continues. If the answer is wrong, the round ends and the player can restart.

That is the whole game loop, but the data makes it interesting. Country population is not always easy to guess. Some countries are very large on a map but have relatively small populations. Other countries are geographically smaller but have far more people living there.

That makes the game more interesting than just recognizing flags or country names. It becomes a small guessing game about geography, population, and how misleading country size can be.

Using REST Countries for the data

For the country data, I used REST Countries. REST Countries describes itself as a RESTful API for getting information about countries. The official website lists v3.1 as the current version, with v4 currently in preview. It also says the API covers more than 250 countries and is open source and free to use.

For my game, I only needed a small part of that data. The important values were the country name, the population, and the country code. The country name is shown to the player, the population is used for the comparison, and the country code becomes useful later for displaying the flag.

I did not want the game to call the API every time someone plays a round. That would be unnecessary for this kind of project. Country population data does not need to be fetched again and again during gameplay.

Instead, I wrote a JSON builder. The builder fetches the country data once, extracts the useful values, and creates a local JSON file. The actual game then reads from that local JSON file.

This keeps the game simple and fast. It also works well for a static GitHub Pages project because the game does not depend on live API requests while someone is playing.

Why I did not use live API calls during gameplay

Using an API live can be useful when the data changes constantly or when the user needs the newest possible result. But this game does not need that. For a small higher-or-lower game, it is enough to prepare the country dataset once and use it locally.

This is similar to what I did with my Pokémon Higher or Lower project. At first, using live API requests feels like the most direct solution. But once the project becomes a game, repeated requests can become unnecessary.

A local JSON file is a cleaner solution here. The game loads the prepared data, picks countries from it, and compares their populations. The API is still useful, but it is used as a data source during the build process instead of being called during every game session.

The flag problem I did not expect

One problem I did not expect was how annoying country flags can be in a browser project. At first, I wanted to use flag emojis. That felt like the smartest and most efficient solution. I would not have to download images, store image files, or load a separate flag service. I could simply display a flag as text.

But then I tested it and noticed a problem. Flag emojis are not displayed reliably on Windows. On many Windows systems, country flag emojis do not appear as actual flags. Instead, they appear as two regional indicator letters. So instead of seeing a German flag, French flag, or Japanese flag, the user may only see something like DE, FR, or JP.

This happens because emoji rendering depends heavily on the operating system and the browser. On Windows, the Segoe UI Emoji font does not include country flag emoji support. A web developer writeup by Nolan Lawson explains that Microsoft’s emoji font does not have country flags on Windows 10 or Windows 11, so Chrome on Windows can show country codes instead of actual flag images. A Stack Overflow discussion about flag emojis also points out that Windows users often need a separate font, image replacement, or another workaround if proper flag display is important.

That was a problem for this project. For a normal text article, maybe this would not matter much. For a country guessing game, it matters a lot. If some users see flags and other users only see two-letter codes, the game feels inconsistent.

This is one of those problems that only appears once you actually build something. I thought emojis would be a clean solution, but the real browser and operating system behavior made them unreliable. So I decided not to use emoji flags in the game.

Why I used FlagCDN instead

After dropping the emoji idea, I needed another way to display country flags. One option would have been to download and store flag images directly in the project. But that did not feel like the best solution. It would make the project larger, and it would also raise the question of where the images come from and whether they are okay to use.

The better solution was to use FlagCDN. FlagCDN describes itself as a free flag API and CDN service. Its website says it includes all 254 country flags and provides them in formats such as PNG, WebP, SVG, and JPEG.

This worked very well with the structure I already had. My JSON file already contained country codes from the REST Countries data. FlagCDN uses country-code-based URLs. That means the game can take the country code and generate the correct flag image URL.

This made the final solution much cleaner. REST Countries gives me the country data I need for the game. FlagCDN gives me reliable flag images. The country code connects both systems.

I later realized that REST Countries also has flag-related fields. But that was not the original path I took. My first plan was to use emoji flags. When that did not work reliably on Windows, I looked for a solution that could use the country codes I already had in my JSON file. That is how I ended up using FlagCDN.

Why this solution felt clean

The part I like about this project is that the final structure is simple. The game does not store hundreds of flag images. It does not depend on flag emojis working correctly across every operating system. It also does not send unnecessary API requests while the user is playing.

The JSON builder prepares the data. The game reads the local JSON file. The country code is used to load the correct flag image. The player only sees the clean result: two countries, two flags, one simple choice.

That is the kind of solution I like in small web projects. It is not overengineered, but it solves the real problem.

What I learned from this project

The biggest lesson from this project was that the data source is only part of the work. Getting country data was not the hardest part. The more interesting problem was turning that data into a game that works reliably in the browser.

The flag issue is a good example. At first, emojis looked like the easiest solution. But once I tested them across systems, they were not reliable enough. Using FlagCDN with country codes ended up being a much better solution.

I also learned again that local JSON files are very useful for small static projects. You can still use an API during development, but the final game does not always need to call the API live. Sometimes the better structure is to fetch the data once, clean it, and let the game use a prepared dataset.

This project is simple, but it connects several useful web development ideas. It uses API data, a JSON builder, local data loading, generated image URLs, JavaScript game logic, and static hosting. None of these things are huge on their own, but together they create a complete little browser game.

Related projects

This game is connected to a few of my other projects and articles. If you want to try the earlier version of this idea, you can play my Pokémon Higher or Lower game here: Play Pokémon Higher or Lower

I also wrote a longer article about using APIs and local JSON files in browser games: How to Use APIs in a Web Game with JSON Optimization

You can find more of my projects here: View my projects

Conclusion

Country Higher or Lower started as another version of my Pokémon Higher or Lower idea, but it became its own useful project. Countries are more general than Pokémon stats, so the game is easier for more people to understand. The topic is simple, but the implementation still led to interesting technical problems.

The most surprising problem was the flag display. I expected emojis to be an easy solution, but they were not reliable enough on Windows. Using FlagCDN with country codes turned out to be cleaner and more dependable.

For me, this is exactly why small projects are useful. You start with a simple idea, but while building it, you run into real problems that teach you something. In this case, I learned more about APIs, JSON files, country codes, flag images, emoji rendering, and browser differences.

That is what makes these projects worth building. They are not only small games. They are also a way to learn how the web actually behaves.

Sources and credits

Country data is based on REST Countries: REST Countries

Flag images are loaded through FlagCDN: FlagCDN

For more background on the flag emoji issue on Windows and in browsers, these explanations were useful: The struggle of using native emoji on the web and Flag Emojis not rendering

This project is created for educational and entertainment purposes. It is not affiliated with REST Countries or FlagCDN.

Friday, April 24, 2026

How to Embed a GitHub Pages Project in Blogger

How to Embed a GitHub Pages Project in Blogger

One of the most useful things I learned while building small web projects is that you can host a project on GitHub Pages and then embed it directly into a Blogger post or page.

This is especially helpful if you are making small tools, browser games, JavaScript experiments, generators, calculators, or interactive pages. Instead of only writing about the project, you can let people try it directly on your blog.

For example, I use this workflow for some of my own web tools. I build the project with HTML, CSS, and JavaScript, publish it with GitHub Pages, and then place it inside a Blogger page using a simple iframe.

What you need

To embed a GitHub Pages project in Blogger, you need three things:

  • A working GitHub Pages project
  • A Blogger post or Blogger page
  • A little bit of HTML knowledge

You do not need a complex setup. The main idea is simple: GitHub Pages hosts the actual project, and Blogger displays it inside your blog using an iframe.

What is an iframe?

An iframe is an HTML element that lets you display another webpage inside your current page. You can think of it like a small window inside your blog post.

The iframe does not copy the project into Blogger. The project still runs on GitHub Pages. Blogger only shows it inside the post or page.

A basic iframe looks like this:

<iframe 
  src="https://your-username.github.io/your-project/" 
  width="100%" 
  height="600" 
  style="border: none;">
</iframe>

The most important part is the src attribute. This is where you put the link to your published GitHub Pages project.

Do not use the normal GitHub repository link

One common mistake is using the normal GitHub repository URL. That will usually look something like this:

https://github.com/your-username/your-project

That is not the link you want to embed. That link points to the source code repository, not the actual published website.

For the iframe, you need the GitHub Pages URL. That usually looks like this:

https://your-username.github.io/your-project/

That is the public website version of your project. If you open that link in your browser and your project loads like a normal website, then it is the right link to use inside the iframe.

How to enable GitHub Pages

Before you can embed the project, you need to publish it with GitHub Pages.

In your GitHub repository, go to:

Settings → Pages

Then look for the GitHub Pages build and deployment settings. In many simple projects, you can choose to deploy from a branch, usually the main branch, and select the root folder or the /docs folder depending on where your project files are.

For a simple HTML, CSS, and JavaScript project, your project usually needs an index.html file. That file is the entry point of the website. When someone opens your GitHub Pages URL, GitHub Pages loads that file first.

After you save the GitHub Pages settings, GitHub will publish your project. Sometimes it takes a few minutes before the page is available.

Embedding the project in Blogger

Once your GitHub Pages project is online, go to Blogger and create a new post or page.

Then switch from Compose view to HTML view. This is important because the iframe is HTML code.

Now paste this code where you want the project to appear:

<div style="margin: 20px 0;">
  <iframe 
    src="https://your-username.github.io/your-project/" 
    width="100%" 
    height="650" 
    style="border: none; max-width: 100%;" 
    loading="lazy">
  </iframe>
</div>

Then replace the example URL with your own GitHub Pages link.

What the iframe code does

Here is the same code again:

<iframe 
  src="https://your-username.github.io/your-project/" 
  width="100%" 
  height="650" 
  style="border: none; max-width: 100%;" 
  loading="lazy">
</iframe>

The src value is the page you want to embed. This should be your GitHub Pages URL.

The width="100%" part makes the iframe fill the available width of the blog post. This is usually better than using a fixed pixel width because it works better on different screen sizes.

The height="650" part controls how tall the embedded project appears. If your project is small, you can use a lower number. If your game or tool needs more vertical space, you can increase it.

The style="border: none;" part removes the default iframe border. Without this, the embedded project may have an old-looking frame around it.

The loading="lazy" part tells the browser that it can load the iframe later, when the visitor scrolls near it. This can help the page feel lighter, especially if the embedded project is not visible immediately.

A cleaner Blogger embed example

For Blogger, I like wrapping the iframe inside a div. This makes it easier to add spacing around the project.

<div style="margin: 24px 0; text-align: center;">
  <iframe 
    src="https://your-username.github.io/your-project/" 
    width="100%" 
    height="700" 
    style="border: 0; max-width: 100%;" 
    loading="lazy">
  </iframe>
</div>

This version gives the embedded project some space above and below, which usually looks better inside a blog article.

Optional: Add a button below the iframe

Sometimes it is useful to add a direct link below the embedded project. That way, if the iframe does not load properly on someone’s browser, they can still open the tool directly.

<div style="margin: 20px 0; text-align: center;">
  <a 
    href="https://your-username.github.io/your-project/" 
    target="_blank" 
    style="background: #333; color: white; padding: 12px 18px; border-radius: 6px; text-decoration: none; font-weight: bold;">
    Open Project in New Tab
  </a>
</div>

I think this is a good idea because it gives visitors a backup option. Some embedded projects may feel better in a full browser tab, especially games.

Common problems

The iframe is too small

If your project looks cut off, increase the height value:

height="800"

You can test different values until the project fits nicely.

The project does not load

First, open the GitHub Pages URL directly in your browser. If it does not work there, the problem is not Blogger. The project itself is probably not published correctly yet.

Also make sure you are using the GitHub Pages link, not the GitHub repository link.

The project works on desktop but feels bad on mobile

This usually means the project itself needs better responsive design. Blogger can show the iframe, but the project inside the iframe still needs to be mobile-friendly.

For small tools and games, it helps to design the project with flexible widths and simple layouts.

The iframe has weird spacing

You can adjust the wrapper div:

<div style="margin: 20px 0;">
  iframe goes here
</div>

The first value controls the top and bottom spacing. The second value controls left and right spacing.

Why this workflow is useful

I like this workflow because it connects coding projects with blogging. A normal blog post can explain the idea, the process, and what I learned. The embedded GitHub Pages project lets the reader actually try it.

This is much better than only showing screenshots. Screenshots are useful, but interactive projects make the post feel more alive.

It also helps organize small projects. Instead of having random GitHub repositories that nobody sees, you can turn each project into a blog post, a tool page, or both.

Example workflow

A simple workflow could look like this:

  1. Build a small HTML, CSS, and JavaScript project.
  2. Publish it with GitHub Pages.
  3. Create a Blogger page for the tool.
  4. Embed the GitHub Pages project with an iframe.
  5. Write a blog post explaining how and why you built it.
  6. Link the blog post and tool page together.

That last step is important. The tool page gives people a place to use the project, and the blog post gives search engines and readers more context.

Final thoughts

Embedding GitHub Pages projects into Blogger is a simple way to turn small coding experiments into real interactive content.

You only need a published GitHub Pages project, a Blogger post or page, and a small iframe snippet. Once you understand how the iframe works, you can reuse the same structure for games, generators, tools, calculators, and other small web projects.

For me, this makes Blogger much more useful. It is not just a place for writing. It can also become a small hub for projects, experiments, and interactive tools.

Related posts and tools

Wednesday, April 1, 2026

How to Use APIs in a Web Game (Pokémon Example with JSON Optimization)

I built a simple browser game where you compare two Pokémon and guess which one has higher total stats. The idea is simple: you see two Pokémon, choose the one you think is stronger, and then the game checks the real data.

The project started as a small JavaScript game using the PokéAPI. At first, the game fetched new Pokémon data directly from the API during gameplay. That worked, but it also created a scaling problem. Every round needed new API requests, and every player would create more requests.

To fix that, I changed the project so the game uses a local JSON file instead. The API is still used, but only once during the data-building step. During gameplay, the game loads the prepared JSON file and runs almost completely locally in the browser.


Overview

At the beginning, I used the most direct approach. Every time the user started a new round, the game sent fetch requests to the API. I had already used this pattern in an earlier project, a random Pokémon generator, so it felt natural to reuse it.

For a simple random generator, this works fine. One button click means one request, and the result appears on the page. But a game is different. A higher or lower game needs repeated rounds, and each round needs two Pokémon. Ten rounds can easily mean twenty API requests from one player.

This is a common beginner mistake when working with APIs. Just because you can fetch data on every interaction does not always mean you should. Many APIs have rate limits, require tokens, or cost money at scale. Even when an API is free, it is still better practice to reduce unnecessary requests.

I chose the PokéAPI because it is open, free to use, and does not require authentication. That makes it perfect for learning and experimenting. But even with a free API, the scaling problem is still worth understanding.

In this post, I explain how I built the game, what problems appeared when using live API requests, and how switching to a local JSON file made the project faster, simpler, and easier to expand.


The earlier project: Random Pokémon Generator

Before building the higher or lower game, I had already worked on a smaller project: a random Pokémon generator. This project is very simple. You click a button, and it shows a random Pokémon with its name and sprite.

The generator works by creating a random Pokémon ID in JavaScript. The script then sends a request to the PokéAPI using that ID. From the response, it only takes the data needed for the project: the Pokémon name and sprite image.

That data is then displayed on the page. The logic is small, but it teaches an important idea: a website can request real external data and use it to update the page.

random ID → API request → Pokémon data → update the page

This approach is completely fine for a small tool. One click usually means one request, and the user gets a result immediately.

I also wrote a separate post about that generator and made the tool available here:

Try the Random Pokémon Generator Read the Generator Article

The random generator gave me the starting point for the higher or lower game. I reused the same basic idea, but once I turned it into an actual game loop, the weaknesses of repeated live API requests became much more obvious.


The first approach: fetching data per round

When I started building the Pokémon Higher or Lower game, I first used the same approach as the random generator. The game selected two random Pokémon IDs, fetched both Pokémon from the API, extracted their names, sprite URLs, and stats, and then calculated the total stat value.

After that, the player chose which Pokémon they thought had the higher total. The game checked the answer, updated the score, and then started the next round.

The early version worked like this:

start round → fetch Pokémon A → fetch Pokémon B → compare stats → player guesses → next round

At first, this felt fine. The game worked, the logic was easy to understand, and the API gave me the exact data I needed. But the problem becomes clear when you count the requests.

One round already needs two API calls. If one player plays ten rounds, that is around twenty requests. If several people play the game, the number grows quickly. The more successful the game becomes, the more requests it creates.


Why repeated API requests became a problem

The main issue was that the game depended on live API requests during gameplay. Every new round needed fresh data from an external service.

That creates three practical problems.

1. The game becomes slower

Even if the API responds quickly, there is still a delay. The browser has to send the request, wait for the response, parse the JSON, and then update the game. In a game with many rounds, even small delays can make the experience feel less responsive.

2. The game depends on the API being available

If the API is slow, unavailable, or temporarily blocked, the game stops working properly. That is not ideal for a browser game, especially when the data does not actually need to be live.

3. The request count scales with the number of players

This is the biggest problem. If every player creates API requests during every round, the number of requests increases with traffic. For a learning project, this may not matter much, but it is still a bad pattern to rely on if the data can be prepared in advance.

One possible improvement would be browser caching. If a Pokémon was already fetched once, the game could store it and reuse it. That would reduce repeated requests for the same Pokémon.

But caching only partially solves the issue. There are over 1000 Pokémon, and in a short play session the chance of randomly getting the same Pokémon again is not always high. The game would still depend on live API requests for new entries.

So I decided to solve the problem differently. Instead of fetching Pokémon during gameplay, I moved the required data into a local JSON file.


The solution: using a local JSON file

The better solution was to separate the data-building step from the gameplay step.

Instead of asking the API for Pokémon during every round, I wrote a separate script that fetches all required Pokémon data once and saves it into a local JSON file.

That script goes through the Pokémon list, fetches each Pokémon from the PokéAPI, extracts the fields I need, and stores them in a smaller format. For this game, I only needed a few things:

  • Pokémon ID
  • Pokémon name
  • Sprite image URL
  • Individual stats
  • Total stat value

The total stat value can be calculated during the build step by adding the individual stats together. That way, the game itself does not need to do unnecessary work every time a round starts.

build script → fetch all Pokémon once → save useful fields → load JSON in the game

One important detail is how the images are handled. The JSON file does not store the actual image data. It only stores the URL to each Pokémon sprite. This keeps the JSON file much smaller and easier to load.

If the JSON file stored image data directly, it would become much larger and slower. For this project, sprite URLs are enough.

After the JSON file is created, the game no longer needs to call the API during gameplay. It loads the local JSON file once, stores the data in memory, and then selects random Pokémon from that local dataset.

The gameplay version then becomes much simpler:

load JSON once → pick two random entries → compare total stats → next round

This is a much better structure for this kind of project because Pokémon data does not need to be live. It does not change every minute like weather data, stock prices, or live sports scores.

If new Pokémon are added later, the JSON file can simply be rebuilt. That could be done manually, or it could be automated with something like a GitHub Action.


Performance and reliability improvements

The JSON approach improves the game in several ways.

Faster rounds

When the game used live API requests, every round depended on network speed. With local JSON data, the round can start almost instantly after the initial data load.

Fewer external dependencies

The game no longer depends on the PokéAPI being available during gameplay. If the API is slow or temporarily unavailable, the game can still work because the needed data is already stored locally.

Lower request count

During gameplay, the number of API requests is reduced to zero. The API is only used during the data-building process.

More consistent user experience

Every player uses the same local dataset. That means the game feels more consistent because each round no longer depends on external response times.

This shows an important principle when working with APIs: live requests are useful, but they should be used carefully. If the data does not need to change constantly, it is often better to load prepared data once and reuse it.


Why the JSON structure is easier to expand

Another advantage of the JSON approach is that the project becomes easier to extend.

Right now, the game compares total stats. But because the data is already stored locally, it would be easy to add more game modes later.

For example, the game could compare:

  • HP
  • Attack
  • Defense
  • Speed
  • Generation
  • Legendary status

It would also be possible to add filters, such as only showing Pokémon from a specific generation, only legendary Pokémon, or only final evolutions.

With live API requests, every new feature could require more request logic and more waiting. With local JSON data, the game already has the information available and can use it immediately.

That is why this project became more than just a small Pokémon game. It became a useful example of how preparing data in advance can make a web project faster, cleaner, and easier to expand.


Conclusion

The first version of the game worked, but it was not the best structure. Fetching Pokémon from the API during every round made the game depend too much on live requests.

Moving the required data into a local JSON file made the game faster, more reliable, and easier to expand. The API is still useful, but it is used at the right time: during the build process, not during every player interaction.

This is the main lesson from the project. APIs are powerful, but good API usage is not only about knowing how to fetch data. It is also about knowing when not to fetch data.

For static or rarely changing data, a prepared JSON file can be a better choice than repeated live requests. For real-time data, live API calls are still necessary. The important part is choosing the right structure for the type of data you are using.

Try the project

You can play the finished browser game here:

Play the Pokémon Higher or Lower Game

You can also view the source code on GitHub:

View Source Code on GitHub

Related projects

Disclaimer: This project is not affiliated with Pokémon, Nintendo, Game Freak, Creatures Inc., or any related companies. It is a personal fan-made project created for educational purposes only.

Monday, March 30, 2026

Pokémon Higher or Lower Game in JavaScript (From Generator to Game)

I Turned My Old Pokémon Generator Into a Higher or Lower Game

I recently made a small Pokémon Higher or Lower game, and the idea for it actually came from something I built a long time ago.

A few days ago I realized that I can embed my GitHub projects directly into my Blogger site. That made me go back and look through some of my older projects, and one of them was my random Pokémon generator.

That project is already a few years old, maybe two or three years, but it still works. It was also a good base to build something new on top of, because it already had the basic idea: pick a Pokémon, get data from the PokéAPI, and show it on the page.

I wanted to make something simple and fun. Not a big project, not a full game with lots of systems, just something small that I could finish quickly and embed on the blog.

The idea that came to mind was a classic Higher or Lower game. It fits really well with Pokémon because every Pokémon has stats, so you can compare them in a way that feels simple but still interesting.

So I built a small game where you get two Pokémon and you have to guess which one has the higher total stats. After each round you get a new comparison and try to keep your score going.

How the game works

The game is built around one simple question: which Pokémon has the higher total base stats?

Each Pokémon has different stats, like HP, attack, defense, special attack, special defense, and speed. For the game, those values are added together into one total number. That makes it easy to compare two Pokémon without making the rules too complicated.

The player sees two Pokémon, chooses the one they think has the higher total, and then the game checks the answer. If the guess is correct, the score goes up and the next round starts.

That is a simple loop, but it works because Pokémon are already familiar. Even if you do not know the exact stats, you probably have some feeling for which Pokémon might be stronger.

How it connects to the old generator

The original random Pokémon generator was much simpler. It picked a random Pokémon, fetched the data, and displayed the name and sprite.

For the Higher or Lower game, I reused that basic idea, but added game logic around it. Instead of only showing one random Pokémon, the game needs two Pokémon at the same time. It also needs to compare their total stats, track the score, and decide if the player guessed correctly.

So the project went from this:

random Pokémon → show name and image

to this:

two random Pokémon → compare stats → player guesses → update score

That small change makes the project feel much more like a game, even though the code is still beginner-friendly.

Why I used Pokémon data

Pokémon data works really well for beginner web projects because it is visual and structured. You get names, sprites, types, stats, and other information from the API.

The PokéAPI is also public and easy to experiment with. You can request data for a Pokémon and get a JSON response back. That makes it a good API for learning how browser projects can use external data: PokéAPI documentation.

For this game, the most useful part was the stats data. The API gives each Pokémon multiple stat values, and those values can be added together into one total score for the comparison.

The first version used live API requests

The whole project was actually very quick. I think the first version took me around two hours or something like that. It was not about building something complex. It was more about getting back into web development and using something I already had.

At first, the game requested Pokémon data live while the player was using it. That worked, but it was not the best long-term solution.

Every round needed new Pokémon data. That means more fetch requests, more waiting, and more dependence on the API being available. For a small project, that is not a disaster, but it made me think about how the project should work if more people used it.

The JavaScript Fetch API is the browser feature that makes this kind of request possible. MDN has a good explanation of how fetch works here: MDN: Fetch API.

Why I changed the data approach later

After building the first version, I realized that I did not really need to request the same Pokémon data over and over again while people were playing.

Pokémon base stats do not change every second. The game does not need live data for each round. So I changed the project to use a local JSON file instead.

That made the game faster and reduced unnecessary API requests. The browser can load the prepared data directly, and the game can choose random Pokémon from that local file.

I wrote a separate post about that part because it became the most interesting technical lesson from the project: How I used JSON to improve my Pokémon API game.

What I liked about this project

What I liked about this is that it connects directly to my old Pokémon generator. Instead of starting from zero, I could reuse the idea and turn it into something more interactive.

It also reminded me that small projects are useful. Not every project has to become a huge app. Sometimes a small idea is enough to practice JavaScript, work with APIs, and finish something playable.

For me, this was mainly about getting back into building things. I had an old project, I saw a way to expand it, and I turned it into a simple browser game.

Play the game

You can play the Pokémon Higher or Lower game here:

If you are interested in the original generator, you can check it out here:

I also wrote a separate post about the Pokémon generator itself and how it works:

Source code

The source code for both projects is available on GitHub:

Final thoughts

This project was not about making something huge. It was about taking an old idea and turning it into something more game-like.

The random Pokémon generator taught me how to fetch and display API data. The Higher or Lower game added comparison logic, scoring, and better data handling.

That is why I like projects like this. They are small, but each one teaches something new.

Sources and further reading

Friday, March 27, 2026

CD-R vs CD-RW: What’s the Difference and Which Should You Use?

When you want to start burning your own CDs, you will quickly come across two types of discs: CD-R and CD-RW. At first, CD-RW sounds like the better option because it can be reused. But for most people, especially if you want to make music CDs, CD-R is usually the better choice.

The main difference is simple: a CD-R can only be burned once, while a CD-RW can be erased and rewritten multiple times. That makes CD-RW more flexible, but it also comes with some downsides.

What is a CD-R?

CD-R stands for Compact Disc Recordable. It is a write-once disc. Once you burn data or music onto it, the disc cannot be erased and used again.

A CD-R uses a photosensitive dye layer inside the disc. When the burner writes to the disc, the laser changes parts of that layer and creates differences in reflectivity. A CD player or computer drive can then read those differences as data.

Canon has a good technical explanation of this process. They describe CD-Rs as using an organic dye layer, where the writing laser changes the dye and reflective layer so the burned areas behave like the pits on a normal pressed CD. If you are curious about the actual optical technology behind CDs, their explanation is worth reading: Canon Science Lab: CDs and DVDs.

Because CD-R is only written once, it is simple and usually works well for finished CDs. If you want to burn a playlist, make a CD for your car, create a small backup, or make a physical gift, CD-R is usually the safer option.

What is a CD-RW?

CD-RW stands for Compact Disc ReWritable. Unlike a CD-R, a CD-RW can be erased and used again.

It works differently from CD-R. Instead of using a normal dye layer, CD-RW uses a phase-change material. The burner heats this material in different ways so it changes between states with different reflectivity. That is what makes it possible to write data, erase it, and write new data again.

This is one of the more interesting technical differences between the two formats. CD-R is basically about permanently changing a dye layer, while CD-RW is about changing the physical state of a rewritable alloy. Canon explains that CD-RW discs use phase-change alloys that can switch between crystalline and amorphous states when heated by the laser.

That sounds useful, and sometimes it is. If you want to test something, move temporary files, or reuse the same disc several times, CD-RW can make sense.

Why CD-R is usually better for music

If you want to make a music CD, I would usually choose CD-R.

The biggest reason is compatibility. Many older CD players, car stereos, and retro devices are more likely to read CD-R than CD-RW. A CD-RW may be burned correctly and still not play in some devices.

This is especially annoying if your goal is to make a custom playlist for a car or an old CD player. In that case, you do not really care that the disc is reusable. You just want it to work.

Why CD-RW is not always worth it

CD-RW sounds convenient because you can erase it and use it again. But in practice, that feature is not always that useful.

Before reusing a CD-RW, you usually have to erase it first. That takes extra time. CD-RW discs can also be slower to burn, and they are often less compatible with older players.

For normal music CDs, it is usually easier to use a new CD-R instead of rewriting an old disc. Blank CD-R discs are often sold in packs and are usually cheap enough that the write-once limitation is not a big problem.

What about long-term storage?

Long-term storage is a bit tricky because it depends on the disc quality and how the discs are stored. Heat, sunlight, humidity, scratches, labels, manufacturing quality, and even the quality of the first burn can all affect how long a disc lasts.

The Library of Congress has a really useful page about CD-R and DVD-R/RW longevity research. What I like about it is that it does not give one simple fake answer like “all CDs last exactly 50 years.” Instead, it explains that recordable discs are made from different materials, that aging and storage conditions matter, and that actual lifetime predictions are difficult.

Their page also explains the material difference between recordable and rewritable discs: CD-R uses a photosensitive organic dye as the data layer, while rewritable media use a phase-changing metal-alloy film. If you want to go deeper into the preservation side, this is the most interesting source to read: Library of Congress: CD-R and DVD-R/RW longevity research.

In general, I would not treat any burned CD as a perfect archive forever. If something is truly important, keep another backup somewhere else too. But if you are burning music, personal files, or creative projects, CD-R is usually the better choice for something you want to keep.

So which one should you pick?

For most people, I would pick CD-R.

Use CD-R if you want to burn music, make a playlist, use an older CD player, play the disc in a car, or keep the disc for longer.

Use CD-RW if you specifically want to erase and reuse the disc, or if you just want to test something before burning the final version.

That is basically the whole decision. CD-RW is more flexible on paper, but CD-R is usually more useful in real life.

Related guides

If you are getting back into CDs, these guides may also help:

Sources and further reading

Stop Making Platformers First

A lot of indie games start as prototypes. You make something small, it feels fun, someone plays it, and suddenly the idea appears: maybe ...