Skip to content

Instantly share code, notes, and snippets.

@cgcardona
Last active May 3, 2023 20:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cgcardona/4177194ba2cd3cb3633d3d65acf26c12 to your computer and use it in GitHub Desktop.
Save cgcardona/4177194ba2cd3cb3633d3d65acf26c12 to your computer and use it in GitHub Desktop.

$RUN

Slides

https://docs.google.com/presentation/d/1az-hY7Zas2lEQm0jS6laDcoxnJz__FxM0uNd7Gqy5ec/edit?usp=sharing

Introduction

Let's go ahead and get started. My name is Gabriel Cardona and I am a Developer Evangelist from Ava Labs. I'm on the Developer Relations Squad. We are led by the amazing Luigi Demeo and the team now consists of Usman, Martin, Meaghan, Andrea and myself. Our goal is to accelerate the adoption of Avalanche by developers by providing resources needed for devs to go from idea to application and from hobbyist to professional. We are always hiring so check out https://www.avalabs.org/careers to see which opening we have on the DevRel Squad and across all of Ava Labs.

This workshop is on building a simple Web3 game. We're going to talk about the convergence of Web3 and gaming and then we're going to build a simple game to demonstrate the potential of Web3 games.

Traditional Gaming

Gaming has become the world’s most popular form of entertainment. The sector is now worth more than $200B and is growing at over 10% yearly. By the end of 2023, we expect to see 3 billion gamers – out of the total population of 8 billion.

Gaming spans dozens of genres with 3 major platforms: PC, mobile, and console – both online and offline. But when it comes to monetization, there are only two main models. First is premium games that charge a fixed price for full access. Second is free-to-play games that attract a larger player base and gain profit via in-game purchases and advertisements.

As the gaming sector grows, its setbacks also become more evident. There’s a strain on consumer spending since purchased assets only hold value within the ecosystem of that particular game. And even so, gamers don’t truly “own” anything – from in-game assets to personal data – game publishers are the ones calling the shot.

Web3 gaming facilitates a power shift from creators to players.

What is Web3?

First, what is Web3? The original web was created by Sir Tim Berners Lee in 1989. His idea was to create open, decentralized protocol that allowed information-sharing from anywhere on Earth.

The first inception of Berners-Lee's creation, now known as 'Web 1.0', occurred roughly between 1990 to 2004. Web 1.0 was mainly static websites owned by companies, and there was close to zero interaction between users - individuals seldom produced content - leading to it being known as the read-only web.

The Web 2.0 period began in 2004 with the emergence of Web Standards and conventions such as AJAX and additionally social media platforms. Instead of a read-only, the web evolved to be read-write. Instead of companies providing content to users, they also began to provide platforms to share user-generated content and engage in user-to-user interactions. As more people came online, a handful of top companies began to control a disproportionate amount of the traffic and value generated on the web. Web 2.0 also birthed the advertising-driven revenue model. While users could create content, they didn't own it or benefit from its monetization.

Web3 has become a catch-all term for the vision of a new, better internet. At its core, Web3 uses blockchains, cryptocurrencies, and NFTs to give power back to the users in the form of ownership. A 2020 post on Twitter said it best: Web1 was read-only, Web2 is read-write, Web3 will be read-write-own.

  • Web3 is decentralized: instead of large swathes of the internet controlled and owned by centralized entities, ownership gets distributed amongst its builders and users.
  • Web3 is permissionless: everyone has equal access to participate in Web3, and no one gets excluded.
  • Web3 has native payments: it uses cryptocurrency for spending and sending money online instead of relying on the outdated infrastructure of banks and payment processors.
  • Web3 is trustless: it operates using incentives and economic mechanisms instead of relying on trusted third-parties.

Credit to https://metav.rs/blog/web3-market-statistics-2022-2023 for the graphic

Web3 Gaming

So what exactly is Web3 gaming? Web3 games are different from traditional games in that they allow players to earn cryptocurrency and non-fungible tokens (NFTs), powered by blockchain technology. Players can own their acquired assets, which can then be traded on marketplaces for virtual or real-world currencies.

On the right you can see a comparison of Web3 gaming with traditional gaming.

Credit to https://ekoios.vn/web3-games-and-traditional-games-comparison for the graphic

As mentioned before, Web3 gaming facilitates a power shift from creators to players and it absolutely here to stay.

Demo

