Skip to main content
null đź’» notes

Building incremental games as web applications

Have you ever wanted to make an idle clicker game like this?

Click at least three times.Good! Now try to get your number up to 100. Great job! Can you make it to 1,000?

Total clicks ever: ; Auto Clickers: ( clicks/second)

I really like these kinds of games. There's something about the predictability of numbers-go-up combined with exploration and unfolding that my brain is deeply attracted to. Lately I've been experimenting with different kinds of clicker game ideas, and when I do, I reach for starter code to get me up and running quickly that is very similar to what we are building in this tutorial.

My definition of an idle clicker game (or "incremental" game) is one that has at least three features:

In this guide, I'll walk you through how to start building a basic web-based idle clicker game like the one above that leverages Vue, Vite, Pinia, and TypeScript. By using web technologies for our prototypes or production games, we gain all the affordances of a modern browser coupled with an unparalleled distribution strategy (e.g., share a link to the game instead of having someone download and install a game).

By the end of the tutorial, you'll have a foundation from which to build your own web-based incremental game—and hopefully some inspiration to start making it your own!

We cover the following topics:

A follow-on tutorial will expand on this template, and add to our game:

For now, the goal is to keep it simple.

We'll be using the following computer instruction technologies:

Tool To follow along, you should be...
Vue Familiar (can read and understand what's going on)
Vite Somewhat familiar (have used or could use)
Pinia Vaguely familiar (have heard of)
TypeScript Familiar (can read and understand what's going on)

Demo and code #

⏭️ If you'd like to skip the tutorial and just get started with the template, it's available here on GitHub.

đź’» You can also play the demo of what we're building before we begin.

Designing the game #

We're building a foundation from which to build our clicker game ideas, so it makes sense to think about what a "foundational" clicker game might be so we can figure out what needs to be built.

Every clicker game starts out with some thing to click. Clicking the thing generates some kind of resource, and a resource is something that can be exchanged for other things and/or resources. Additionally, there will be some kind of way to automation resource generation, sometimes called "auto-clickers", for one or more resources.

The game we're building in this tutorial will consist of these basic elements of an incremental game—which I have helpfully referenced here for you as The Three Laws of incremental games— implemented as energy, capacitors, and circuits.

Here's the general outline of what the game will consist of:

  1. You generate energy. The core resource is Energy; you'll click a Create Energy button to create Energy.

  2. Capacitors increase energy per click. You can spend energy to purchase (just another word for generate, if you think about it) capacitors. Each capacitor increases the amount of energy you generate per click.

  3. Circuits auto-generate Energy. You can spend energy and capacitors on circuits. Each circuit automatically clicks the button that creates energy once per second. This allows players to generate energy even when they're not actively clicking the button (e.g., while they have your game running in a browser tab somewhere in the background).

When finished, we'll have a working prototype that is playable ad infinitum, with just enough scaffolding to start customizing and implementing functionality unique to the experience you want to create. It's a foundation for a game moreso than a working game demo per se; it's meant for you to finish this and then get to tinkering.

Setting up the project #

Let's start by setting up a project for our incremental game with create-vue, a tool for rapidly scaffolding a Vue project:

npm create vue@3

If you've never used create-vue before, you'll be prompted to confirm installation. Go ahead and proceed.

You'll be prompted to enter a project name. I'll be using pinia-clicker for the duration of this tutorial.

You'll be asked whether you want to install TypeScript. Since this demo uses TypeScript, choose yes.

As for the rest of the questions, the only thing I included was Pinia. Here's what my terminal looks like now that I'm done:

Vue.js - The Progressive JavaScript Framework

✔ Project name: … pinia-clicker
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Scaffolding project in /Users/jesselawson/dev/pinia-clicker...

Done. Now run:

  cd pinia-clicker
  npm install
  npm run dev

Follow the directions (cd into your project dir and run the commands above). When you're finished, you will have started the Vite development server and should be able to open the link in your terminal to view your application.

With the project scaffolded, we're ready to get started building the game.

Building the game components #

Let's start out defining some types that will give the game resources some structure. From there, we'll create a new Pinia store to manage the game's state and state mutations (e.g., when someone clicks, increment a resource, when someone purchases a resource, subtract the costs correctly, etc). Finally, we'll create some Vue components to collect input and represent our game state to the player.

Defining types #

Since type systems are syntactic methods of enforcing software abstractions —and writing software involves reasoning about different abstractions and their relationships—let's start our game by defining some types.

In the src folder of your project, create a new file called types.ts.

The first type we'll create represents a resource cost:

export type Cost = {
  [key: string]: number;
} & {
  energy?: number;
  capacitors?: number;
  circuits?: number;
};
What is [key: string]: number } & {?

This is an index signature. Generally, index signatures are used when you don't know the structure of a type ahead of time. We're using it here as part of an intersection type so that we can have a type of optional (but known) properties.

Technically, Cost is an intersection type comprised of of an index signature ([key: string]: number) and an object type with optional properties ({ energy?: number; capacitors?: number; circuits?: number; }).

const someCost: Cost = {
    energy: 10,
    circuits: 5,
    // note that the `capacitors` property is omitted
};

console.log(someCost.energy);       // Output: 10
console.log(someCost.capacitors);   // Output: undefined
console.log(someCost.circuits);     // Output: 5

Something in this game can cost any combination of the following:

Next, let's create a type that represents the base costs of things that we can purchase:

export type BaseCosts = {
  [key: string]: Cost;
} & {
  energy?: Cost;
  capacitors: Cost;
  circuits: Cost;
};
Why is energy optional?

Energy is the basic resource of the game, so I don't want it to cost anything. Since a cost of some combination of energy, capacitors, and circuits, the resulting base cost for what is essentially a free resource would be:

energy: {
  energy: 0,
  capacitors: 0,
  circuits: 0
} as Cost,

Making it optional here with the ? operator will allow us to define base costs for capacitors and circuits and elide a definition of energy costs.

Use this technique for other resources you add to the game that don't cost anything to produce (e.g., maybe you have a research tree that just costs time instead of resources).

Why do we need to define base costs?

The base costs are used along with a few other variables to calculate the costs of each purchase. The formula—sometimes called a "growth formula" or "cost function"—goes like this:

next cost = [base cost] * [cost multiplier]^[# owned]

Here, "cost multiplier" is synonymous with "rate of growth". For our game, we're going to use a cost multiplier of 1.35. This is a completely arbitrary number; tweak it while you play your prototype until you settle on a value that feels nice, just make sure it's always above 1.00. The multiplier is the rate of growth, so a larger multiplier is going to produce a more drastic exponential increase than a smaller one.

To illustrate how this works, let's say we set the base cost for circuits to 25 energy and 10 capacitors. Using our formula, the first circuit will cost 25 energy and 10 capacitors:

next energy cost    = 25 * 1.35^0 = 25
next capacitor cost = 10 * 1.35^0 = 10

After we purchase our first capacitor, the cost of the next capacitor will be:

next energy cost    = 25 * 1.35^1 = 33
next capacitor cost = 10 * 1.35^1 = 13

The last type we'll define has to do with our game's state:

export type GameState = {
  [key: string]: number;
} & {
  energy: number;
  capacitors: number;
  circuits: number;
  costMultiplier: number;
  baseCosts: {
    capacitors: Cost;
    circuits: Cost;
  };
};

These types give us an expandable structure from which to build an incremental game. In the next section, we'll use them to implement the core features of our game.

Managing state #

Let's now implement our state management system with Pinia.

By the end of this part, you will have:

What is Pinia and why are we using it?

Pinia is a state management library for Vue. With Pinia, you create a place to store your data aptly called a "store". Your stores have an initial state, getters for retrieving state data, and actions for mutating state data.

If you'd like to learn more about Pinia, I recommend the official documentation.

Inside the src folder you'll find that create-vue has setup a basic Hello World project for us. Since we opted in to Pinia for state management during the setup process, there is a src/stores/ folder with an example store counter defined in counter.ts.

Let's rename src/stores/counter.ts to src/stores/gamestate.ts. We'll use this file to define a Pinia store that represents and manages our game's state.

Next, delete everything in that file, then import and use defineStore to start things off:

import { defineStore } from 'pinia'

export const gameState = defineStore({
  id: 'gamestate',

Here we begin defining gameState, something we'll import into other parts of our game engine here to get and mutate state.

A Pinia store has, in addition to an id, three main sections:

We'll define these in order, starting with state:

// Describe the initial state of our store:
state: () => ({
  energy: 0,            // Start at 0 energy
  capacitors: 1,        // Start with 1 capacitor
  circuits: 0,          // Start with 0 circuits
  costMultiplier: 1.35, // Set rate of growth to 1.35
  baseCosts: {
    capacitors: {
      energy: 3        // Capacitors base cost is
    } as Cost,         // 3 energy
    circuits: {
      energy: 5,       // Circuits base cost is
      capacitors: 5    // 5 energy and 5 capacitors
    } as Cost
  } as BaseCosts
} as GameState),

The state property is where we describe our game's state— the variables we want to keep track of and be able to pass into other parts of our game.

State property What it's for
id The unique identifier for the store
energy The total amount of energy generated in the game
capacitors The number of capacitors purchased; each increase the amount of energy generated per click
circuits The number of auto-clickers purchased; each automatically click one time per second per unit purchased

That takes care of our game's initial state. Our next step is to define some methods to retrieve data from our game's state manager. For the game we are building, we need to know the following:

Before we define these getters, though, let's build some helper functions based on the information we know we'll need.

Generating the next resource cost #

One thing we'll need to know throughout this game is the cost of the next resource we want to purchase. I'm using the word "resource" loosely here. In your game, you may call it an "upgrade" or "unlockable" or something like that. Either way, the basic premise is the same: I should be able to see the amount of other resources I will need to purchase something.

At the top of src/stores/gamestate.ts, just after the import statement, let's define a helper function that will calculate the next resource cost of a given resource:

const generateNextCost = (state: any, resource: string): Cost => {
  // Get the base cost for the resource:
  const baseCosts = state.baseCosts[resource];

  // Get how many of these resources we already own:
  const currentResourceCount = state[resource];

  // Iterate over baseCosts to dynamically calculate
  // the next cost for each cost type (e.g., energy,
  // capacitors, circuits). The reduce function helps us
  // accumulate the next cost values into a new `Cost`
  // object that we can then return:
  return Object.keys(baseCosts as BaseCosts).reduce(
    (nextCost: Cost, costKey: string) => {
      // Get the base cost—or, if baseCosts[costKey] is falsy,
      // default the base cost to zero:
      const baseCost = baseCosts[costKey] || 0;

      // Calculate the next cost by multiplying the
      // base cost by the cost multiplier raised to the
      // power of the current resource count, and wrap it
      // in Math.floor() to ensure the result is an integer:
      nextCost[costKey as keyof Cost] = Math.floor(
        baseCost * Math.pow(state.costMultiplier, currentResourceCount),
      );
      return nextCost;
    },
    {},
  );
};

With this function, we'll be able to pass along a reference to our game's current state and the string name of something we can buy, then get a Cost object in return containing the cost of that purchasable item:

// For example:
const nextCircuitCost = (state) => generateNextCost(state, "circuits");

The state parameter will come from Pinia; each getter will be passed the current state so we can always get the most current value of everything.

Checking if player can afford next resource #

Knowing the cost of the next purchase with generateNextCost, we can now create a second helper function to return whether we can actually afford it. This second helper function will compare the next cost with the player's current inventory, then return true or false base on whether the player can afford it:

const canAffordNext =

  // Check if the player can afford the next resource
  // based on the current state:
  (state: GameState, resource: string): boolean => {
    // Generate the cost of the next resource based on the current state:
    const nextCost = generateNextCost(state, resource);

    // Check if player has enough of each cost key:
    return Object.keys(nextCost).every(
      // For every cost key, see if the matching state key
      // is greater than or equal (if yes, then player
      // can afford):
      (costKey) => {
        // Get the current count of the resource,
        // and default to zero if it doesn't exist:
        const currentResourceCount = state[costKey] || 0;

        // Get the cost of the resource,
        // and default to zero if it doesn't exist:
        const cost = nextCost[costKey as keyof Cost] || 0;

        // Return whether the current count is
        // greater than or equal to the cost:
        return currentResourceCount >= cost;
      },
    );
  };

With these two helper functions, we're now ready to implement the getters for our Pinia store that holds are game's state.

Getter functions #

Our src/stores/gamestate.ts file has two helper functions followed by the beginning of our Pinia store. The store has the state configured, and we're now ready to get started on the next section: the getter functions.

Getter functions are used to "get" variables from the state and bring them into our presentation logic—which, for this game, are Vue components.

Just like how the initial state is defined in a state property, getters in a Pinia store are defined in a getters property. So let's start by creating a getters property that we can put our getters in, along with our first getter:

  } as GameState),

  // Define our getters:
  getters: {
    getEnergy: (state) => state.energy,

Our first getter returns the total amount of energy in our current state. Notice how we provide state as a parameter. In Pinia, getters rely on state, so any getter function should, at a minimum, expect a state.

Since capacitors represent how much energy is generated per click and circuits represent how many clicks are automatically made per second, let's create a getter for each of those next:

// Define our getters:
getters: {
  getEnergy: (state) => state.energy,
  energyPerClick: (state) => state.capacitors,
  energyPerSecond: (state) => state.circuits,

Now let's create some getters to provide resource costs.

We can start with a getter that leverages our generateNextCost helper function to use the state and a name of a resource to calculate the next cost of something:

// Define our getters:
getters: {
  getEnergy: (state) => state.energy,
  energyPerClick: (state) => state.capacitors,
  energyPerSecond: (state) => state.circuits,
  nextResourceCost: (state) =>
    (resource:string) => generateNextCost(state, resource),

In the nextResourceCost getter, we pass along the state and also the name of a resource into generateNextCost. While this is a good general-use getter, we can create explicit getters for the resources we already know we'll need to know the next cost of (i.e., capacitors and circuits) as well:

// Define our getters:
getters: {
  getEnergy: (state) => state.energy,
  energyPerClick: (state) => state.capacitors,
  energyPerSecond: (state) => state.circuits,
  nextResourceCost: (state) =>
    (resource:string) => generateNextCost(state, resource),
  nextCapacitorCost: (state) => generateNextCost(state, "capacitors"),
  nextCircuitCost: (state) => generateNextCost(state, "circuits"),

Just like when we built the helper functions, we not only need to know what the next resource costs are but also whether we can afford the next resource. Let's build our final getter functions that leverage the other helper function:

// Define our getters:
getters: {
  getEnergy: (state) => state.energy,
  energyPerClick: (state) => state.capacitors,
  energyPerSecond: (state) => state.circuits,
  nextResourceCost: (state) =>
    (resource:string) => generateNextCost(state, resource),
  nextCapacitorCost: (state) => generateNextCost(state, "capacitors"),
  nextCircuitCost: (state) => generateNextCost(state, "circuits"),
  canAffordCircuit: (state) => canAffordNext(state, "circuits"),
  canAffordCapacitor: (state) => canAffordNext(state, "capacitors"),
},

All of these getters will be used when we implement the game's Vue components (our presentation—or "view"—layer). But we're not quite ready to move on to our presentation logic just yet!

While getters are used to get data from our game's state, actions are used to mutate—or change—data in our game's state.

In the next section, let's define some actions that our game components will use to modify state values.

Action functions #

The third and final section of our Pinia store's definition is its actions. Actions are functions that modify state values.

There are three actions that can happen within our game's state: energy can be generated, a capacitor can be purchased, and a circuit can be purchased. Each of these actions will have their own separate function in our store's actions property.

Let's go ahead and define them all now:

  // Define actions that mutate state values:
  actions: {
    // If amt given, just generate that much energy.
    // Otherwise, assume click
    generateEnergy(amt?:number) {
      if(amt) {
        this.energy += amt
      } else {
        this.energy += this.energyPerClick
      }
    },

    addCircuit() {
      if(this.canAffordCircuit) {
        this.energy -= this.nextResourceCost("circuits")?.energy ?? 0
        this.capacitors -= this.nextResourceCost("circuits")?.capacitors ?? 0
        this.circuits++
      }
    },
    addCapacitor() {
      if(this.canAffordCapacitor) {
        this.energy -= this.nextResourceCost("capacitors")?.energy ?? 0
        this.capacitors++
      }
    }
  }
}) // End of pinia store definition

Notice how actions can call getters by using this, like how addCircuit() first checks if the player can even afford a circuit via this.canAffordCircuit.

Each of these actions are generally related to a button that the player can press, i.e., addCapacitor will be called when the player tries to buy a capacitor and addCircuit will be called when the player tries to buy a circuit. The dual-purpose generateEnergy function will either generate some amount that's passed as the amt parameter or however much we currently generate per click.

With the state, getters, and actions all defined, our Pinia store is complete—and with it, most of the logic for our game!

In our next and final step, we'll bring all of this game logic onto the screen and build something we can start actually playing in the browser.

Game components #

Our final task is to bring all of what we've created so far together into the presentation layer of our app. To do that, we'll be building two Vue components: one for our main clicker button, and another for the current state of all our resources along with buttons to purchase capacitors and circuits.

By the end of this part, you will have:

Creating the Clicker component #

Let's begin by creating the first button of the game.

First, go to the src/components folder and delete the placeholder Vue files (*.vue). These are the files that came with the project when we first set it up.

Then, create a new file called Clicker.vue in the src/components directory, and add the following code:

<script setup lang="ts">
import { gameStateStore } from '@/stores/gamestate';

const gameState = gameStateStore();
</script>

<template>
    <div>
      <button @click="gameState.generateEnergy()">Generate  Energy</button>
    </div>
</template>

This creates a new Clicker component with a button that generates energy when clicked. Notice how we can invoke the Pinia store's actions in the template after we import the store in the setup script.

Wait—what are the elements of a Vue component file?

Using TypeScript with Vue's Composition API, our files are going to be split into two parts (technical three, but more on that in a bit): the setup script and the template. They can be in any order. Review the Vue docs for more.

The setup script is defined in a script tag, like this:

<script setup lang="ts">
//...
</script>

The template is defined in a template tag, like this:

<template>
//...
</template>

Put them together, and you get a full Vue component file:

<script setup lang="ts">
//...
</script>

<template>
//...
</template>

There's a third, optional part to a Vue component file, which is a scoped style block. You can include component-specific styling in a style tag just like you would on a normal HTML page. We'll be doing this in the last section of this part.

This component gives us both our starting resource generation button and a template to use when you're ready to add another button like it.

With our primary button finished, let's now create a component to display all the game data variables we're managing with Pinia.

Creating the StoreDisplay component #

The next component we'll create displays all the state variables we want the player to know about and includes two buttons for making purchases (capacitors and circuits).

Create a new file called StoreDisplay.vue in the src/components directory and add the following code:

<script setup lang="ts">
import { gameStateStore } from '@/stores/gamestate';

const gameState = gameStateStore();
</script>

<template>
  <div>
    <p>Energy: </p>
    <p>Capacitors: </p>
    <p>Circuits: </p>
    <p>Energy per click: </p>
    <p>Energy per second: </p>
    <button @click="gameState.addCapacitor" :disabled="!gameState.canAffordCapacitor">Purchase Capacitor (
      <span v-if="gameState.nextCapacitorCost.energy !== undefined"> Energy </span>
      <span v-if="gameState.nextCapacitorCost.capacitors !== undefined"> Capacitors </span>
      <span v-if="gameState.nextCapacitorCost.circuits !== undefined"> Circuits </span>
      )</button>
      <br/>
      <button @click="gameState.addCircuit" :disabled="!gameState.canAffordCircuit">Purchase Circuit (
      <span v-if="gameState.nextCircuitCost.energy !== undefined"> Energy </span>
      <span v-if="gameState.nextCircuitCost.capacitors !== undefined"> Capacitors </span>
      <span v-if="gameState.nextCircuitCost.circuits !== undefined"> Circuits</span>
      )</button>
  </div>
</template>

Here we being the template by outputing some variables from our game's state, then we provide two buttons. Each button has a set of span elements for a label; the v-if directive will only display the element if the given condition true. These spans assemble the full cost of something based on whether or not it has any energy, capacitors, or circuits costs associated with it.

Let's deconstruct the second button that adds a circuit to see how it works.

<button @click="gameState.addCircuit" :disabled="!gameState.canAffordCircuit">Purchase Circuit (

First we create a button element, assign the addCircuit action from the store to a click event, and disable the button if ever canAffordCircuit is false. The button's text label starts out with "Purchase Circuit (" and then we move on to the dynamic span elements for cost:

<span v-if="gameState.nextCircuitCost.energy !== undefined"> Energy </span>

If the next circuit cost's energy property is not undefined (i.e., if there is an energy cost associated with purchasing the next circuit), then output the amount followed by the word "Energy."

This pattern is repeated for the potential capacitor and circuit costs as well:

<span v-if="gameState.nextCircuitCost.capacitors !== undefined"> Capacitors </span>
<span v-if="gameState.nextCircuitCost.circuits !== undefined"> Circuits</span>
)</button>

Then at the end, we close the button element.

Our component implementations are now complete, so now let's register them with our app and do some end-of-tutorial tidying up to finish our game template. We're almost done!

Integrating the components into the game #

With our two components built, let's now integrate them into our Vue app so that we can tinker and interact with what we've built so far.

Open the App.vue file and replace the existing code with the following:

<script setup lang="ts">
import Clicker from '@/components/Clicker.vue'
import StoreDisplay from '@/components/StoreDisplay.vue'

</script>

<template>
  <div id="app">
    <Clicker />
    <StoreDisplay />
  </div>
</template>

<style>
#app {
  display: flex;
  justify-content: center;
  align-items: top;

  height: 100vh;
}

#app > * {
  margin: 1rem;
}
</style>

The template here is where we lay out how the game appears—which, for the purpose of being a template, is very basic. This component is used in the project's main.ts file to define our app and then mount it to an HTML element.

At this point, you're ready to play your game!

Start the dev server, and open the link to localhost emitted in your terminal window. For example, mine was:

npm run dev
  VITE v4.3.9  ready in 360 ms

  âžś  Local:   http://localhost:5174/
  âžś  Network: use --host to expose
  âžś  press h to show help

Everything should be working as expected—but as soon as you purchase a circuit (technically, one second after), you'll see we still have some more work left to do. We need a way to account for changes to energy every second. We can do that in the browser with setTimeout(), but we're actually going to roll our own version more appropriate for a game's needs.

Remember back in src/stores/gamestate.ts when we created those two helper functions, generateNextCost and canAffordNext? I'm about to introduce a third helper function we'll use instead of setTimeout(), which means there's opportunity now to clean up this game template we're writing by creating a dedicated space for helper functions. I wont put the two we've already created in there so that you have something to do in case you're out of ideas but still want to feel productive.

Let's create a new file called src/helpers.ts, and in it, define setTimeout2:

export const setInterval2 = (fn: VoidFunction, time: number) => {
  // A place to store the timeout Id (later)
  let timeout: any = null;

  // calculate the time of the next execution
  let nextAt = Date.now() + time;

  const wrapper = () => {
    // calculate the time of the next execution:
    nextAt += time;

    // set a timeout for the next execution time:
    timeout = setTimeout(wrapper, nextAt - Date.now());

    // execute the function:
    return fn();
  };

  // Set the first timeout, kicking off the recursion:
  timeout = setTimeout(wrapper, nextAt - Date.now());

  // A way to stop all future executions:
  const cancel = () => clearTimeout(timeout);

  // Return an object with a way to halt executions:
  return { cancel };
};

If you're curious about why I wrote a previous blog post about this function here.

To get our circuits generating energy every second, go to App.vue, import our new helper, and use Vue's onMounted method to handle passive energy production with our helper's help:

<script setup lang="ts">
import Clicker from '@/components/Clicker.vue'
import StoreDisplay from '@/components/StoreDisplay.vue'
import { gameStateStore } from './stores/gamestate';
import { setInterval2 } from '@/helpers';
import { onMounted } from 'vue';

const gameState = gameStateStore();

onMounted( () => {
  setInterval2(()=>{
    gameState.energy += gameState.energyPerSecond * gameState.circuits
  }, 1000);
});

</script>


<template>
  <div id="app">
    <Clicker/>
    <StoreDisplay/>
  </div>
</template>


<style>
#app {
  display: flex;
  justify-content: center;
  align-items: top;

  height: 100vh;
}

#app > * {
  margin: 1rem;
}
</style>

So every 1000ms, add the product of gameState.energyPerSecond and gameState.circuits to gameState.energy.

Vue's onMounted lifecycle hook docs go into more detail, but basically, when the component is ready to have things done to it, it invokes this method and our timer starts.

Run the development server again, and buy a circuit. You should see energy accumulating passively.

Congratulations! You finished this tutorial, and in doing so, built your very own incremental game template. What feature will you add to it next? What game idea do you want to prototype and play around with? I'm excited to see what you build, so don't hesitate to reach out and let me know when your game is ready to share.

Where to go from here #

The basic features are here, but there is so much more than just generating resources and purchasing items that makes a great incremental game. What's the next feature to implement? Where do you go from here? Well, I have a few ideas:

Some of these things will be available in the next version of this tutorial—a free online book, just like my other ones.

All source code for this project is available here.

Questions? Comments? Feedback?
Please open an issue or reach out on Mastodon.

If you enjoyed this tutorial, feel free to show your support by buying me a coffee, starring the demo project on GitHub, or saying hello on Mastodon.

Have fun!

--Jesse