Building the 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.

# 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 {{ gameState.energyPerClick }} 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 (technically 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.

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

Here we begin the template by outputting 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 is 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">{{ gameState.nextCircuitCost.energy }} 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">{{ gameState.nextCircuitCost.capacitors }} Capacitors </span>
<span v-if="gameState.nextCircuitCost.circuits !== undefined">{{ gameState.nextCircuitCost.circuits }} Circuits</span>
)</button>

Then at the end, we close the button element.

# 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, you’ll notice something’s missing. In the final part, we’ll make circuits actually do their job.

Checkpoint

Before moving on, you should be able to confidently:

  • Create a Clicker component that generates energy when clicked

    clicking the button increases the displayed energy

  • Create a StoreDisplay component that shows state with purchase buttons

    the capacitor and circuit buttons disable when you can’t afford them

  • Integrate both components into the app and run it

    the dev server serves a playable page at localhost