Arnov Sharma
Published © MIT

PICO Blasters

PICO blaster is a Space invaders like game running on my Custom PICO 2 Based game console.

BeginnerFull instructions provided1 hour395
PICO Blasters

Things used in this project

Hardware components

Raspberry Pi Pico 2
Raspberry Pi Pico 2
×1
PCBWay Custom PCB
PCBWay Custom PCB
×1

Software apps and online services

Fusion
Autodesk Fusion
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

cad file

Schematics

SCH

Code

code

C/C++
#include <Adafruit_Protomatter.h>
#include <SPI.h>
#include <stdint.h>
#include <math.h>
// Matrix configuration
#define WIDTH 64
#define HEIGHT 32
uint8_t rgbPins[] = {2, 3, 4, 5, 8, 9};
uint8_t addrPins[] = {10, 16, 18, 20};
#define CLK 11
#define LAT 12
#define OE 13
Adafruit_Protomatter matrix(WIDTH, HEIGHT, 1, rgbPins, 4, addrPins, CLK, LAT, OE, false);
// Button pins
#define BUTTON_UP 7
#define BUTTON_DOWN 6
#define BUTTON_LEFT 15
#define BUTTON_RIGHT 14
#define BUTTON_FIRE 27
#define BUTTON_MISSILE 28 // GPIO28 for missile
// Game state variable
bool gameOver = false;
unsigned long gameOverStartTime = 0;
const unsigned long gameOverDuration = 5000; // 5 seconds
// Ship parameters
#define SHIP_WIDTH 7
#define SHIP_HEIGHT 5
int shipX = 0;
int shipY = HEIGHT / 2 - SHIP_HEIGHT / 2;
// Projectile variables
#define PROJECTILE_WIDTH 2
#define PROJECTILE_HEIGHT 2
#define MAX_PROJECTILES 5
int projX[MAX_PROJECTILES];
int projY[MAX_PROJECTILES];
bool projectileActive[MAX_PROJECTILES];
const uint16_t projectileColor = matrix.color565(0, 255, 0);
int nextProjectile = 0;
// Rock variables
#define MAX_ROCKS 5
#define ROCK_SMALL_SIZE 3
#define ROCK_MEDIUM_SIZE 5
#define ROCK_LARGE_SIZE 7
int rocks[MAX_ROCKS][3]; // [x, y, size]
unsigned long lastRockSpawn = 0;
const unsigned long rockSpawnInterval = 500;
const uint16_t rockColor = matrix.color565(255, 100, 0);
const int rockSpeed = 1;
int rockHitCount[MAX_ROCKS];
bool blastActive = false; // Add blast active flag
int blastX, blastY;       // Blast coordinates
unsigned long blastStartTime;
const unsigned long blastDuration = 100; //ms
// Missile variables
#define MISSILE_WIDTH 4
#define MISSILE_HEIGHT 4
int missileX = -1;
int missileY = -1;
bool missileActive = false;
const uint16_t missileColor = matrix.color565(255, 0, 0);
unsigned long lastMissileTime = 0;
const unsigned long missileCooldown = 10000;
// Spaceship sprite data (arrow pointing right)
static const uint8_t shipSprite[SHIP_HEIGHT] = {
0b0010000,
0b0011000,
0b1111111,
0b0011000,
0b0010000
};
const uint16_t shipColor = matrix.color565(0, 255, 255);
// Variables for fire rate control
unsigned long lastFireTime = 0;
const unsigned long fireRate = 200;
// Function to draw a circle
void drawCircle(int x0, int y0, int r, uint16_t color) {
int f = 1 - r;
int ddF_x = 1;
int ddF_y = -2 * r;
int x = 0;
int y = r;
matrix.drawPixel(x0, y0 + r, color);
matrix.drawPixel(x0, y0 - r, color);
matrix.drawPixel(x0 + r, y0, color);
matrix.drawPixel(x0 - r, y0, color);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
matrix.drawPixel(x0 + x, y0 + y, color);
matrix.drawPixel(x0 - x, y0 + y, color);
matrix.drawPixel(x0 + x, y0 - y, color);
matrix.drawPixel(x0 - x, y0 - y, color);
matrix.drawPixel(x0 + y, y0 + x, color);
matrix.drawPixel(x0 - y, y0 + x, color);
matrix.drawPixel(x0 + y, y0 - x, color);
matrix.drawPixel(x0 - y, y0 - x, color);
}
}
// Function to draw the blast animation
void drawBlast() {
if (blastActive) {
matrix.drawPixel(blastX, blastY, matrix.color565(255, 255, 255));
matrix.drawPixel(blastX + 1, blastY, matrix.color565(255, 200, 0));
matrix.drawPixel(blastX - 1, blastY, matrix.color565(255, 200, 0));
matrix.drawPixel(blastX, blastY + 1, matrix.color565(255, 200, 0));
matrix.drawPixel(blastX, blastY - 1, matrix.color565(255, 200, 0));
if (millis() - blastStartTime > blastDuration) {
blastActive = false; // Clear the blast after duration
}
}
}
// Function to draw text (using Adafruit_GFX style)
void drawText(int x, int y, const char *text, uint16_t color) {
matrix.setTextColor(color);
matrix.setCursor(x, y);
matrix.print(text);
}
// Function to draw the game over screen
void drawGameOver() {
matrix.fillScreen(0); // Clear the entire screen buffer
// Draw a big circle for the face
int centerX = WIDTH / 2;
int centerY = HEIGHT / 2;
int radius = 10;
uint16_t circleColor = matrix.color565(255, 255, 0); // Yellow
drawCircle(centerX, centerY, radius, circleColor);
// Draw the sad eyes as crosses
uint16_t eyeColor = matrix.color565(0, 0, 0); // Black
matrix.drawPixel(centerX - 5, centerY - 5, eyeColor);
matrix.drawPixel(centerX - 4, centerY - 4, eyeColor);
matrix.drawPixel(centerX - 5, centerY - 4, eyeColor);
matrix.drawPixel(centerX - 4, centerY - 5, eyeColor);
matrix.drawPixel(centerX + 4, centerY - 5, eyeColor);
matrix.drawPixel(centerX + 5, centerY - 4, eyeColor);
matrix.drawPixel(centerX + 4, centerY - 4, eyeColor);
matrix.drawPixel(centerX + 5, centerY - 5, eyeColor);
// Draw the sad mouth
for (int x = centerX - 4; x <= centerX + 4; x++) {
matrix.drawPixel(x, centerY + 3, eyeColor);
}
matrix.show();
}
// Setup function
void setup() {
matrix.begin();
matrix.fillScreen(0);
Serial.begin(9600);
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUTTON_LEFT, INPUT_PULLUP);
pinMode(BUTTON_RIGHT, INPUT_PULLUP);
pinMode(BUTTON_FIRE, INPUT_PULLUP);
pinMode(BUTTON_MISSILE, INPUT_PULLUP);
// Initialize rocks
for (int i = 0; i < MAX_ROCKS; i++) {
rocks[i][0] = -ROCK_LARGE_SIZE * 2; // Initialize off-screen
rocks[i][1] = 0;
rocks[i][2] = ROCK_LARGE_SIZE; // Start with largest size
rockHitCount[i] = 0;
}
// Initialize projectiles
for (int i = 0; i < MAX_PROJECTILES; i++) {
projX[i] = -PROJECTILE_WIDTH;
projY[i] = -PROJECTILE_HEIGHT;
projectileActive[i] = false;
}
missileX = -MISSILE_WIDTH;
missileY = -MISSILE_HEIGHT;
missileActive = false;
gameOver = false;
gameOverStartTime = 0; // Initialize
Serial.println("Starting up...");
}
// Function to draw the spaceship
void drawShip() {
if (!gameOver) { // Only draw if game is not over
for (int y = 0; y < SHIP_HEIGHT; y++) {
for (int x = 0; x < SHIP_WIDTH; x++) {
if (bitRead(shipSprite[y], 6 - x)) {
matrix.drawPixel(shipX + x, shipY + y, shipColor);
}
}
}
}
}
// Function to draw the projectiles
void drawProjectiles() {
for (int i = 0; i < MAX_PROJECTILES; i++) {
if (projectileActive[i]) {
matrix.fillRect(projX[i], projY[i], PROJECTILE_WIDTH, PROJECTILE_HEIGHT, projectileColor);
}
}
}
// Function to draw the missile
void drawMissile() {
if (missileActive) {
matrix.fillRect(missileX, missileY, MISSILE_WIDTH, MISSILE_HEIGHT, missileColor);
}
}
// Function to reset the game state
void resetGame() {
gameOver = false;
gameOverStartTime = 0;
shipX = 0;
shipY = HEIGHT / 2 - SHIP_HEIGHT / 2;
for (int i = 0; i < MAX_PROJECTILES; i++) {
projX[i] = -PROJECTILE_WIDTH;
projY[i] = -PROJECTILE_HEIGHT;
projectileActive[i] = false;
}
for (int i = 0; i < MAX_ROCKS; i++) {
rocks[i][0] = -ROCK_LARGE_SIZE * 2; // Reset all rocks offscreen
rocks[i][1] = 0;
rocks[i][2] = ROCK_LARGE_SIZE;
rockHitCount[i] = 0;
}
missileX = -MISSILE_WIDTH;
missileY = -MISSILE_HEIGHT;
missileActive = false;
lastRockSpawn = 0;
lastFireTime = 0;
lastMissileTime = 0;
blastActive = false;
}
// Main loop
void loop() {
if (gameOver) {
drawGameOver();
if (millis() - gameOverStartTime >= gameOverDuration) {
resetGame();
}
return; // Stop updating the game
}
matrix.fillScreen(0);
// Movement handling
if (!digitalRead(BUTTON_UP)) {
shipY = max(shipY - 1, 0);
}
if (!digitalRead(BUTTON_DOWN)) {
shipY = min(shipY + 1, HEIGHT - SHIP_HEIGHT); // Corrected variable name here
}
if (!digitalRead(BUTTON_LEFT)) {
shipX = max(shipX - 1, 0);
}
if (!digitalRead(BUTTON_RIGHT)) {
shipX = min(shipX + 1, WIDTH - SHIP_WIDTH);
}
// Firing projectiles
if (!digitalRead(BUTTON_FIRE) && (millis() - lastFireTime >= fireRate)) {
int projectileIndex = -1;
for (int i = 0; i < MAX_PROJECTILES; i++) {
if (!projectileActive[i]) {
projectileIndex = i;
break;
}
}
if (projectileIndex != -1) {
projX[projectileIndex] = shipX + SHIP_WIDTH;
projY[projectileIndex] = shipY + SHIP_HEIGHT / 2 - PROJECTILE_HEIGHT / 2;
projectileActive[projectileIndex] = true;
lastFireTime = millis();
Serial.println("Fire!");
}
}
// Fire Missile
if (!digitalRead(BUTTON_MISSILE) && (millis() - lastMissileTime >= missileCooldown) && !missileActive) {
missileX = shipX + SHIP_WIDTH;
missileY = shipY + SHIP_HEIGHT / 2 - MISSILE_HEIGHT / 2;
missileActive = true;
lastMissileTime = millis();
Serial.println("Missile Fire!");
}
// Projectile movement
for (int i = 0; i < MAX_PROJECTILES; i++) {
if (projectileActive[i]) {
projX[i] += 3;
if (projX[i] >= WIDTH) {
projectileActive[i] = false;
projX[i] = -PROJECTILE_WIDTH;
projY[i] = -PROJECTILE_HEIGHT;
}
}
}
// Missile movement
if (missileActive) {
missileX += 2;
if (missileX >= WIDTH) {
missileActive = false;
missileX = -MISSILE_WIDTH;
missileY = -MISSILE_HEIGHT;
}
}
// Rock spawning
if (millis() - lastRockSpawn > rockSpawnInterval) {
int availableRockSlot = -1;
for (int i = 0; i < MAX_ROCKS; i++) {
if (rocks[i][0] <= -ROCK_LARGE_SIZE * 2) { // Check if rock is off-screen
availableRockSlot = i;
break;
}
}
if (availableRockSlot != -1) {
rocks[availableRockSlot][0] = WIDTH;
rocks[availableRockSlot][1] = random(HEIGHT - ROCK_LARGE_SIZE * 2 + 1) + ROCK_LARGE_SIZE;
rocks[availableRockSlot][2] = random(3) == 0 ? ROCK_SMALL_SIZE : (random(2) == 0 ? ROCK_MEDIUM_SIZE : ROCK_LARGE_SIZE); // Random size
lastRockSpawn = millis();
}
}
// Rock handling and collision
for (int i = 0; i < MAX_ROCKS; i++) {
if (rocks[i][0] >= 0) {
rocks[i][0] -= rockSpeed;
int rockSize = rocks[i][2];
uint16_t drawColor = rockColor;
if (rockSize == ROCK_SMALL_SIZE) {
drawColor = matrix.color565(255, 140, 0); // Darker Orange #FF8C00
} else if (rockSize == ROCK_MEDIUM_SIZE) {
drawColor = matrix.color565(255,255,0); // Yellow
} else {
drawColor = matrix.color565(255, 0, 0); // Red
}
drawCircle(rocks[i][0], rocks[i][1], rockSize, drawColor);
// Game over check
if (shipX < rocks[i][0] + rockSize &&
shipX + SHIP_WIDTH > rocks[i][0] - rockSize &&
shipY < rocks[i][1] + rockSize &&
shipY + SHIP_HEIGHT > rocks[i][1] - rockSize) {
gameOver = true;
gameOverStartTime = millis(); // Record start time
Serial.println("Game Over - Ship hit by rock!");
break; // Exit the loop
}
// Check collision with projectiles
for (int j = 0; j < MAX_PROJECTILES; j++) {
if (projectileActive[j] &&
projX[j] < rocks[i][0] + rockSize &&
projX[j] + PROJECTILE_WIDTH > rocks[i][0] - rockSize &&
projY[j] < rocks[i][1] + rockSize &&
projY[j] + PROJECTILE_HEIGHT > rocks[i][1] - rockSize) {
rockHitCount[i]++;
projectileActive[j] = false;
projX[j] = -PROJECTILE_WIDTH;
projY[j] = -PROJECTILE_HEIGHT;
Serial.println("Hit Rock!");
if ((rocks[i][2] == ROCK_SMALL_SIZE && rockHitCount[i] >= 1) ||
(rocks[i][2] == ROCK_MEDIUM_SIZE && rockHitCount[i] >= 2) ||
(rocks[i][2] == ROCK_LARGE_SIZE && rockHitCount[i] >= 3)) { // 3 hits for large
rocks[i][0] = -ROCK_LARGE_SIZE * 2;
rockHitCount[i] = 0;
blastX = rocks[i][0]; // Store blast coordinates
blastY = rocks[i][1];
blastActive = true;    // Trigger blast
blastStartTime = millis();
}
break;
}
}
// Check collision with missile
if (missileActive &&
missileX < rocks[i][0] + rockSize &&
missileX + MISSILE_WIDTH > rocks[i][0] - rockSize &&
missileY < rocks[i][1] + rockSize &&
missileY + MISSILE_HEIGHT > rocks[i][1] - rockSize) {
rocks[i][0] = -ROCK_LARGE_SIZE * 2;
rockHitCount[i] = 0;
missileActive = false;
missileX = -MISSILE_WIDTH;
missileY = -MISSILE_HEIGHT;
Serial.println("Missile Hit Rock!");
blastX = rocks[i][0]; // Store blast coordinates
blastY = rocks[i][1];
blastActive = true;    // Trigger blast
blastStartTime = millis();
}
}
}
drawShip();
drawProjectiles();
drawMissile();
drawBlast(); // Draw blast
matrix.show();
delay(50);
}

Credits

Arnov Sharma
352 projects • 360 followers
I'm Arnov. I build, design, and experiment with tech—3D printing, PCB design, and retro consoles are my jam.

Comments