01 March 2026

Scaffholding: Mapping - First person shooter

 






The project involves creating a first-person shooter game using the Arduino system, specifically the Circuit Playground Express, where the goal is to practice value range mapping with analog I/O. The core gameplay loop requires the player to use a potentiometer to aim a blue Neopixel which serves as a crosshair, around the circle of lights to align with a randomly placed red dot which is the enemy. The external button is used to "fire," and if the crosshair is aiming at the enemy, a successful shot is registered, the lights briefly flash green, and a new red target appears. If the player fails to hit a target within five seconds, they are stunned by the enemy, the pixels turn yellow and the player cannot shoot. While stunned the player must shake the Circuit Playground Express to stop being stunned, and they then gain full control again, with the 5 second timer starting back up. Once an enemy is defeated and a new one spawns, the timer resets. The accelerometer's shake detection is used for this shake detection. The game's objective is to successfully hit the target 15 times, at which point the neopixels will turn green, a jingle is played. And after 5 seconds the game resets.
#include <Adafruit_CircuitPlayground.h>
#include <Adafruit_Circuit_Playground.h>

int potentiometerPin = A2;
int buttonPin = 10;
int potValue = 0;
int mappedValue = 0;
bool aimingAtEnemy = false;
int enemyPixel;
int enemiesDefeated = 0; // how many enemies defeated currently
int maxEnemies = 15; // number of enemies to beat the game
int winDelay = 5000; //  how much time to wait after winning before game starts again
bool isStunned = false;
int enemySpawnedTime;
const int stunnedTimer = 5000; // 5 seconds to stun after enemy spawn
int shakeThresh = 20;

void setup() {
  // put your setup code here, to run once:
  CircuitPlayground.begin();
  pinMode(buttonPin, INPUT);
  randomSeed(analogRead(A0)); // this SHOULD make a random seed using A0 every time the board starts
  spawnEnemy();
}

void loop() {
  // put your main code here, to run repeatedly:
  if(CircuitPlayground.slideSwitch()) { // slide switch to stop the game
    readPotentiometer();
    if(isStunned){ // if player is stunned by enemy
      checkShake(); // check if CPE is being shook
      stunnedIndicator(); // turn on stunned pixels
    } else {
      if ((int)millis() - enemySpawnedTime >= stunnedTimer) {
        isStunned = true; // stun player 5 seconds after enemy spawns
      } else {
        checkIfFacingEnemy();
        checkShoot();
      }
    }

    delay(50);
  }
}

void readPotentiometer() // take pot input and map it to the pixels
{
  potValue = analogRead(potentiometerPin);
  mappedValue = map(potValue, 0, 1023, 0, 10); //10 instead of 9 so it can turn whole way, clamp w/ next line
  if(mappedValue > 9) mappedValue = 9; // clamp to 9, fix for not being able to turn the pot to pixel 9
}

void spawnEnemy()
{
  enemyPixel = random(0, 10); // random neopixel
  aimingAtEnemy = false;
  isStunned = false; // new enemy, makes sure stun is reset
  enemySpawnedTime = (int)millis(); // store cuurrent time since spawn in milliseconds
  CircuitPlayground.clearPixels();
  CircuitPlayground.setPixelColor(enemyPixel, 255, 0, 0); // show enemy on the random neopixel
}

void checkIfFacingEnemy() // check if player is looking at enemy
{
  CircuitPlayground.clearPixels();
  CircuitPlayground.setPixelColor(enemyPixel, 100, 0, 0); // enemy placement
  CircuitPlayground.setPixelColor(mappedValue, 0, 0, 150); // crosshair

  if (mappedValue == enemyPixel)
  {
    enemyAimIndicator(); //turn all neopixels red (aiming at enemy)
  }
  else
  {
    aimingAtEnemy = false;
  }
}

void enemyAimIndicator() // aiming at enemy, pixels turn red
{
  aimingAtEnemy = true;
  for (int i = 0; i < 10; i++)
  {
    CircuitPlayground.setPixelColor(i, 255, 0, 0); // tuurn all pixels red
  }
}

void checkShoot() // did player press button
{
  int buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH && aimingAtEnemy) //only shoot if aiming at enemy
  {
    CircuitPlayground.playTone(800, 100); // play shooting sound
    defeatEnemy();
  }
}

void defeatEnemy()
{
  for (int i = 0; i < 10; i++)
  {
    CircuitPlayground.setPixelColor(i, 0, 255, 0); // tunr the pixels green when you shoot enemy
  }
  delay(500);
    enemiesDefeated++; // increase enmies defeated int
  if (enemiesDefeated >= maxEnemies) //did player win
  {
    winGame(); // won
  }
  else
  {
    spawnEnemy(); // not won yet
  }
}

void winGame()
{
  CircuitPlayground.playTone(500, 200); // play little jingle for winning
  CircuitPlayground.playTone(700, 200);
  CircuitPlayground.playTone(900, 300);

  for (int i = 0; i < 10; i++)
  {
    CircuitPlayground.setPixelColor(i, 0, 255, 0); // turn all the leds green to show you won
  }

  delay(winDelay); // stay on green leds for 5 seconds
  enemiesDefeated = 0; // reset the game
  spawnEnemy(); // start again
}

void stunnedIndicator() {
  for (int i = 0; i < 10; i++) {
    CircuitPlayground.setPixelColor(i, 255, 255, 0); // turns all pixels yellow while stunned
  }
}

void checkShake() { // see if CPE is being shook
  float shake = abs(CircuitPlayground.motionX())
    + abs(CircuitPlayground.motionY())
    + abs(CircuitPlayground.motionZ());
  if(shake > shakeThresh) { // if its being shook enough
      isStunned = false; // undo stun
      enemySpawnedTime = (int)millis(); // start timer again
    }
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.