Now that we have a better understanding of Web3 gaming let's build one! Today we're building a simple came called $RUN. It has the same game mechanics as the dinosaur game which appears in Chrome when you don't have a network connection. Your player runs forward and you have to press the space bar to jump and avoid obstacles. There are 2 types of awards. First you get awarded $RUN tokens for the longer you stay alive. Second you get NFTs for each of the NFT icons that you jump and collect. The NFTs are visible in OpenSea's Fuji NFT Marketplace.

Check out this github repo to play along at home later on.

Tech Stack

First let's talk about what tech we're going to be using to create our game. We'll be using traditional Web2 tech for creating the website. HTML for creating the structure of the website. CSS for styling the website. Javascript for adding interactivity to the website.

Next we're using Solidity for writing the smart contracts. We're going to have 2 smart contracts. The first is an ERC20 fungible token with the ticket symbol $RUN. This will be a token which the user is rewarded for the longer they play before hitting an obstacle. There will also be an ERC1155 token collection which we're making our non-fungible tokens which the user is rewarded for each time they jump and collect an NFT.

Next you'll need NodeJS and NPM to install all of the dependencies.

We're using hardhat as our Web3 development environment. It contains all the tools you need to develop, debug, test and deploy smart contracts.

We're using EthersJS to interact with the blockchain. This will be used for the website to talk to the smart contracts and reward the user NFTs and $RUN tokens.

Lastly we're using the Live Server Extension to launch a local development server and running our app

index.html

Now to get started I have 2 empty directories, contracts/ and game/. Inside the game/ directory I'm going to create a new file called index.html which is where we're going to create the structure of our app. In this empty file type ! and it should give you a boilerplace empty html file. Replace the <title> with something like "Blockchain Game"

<link>

All of the styles or CSS will be placed inside of a new file which we'll create called styles.css. Inside of our HTML we want to link to this file by using the <link> tag and we'll point the href attribute to styles.css.

<script>

Now for users to interact with the game we're going to need a new file called script.js. We also want to link this in the HTML file but for this we're going to use the <script> tag and point the src attribute to script.js. Note that we added the type='module' attribute. This is because we're using the script as a module which allows us to add the data- attribute to this span and reference it later using javascript.

claimNFT button

Inside the <body> we'll have 3 buttons which interact with the blockchain. claimNFT with a class of button1. This class gives a style and position of our button which we'll create later in styles.css. This button also has an onclick event attribute which calls the claimNFT function when the button is clicked. This function will interact with the blockchain and will claim the user's NFTs. We'll define this function in a separate javascript file soon.

claimTokens button

Similarly we'll create another button for claiming tokens. It will have a class of button2 and an onclick event attribute which calls the claimTokens function.

connectWallet button

Also before the user claims their tokens or NFTs they need to connect the game to MetaMask using a connectWallet button with a class of button3 and an onclick event attribute which calls the connectWallet function.

Total Score and Current Score

This game will have a total score and a current score.

nftTotalScore

For this we'll create a nftTotalScore <span> starting at 0. This is the total number of NFTs which you have accumulated across all your times playing the game which you can claim later.

grunTotalScore

We'll also create a grunTotalScore <span> starting at 0. This is the total number of $RUN which you have accumulated across all your times playing the game which you can claim later.

nftCurrentScore

Next we'll create an nftCurrentScore <span> also starting at 0. This is the NFT score of your current game session.

runCurrentScore

Lastly we'll create an runCurrentScore <span> also starting at 0. This is the $RUN score of your current game session.

Note that all of the spans have a class for style and position via CSS and an HTML5 data- attribute for interactivity via javascript.

game

Now we create a game <div> which will contain the actual components of the game. It has a class of game and a data attribute of data-game

start-screen

We have another div which says "Press Any Key To Start"

Assets

To add the graphics for our game we're going to create an imgs/ directory and copy over all of the graphics for the game. Inside we have a ground image. This is the ground that the player will run on. Next we have an NFT image. This is the NFT that the player will collect. Then we have an obstacle image. This is the obstacle that the player must avoid by jumping over. We have a player jump image which will be shown when the player jumps. We have player runs 0 and player runs 1. We will swap back and forth between these images to make it appear that the player is running. When you start the game this player stands image will show.

ground

Once we have the assets in place I'll create an <img> tag with a class of ground and data-ground attribute. I'll create an identical <img> tag because we'll show this ground image once and as this img goes out of our view then we'll show another image and we want to continue looping these.

playerStands

Lastly in the game <div> we add the playerStands <img> which is the graphic of the player standing

Ethers Script

Next we need to include the EthersJS library which we'll use to interact with the blockchain. We can do that by placing a <script> tag and including it from a CDN.

Blockchain script

