https://docs.google.com/presentation/d/1az-hY7Zas2lEQm0jS6laDcoxnJz__FxM0uNd7Gqy5ec/edit?usp=sharing
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.
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.
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
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.
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.
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
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"
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
.
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.
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.
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.
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.
This game will have a total score and a current score.
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.
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.
Next we'll create an nftCurrentScore
<span>
also starting at 0. This is the NFT score of your current game session.
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.
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
We have another div which says "Press Any Key To Start"
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.
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.
Lastly in the game
<div>
we add the playerStands
<img>
which is the graphic of the player standing
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.
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.
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.
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.
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.
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.
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.
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 import
ed 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.
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.
This function takes in a rect1
and rect2
and checks if there has been a collision
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.
This function takes in the delta
and updates the speed scale by adding the delta
times the SPEED_SCALE_INCREASE
constant.
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.
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
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.
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.
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.
This gets the prop from an element and returns it in javascript.
This sets a prop from an element using javascript.
This increments a prop from an element using javascript.
First we import the custom property methods which we just implemented in updateCustomProperty.js
Next we define the SPEED
constant which is the speed at which the ground will move.
Then we get the ground DOM element.
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.
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.
First we import the custom property methods which we just implemented in updateCustomProperty.js
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.
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.
This is being called in script.js
. It takes in a delta
and a speedScale
and handles the run and jump.
This returns the dimensions of our player element.
This sets the player to the losing state
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
If the person isn't jumping then we exit out of the function. If they are jumping then we increment their bottom
property.
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
First we import the custom property methods which we just implemented in updateCustomProperty.js
We get the game DOM element.
We create an obstacle with the minimum spawn time and at the same time remove all the other obstacles.
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.
Returns the dimensions of our obstacle.
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.
This creates a random number which is used for spacing new obstacles entering the screen.
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
Now we need to fill out the blockchain.js file which will be used for interacting with the blockchain
This function is for connecting the game to metamask
This function is for claiming the $RUN tokens
This function is for claiming the NFTs
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.
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.
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.
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.
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.
copy file from contracts/
copy file from contracts/
copy file from contracts/