JavaScript For Loops and Sprites
An introduction to JavaScript for loops and working with Sprites.
MetaData, Class, For Loops
The objective of this article is to enhance your knowledge and understanding of MetaData, Class Definitions, and For-Loops.
MetaData: In this article, MetaData contains information about the sprite, including its name, source URL, and orientation details such as the number of rows and columns, header size, padding, and jagged rows.
Class: In this cotext, the canvas and drawing operations are initialized and stored in a class. These are used to output the sprite sheet image and individual frames within the sprite sheet.
- constructor: Initializes the canvas, context, and sprite image.
- draw() method: Uses nested for-loops to iterate through the sprite sheet and draw each frame independently on the canvas. It calculates the source and destination coordinates for each frame, taking into account the header and padding.
Introduction to For Loops
For Loops are commonly used to iterate over data structures, including JavaScript Arrays and Objects.
Below is an example of a conventional for loop that iterates over an array of names and displays each name in a paragraph (<p>
) tag within a designated HTML div.
%%html
<!-- HTML output div -->
<div id="forConventional"></div>
<script>
var names = ['turtle', 'fish', 'frog', 'penguin'];
// Conventional for loop with index used to access elements
for (let i = 0; i < names.length; i++) {
// Create a p tag for each name and add it to the forConventional div
const p = document.createElement('p');
p.innerHTML = names[i];
document.getElementById('forConventional').appendChild(p);
}
</script>
ForEach Loop
The ForEach loop is another way to iterate over data structures, such as JavaScript Arrays and Objects. Below is an example of a ForEach loop that iterates over an array of coding adventures and displays each adventure in a paragraph (<p>
) tag within a designated HTML div.
%%html
<!-- HTML output div -->
<div id="forEach"></div>
<script>
var codingAdventures = ['GitHub', 'Terminal', 'Jupyter', 'JavaScript'];
// ForEach loop to iterate over the array
for (let adventure of codingAdventures) {
// Create a p tag for each adventure and add it to the forEach div
const p = document.createElement('p');
p.innerHTML = adventure;
document.getElementById('forEach').appendChild(p);
}
</script>
2D array
There is a data structure called arrays in arrays or 2D arrays. The data structure helps organize the data efficiently and access it using nested loops. Each row in the 2D array will represent a category (e.g., GitHub, Terminal), and each column will contain an array of questions and answers for that category.
%%html
<h1>Help Guide</h1>
<div id="questionsAnswers"></div>
<script>
// 2D array of questions and answers with titles
var qaArray = [
[
'Powerups',
[
{ question: 'What are powerups?', answer: 'Power ups are something the user can pick up to change their survivability.' },
{ question: 'Are all powerups good?', answer: 'No, some powerups are fake and actully hurt the person who picks them up.' },
{ question: 'How do you pick up a powerup?', answer: 'You can pick up a powerup by walking over it' },
{ question: 'Can you get rid of a bad power up?', answer: 'All powerups go away within a few seconds!' }
]
],
[
'Targetting',
[
{ question: 'What happens when a projectile hits you', answer: 'Currently nothing, but I am planning to make it so that you either lose or lose health.' },
{ question: 'How does projectile targetting work', answer: 'The targetting is not were I want it right now, but currently it goes to the nearest player, and after 10 seconds locks on the next closest player.' },
{ question: 'How did I make projectile targetting?', answer: 'I used the distance formula to check which player was closest.' },
{ question: 'Where did you get that amazing sprite sheet from?', answer: 'I made it.' }
]
],
[
'How I made it',
[
{ question: 'How did you change the sprites sprite when going a direction', answer: 'I changed the sprites sprite using a switch command that checked the x and y velocity of the sprite.' },
{ question: 'How did you fix the frog & fish movement', answer: 'I made it so the position increase of the frog/fish remains constant throughout holding down a key.' },
{ question: 'How did you make the sprite kind of go in a wave when near the player', answer: 'cosineeeeeee!!' },
{ question: 'How did you add the timer', answer: 'I spent what felt like 15 years trying to figure out how to make a function of setinveral to do this. I probably could have simplified it a lot tho' }
]
]
];
// Nested for loops to display questions and answers with titles
for (let category of qaArray) {
// Create an h2 tag for each category title
const h2 = document.createElement('h2');
h2.innerHTML = category[0]; // index 0 is the title of the category
document.getElementById('questionsAnswers').appendChild(h2);
// Iterate through each question and answer in the category
for (let qa of category[1]) { // index 1 is the array of questions and answers
// Create a p tag for each question and answer
const p = document.createElement('p');
p.innerHTML = `<strong>Q:</strong> ${qa.question} <br> <strong>A:</strong> ${qa.answer}`;
document.getElementById('questionsAnswers').appendChild(p);
}
}
</script>
Help Guide
Hack #1: Apply Your Own Game Idea
Create new code cells to implement some of the sprite interactions or features you have ideated for your game. This exercise is crucial if you plan to have interactions with a Non-Player Character (NPC).
I’ve added my game idea in the form of a projectile that targets the closest player for 10 seconds, then latches onto the closest player at that point and follows them.
Challenge: Use the concepts of 2D arrays and nested loops to create and display interactions or features for your game. Think about how you can organize and manage different elements, such as NPC dialog, questions, and receiving answers.
Sprite Files
Transition to Sprite Files
Now that we have a solid understanding of data structures and loops, we will transition to working with Sprite Files. This section will help you understand how to manage and display sprite images, which are essential for creating animations in your game.
Sprite Files
Sprite files are essentially a 2D table of sprite images. They contain 2D columnar sequences of pictures that aid in creating animation.
Display Sprite File
The next code block shows a sprite file. This can be helpful in understanding the properties of your sprite. It contains console.log
output that shows the sprite properties.
Here are some terms that you will see in the next code block:
- MetaData: Data that describes the file
- name: A friendly identifier naming the file
- src: The location of the file
- drawImage: A function call that, when used with five parameters, outputs the entirety of the file
- class: A coding structure that contains a constructor, data, and method (draw) to read and output a file
%%html
<style>
#gameCanvas {
border: 4px solid rgb(102, 4, 4); /* Red border for the canvas */
}
</style>
<canvas id="gameCanvas" width="521" height="967"></canvas>
<script>
function defineAndDrawImage() {
/**
* Function to define the sprite metadata for Tux the penguin
* @returns {Object} spriteMetaData - The metadata for the Tux sprite
*/
function TuxSpriteMetaData() {
// NPC sprite data (Tux the penguin)
const isLocal = window.location.protocol === 'vscode-webview:' | false;
const baseUrl = isLocal ? '.' : '/Santhosh_2025';
console.log(baseUrl);
const spriteMetaData = {
name: 'tux',
src: `${baseUrl}/images/tux.png`,
};
return spriteMetaData;
}
/**
* Class to handle the canvas data and drawing of the image file
*/
class CanvasDrawImage {
constructor(spriteMetaData) {
this.INIT_POSITION = { x: 0, y: 0 };
this.spriteMetaData = spriteMetaData;
this.canvas = document.getElementById('gameCanvas');
this.ctx = this.canvas.getContext('2d');
this.spriteImage = new Image();
this.spriteImage.src = spriteMetaData.src;
this.spriteImage.onload = () => this.draw(); // Ensure draw is called after image is loaded
}
// Method to draw the sprite on the canvas
draw() {
// This is the size of the sprite file, calculated from the PNG file
const width = this.spriteImage.width;
const height = this.spriteImage.height;
console.log(`Drawing sprite: ${this.spriteMetaData.name}`);
console.log(`Sprite Dimensions: ${width}x${height}`);
this.ctx.drawImage(this.spriteImage, 0, 0, width, height);
}
}
const tux = new CanvasDrawImage(TuxSpriteMetaData());
}
// Call the function to define the class and draw the sprite
defineAndDrawImage();
</script>
Display Frames in Sprite File
The next code block contains logic to extract frames within the sprite sheet. This is a more practical game enhancement compared to the previous example.
Here are terms to describe key elements in the code:
- MetaData: Contains information about the sprite file, including its name, source URL, and orientation details.
- orientation: Describes the layout of the sprite in the PNG file.
- header: Size of the area of description above the sprite.
- pad: Size of the area between the sprites.
- jagged: Indicates that each row can contain a different number of sprites.
- orientation: Describes the layout of the sprite in the PNG file.
- drawImage: In the 9-property format, it provides the ability to scale the source into the destination.
- class: Continues using the constructor and draw methods for source and output; adds math to abstract each frame independently.
- for-loops: Demonstrates nested for loops to process each frame within the 2D sprite sheet.
%%html
<canvas id="spriteCanvas" width="1280" height="512"></canvas>
<script>
// Variables for the spritesheet and sprite dimensions
const spritesheetSrc = "https://i.ibb.co/s6449nn/character.png"; // Path to the spritesheet
const spritesheetWidth = 640;
const spritesheetHeight = 256;
const spriteWidth = 64;
const spriteHeight = 128;
const totalFrames = 20;
const fps = 10;
// Select the canvas and get context
const canvas = document.getElementById('spriteCanvas');
const ctx = canvas.getContext('2d');
// Load the spritesheet
const spriteSheet = new Image();
spriteSheet.src = spritesheetSrc;
let currentFrame = 0;
// Function to draw the current sprite frame
function drawSprite() {
// Calculate the column and row of the current frame
const col = currentFrame % (spritesheetWidth / spriteWidth); // Total columns
const row = Math.floor(currentFrame / (spritesheetWidth / spriteWidth)); // Total rows
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the current sprite on the canvas
ctx.drawImage(
spriteSheet,
col * spriteWidth, // Source x in the spritesheet
row * spriteHeight, // Source y in the spritesheet
spriteWidth, // Width of the sprite
spriteHeight, // Height of the sprite
0, // Destination x on the canvas
0, // Destination y on the canvas
spriteWidth, // Destination width
spriteHeight // Destination height
);
// Move to the next frame, looping back to the first after the last one
currentFrame = (currentFrame + 1) % totalFrames;
}
// Start the animation once the image is loaded
spriteSheet.onload = function() {
setInterval(drawSprite, 1000 / fps); // Change frame based on FPS
};
</script>
Hack #2: Display Individual Sprites
Create new code cell(s) to display individual sprites from a sprite sheet. This sprite sheet will potentially be used in your game.
Challenge: Use the concepts of 2D arrays, nested loops, and sprite metadata to extract and display individual sprites. Think about how you can manage and display different frames or animations for your game characters or objects.