ScriptCrew

3D Maze Game

By Victoria Santiago

Step-by-Step Instructions

Step 1: Setting Up Your Project

In this step, you'll set up the basic structure for your project. Follow these instructions to create and configure your project files.

Create Your HTML File

First, create an HTML file named index.html. This file will set up the structure of your web page and include necessary libraries and scripts. Use the following code for your index.html file:

<!DOCTYPE html>
          <html>

          <head>
            <meta charset="utf-8">
            <meta name="viewport" content="width=device-width">
            <title>repl.it</title>
            <link href="style.css" rel="stylesheet" type="text/css" />

            <!-- p5play -->
            <script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>
            <script src="https://cdn.jsdelivr.net/npm/p5@1/lib/addons/p5.sound.min.js"></script>

            <!-- updated to local files -->
            <script src="planck.min.js"></script>
            <script src="p5play.js"></script>

          </head>

          <body>
            <script src="script.js"></script>
          </body>

          </html>

Explanation

  • <!DOCTYPE html>: This declaration defines the document type and version of HTML being used (HTML5 in this case).
  • <html>: The root element that wraps all your HTML content.
  • <head>: Contains meta-information about the document, such as character encoding and viewport settings.
  • <meta charset="utf-8">: Sets the character encoding to UTF-8, ensuring proper display of characters.
  • <meta name="viewport" content="width=device-width">: Ensures proper scaling on mobile devices.
  • <title>repl.it</title>: Sets the title of the web page, which appears in the browser tab.
  • <link href="style.css" rel="stylesheet" type="text/css">: Links to an external CSS file for styling your project.
  • <script src="https://cdn.jsdelivr.net/npm/p5@1/lib/p5.min.js"></script>: Includes the p5.js library for creating graphical and interactive experiences.
  • <script src="https://cdn.jsdelivr.net/npm/p5@1/lib/addons/p5.sound.min.js"></script>: Includes the p5.sound library for handling sound in your project.
  • <script src="planck.min.js"></script>: Includes the Planck.js library, a 2D physics engine.
  • <script src="p5play.js"></script>: Includes the p5.play library, which extends p5.js with game development functionality.
  • <script src="script.js"></script>: Includes your custom JavaScript file where you'll write the game logic and other functionalities.

Step 2: Define the Maze and Variables

In your JavaScript file, you need to define the maze grid and several key variables. Each of these variables plays an important role in how your maze game functions:

  • maze: This is a 2D array representing the maze layout. Each element in the array specifies different parts of the maze:
    • 1 represents walls or obstacles that the player cannot pass through.
    • 0 represents open paths where the player can move.
    • 2 denotes special items or the goal location within the maze.

    For example:

    let maze = [
              [1, 1, 1, 1, 1],
              [1, 0, 0, 0, 1],
              [1, 0, 1, 0, 1],
              [1, 0, 1, 0, 1],
              [1, 1, 1, 2, 1]
            ];

    This layout creates a maze with walls surrounding the paths. The player can navigate through the 0 values and aim to reach the 2 values.

  • blockSize: This variable defines the size of each block or cell in the maze grid, determining how large each block appears on the canvas. For instance, if blockSize is set to 50, each block in the maze will be rendered as a 50x50 pixel square on the screen.
  • playerX and playerY: These variables track the player's position in the maze grid. They correspond to the player's coordinates and are used for movement and collision detection:
    • playerX represents the player's horizontal position in the maze.
    • playerY represents the player's vertical position in the maze.

    For example:

    let playerX = 1; // Player's x-coordinate in the maze
            let playerY = 1; // Player's y-coordinate in the maze
  • gameWon: This boolean variable indicates whether the game has been won or not. If gameWon is set to true, the game will display a win message and prevent further gameplay. This variable helps manage the game state and control the win condition.

Here’s how you can initialize these variables in your JavaScript code:

