MetaData, Class, For Loops

Objective is to increase knowledge and understanding on MetaData, Class Definition, and For-Loops.

MetaData: In this article the 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 context the canvas and drawing operations are intialized and stored in a class. These are used to output 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.

Hack #1

Make a new code block for some Sprite(s) you have in your “Ideation”. This project is planning on a Penguin called Tux.

  • MetaData: data that describes the file
    • name: is friendly identfier for the file
    • src: is the location of the file
  • drawImage: when used with five parameters outputs the entirety of the file
  • class: contains a constructor and draw method to read and output file
%%html

<style>
    #gameCanvas {
        border: 4px solid rgb(102, 4, 4);
    }
</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 spriteMetaData = {
                name: 'tux',
                src:  "./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>

Hack #2

See if you can get the frames of your sprite frames to display indepedently.

  • MetaData: name, src, orientation describe the sprite file
    • orientation: describes layout of sprite in PNG file
      • header: size of area of description above the sprite
      • pad: size of area between the sprites
      • jagged: means each row can contain a different amout of sprites
  • drawImage: In the 9 property format it provides ability to have source scale into destination.
  • class: continues using constructor and draw for source and output; adds math to abstract each frame independently
  • for-loops: show nested for loops to process each frame within the 2D sprite sheet.
%%html

<style>
    #gameCanvasUnique {
        border: 4px solid rgb(4, 102, 33);
    }
</style>

<canvas id="gameCanvasUnique" width="521" height="967"></canvas>

<script>
// outer fucntion is required by Jupyter Notebook to avoid conflicts
function defineAndDrawSprite() {

    /**
     * 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 spriteMetaData = {
            name: 'tux',
            src:  "./tux.png",
            orientation: {rows: 10, columns: 8, 
                    header: 16, pad: 2, 
                    jagged: [1, 2, 1, 1, 1, 1, 1, 7, 8, 4] 
                },
        };

        return spriteMetaData;
    }

    /**
     * Class to handle the canvas data and drawing of the sprite
     */
    class CanvasData {

        constructor(spriteMetaData) {
            this.spriteMetaData = spriteMetaData;
            this.INIT_POSITION = { x: 0, y: 0};
            this.canvas = document.getElementById('gameCanvasUnique');
            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 each sprite individually
        draw() {
            // This is the size of the sprite file, calculated from the PNG file 
            const sheetWidth = this.spriteImage.width; 
            const sheetHeight = this.spriteImage.height;
            // This meta data describes the sprite sheet
            const rows = this.spriteMetaData.orientation.rows;
            const cols = this.spriteMetaData.orientation.columns;
            const jagged = this.spriteMetaData.orientation.jagged || null;
            const header = this.spriteMetaData.orientation.header || 0;
            const pad = this.spriteMetaData.orientation.pad || 0;
            // This is initial output position on the canvas
            const x = this.INIT_POSITION.x;
            const y = this.INIT_POSITION.y;

            // Calculate the dimensions of each individual sprite
            const spriteWidth = sheetWidth / cols;
            const spriteHeight = (sheetHeight - header * rows) / rows;

            console.log(`Sprite Sheet Dimensions: ${sheetWidth}x${sheetHeight}`);
            console.log(`Individual Sprite Dimensions: ${spriteWidth}x${spriteHeight}`);
            console.log(`Rows: ${rows}, Columns: ${cols}`);

            // Nested for loop to draw 2 dimensional sprite sheet
            for (let row = 0; row <  rows; row++) {
                const columnsInRow = jagged ? jagged[row] || cols : cols;
                for (let col = 0; col < columnsInRow; col++) {
                    const srcX = col * spriteWidth;
                    const srcY = row * (spriteHeight + header ) - (pad * row);
                    const destX = x + col * spriteWidth;
                    const destY = y + row * spriteHeight;
                    const destWidth = spriteWidth;
                    const destHeight = spriteHeight;

                    console.log(`Drawing row: ${row}, column: ${col}`);
                    console.log(`Source: (${srcX}, ${srcY}, ${spriteWidth}, ${spriteHeight})`);
                    console.log(`Destination: (${destX}, ${destY}, ${destWidth}, ${destHeight})`);

                    this.ctx.drawImage(
                        this.spriteImage,
                        srcX, srcY + header, spriteWidth, spriteHeight, // Source rectangle
                        destX, destY, destWidth, destHeight // Destination rectangle
                    );
                }
            }
        }

    }

    // Setup to Tux sprite
    const tux = new CanvasData(TuxSpriteMetaData());
 }

defineAndDrawSprite();
</script>