In this game, the player controls an archer who must shoot arrows at targets. The player can move using the WASD keys, and can shoot arrows by pressing the space bar. The target will move back and forth across the screen, and the player must time their shots to hit the target. The game will keep track of the player’s score, which increases each time they hit the target. The game ends when the player reaches 30 points.

flowchart TD

    A[Game Starts] --> B[Explore Map]

    B --> C{Interact w/ Villager NPC?}

    C -->|No| B
    C -->|Yes| D[NPC Asks to Start Game]

    D --> E{Player Choice}

    E -->|Nevermind| B
    E -->|Start| F[Remove Barrier, Start Target Movement, Destroy NPC, Enable Player Attacks]

    F --> G{Player Presses Space?}

    G --> |True| H{Spawn Arrow}
    G --> |False| I
    
    H --> I{Arrow Obj Exists?}

    I --> |True| J[Move arrow up screen]
    J --> K{Arrow hits target?}
    K --> |True| L[Arrow sticks to target, hits remaining decreases by 1]

    L --> M{Hits remaining <= 0?}
    M --> |True| N[Show end screen, stop game]
    
    M --> |False| O{Arrow is offscreen?}

    O --> |True| P[Destroy Arrow]
    O --> |False| Q[Draw all]
    P --> Q

    Q --> G

Key Features

  • OOP (e.g. Inheritance)
    • The FightingPlayer class inherits from the Player class, and the Projectile class inherits from the Character class.
  • Collision Detection
    • Custom error detection for when the arrow hits the target, and when the arrow goes offscreen.
  • Data Structures
    • Arrays (for example, for projectiles)
    • Objects (sprite data)
  • Game Loop
    • Handles movement, drawing, and collision detection every frame
  • Event Handling
    • Keyboard input for player movement and shooting
  • Conditionals, Loops, other simple programming concepts

File Structure

All code is in /assets/js/castleGame.

  • /assets/js/castleGame/GameLevelArchery > main file
  • /assets/js/castleGame/castleArchery.md > Markdown file to view the game
  • /assets/js/castleGame/custom/EndScreen.js > Displays an end screen upon winning
  • /assets/js/castleGame/custom/FightingPlayer.js > A special kind of Player that can attack
  • /assets/js/castleGame/custom/Projectile.js > Used for the arrows, implements animation, control, collision detection

Challenge

Archery Game

Lines: 1 Characters: 0
Game Status: Not Started

Code example: FightingPlayer.js

import Player from '../essentials/Player.js';
import Projectile from './Projectile.js';

/**
 * A version of the Player class with added functionality for shooting projectiles (arrows).
 * @param data - Initial data for the player character.
 * @param gameEnv - The game environment object, providing access to game state and resources.
 * 
 * This class extends the basic Player class to allow for SPACE to create arrows
 */
class FightingPlayer extends Player {
    // Construct the class, with a list of stored projectiles
    constructor(data = null, gameEnv = null) {
        super(data, gameEnv);
        this.projectiles = [];
        this.lastAttackTime = Date.now();
        this.attackCooldown = 500; // 500ms between shots
        this.currentDirection = 'right'; // track facing direction

        // Bind attack to spacebar
        if (typeof window !== 'undefined') {
            this._attackHandler = (event) => {
                if (event.code === 'Space' || event.key === ' ') {
                    this.attack();
                }
            };
            window.addEventListener('keydown', this._attackHandler);
        }
    }

    // Update spook and the projectiles
    update(...args) {
        super.update(...args);  // Do normal player updating
        
        // Track facing direction based on movement
        if (this.velocity.x > 0) this.currentDirection = 'right';
        else if (this.velocity.x < 0) this.currentDirection = 'left';
        
        // Update and clean up projectiles
        this.projectiles = this.projectiles.filter(p => !p.revComplete);
        this.projectiles.forEach(p => p.update());
    }

This code includes the constructor (which calls the parent class’s constructor and then its own additions). It also includes a feature to update projectiles every tick.


    // Execute an attack
    attack() {
        // Don't allow shooting until the game has started
        if (typeof window !== 'undefined' && !window.archeryGameStarted) {
            return;
        }
        
        const now = Date.now();
        if (now - this.lastAttackTime < this.attackCooldown) return;
        
        // Calculate target point in direction player is facing
        const facingRight = this.currentDirection === 'right';
        // Shoot arrow 500 pixels in facing direction
        const targetX = this.position.x + (facingRight ? 500 : -500);
        const targetY = this.position.y;
        
        // Create arrow projectile
        this.projectiles.push(
            new Projectile(
                this.gameEnv,
                targetX, 
                targetY,
                // Offset source position to start at player center
                this.position.x + this.width/2,
                this.position.y + this.height/2,
                "PLAYER"  // Special type for player projectiles
            )
        );
        
        this.lastAttackTime = now;
    }

This is the main addition to the class. This code creates a new projectile every time attack() is called.


    // Clean up event listeners when destroyed
    destroy() {
        if (typeof window !== 'undefined' && this._attackHandler) {
            window.removeEventListener('keydown', this._attackHandler);
        }
        super.destroy();
    }
}

export default FightingPlayer;

This code wraps up the fighting player with a destroy function.