How to add flashing lights to Vehicles & Terrain

add flashing leds to terrain

Lighting can bring your tabletop terrain to life, especially when it comes to sci-fi vehicles. In this quick guide, I’ll walk you through how I added working red and blue emergency lights to a cyberpunk police cruiser model with just a few cheap components.

What You’ll Need:

  • ESP32 development board
  • Red and blue 5mm LEDs (optional extra warn lights for headlights)
  • Some basic wiring (use Dupont wires for no soldering)
  • Soldering gear (optional, but recommended)
  • Power source of choice

Step 1: Wiring It Up

Start by connecting the long legs (positive) of the red and blue LEDs to pins 18 and 19 on the ESP32 board. The short legs (negative) go to ground. That’s your basic circuit done!

Next, plug the board into your computer and open the free Arduino IDE software. If coding isn’t your thing, just ask a language model (like Claude or ChatGPT) to generate a simple flashing sequence for emergency lights. Upload the code, and you’ll be good to go. See the code I used below:

/*
  ESP32 Police Lights with Pulsing Headlights
  
  This sketch simulates police car lights using two LEDs (red and blue)
  connected to pins 18 and 19 of an ESP32, and adds pulsing headlights on pin 17.
  
  The police LEDs will flash in alternating patterns while the headlights pulse
  with a breathing effect.
*/

// Pin definitions
const int HEADLIGHT_LED = 17;  // Headlight LED connected to pin 17
const int RED_LED = 18;        // Red police light connected to pin 18
const int BLUE_LED = 19;       // Blue police light connected to pin 19

// Timing variables (in milliseconds)
const int FLASH_DELAY = 150;     // Delay between flashes
const int PATTERN_DELAY = 1000;  // Delay between pattern changes

// Headlight pulsing parameters (adjust these to change the pulsing behavior)
const int PULSE_STEP = 10;        // Step size for breathing effect (higher = faster pulse)
                                 // Try values between 1-20

const int PULSE_MIN = 0;         // Minimum brightness (0 = fully off)
const int PULSE_MAX = 255;       // Maximum brightness (255 = fully on)
                                 // Adjust these min/max values to change the range

// Global variable to track the pulse value
int pulseValue = 0;
int pulseDirection = 1;  // 1 for increasing, -1 for decreasing

void setup() {
  // Initialize the LED pins as outputs
  pinMode(RED_LED, OUTPUT);
  pinMode(BLUE_LED, OUTPUT);
  pinMode(HEADLIGHT_LED, OUTPUT);
  
  // Initially all LEDs are off
  digitalWrite(RED_LED, LOW);
  digitalWrite(BLUE_LED, LOW);
  digitalWrite(HEADLIGHT_LED, LOW);
}

// Function to update the pulsing headlights
void updateHeadlights() {
  // Update the pulse value based on the direction
  pulseValue += PULSE_STEP * pulseDirection;
  
  // Change direction if we hit the limits
  if (pulseValue >= PULSE_MAX) {
    pulseValue = PULSE_MAX;
    pulseDirection = -1;
  } else if (pulseValue <= PULSE_MIN) {
    pulseValue = PULSE_MIN;
    pulseDirection = 1;
  }
  
  // Set the analog output value (brightness)
  analogWrite(HEADLIGHT_LED, pulseValue);
}

void loop() {
  // Pattern 1: Alternating flashes
  for (int i = 0; i < 5; i++) {
    updateHeadlights();
    
    // Red LED on, Blue LED off
    digitalWrite(RED_LED, HIGH);
    digitalWrite(BLUE_LED, LOW);
    delay(FLASH_DELAY);
    
    updateHeadlights();
    
    // Red LED off, Blue LED on
    digitalWrite(RED_LED, LOW);
    digitalWrite(BLUE_LED, HIGH);
    delay(FLASH_DELAY);
  }
  
  // Pattern 2: Double flashes
  for (int i = 0; i < 3; i++) {
    updateHeadlights();
    
    // Red double flash
    digitalWrite(RED_LED, HIGH);
    digitalWrite(BLUE_LED, LOW);
    delay(FLASH_DELAY);
    digitalWrite(RED_LED, LOW);
    
    updateHeadlights();
    
    delay(FLASH_DELAY / 2);
    digitalWrite(RED_LED, HIGH);
    delay(FLASH_DELAY);
    digitalWrite(RED_LED, LOW);
    
    updateHeadlights();
    
    delay(FLASH_DELAY / 2);
    
    // Blue double flash
    digitalWrite(BLUE_LED, HIGH);
    delay(FLASH_DELAY);
    digitalWrite(BLUE_LED, LOW);
    
    updateHeadlights();
    
    delay(FLASH_DELAY / 2);
    digitalWrite(BLUE_LED, HIGH);
    delay(FLASH_DELAY);
    digitalWrite(BLUE_LED, LOW);
    delay(FLASH_DELAY / 2);
  }
  
  // Pattern 3: Both LEDs flashing together and then alternating
  for (int i = 0; i < 2; i++) {
    updateHeadlights();
    
    // Both LEDs on
    digitalWrite(RED_LED, HIGH);
    digitalWrite(BLUE_LED, HIGH);
    delay(FLASH_DELAY * 2);
    
    updateHeadlights();
    
    // Both LEDs off
    digitalWrite(RED_LED, LOW);
    digitalWrite(BLUE_LED, LOW);
    delay(FLASH_DELAY);
    
    // Alternating quick flashes
    for (int j = 0; j < 6; j++) {
      updateHeadlights();
      
      digitalWrite(RED_LED, j % 2 == 0 ? HIGH : LOW);
      digitalWrite(BLUE_LED, j % 2 == 0 ? LOW : HIGH);
      delay(FLASH_DELAY / 2);
    }
    
    updateHeadlights();
    
    // Both LEDs off
    digitalWrite(RED_LED, LOW);
    digitalWrite(BLUE_LED, LOW);
    delay(FLASH_DELAY);
  }
}

Step 2: Modifying the STL File

With the circuit working, I turned to the 3D model itself. I edited the STL file to include mounting holes for 5mm LEDs in the light bar. I also carved out space for smaller headlight LEDs while I was at it.

Note – You can get the modified version of this file here

To avoid wasting filament, I printed a quick cross-section of the vehicle to test the LED fit. Once I was happy with it, I went ahead and printed the full model using my Bambu Labs P1S.

Step 3: Assembly

With the parts printed, I installed the LEDs and connected everything to the ESP32 board. A quick test confirmed everything was working perfectly. The flashing red and blue lights added just the right touch of realism to the vehicle.


Adding lighting to your prints doesn’t have to be expensive or complicated, and it adds so much character to your builds. Hopefully this inspires you to try it for yourself!

Want to grab the police cruiser STL – Grab the STL file here