Lastly we want to include another <script> tag which we'll point to blockchain.js which will include our blockchain logic.

Now let's implement the game logic in the script.js file.

script.js

Script Imports

First I want to include several import statements at the top of the file. You can see that I'm importing functions from several files which don't yet exist. I'll implement those shortly. You can see that we will be creating scripts for ground, player, obstacle, and nft. In each script we'll be implementing functions which setup the respective component, get the components rectangle so that we can ensure there are no collisions and lastly update the components variables.

Constants

Next I'm adding constants. GAME_WIDTH is how long the running track will be. GAME_HEIGHT is of course the height. SPEED_SCALE_INCREASE is to continuousy make the game a bit harder.

Script DOM

Now I'll include all of my DOM API calls. The DOM is the Document Object Model. It's an in-memory representation of an HTML document. It's the data representation of the objects that comprise the structure and content of a document on the web. The Document Object Model (DOM) is a programming interface for web documents. It represents the page so that programs can change the document structure, style, and content. The DOM represents the document as nodes and objects; that way, programming languages can interact with the page.

We grab the game element which holds the main components of the game. Also the score element, the start screen element, the run total score element, the nft total score element, and the nft score element.

setPixelToGameScale definition

We want to make our game responsive. For that we'll create a setPixelToGameScale function which will scale our game based on our screen size.

setPixelToGameScale invocation

I'll call the function right above it and I'll add a window event listener so that anytime you resize we call the setPixelToGameScale function. I'll also add a keydown event listener to the document object so that whenever you press some key it will handle the start of the game by calling handleStart only once.

update function

As with every game we want some type of game loop which will be our update function. We'll call requestAnimationFrame on the window object. This function gets called whenever we can render content on the screen. We'll pass in the update function. We'll call the same requestAnimationFrame function within the update function so that we can create a loop.

Inside the update function we want to update our player on each frame and we calculate this via the lastTime variable. The first time it'll be null and we want to set the lastTime variable to the time it took and then call requestAnimationFrame again and then just exit out of the function.

We'll calculate the time between frames with the delta variable. We only want to update our ground, player, obstacle and other things on each frame. We'll create these update functions in their respective files. If you remember I imported them at the top but we still need to implement them.

Each time the update function gets called we want to check if we have lost and if we have call handleLose to reset all the variables.

checkLose

Next I'll implement a checkLose function which checks if there is a collision with the player. This uses a isCollision function which we'll implement.

isCollision

This function takes in a rect1 and rect2 and checks if there has been a collision

checkIfWeGotNFT

In a similar way we want to check if we got an NFT. Inside we want to get the player rect and use isCollision and if there is a collision we want to remove the NFT and increment the score by 1 and update the UI.

updateSpeedScale

This function takes in the delta and updates the speed scale by adding the delta times the SPEED_SCALE_INCREASE constant.

updateScore

This function also takes in the delta and adds it to delta plus 0.01. This is our current $RUN score. We want to take in the score. Just make sure it's a whole number by using Math.floor and update the UI for our score element.

handleStart

Next we have the handleStart function. This is where we'll call all of the setup function for ground, player, obstacle and NFT. And we'll reset the score and other variables. We also want to hide the start screen which says "Press Any Key To Start" and we'll call the requestAnimationFrame

window properties

For the handleLose function I'll create a totalNFTScore and totalRUNScore properties on the window object which is a DOM API which is you get from the Browser. I'm purposely creating them on the window object because I want to be able to access them later in the blockchain.js file because I want to know the total number of NFTs that I have so that I can claim them from the blockchain.

handleLose function

Lastly we implement the handleLose function. Here we just want to add the current score we have after we lose our game and update the UI and reset the scores to 0. And after 100ms we want to listen for events again so when you click the button we're ready to play again.

updateCustomProperty.js

Next we want to create an updateCustomProperty.js file. These functions are for taking in a CSS value on an element and update it using javascript.

getCustomProperty

This gets the prop from an element and returns it in javascript.

setCustomProperty

This sets a prop from an element using javascript.

incrementCustomProperty

This increments a prop from an element using javascript.

ground.js

Ground Imports

First we import the custom property methods which we just implemented in updateCustomProperty.js

SPEED constant

Next we define the SPEED constant which is the speed at which the ground will move.

Ground DOM

Then we get the ground DOM element.

setupGround

This is an example of how we're using the custom property functions. Remember in the index.html we set 2 ground DOM elements. The first will have a left of 0 and the second will have a left of 300 because that's how wide our game is.

updateGround