// Define the maze as a 2D array
        let maze = [
          // Define your maze layout here
          [1, 1, 1, 1, 1],
          [1, 0, 0, 0, 1],
          [1, 0, 1, 0, 1],
          [1, 0, 1, 0, 1],
          [1, 1, 1, 0, 1]
        ];

        // Define the size of each maze block
        let blockSize = 50;

        // Initialize the player's starting position
        let playerX = 1; // Player's x-coordinate in the maze
        let playerY = 1; // Player's y-coordinate in the maze

        // Flag to check if the game is won
        let gameWon = false;

Step 3: Setup Function

In the `setup` function, create a canvas for your game and set up text display for win messages.

function setup() {
  createCanvas(750, 600, WEBGL);
  noStroke();

  txt = createDiv('');
  txt.position(width / 4, height / 4);
  txt.style('font-size', '40px');
  txt.style('font-weight', 'bold');
  txt.style('color', 'green');
  txt.style('width', '400px');
  txt.html("You found hope! Even though you are afraid of the darkness, you still can always find hope. There will always be a light that you can follow.");
  txt.hide(); // txt.show() will bring it back
}

The creatCanvas function sets up the width and height of the screen. WEBGL allows you to make objects in 3D!!

The noStroke function ensures that your 3D shapes don't have visible lines

You can change the width, height, font, color of your text to anything you'd like!!

Step 4: Drawing the Maze and Player

In this step, you'll learn how to visually represent the maze and the player in your game. We'll cover how to use the draw function to render the maze and player, including how to set up the camera view, light sources, and rendering the game elements in 3D space.

Setting Up the Camera

The camera is essential for viewing your 3D game world. In this code, the camera position and orientation are set based on the player’s position to follow the player as they move through the maze.


          let camX = (playerX - maze[0].length / 2) * blockSize;
          let camY = (playerY - maze.length / 2) * blockSize;
          let camZ = (height / 2) / tan(PI / 6);

          camera(camX, camY, camZ, camX, camY, 0, 0, 1, 0);
          
  • camX and camY: These variables set the camera's X and Y positions, ensuring that the camera follows the player by centering the view around the player's current location.
  • camZ: This determines the distance of the camera from the maze. It is calculated to give an appropriate field of view, ensuring the maze and player are visible from a good perspective.
  • camera(camX, camY, camZ, camX, camY, 0, 0, 1, 0): This function sets the camera's position and orientation. The camera is placed at (camX, camY, camZ) and looks at the point (camX, camY, 0) with the up direction along the Z-axis.

Adding Lighting

Lighting helps to make the maze and player visible and realistic. In this example, we use ambient light for general illumination and a point light that follows the player.


          ambientLight(20, 20, 20);

          let lightX = (playerX - maze[0].length / 2) * blockSize;
          let lightY = (playerY - maze.length / 2) * blockSize;
          pointLight(255, 255, 255, lightX, lightY, 100);
          
  • ambientLight(20, 20, 20): Adds a soft, general light to the entire scene. This helps to ensure that all parts of the maze and player are somewhat illuminated.
  • pointLight(255, 255, 255, lightX, lightY, 100): Creates a white light that follows the player. The light is positioned at (lightX, lightY, 100), which is based on the player's position. The 100 represents the Z-coordinate, which affects the light's distance from the scene.

Drawing the Maze

The maze is represented as a grid of blocks, with each cell in the grid either a wall or an open space. The drawing logic translates each cell of the maze into a 3D box or colored block.


          for (let y = 0; y < maze.length; y++) {
            for (let x = 0; x < maze[0].length; x++) {
              push();
              translate((x - maze[0].length / 2) * blockSize, (y - maze.length / 2) * blockSize, 0);
              if (maze[y][x] === 1) {
                box(blockSize);
              } else if (maze[y][x] === 2) {
                fill(255, 0, 0);
                box(blockSize);
                fill(255);
              }
              pop();
            }
          }
          
  • push() and pop(): These functions are used to save and restore the current drawing style and transformations. This ensures that transformations applied to one block do not affect others.
  • translate((x - maze[0].length / 2) * blockSize, (y - maze.length / 2) * blockSize, 0): Moves the drawing context to the position of the current maze cell. This centers the maze in the view.
  • if (maze[y][x] === 1): Checks if the current cell is a wall (represented by 1) and draws a block if true.
  • else if (maze[y][x] === 2): Checks if the cell is a goal (represented by 2) and draws a red block if true.

