Ok, so firstly, your project goal is huge so take a deep breath and give yourself a year timeline just to get something remotely close to your idea. It is doable, likely, just start breaking down the concepts into subparts you can work on in a modular way.
Endorsed by the mighty @alto777 in another thread today, and oft referenced by yours truly, you need to learn the magic of State Machines, aka Finite State Machines. It's the best way in this humble hobbyist's opinion to go from blink to blink when a button is pressed, but only if the room is dark, say.
This video will change the way you think about Arduino projects, since you are interested in game design, might as well do it like the best in the business, Nintendo.
Here's a code I wrote for some student in the forums a while back that approaches some of what I think you're thinking of. You should be able to try it out yourself with only a few parts and a few minutes to slap the circuit together.
Note that, if nothing else, there are State Machines used, millis
timers, EEPROM high score keeping and resetting as an option, and something like a splash "screen" awaiting players.
/* Blast 'Em! Note to self: DO NOT MODIFY!
by Hallowed31
May 14-15, 2024
Additional parts needed:
- One or two light-dependent resistors. No
additional pullup or pulldown resistors needed.
- One or two servos. Attach some target to the servo
horn after installing LDR through each one, like a
bullseye.
- One pushbutton.
- Some light source as blaster. Cat laser toy, perhaps
- External power supply for servos. Never use Arduino
+5V regulator to power servos.
- Computer connected to Arduino to use Serial Monitor
or why not try your favorite terminal emulation program
such as puTTY or CoolTerm?
Gameplay circuit:
- LDRs from A0 and A1 to ground.
- Servos powered externally (+5V) from Arduino, signal pins
to D9 and D10 and don't forget
to ground servos to both Arduino AND external power supply
- Start button (pushbutton) to D2 and Arduino GND.
- Works with one sensor/servo combo or two.
- Known issue: the timer will occasionally overflow/keep running
when cycling between modes such as manual, BIOS, gameplay
- Ensure Serial monitor is open, default size prints prettiest.
Autoscroll should be checked, show timestamp unchecked,
set to 115200 baud, no line ending.
- CoolTerm settings: "blastEm.stc"
*/
#include <EEPROM.h>
#include <Servo.h>
Servo servoOne, servoTwo;
int targetOne = A0;
int targetTwo = A1;
int led = 13;
int playerButton = 2;
int target1Value, target2Value;
int gameMode, highScore, playerState, lastPlayerState;
int eeAddress;
unsigned long score, gameTime, lastGameTime, gameTimeLimit, targetDown, lastTargetDown, tar2Down, lastTar2Down;
unsigned long previousGameTimer;
const long interval = 1000;
const long biosWait = 3200;
unsigned long prevBios = 0;
int timer = 30;
int biosTimer = 3;
int bios;
void setup() {
Serial.begin(115200);
eeAddress = 0;
pinMode(targetOne, INPUT_PULLUP);
pinMode(targetTwo, INPUT_PULLUP);
pinMode(playerButton, INPUT_PULLUP);
pinMode(led, OUTPUT);
/* uncomment resetHighScore() function and upload to reset
saved high score to zero,
then comment out again, upload sketch again
to resume playing normal game */
// resetHighScore();
servoOne.attach(10);
servoTwo.attach(11);
servoOne.write(0);
servoTwo.write(0);
cycleServos();
gameTimeLimit = 31330;
targetDown = 500;
tar2Down = 500;
lastTargetDown = 0;
lastTar2Down = 0;
lastPlayerState = LOW;
target1Value = 0;
target2Value = 0;
credits();
biosPrompt();
unsigned long biosT = millis();
while (biosT + biosWait >= millis()) {
unsigned long thisBiosTimer = millis();
if (Serial.available() > 0) {
char biosMode = Serial.read();
switch (biosMode) {
case 'b':
bios = true;
break;
case 'B':
bios = true;
break;
}
}
if (thisBiosTimer - prevBios >= interval) {
if (biosTimer > 0) {
Serial.println(biosTimer);
}
biosTimer --;
if (biosTimer == -1) {
timer = 0;
}
prevBios = thisBiosTimer;
}
}
playerState = digitalRead(playerButton);
while (playerState != lastPlayerState) {
playerState = digitalRead(playerButton);
awaitingPlayerMessage();
viewHighScore();
}
lastPlayerState = playerState;
score = 0;
gameTime = 0;
lastGameTime = 0;
previousGameTimer = 0;
if (bios == true) {
gameMode = 1;
}
else if (bios == false) {
gameMode = 2;
}
}
void loop() {
// start game timer
gameTime = millis();
if (Serial.available() > 0) {
char switchMode = Serial.read();
switch (switchMode) {
case 'b':
gameMode = 1; // viewBios/menu
break;
case 'n':
gameMode = 2; // new player
break;
case ' ':
gameMode = 3; // play game
break;
case 'r':
gameMode = 4; // gameover
break;
case '?':
gameMode = 5; // view raw
break;
case 'h':
gameMode = 6; // view hi scores
break;
case 'm':
gameMode = 7; // read manual
break;
}
}
switch (gameMode) {
case 1:
viewBios();
lastGameTime = gameTime;
break;
case 2:
newPlayer();
lastGameTime = gameTime;
break;
case 3:
normalGameplay();
if (gameTime - lastGameTime > gameTimeLimit) { // outta time
gameMode = 4;
}
break;
case 4:
gameOver();
break;
case 5:
viewRawTargetReadings();
break;
case 6:
viewHighScore();
break;
case 7:
manual();
break;
}
}
// gameMode 2
void newPlayer() {
newPlayerMessage();
lastGameTime = 0;
score = 0;
timer = 30;
gameMode = 3;
}
// gameMode 3
void normalGameplay() {
unsigned long thisGameTimer = millis();
if (thisGameTimer - previousGameTimer >= interval) {
timer --;
if (timer == -1) {
timer = 30;
}
previousGameTimer = thisGameTimer;
}
target1Value = analogRead(targetOne);
target2Value = analogRead(targetTwo);
if (target1Value < 75) {
lastTargetDown = gameTime;
if (gameTime - lastTargetDown <= targetDown) {
digitalWrite(led, HIGH);
servoOne.write(90);
score = score + 1;
if (score > highScore) {
highScore = score;
EEPROM.put(eeAddress, highScore);
}
}
}
if (target2Value < 75) {
lastTar2Down = gameTime;
if (gameTime - lastTar2Down <= tar2Down) {
digitalWrite(led, HIGH);
servoTwo.write(90);
score = score + 1;
if (score > highScore) {
highScore = score;
EEPROM.put(eeAddress, highScore);
}
}
}
if (gameTime - lastTargetDown >= targetDown) {
digitalWrite(led, LOW);
servoOne.write(0);
}
if (gameTime - lastTar2Down >= tar2Down) {
digitalWrite(led, LOW);
servoTwo.write(0);
}
Serial.print("score ");
Serial.print(score);
Serial.print("\t\t");
if (timer >= 10) {
Serial.print("Time ");
}
else if (timer < 10) {
Serial.print("Time 0");
}
Serial.println(timer);
}
// gameMode 4
void gameOver() {
gameOverMessage();
awaitingPlayerMessage();
playerState = digitalRead(playerButton);
delay(20); // simple debouncing
playerState = digitalRead(playerButton);
if (playerState == LOW) {
gameMode = 2; // reset
}
}
/*****************************************************
*********** UTILITIES ******************
*****************************************************/
unsigned long ma, mb, mc, lastMa, lastMb, lastMc;
void viewBios() {
Serial.println();
Serial.println(F(" Blast 'Em v2"));
Serial.println();
Serial.println(F(" Type SPACE during game to resume normal gameplay"));
Serial.println();
Serial.println((" Type r during game to reset game"));
Serial.println();
Serial.println((" Type m to view manual"));
Serial.println((" Type ? during game to view raw sensor readings"));
Serial.println((" Type h during game to pause game and see high score"));
EEPROM.get(eeAddress, highScore);
Serial.println();
Serial.print((" All Time High Score: "));
Serial.println(highScore);
Serial.println();
Serial.println((" Type X to exit BIOS and return"));
Serial.println();
while (!Serial.available()) {
}
if (Serial.available() > 0) {
char myBios = Serial.read();
switch (myBios) {
case 'x':
gameMode = 2;
bios = false;
lastGameTime = gameTime;
break;
case 'X':
gameMode = 2;
bios = false;
lastGameTime = gameTime;
break;
case ' ':
gameMode = 3; // play game
break;
case 'r':
gameMode = 4; // gameover
break;
case '?':
gameMode = 5; // view raw
break;
case 'h':
gameMode = 6; // view hi scores
break;
case 'm':
gameMode = 7; // read manual
break;
}
}
}
void manual() {
Serial.println(F("press button when prompted to begin normal play"));
Serial.println();
Serial.println(F("Enter key commands during game and press ENTER"));
Serial.println();
Serial.println(("Key Commands: "));
Serial.println((" ? during game to view sensor data for calibration"));
Serial.println((" h to view high score"));
Serial.println((" r to reset game"));
Serial.println((" h to view high score"));
Serial.println((" Space bar + Enter to return to game time and score"));
Serial.println((" r to reset game"));
Serial.println();
Serial.println();
Serial.println((" Type X to return"));
Serial.println();
while (!Serial.available()) {
}
if (Serial.available() > 0) {
char escape = Serial.read();
switch (escape) {
case 'x':
gameMode = 2;
lastGameTime = gameTime;
break;
case 'X':
gameMode = 2;
lastGameTime = gameTime;
break;
}
}
}
// called in newPlayer() gameMode 2
void newPlayerMessage() {
score = 0;
Serial.println();
Serial.println(F(" New Player Blast Em!"));
Serial.println();
delay(2000);
Serial.print("score ");
Serial.println(score);
}
// called in gameOver() gameMode 4
void awaitingPlayerMessage() {
ma = millis();
const unsigned long maTime = 5000;
if (ma - lastMa >= maTime) {
lastMa = ma;
Serial.println();
Serial.println(F(" Press Button to Play"));
Serial.println();
Serial.println(F(" Awaiting Player"));
Serial.println();
}
}
// called in gameOver() gameMode 4
void gameOverMessage() {
mb = millis();
const unsigned long mbTime = 7000;
if (mb - lastMb >= mbTime) {
lastMb = mb;
Serial.println();
Serial.println(F(" Time's Up -------- Game Over"));
Serial.println();
Serial.print(F(" Your Score: "));
Serial.print(score);
Serial.print(F(" High Score: "));
Serial.println(highScore);
Serial.println();
}
}
// gameMode 6
void viewHighScore() {
mc = millis();
EEPROM.get(eeAddress, highScore);
const unsigned long mcTime = 11000;
if (mc - lastMc >= mcTime) {
lastMc = mc;
Serial.println();
Serial.print(F(" High Score: "));
Serial.println(highScore);
Serial.println();
}
}
void credits() {
Serial.println(F("Blast 'Em!"));
Serial.println(F("by Hallowed31"));
delay(1000);
for (int i = 0; i < 3; i++) {
Serial.println();
delay(200);
}
}
void biosPrompt() {
Serial.println(F("Type in B and use button"));
Serial.println(F("at prompt to enter BIOS"));
delay(1500);
for (int i = 0; i < 3; i++) {
Serial.println();
delay(200);
}
}
// gameMode 5
void viewRawTargetReadings() {
unsigned long readInterval = 1000;
if (gameTime - lastGameTime >= readInterval) {
lastGameTime = gameTime;
target1Value = analogRead(targetOne);
target2Value = analogRead(targetTwo);
Serial.print(F("ADC readings : Target 1 (A0) value is "));
Serial.print(target1Value); // for checking Photoresistor input
Serial.print(F(" Target 2 (A1) value is "));
Serial.println(target2Value);
Serial.println();
}
}
void resetHighScore() {
highScore = 0;
EEPROM.put(eeAddress, highScore);
while (1);
}
void cycleServos() {
servoOne.write(90);
servoTwo.write(90);
delay(250);
servoOne.write(0);
servoTwo.write(0);
}