Next we implement the updateGround function that we call in our script.js file. Inside here we loop over both grounds and increment the custom properties so that it moves. We're multiplying times -1 because it's moving backwards. Once the first ground goes off the screen we want to update the second one and if it's -300 then we want to add 600.

player.js

Player Imports

First we import the custom property methods which we just implemented in updateCustomProperty.js

Player constants

Next we add the player constants. This includes speed and gravity which you can tweak however you like. The PLAYER_FRAME_COUNT is 2 because we have 2 images which we're going to be toggling between to make it look like the player is running.

Player DOM

setupPlayer

Here we use the setCustomProperty to set the bottom to 0. And we add an event listener for keydown which will cause the player to jump.

updatePlayer

This is being called in script.js. It takes in a delta and a speedScale and handles the run and jump.

getPlayerRect

This returns the dimensions of our player element.

setPlayerLose

This sets the player to the losing state

handleRun

This is the handle function that we're calling whenever we updatePlayer. Here we want to check if the player is jumping and if so we add the jumping image. Here we also toggle between the two images to make it look like the player is running. make sure to fix the interpolation

handleJump

If the person isn't jumping then we exit out of the function. If they are jumping then we increment their bottom property.

onJump

This checks if we've hit space and is jumping and if so we exit so that we don't jump again. If we're jumping then we update these properties

obstacle.js

Obstacle Imports

First we import the custom property methods which we just implemented in updateCustomProperty.js

Obstacle constants

Obstacle DOM

We get the game DOM element.

setupObstacle

We create an obstacle with the minimum spawn time and at the same time remove all the other obstacles.

updateObstacle

This is called from the game loop. We query all the obstacles and if they have a left property less than 100 then we remove them because they are off of the screen and the player no longer sees them. Then we check if the nextObstacleTime is less than 0 and if so create a new obstacle. We calculate the obstacle time by using this randomNumberBetween function.

getObstacleRects

Returns the dimensions of our obstacle.

createObstacle

Here we're creating an IMG DOM element and creating an data-obstacle attribute so that we can reference it later. We set the image src so that we can see it and then we add it to the DOM.

randomNumberBetween

This creates a random number which is used for spacing new obstacles entering the screen.

nft.js

This file is pretty much the same as obstacle.js

nft imports nft constants nft dom nft setupNFT function nft updateNFT function nft getNFTRects function nft createNFT function nft randomNumberBetween function

blockchain.js

Now we need to fill out the blockchain.js file which will be used for interacting with the blockchain

connectMetaMask

This function is for connecting the game to metamask

claimTokens

This function is for claiming the $RUN tokens

claimNft

This function is for claiming the NFTs

styles.css

Test Web2 Components Working

Now we can test the Web2 components to make sure they're working.

If you're using VSCode then install the Live Server Extension and right-click on game/index.html and select "Open with Live Server" and load http://127.0.0.1:5500/game/ in your browser.

demo-contracts

Everything is working fine w/ our Web2 components. Now it's time to add the Web3 components. First create a new directory called demo-contracts and cd into that directory.

npx hardhat

Now run npx hardhat and create a javascript project. This gives us the all of the skeleton directories and files that we need for our Web3 components. We have a contracts/ directory where we'll write our token and NFT smart contracts. We have node_modules/ which are the installed npm dependencies. We have a scripts/ directory that will deploy the contracts. We have a test/ directory for writing tests. We have our .gitignore file. We have a package.json file to specify all the dependencies. And a simple README file.

RunToken.sol

Now let's delete the contract which was populated by hardhat and create a new file called RunToken.sol then populate the file w/ the RunToken ERC20 contract. Note that we're importing ERC20 from OpenZeppelin.

Now we inherit from this ERC20 contract by saying RunToken is ERC20 and we need to override the constructor by constructor() ERC20('RunToken', 'RUN'){. It expects two arguments. The first is the name of the token and the second is the token symbol.

Also we have this mint function which takes an address and an amount and mints $RUN tokens to them. This will be called when the user claims their $RUN tokens.

RunnerCollection.sol

Now create another file in contracts/ called RunnerCollection.sol. This is an ERC1155 which we're using to create our NFTs. We're importing from openzeppelin and extending the ERC1155 contract. We set some properties at the top and then override the constructor.

We have this uri function which we need to override in order for our NFTs to show up on opensea. Then we have the mint function which increments the tokenCount property and mints the NFTs to the provided address. This will be used when the user retrieves their NFT.

package.json

deployNFTCollection.js

copy file from contracts/

deployRunToken.js

copy file from contracts/

hardhat.config.js

copy file from contracts/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment