Cosmic Clash: A Laser Shooting Game – Yancy Jairo Tavares – Andy Garcia
VIDEO DOCUMENTATION:
https://drive.google.com/file/d/1hdaXuMpcq6xzkq17s7jbGnHaVP62Jkvf/view?usp=sharing
CONCEPTION AND DESIGN:
The project centered on designing and integrating various elements to create an engaging alien-shooting game that combined physical and digital interactions. The concept emerged from preparatory research and essay work, merging the fast-paced elements of reaction games with the thematic shooting mechanics of games like Galactica.
Space Invaders: Galactica
The design process began with creating a physical backdrop to house the light sensors, ensuring consistent lighting through the addition of a covered roof to eliminate external interference. The gun, consisting of an Arduino-powered laser and a button trigger, was designed to be intuitive and self-explanatory, enabling users to interact naturally without extensive instructions. Features such as a tutorial, hit-and-miss visual effects, and background music were added to guide players and immerse them in the dystopian gameplay. The Processing code dynamically handled visual feedback, including a scoreboard, timers, and a gameover screen, providing a cohesive and polished experience. These elements were chosen for their ability to create a simple yet engaging gameplay loop while remaining practical for transport and showcasing.
Understanding how users would interact with the game heavily informed key design decisions, and user testing played a critical role in refining these choices. For instance, the tutorial was highly effective in addressing potential learning curves, guiding players on how to aim and shoot accurately, which enhanced their overall experience. Feedback from testing revealed challenges like spamming the gun, disrupting gameplay, and difficulty in hitting the sensors accurately. This led to the introduction of a clearer explanation that after each shot, a new alien would appear, encouraging players to be more deliberate with their actions. Visual indicators were also added to guide players on where to aim, significantly improving accessibility and making the game more intuitive. The gameover screen, with its own music and countdown timer, provided a smooth transition back to the start, maintaining the game’s flow and replayability. Background music and sound effects for hits and misses enriched immersion, making the game feel more dynamic and responsive. The covered roof ensured reliable sensor performance in various lighting conditions, while the anti-spam features preserved gameplay integrity. Together, these adaptations not only addressed user feedback but also elevated the game to a polished, interactive, and enjoyable experience, aligning perfectly with the project’s goals.
FABRICATION AND PRODUCTION:
The production process for my project involved merging two initial concepts: a shooting alien game and a reaction-based hitting game. The design combined reaction game elements, such as timers, scores, and structured gameplay loops, with a thematic shooting experience inspired by games like Galactica and Bop It. I selected light sensors as the primary input devices to detect laser hits on specific targets, paired with an Arduino-powered laser and a button trigger to simulate an interactive gun. These components were chosen for their reliability, precision, and ease of integration, aligning perfectly with the project’s goal of creating a simple yet engaging gameplay experience. An alternative idea was to use motion sensors to track player movements for targeting aliens, but this was rejected due to concerns about portability, efficiency, and the complexity of implementation for showcasing. The light sensor-based approach proved optimal for its balance of simplicity and functionality, ensuring smooth interaction and effective user feedback.
initial sketch of game
Initial Core Circuit
Completed Core Circuit
Key features, such as a tutorial, gameover screen, scoreboard, and real-time hit and miss effects, were integral to enhancing the player’s experience. The tutorial guided users through the mechanics of aiming and shooting, making the game accessible to players of varying skill levels. The scoreboard and timer, rendered dynamically through Processing, provided a competitive and engaging element to the gameplay loop.
Initial Gameplay Loop
User Testing Gameplay Loop
Gameplay with background score and effects
Background music, combined with sound effects for hits and misses, further immersed players in the dystopian setting. The Game Over screen included its own distinct music track and a countdown to reset the game, ensuring a smooth transition back to the starting screen. User testing revealed areas for improvement, such as spamming the gun and difficulty in targeting the sensors, leading to important adaptations like anti-spam functionality, visual indicators for targets, and a covered roof for consistent lighting. These adjustments, paired with polished code and hardware integration, created a cohesive, immersive, and stable final product.
Gameplay with proper sounds and tutorial
CONCLUSIONS:
The goal of my project was to create an engaging and interactive alien-shooting game that combined physical and digital elements to provide an immersive experience for the audience. The project successfully achieved its stated goals by delivering an intuitive gameplay loop with features such as a tutorial, hit-and-miss effects, a scoreboard, and dynamic feedback through Processing. Based on my expectations, the audience interacted with the project with interest, showing appropriate reactions of excitement when they hit targets and mild frustration when they missed, which enhanced the competitive and immersive aspects of the game. Initially, many players spammed the button, misunderstanding the mechanics, but once I explained that a new alien appeared only after each shot, they adjusted their approach, becoming more strategic. This interaction aligns well with my definition of interaction, as the audience’s input directly influenced the game’s responses, creating a dynamic and engaging exchange.
What sets this project apart is the deliberate attention to how players would engage with the system and the seamless integration of feedback-driven design elements. The tutorial, for example, not only enhanced accessibility but demonstrated the value of guiding users toward intended interactions without compromising their sense of discovery. The audience’s shift from spamming to strategic play underscored the importance of clear communication and visual cues, which became a critical design takeaway. The score, little visual feedback, and immersive sound design were particularly valuable, as they showed how subtle but intentional details could dramatically improve user experience. If I had more time, I would have refined several aspects, such as improving the aesthetic appeal of the door and aligning the gun design with the game’s theme by creating a space gun rather than a cardboard pistol. I would also add more levels to the game to create a more progressive experience. I would also have added more targets, adjusted the randomization to prevent aliens from reappearing in the same location, and ensured the game reset itself properly. From setbacks, I learned the importance of iterative testing to uncover unexpected user behaviors, which is essential for refining any interactive system. Ultimately, this project has distinct value in illustrating how thoughtful design choices, informed by user feedback and iterative improvement, can transform technical execution into an experience that captivates and delights its audience, while also deepening my understanding of interaction design.
E. DISASSEMBLY:
F. APPENDIX
Creation of Backdrop and Gun
Arduino Code:
const int buttonPin = 2;
const int laserPin = 9;
const int sensorPins[] = {A0, A1, A2, A3, A4};
int thresholds[] = {700, 700, 800, 850, 900}; // Thresholds
bool laserState = false; // Laser state: ON or OFF
bool feedbackSent = false; // ensure one feedback is sent per button press
void setup() {
pinMode(buttonPin, INPUT_PULLUP); // Button with internal pull-up resistor
pinMode(laserPin, OUTPUT);
for (int i = 0; i < 5; i++) { // Set up all 5 sensor pins
pinMode(sensorPins[i], INPUT);
}
Serial.begin(9600); // Start serial communication
}
void loop() {
int buttonState = digitalRead(buttonPin); // Read the button state
// Check if the button is pressed
if (buttonState == LOW && !feedbackSent) { // Button pressed and feedback not yet sent
feedbackSent = true; // feedback is only sent once per press
laserState = true;
digitalWrite(laserPin, HIGH); // Turn on the laser
// Read the light sensor values
for (int i = 0; i < 5; i++) {
int sensorValue = analogRead(sensorPins[i]);
Serial.print("Sensor ");
Serial.print(i + 1);
Serial.print(" Value: ");
Serial.println(sensorValue);
// Determine hit or miss for the current sensor
if (sensorValue > thresholds[i]) { // Use sensor-specific threshold
Serial.print("HIT ");
Serial.println(i + 1); // Send "HIT x" message where x is the sensor index
break;
} else {
Serial.print("MISS ");
Serial.println(i + 1); // Send "MISS x" message where x is the sensor index
}
}
}
// Reset feedbackSent when the button is released
if (buttonState == HIGH) {
laserState = false;
feedbackSent = false; // Allow feedback for the next press
digitalWrite(laserPin, LOW); // Turn off the laser
}
}
Processing Code:
import processing.serial.*;
import ddf.minim.*;
PImage backgroundImg;
PImage aliveSprite, deadSprite;
Serial myPort;
Minim minim;
AudioPlayer gameOverMusic, backgroundMusic, bloodSound, shootSound, hitSound, missSound; // Sound players
Alien[] aliens; // Array of aliens
int activeAlienIndex; // Index of the active alien
String feedback = ""; // Current feedback to display ("HIT" or "MISS")
int displayTime = 1000; // Time to display the message in milliseconds
long messageStartTime; // Timestamp of the last message
// Feedback delay
boolean feedbackPending = false; // Whether feedback is waiting to be shown
// Score and timer
int score = 0; // Initial score
int gameDuration = 30000; // Game duration in milliseconds (e.g., 30 seconds)
long gameStartTime; // Time when the game starts
boolean gameOver = false; // Game over state
boolean gameStarted = false; // Game start state
// Start Screen
boolean showStartScreen = true; // Tracks whether the "Shoot anywhere to start" screen is active
// Tutorial
boolean tutorialActive = false;
Alien tutorialAlien;
boolean tutorialComplete = false;
long tutorialStartTime; // Timestamp when the tutorial starts
boolean showTutorialAlien = false; // Controls when the tutorial alien appears
String tutorialFeedback = ""; // Feedback during the tutorial ("HIT" or "MISS")
long tutorialFeedbackStartTime; // Start time for showing feedback. Online resources for learning "long"
int tutorialFeedbackDisplayTime = 1000; // How long to display the feedback (in milliseconds)
//Gameover reset
long gameOverStartTime = 0; // Time when the game over screen starts
void setup() {
size(1920, 1080);
backgroundImg = loadImage("background.png");
aliveSprite = loadImage("alive.png");
deadSprite = loadImage("dead.png");
println(Serial.list());
myPort = new Serial(this, "/dev/cu.usbmodem14401", 9600);
myPort.bufferUntil('\n');
// Initialize Minim and load sounds
minim = new Minim(this);
shootSound = minim.loadFile("shoot.wav");
hitSound = minim.loadFile("ding.wav");
missSound = minim.loadFile("miss.wav");
bloodSound = minim.loadFile("blood.mp3");
backgroundMusic = minim.loadFile("background_music.mp3");
gameOverMusic = minim.loadFile("gameover_music.mp3");
backgroundMusic.loop(); // Play background music in a loop
backgroundMusic.setGain(-10); // Set initial volume
// Initialize aliens for main game
aliens = new Alien[5];
aliens[0] = new Alien(300, 300, aliveSprite, deadSprite);
aliens[1] = new Alien(1620, 300, aliveSprite, deadSprite);
aliens[2] = new Alien(960, 540, aliveSprite, deadSprite);
aliens[3] = new Alien(300, 780, aliveSprite, deadSprite);
aliens[4] = new Alien(1620, 780, aliveSprite, deadSprite);
// Make tutorial alien at the center
tutorialAlien = new Alien(width / 2, height / 2, aliveSprite, deadSprite);
// Set the first active alien randomly
activeAlienIndex = int(random(5));
}
void draw() {
image(backgroundImg, 0, 0, width, height); // Draw the background image
filter(BLUR, 5);
if (showStartScreen) {
// Starting screen
fill(255);
textAlign(CENTER, CENTER);
textSize(72);
text("SHOOT ANYWHERE TO START", width / 2, height / 2);
textSize(36);
text("Press the button and aim anywhere on the screen", width / 2, height / 2 + 80);
} else if (tutorialActive && !tutorialComplete) {
// Tutorial screen
long timeElapsed = millis() - tutorialStartTime;
if (timeElapsed < 5000) { // During the first 5 seconds
fill(255);
textAlign(CENTER, CENTER);
textSize(72);
text("TUTORIAL", width / 2, height / 4);
textSize(36);
text("Hold the button and align the laser with the front sight", width / 2, height / 2);
// Countdown
int countdown = 5 - (int)(timeElapsed / 1000);
textSize(48);
text("Alien will appear in: " + countdown, width / 2, height / 2 + 100);
fill(255, 0, 0); // Red tooltip text
textSize(24);
text("TIP: Aim carefully to hit the alien!", width / 2, height - 100);
} else {
showTutorialAlien = true; // Enable the alien display after 5 seconds
}
if (showTutorialAlien) {
tutorialAlien.display(); // Show tutorial alien at the center
// Display feedback (HIT or MISS) if available
if (!tutorialFeedback.isEmpty() && millis() - tutorialFeedbackStartTime < tutorialFeedbackDisplayTime) {
fill(tutorialFeedback.equals("HIT") ? color(0, 255, 0) : color(255, 255, 0)); // Green for HIT, Yellow for MISS
textSize(48);
text(tutorialFeedback, width / 2, height / 2 - 150);
}
}
} else if (!gameOver) {
// Main game logic
aliens[activeAlienIndex].display();
if (!feedback.isEmpty()) {
fill(feedback.equals("HIT") ? color(255, 0, 0) : color(255, 255, 0)); // Red for HIT, Yellow for MISS
textAlign(LEFT, CENTER);
textSize(48);
text(feedback, aliens[activeAlienIndex].x + 50 + 20, aliens[activeAlienIndex].y);
if (millis() - messageStartTime > displayTime) {
feedback = "";
feedbackPending = false;
aliens[activeAlienIndex].reset(); // Reset alien state
activeAlienIndex = int(random(5));
}
}
fill(255);
textAlign(LEFT, TOP);
textSize(36);
text("Score: " + score, 50, 50);
int timeLeft = max(0, (int)((gameDuration - (millis() - gameStartTime)) / 1000.0));
textAlign(RIGHT, TOP);
text("Time: " + timeLeft + "s", width - 50, 50);
if (millis() - gameStartTime >= gameDuration) {
gameOver = true;
gameOverStartTime = millis(); // Record when the game over screen starts
}
} else {
// Game over screen
fill(255, 0, 0);
textAlign(CENTER, CENTER);
textSize(96);
text("GAME OVER", width / 2, height / 2 - 100);
textSize(48);
text("Final Score: " + score, width / 2, height / 2 + 50);
// Countdown to return to the start screen
int countdown = 5 - (int)((millis() - gameOverStartTime) / 1000);
if (countdown > 0) {
textSize(36);
text("Returning to Start in: " + countdown, width / 2, height / 2 + 150);
} else {
resetGame(); // Reset the game state and return to the start screen //Did not work completely
}
// Switch to Game Over music
if (backgroundMusic.isPlaying()) {
backgroundMusic.pause(); // Stop background music
}
if (!gameOverMusic.isPlaying()) {
gameOverMusic.loop(); // Play Game Over music in a loop
}
}
}
void resetGame() {
score = 0;
gameOver = false;
gameStarted = false;
showStartScreen = true;
tutorialActive = false;
tutorialComplete = false;
feedback = "";
feedbackPending = false;
activeAlienIndex = int(random(5)); // Randomize the first alien
// Stop Game Over music
if (gameOverMusic.isPlaying()) {
gameOverMusic.pause();
}
// Resume background music
if (!backgroundMusic.isPlaying()) {
backgroundMusic.loop();
}
// Reset tutorial alien to its initial state
tutorialAlien.reset();
}
void serialEvent(Serial myPort) {
shootSound.rewind();
shootSound.play();
// Transition from the "Shoot anywhere to start" screen to the tutorial
if (showStartScreen && input.matches("(HIT|MISS) [1-5]")) {
showStartScreen = false;
tutorialActive = true;
tutorialStartTime = millis(); // Start the tutorial timer
println("Transitioning to Tutorial...");
return;
}
// Tutorial logic
if (tutorialActive && !tutorialComplete && showTutorialAlien) {
if (input.equals("HIT 3")) {
tutorialFeedback = "HIT"; // Display HIT feedback
tutorialFeedbackStartTime = millis();
tutorialAlien.setDead(); // Set tutorial alien to dead state
tutorialComplete = true; // tutorial as complete
tutorialActive = false;
hitSound.rewind();
hitSound.play();
bloodSound.rewind();
bloodSound.play(); // Play blood.mp3
println("Tutorial Completed! Game Starting..."); // Did not work
gameStarted = true;
gameStartTime = millis();
return;
} else if (input.equals("MISS 3")) {
tutorialFeedback = "MISS"; // Display MISS feedback
tutorialFeedbackStartTime = millis();
missSound.rewind();
missSound.play();
println("Tutorial: MISS Detected");
return;
}
}
// Main game logic
if (!gameStarted && input.matches("(HIT|MISS) [1-5]")) {
gameStarted = true;
gameStartTime = millis();
return;
}
if (!feedbackPending) {
if (input.equals("HIT " + (activeAlienIndex + 1))) {
feedback = "HIT";
feedbackPending = true;
messageStartTime = millis();
aliens[activeAlienIndex].setDead(); // Set alien to dead state
hitSound.rewind();
hitSound.play();
bloodSound.rewind();
bloodSound.play(); // Play blood.mp3
score += 7;
} else if (input.equals("MISS " + (activeAlienIndex + 1))) {
feedback = "MISS";
feedbackPending = true;
messageStartTime = millis();
missSound.rewind();
missSound.play();
score -= 1;
}
}
}
class Alien {
float x, y;
PImage aliveSprite, deadSprite;
boolean isDead;
Alien(float x, float y, PImage aliveSprite, PImage deadSprite) {
this.x = x;
this.y = y;
this.aliveSprite = aliveSprite;
this.deadSprite = deadSprite;
this.isDead = false;
}
void display() {
float spriteWidth = 200; // Adjust width here
float spriteHeight = 250; // Adjust height here
if (isDead) {
image(deadSprite, x - spriteWidth / 2, y - spriteHeight / 2, spriteWidth, spriteHeight);
} else {
image(aliveSprite, x - spriteWidth / 2, y - spriteHeight / 2, spriteWidth, spriteHeight);
}
}
void setDead() {
isDead = true;
}
void reset() {
isDead = false;
}
}
SPRITES:
BACKGROUND:
SOUND EFFECTS:
BACKGROUND MUSIC: