Posted October 26, 2023 by KuroGamedev
This section is about the pre-requisite code needed for this game before we can fire it up and make the game playable or look like something besides a blank screen.
For this tutorial, we will be working exclusively with the scripts.js page.
These are values that change as the game progresses. They help control the player position, timers, snacks collected, score, current map data, the list goes on. Take a look at the list; each variable includes notes on what they do and how they contribute to the game.
We use let to declare them. We 'could' put a let in front of every single entry, but it is just as effective to place commas between them, so long as the last one ends with a semi-colon. Line breaks do not effect the declaration of variables.
Notes:
Add the following under //VARIABLES:
// //VARIABLES let keyPress=[false,false,false,false,false],keyHold=[0,0,0,0,0]; //controls for keys, notably if one if being pressed/held down // let pBody, //Player's body. This will be an array to store the X and Y positions of each body segment. pCollect, //How many snacks the player had nomnom'ed during this round. pDeaths, //Player deaths. We will add a score bonus if the player completes a level without dying once. pDir, //Player's direction. This will match the keys layout (0=left, 1=up, 2=right, 3=down. // 4 is used as a placeholder to say there is no direction and the player will not automatically move) pLifeScore,//How many points a player must earn to gain another life. pLifeBase, //Base for score required for another life. pLifeInc, //The bonus life score increment. pLives, //Player's remaining lives. If this is at zero when the player crashes, it's game over. pSegMax, //How many body segments the player should have. pRound, //Player's current round. pScore, //Player's score pSnack, //How many snacks the player must collect to clear a round. pTimer; //Player timer. This is used to track the # of remaining frames let endRound, //When the player exits the screen, this ends the round. gfx, //The value that is used for building the graphics output. gTimer, //Game timer. Useful for different things. mapX, //Current map. snack, //Snack. This holds the the X, Y and color values. tempDir; //Temporary direction. This value is moved to pDir when the player moves a square.
This serves as a miniature database for odds and ends, plus values we do not want to see changed in-game.
Add the following under //CONSTANTS:
// //CONSTANTS const addLength=[0,8,7,7,6,6,6,5,5,5,5], //A setup for how many body segments are added on when eating a snack. //Connected to the pCollect variable. dirX=[-1,0,1,0],dirY=[0,-1,0,1], //Controls the changes to the player's X and Y when moving. keys=[37,38,39,40,32], //Keycode values while pressing keys. For WASD, use: keys=[37,38,39,40,32]; GAME=document.getElementById("game"),//Shortcut for accessing the game canvas. GAMEX=GAME.getContext("2d"), //Allows us to handle the game canvas more easily when making graphics. scrWidth=128, scrHeight=128, //Screen width and height. snackColors=[[32,80,168],[168,32,168],[40,176,208],[208,200,32],[192,56,24]]; //Colour setup for snacks (in RGB).
Notes
A canvas box's size can be controlled in two ways: The 'canvas' size and the 'css' size. These do two different things. The 'canvas' is the base size of the project you are working on. This can stay the same and all the variables and scripts will point to this. To change how big it appears on the screen, we will make the css-end of things dynamic and scale to the size of the browser window through the use of scripts.
We will need to some pieces to get the screen size to work.
Add the following under //LISTENERS:
// //LISTENERS addEventListener("resize",function(){SETSIZE();}); //Auto adjusts the game canvas when the browser window is resized.
Then add the following under //FUNCTIONS:
// //FUNCTIONS function SETSIZE(){ //Set the size of the screen. let i,i2; //Set 'css' size (of the canvas). i=(innerHeight-4)/scrWidth,i2=innerWidth/scrHeight; //This will use get the dimensions of the screen as a multiplier of the game screen base size. if(i2<i){i=i2} //Whichever is the smaller scaleup value will be used. if(i<1){i=1;} //This sets a minimum size. GAME.style.width=Math.floor(scrWidth*i)+"px"; //Set the canvas 'css' size. GAME.style.height=Math.floor(scrHeight*i)+"px"; GAME.style.background="white"; //Temporary. Changes the background of the canvas to white. }
Finally, add the following under //EXECUTE THE GAME:
// //EXECUTE THE GAME GAME.width=scrWidth; //Set 'canvas' size. GAME.height=scrHeight; SETSIZE(); //Set 'css' size.
To move the worm around the map (and pause the game), we will need to setup two parts to make ths work. First, is to add two addEventListeners, like we did for checking the size of the browser window. One will keep an eye for keys that are held down and the other is for when a key has been released.
Browsers normally have a default setup for what certain keys on a keyboard do (for instance, arrow keys will scroll the page up and down, so we will disable this feature in the second addEventListener to prevent that from happening).
First, the listeners. Place these under the resize listener:
addEventListener( //This records when certain keys are pressed down. 'keydown',function(e){ if(e.keyCode==32||e.keyCode==37||e.keyCode==38||e.keyCode==39||e.keyCode==40){e.preventDefault()};//This prevents the spacebar and arrow keys from doing their usual thing. keyPress[e.keyCode]=true }, true ); addEventListener( //This records when certain keys are released. 'keyup',function(e){ keyPress[e.keyCode]=false; let i0=keys.length; for(let i=0; i<i0; i++){if(e.keyCode==keys[i]){keyHold[i]=0;}} //If a key is let go, this will free the 'it's held down' value. }, true );
Next, add the two functions that are called on when a key is pressed.
The functions KEYBOARD() and KEYBOARDHOLD() are setup to do something when your arrow keys are pressed.
if(keyPress[37] && keyHold[0]==0) --> if(KEYBOARD(0))
Place these underneath the SETSIZE() function:
// // Keyboard Handling function KEYBOARD(a){return keyPress[keys[a]]&&keyHold[a]==0;} //Returns true when a key is pressed AND the key was not already held down. function KEYBOARDHOLD(a){keyHold[a]=1;} //The key is now 'held down'.
We will fire up the main game functions as well. We are including 2 more functions for the time being: STARTGAME(); and ACTION();
Put this under the SETSIZE() function:
// // -- Start the game. function STARTGAME(){ requestAnimationFrame(ACTION); //Fire up the game. } // // -- Main game cycle function ACTION(){ //Keyboard controls if(KEYBOARD(0)){} //Left else if(KEYBOARD(1)){} //Up else if(KEYBOARD(2)){} //Right else if(KEYBOARD(3)){} //Down if(KEYBOARD(4)){} //Pause Game? // requestAnimationFrame(ACTION); }
Notes:
Prior to firing up the game, we need some values.
To sum this up, this will setup a game with all important stats reset, such are the score for a new life, first round, 2 lives, where the player starts and the map of the first round.
Replace the one line already in the STARTGAME() function with this:
function STARTGAME(){ pBody=[[7,8]], //We start with the head at a specific X & Y. pCollect=0, pDeaths=0, pDir="x", pLifeScore=250, pLifeBase=250, pLifeInc=250, pLives=2, pRound=0, pSegMax=5, pScore=0, pSnack=5, pTimer=0; // endRound=0, gTimer=0, mapX=COPY(mapSrc[0]), tempDir=4; SNACKSPOT(); // requestAnimationFrame(ACTION); //Fire up the game. }
The variable updates call on two functions that do not exist in our code. Let us create them, now.
The first one is calleed COPY(). It is a simple function that lets up duplicate a value / set of values without impacting the original source. For instance, if I try to do something like mapX = mapSrc[0], it will create a link between them, rather than copying them. If I make changes to mapX, it will also change mapSrc[0]. We can avoid this by using this function to duplicate the values, instead.
Copy and paste the following code underneath the functions that control the keyboard arrays.
// // -- COPY value function COPY(a){return JSON.parse(JSON.stringify(a));} //Duplicates the value.
The next function is called SNACKSPOT(). This generates a set of values for the snack array: Where it's X & Y position is and what it's colour/value is. (The 3rd value is used to determine both it's colour and bonus points).
Place this between the COPY() and STARTGAME() functions:
// // -- Get position of snack function SNACKSPOT(){ let x,y,z=1,i0; while(z==1){ //This runs while an obstacle prevents the snack from being //generated. This includes walls and body segments. z=0, x=Math.floor(Math.random()*16), //Pick a random X & Y spot on the map y=Math.floor(Math.random()*14); if(mapX[y*16+x]==1){ //Cancel if this lands on a map tile. z=1; } i0=pBody.length; for(let i=0; i<i0; i++){ if(pBody[i][0]==x && pBody[i][1]==y){ //Cancel is this lands on a body segment. z=1; } } if(z==0){ //Set the snack co-ordinates and colour, here. snack=[x,y,Math.floor(Math.random()*5)]; } } } // // -- Start the game. function STARTGAME(){ pBody=[[7,8]], //We start with the head at a specific X & Y. pCollect=0, pDeaths=0, pDir="x",
Here is the up-to-date progress for the code:
//FIRST THINGS, FIRST "use strict"; //Help better identify problems in the code, especially with bad variable declarations. // //VARIABLES let keyPress=[false,false,false,false,false],keyHold=[0,0,0,0,0]; //controls for keys, notably if one if being pressed/held down // let pBody, //Player's body. This will be an array to store the X and Y positions of each body segment. pCollect, //How many snacks the player had nomnom'ed during this round. pDeaths, //Player deaths. We will add a score bonus if the player completes a level without dying once. pDir, //Player's direction. This will match the keys layout (0=left, 1=up, 2=right, 3=down. // 4 is used as a placeholder to say there is no direction and the player will not automatically move) pLifeScore,//How many points a player must earn to gain another life. pLifeBase, //Base for score required for another life. pLifeInc, //The bonus life score increment. pLives, //Player's remaining lives. If this is at zero when the player crashes, it's game over. pSegMax, //How many body segments the player should have. pRound, //Player's current round. pScore, //Player's score pSnack, //How many snacks the player must collect to clear a round. pTimer; //Player timer. This is used to track the # of remaining frames let endRound, //When the player exits the screen, this ends the round. gfx, //The value that is used for building the graphics output. gTimer, //Game timer. Useful for different things. mapX, //Current map. snack, //Snack. This holds the the X, Y and color values. tempDir; //Temporary direction. This value is moved to pDir when the player moves a square. // //CONSTANTS const addLength=[0,8,7,7,6,6,6,5,5,5,5], //A setup for how many body segments are added on when eating a snack. //Connected to the pCollect variable. dirX=[-1,0,1,0],dirY=[0,-1,0,1], //Controls the changes to the player's X and Y when moving. keys=[37,38,39,40,32], //Keycode values while pressing keys. For WASD, use: keys=[37,38,39,40,32]; GAME=document.getElementById("game"),//Shortcut for accessing the game canvas. GAMEX=GAME.getContext("2d"), //Allows us to handle the game canvas more easily when making graphics. scrWidth=128, scrHeight=128, //Screen width and height. snackColors=[[32,80,168],[168,32,168],[40,176,208],[208,200,32],[192,56,24]]; //Colour setup for snacks (in RGB). // //LISTENERS addEventListener("resize",function(){SETSIZE();}); //Auto adjusts the game canvas when the browser window is resized. addEventListener( //This records when certain keys are pressed down. 'keydown',function(e){ if(e.keyCode==32||e.keyCode==37||e.keyCode==38||e.keyCode==39||e.keyCode==40){e.preventDefault()};//This prevents the spacebar and arrow keys from doing their usual thing. keyPress[e.keyCode]=true }, true ); addEventListener( //This records when certain keys are released. 'keyup',function(e){ keyPress[e.keyCode]=false; let i0=keys.length; for(let i=0; i<i0; i++){if(e.keyCode==keys[i]){keyHold[i]=0;}} //If a key is let go, this will free the 'it's held down' value. }, true ); // //FUNCTIONS function SETSIZE(){ //Set the size of the screen. let i,i2; //Set 'css' size (of the canvas). i=(innerHeight-4)/scrWidth,i2=innerWidth/scrHeight; //This will use get the dimensions of the screen as a multiplier of the game screen base size. if(i2<i){i=i2} //Whichever is the smaller scaleup value will be used. if(i<1){i=1;} //This sets a minimum size. GAME.style.width=Math.floor(scrWidth*i)+"px"; //Set the canvas 'css' size. GAME.style.height=Math.floor(scrHeight*i)+"px"; GAME.style.background="white"; //Temporary. Changes the background of the canvas to white. } // //Keyboard Handling function KEYBOARD(a){return keyPress[keys[a]]&&keyHold[a]==0;} //Returns true when a key is pressed AND the key was not already held down. function KEYBOARDHOLD(a){keyHold[a]=1;} //The key is now 'held down'. // // -- COPY value function COPY(a){return JSON.parse(JSON.stringify(a));} //Duplicates the value. // // -- Get position of snack function SNACKSPOT(){ let x,y,z=1,i0; while(z==1){ //This runs while an obstacle prevents the snack from being //generated. This includes walls and body segments. z=0, x=Math.floor(Math.random()*16), //Pick a random X & Y spot on the map y=Math.floor(Math.random()*14); if(mapX[y*16+x]==1){ //Cancel if this lands on a map tile. z=1; } i0=pBody.length; for(let i=0; i<i0; i++){ if(pBody[i][0]==x && pBody[i][1]==y){ //Cancel is this lands on a body segment. z=1; } } if(z==0){ //Set the snack co-ordinates and colour, here. snack=[x,y,Math.floor(Math.random()*5)]; } } } // // -- Start the game. function STARTGAME(){ pBody=[[7,8]], //We start with the head at a specific X & Y. pCollect=0, pDeaths=0, pDir="x", pLifeScore=250, pLifeBase=250, pLifeInc=250, pLives=2, pRound=0, pSegMax=5, pScore=0, pSnack=5, pTimer=0; // endRound=0, gTimer=0, mapX=COPY(mapSrc[0]), tempDir=4; SNACKSPOT(); // requestAnimationFrame(ACTION); //Fire up the game. } // // -- Main game cycle function ACTION(){ //Keyboard controls if(KEYBOARD(0)){} //Left else if(KEYBOARD(1)){} //Up else if(KEYBOARD(2)){} //Right else if(KEYBOARD(3)){} //Down if(KEYBOARD(4)){} //Pause Game? // requestAnimationFrame(ACTION); } // //EXECUTE THE GAME GAME.width=scrWidth; //Set 'canvas' size. GAME.height=scrHeight; SETSIZE(); //Set 'css' size. STARTGAME(); //Get the game underway.
The next tutorial will cover