Drawing the Player

The player is drawn as a smaller block that follows the player's position in the maze. The player's position is adjusted to be above the maze level.


          push();
          translate((playerX - maze[0].length / 2) * blockSize, (playerY - maze.length / 2) * blockSize, blockSize / 2);
          box(blockSize * 0.5);
          pop();
          
  • translate((playerX - maze[0].length / 2) * blockSize, (playerY - maze.length / 2) * blockSize, blockSize / 2): Moves the drawing context to the player’s position, slightly raised above the maze to avoid overlapping with the maze blocks.
  • box(blockSize * 0.5): Draws a smaller box to represent the player. The size is half the block size used for the maze.

By following these steps, you will render both the maze and the player correctly in the 3D space, ensuring that the game visuals are accurate and the player’s movement through the maze is represented effectively.

Step 5: Player Movement

In this step, you'll implement player movement within the maze using keyboard controls. You'll handle user inputs to move the player sprite and check for collisions or game win conditions.

Code Breakdown

Here's a detailed look at the keyPressed function which handles player movement:


          function keyPressed() {
            let nextX = playerX;
            let nextY = playerY;

            // Check for keyboard inputs and adjust the next position of the player
            if (keyIsDown(LEFT_ARROW)) {
              nextX--; // Move player left by decreasing the X coordinate
            }
            if (keyIsDown(RIGHT_ARROW)) {
              nextX++; // Move player right by increasing the X coordinate
            }
            if (keyIsDown(UP_ARROW)) {
              nextY--; // Move player up by decreasing the Y coordinate
            }
            if (keyIsDown(DOWN_ARROW)) {
              nextY++; // Move player down by increasing the Y coordinate
            }

            // Check if the next position is within the maze and update player position
            if (maze[nextY][nextX] === 0) {
              // The position is an open path (0) - move player to this new position
              playerX = nextX;
              playerY = nextY;
            } else if (maze[nextY][nextX] === 2) {
              // The position is the goal (2) - mark the game as won
              playerX = nextX;
              playerY = nextY;
              gameWon = true; // Set gameWon to true to trigger winning condition
            }
          }
          

Understanding the Code

  • let nextX = playerX; and let nextY = playerY;:
  • These lines create temporary variables nextX and nextY that hold the player's potential next position. They are initially set to the current position of the player.
  • keyIsDown(LEFT_ARROW): This function checks if the left arrow key is being pressed. If true, it decrements nextX, which will move the player to the left by one unit on the X-axis.
  • keyIsDown(RIGHT_ARROW): This checks if the right arrow key is pressed. If true, it increments nextX, moving the player to the right by one unit.
  • keyIsDown(UP_ARROW): This checks if the up arrow key is pressed. If true, it decrements nextY, moving the player up by one unit on the Y-axis.
  • keyIsDown(DOWN_ARROW): This checks if the down arrow key is pressed. If true, it increments nextY, moving the player down by one unit.
  • if (maze[nextY][nextX] === 0): This checks if the next position (where the player intends to move) is an open path (represented by 0) in the maze. If it is, the player’s position is updated to nextX and nextY.
  • else if (maze[nextY][nextX] === 2): This checks if the next position is the goal (represented by 2). If it is, the player’s position is updated and gameWon is set to true, indicating that the player has won the game.

Key Points

  • The function keyPressed is triggered whenever a key is pressed, and it updates the player's position based on which arrow key is pressed.
  • By updating nextX and nextY according to keyboard input, and then checking these positions against the maze grid, the function ensures the player moves only through valid paths or reaches the goal.
  • Make sure that the maze boundaries are considered to prevent the player from moving outside the maze. This is implicitly handled if the maze is correctly defined and the player is restricted by maze dimensions.