Tong’s Halloween Costume Final

My final costume is a self-created half-deity, half-demon with Chinese traditional elements (quite a long title)

I cut a circular board with a diameter of 32 centimeters from an acrylic sheet, then used laser engraving to carve traditional Chinese cloud patterns and concentric circles mimicking a halo. Then, using a glue gun, I attached LED light strips to the edge of the circular board, so when the lights are on, this disc looks like a halo floating behind my head.

My concept is that when the halo is yellow, he is a merciful god, and when the halo turns red, he becomes a demon. However, on Halloween, his halo constantly shifts between yellow and red, as if the red demon is trying to break out, but the yellow god is trying to suppress him, and they are in a struggle.

This is my circuit diagram, it’s very simple.

This is my code,

#include <Adafruit_NeoPixel.h>

#define PIN         1    // 灯带引脚
#define NUMPIXELS   75   // 灯带灯珠数
#define STATIC_R    242  // 静态颜色 R
#define STATIC_G    163  // 静态颜色 G
#define STATIC_B    0    // 静态颜色 B
#define METEOR_R    224  // 流星颜色 R
#define METEOR_G    163  // 流星颜色 G
#define METEOR_B    22   // 流星颜色 B

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();  // 初始化灯带
  pixels.clear();  // 确保开始时灯带关闭
}

void loop() {
  // 1. 从两端向中间依次点亮
  lightUpFromEnds(STATIC_R, STATIC_G, STATIC_B);
  delay(500);  // 短暂停留后进入褪色效果

  // 2. 进入初始褪色效果,单次5秒,共循环20秒
  for (int i = 0; i < 4; i++) {  // 循环4次,20秒
    fadeEffect(STATIC_R, STATIC_G, STATIC_B, 5000);
  }

  // 3. 从中间向两端光点流星效果
  meteorEffect(METEOR_R, METEOR_G, METEOR_B, STATIC_R, STATIC_G, STATIC_B);
  
  // 4. 黄色光点从两端向中间流星效果
  meteorToCenter(METEOR_R, METEOR_G, METEOR_B);

  // 5. 光点分散到第19颗和第56颗并分两段同时环绕点亮
  disperseAndRing(STATIC_R, STATIC_G, STATIC_B);

// 7. 新增褪色效果,单次5秒,共循环20秒
  for (int i = 0; i < 4; i++) {  // 循环4次,20秒
    fadeEffect(STATIC_R, STATIC_G, STATIC_B, 5000);
  }

// 黄色呼吸灯逐渐加快到红色频闪灯效果
  acceleratingBreatheToStrobe(2000, 200, 5000);  // 初始周期2秒,最终周期0.2秒,红色频闪持续5秒

// 黄色常亮2秒
  setAllPixels(242, 163, 0);  // 黄色
  pixels.show();
  delay(6000);  // 常亮2秒

  

  // 6. 从第1颗开始缓慢长拖尾流星效果跑5秒
  runSingleMeteorAroundRing(METEOR_R, METEOR_G, METEOR_B, 3);

  // 7. 进行褪色渐变颜色效果
  fadeCycle();

  // 8. 所有灯常亮2秒后,进行5秒褪色效果,持续30秒
  staticLightAndFade(STATIC_R, STATIC_G, STATIC_B);
}

// 从两端向中间依次点亮的函数
void lightUpFromEnds(int r, int g, int b) {
  int left = 0;
  int right = NUMPIXELS - 1;
  while (left <= right) {
    pixels.setPixelColor(left, pixels.Color(r, g, b));
    pixels.setPixelColor(right, pixels.Color(r, g, b));
    pixels.show();
    delay(50);
    left++;
    right--;
  }
}

// 初始褪色效果函数
void fadeEffect(int r, int g, int b, int cycleTime) {
  int fadeSteps = 255;
  int stepDelay = cycleTime / (2 * fadeSteps);

  // 渐亮
  for (int i = 0; i <= fadeSteps; i++) {
    setAllPixels(r * i / fadeSteps, g * i / fadeSteps, b * i / fadeSteps);
    delay(stepDelay);
  }

  // 渐暗
  for (int i = fadeSteps; i >= 0; i--) {
    setAllPixels(r * i / fadeSteps, g * i / fadeSteps, b * i / fadeSteps);
    delay(stepDelay);
  }
}

// 从中间向两端光点流星效果并变色
void meteorEffect(int midR, int midG, int midB, int finalR, int finalG, int finalB) {
  int left = (NUMPIXELS - 1) / 2;
  int right = left + 1;

  while (left >= 0 || right < NUMPIXELS) {
    for (int i = 0; i < NUMPIXELS; i++) {
      if (i == left || i == right) {
        pixels.setPixelColor(i, pixels.Color(midR, midG, midB));
      } else {
        uint32_t currentColor = pixels.getPixelColor(i);
        int r = (currentColor >> 16) & 0xFF;
        int g = (currentColor >> 8) & 0xFF;
        int b = currentColor & 0xFF;
        pixels.setPixelColor(i, pixels.Color(r * 0.8, g * 0.8, b * 0.8));
      }
    }
    pixels.show();
    delay(50);

    if (left >= 0) left--;
    if (right < NUMPIXELS) right++;
  }

  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(finalR, finalG, finalB));
  }
  pixels.show();
}

// 黄色光点从两端向中间流星效果
// 长拖尾流星效果从两端向中间
void meteorToCenter(int r, int g, int b) {
  int left = 0;
  int right = NUMPIXELS - 1;

  while (left <= right) {
    pixels.clear();

    // 设置左侧和右侧的流星头部光点
    pixels.setPixelColor(left, pixels.Color(r, g, b));
    pixels.setPixelColor(right, pixels.Color(r, g, b));

    // 为左右流星创建长拖尾效果
    for (int j = 1; j <= 15; j++) {  // 拖尾长度为15
      int leftPos = (left - j + NUMPIXELS) % NUMPIXELS;
      int rightPos = (right + j - NUMPIXELS) % NUMPIXELS;

      // 计算拖尾的亮度,越靠近头部亮度越高
      int tailBrightness = 255 * (15 - j) / 15;  // 逐渐减少亮度
      pixels.setPixelColor(leftPos, pixels.Color(r * tailBrightness / 255, g * tailBrightness / 255, b * tailBrightness / 255));
      pixels.setPixelColor(rightPos, pixels.Color(r * tailBrightness / 255, g * tailBrightness / 255, b * tailBrightness / 255));
    }

    pixels.show();
    delay(50);  // 控制流星移动速度

    // 向中间移动
    left++;
    right--;
  }
}

// 从黄色呼吸灯逐渐加快到红色频闪灯的效果
void acceleratingBreatheToStrobe(int initialCycleTime, int finalCycleTime, int strobeDuration) {
  int yellowR = 242, yellowG = 163, yellowB = 0;  // 黄色
  int redR = 255, redG = 0, redB = 0;             // 红色
  int currentCycleTime = initialCycleTime;
  
  // 循环控制呼吸灯逐渐加快
  while (currentCycleTime > finalCycleTime) {
    // 黄色呼吸效果:先亮后暗
    for (int i = 0; i <= 100; i++) {
      setAllPixels(yellowR * i / 100, yellowG * i / 100, yellowB * i / 100);
      delay(currentCycleTime / 200);  // 每次呼吸的亮起延迟
    }
    for (int i = 100; i >= 0; i--) {
      setAllPixels(yellowR * i / 100, yellowG * i / 100, yellowB * i / 100);
      delay(currentCycleTime / 200);  // 每次呼吸的暗下延迟
    }

    // 减少循环时间,使呼吸速度加快
    currentCycleTime = max(currentCycleTime - 100, finalCycleTime);
  }

  // 红色频闪效果
  unsigned long startTime = millis();
  while (millis() - startTime < strobeDuration) {
    setAllPixels(redR, redG, redB);  // 红色亮起
    delay(50);                       // 频闪亮的时间
    setAllPixels(0, 0, 0);           // 灭
    delay(50);                       // 频闪灭的时间
  }
}



// // 光点分散到第19颗和第56颗并分两段环绕点亮
void disperseAndRing(int r, int g, int b) {
  int leftTarget = 18;  // 第19颗灯珠(从0计数)
  int rightTarget = 55; // 第56颗灯珠(从0计数)

  pixels.clear();
  
  // 设置光点在第19颗和第56颗位置
  pixels.setPixelColor(leftTarget, pixels.Color(r, g, b));
  pixels.setPixelColor(rightTarget, pixels.Color(r, g, b));
  pixels.show();
  delay(500);

  // 环绕点亮:第19颗到第55颗
  for (int i = leftTarget; i <= rightTarget; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
    pixels.show();
    delay(50);
  }

  // 环绕点亮:第56颗到第75颗
  for (int i = rightTarget + 1; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
    pixels.show();
    delay(50);
  }

  // 环绕点亮:第1颗到第18颗
  for (int i = 0; i < leftTarget; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
    pixels.show();
    delay(50);
  }
}


// 单颗光点从第1颗开始缓慢长拖尾流星效果跑6圈,最后一圈结束后从第一颗依次点亮整条灯带
void runSingleMeteorAroundRing(int r, int g, int b, int numCircuits) {
  for (int circuit = 0; circuit < numCircuits; circuit++) {
    for (int i = 0; i < NUMPIXELS; i++) {
      pixels.clear();
      
      // 当前光点设置为亮度最大
      pixels.setPixelColor(i, pixels.Color(r, g, b));
      
      // 创建长拖尾效果
      for (int j = 1; j <= 15; j++) {  // 拖尾长度为10
        int pos = (i - j + NUMPIXELS) % NUMPIXELS;
        int tailBrightness = 255 * (10 - j) / 10;  // 逐渐减少亮度
        pixels.setPixelColor(pos, pixels.Color(r * tailBrightness / 255, g * tailBrightness / 255, b * tailBrightness / 255));
      }
      
      pixels.show();
      delay(50);  // 控制光点移动速度
    }
  }

  // 结束后从第一颗依次点亮整条灯带
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
    pixels.show();
    delay(50);  // 控制每颗灯依次点亮的速度
  }
}

// 褪色渐变颜色效果函数
void fadeCycle() {
  for (int i = 0; i < 5; i++) { // 循环2次
    // 渐变 黄色 (242, 163, 0) -> 白色 (255, 255, 255)
    fadeToColor(242, 163, 0, 255, 0, 0, 2500);

    // 渐变 白色 (255, 255, 255) -> 红色 (255, 0, 0)
    fadeToColor(255, 0, 0, 242, 163, 0, 2500);
  }
}

// 渐变到指定颜色的函数
void fadeToColor(int r1, int g1, int b1, int r2, int g2, int b2, int duration) {
  int steps = 100;  // 渐变的步骤数
  int delayTime = duration / steps;  // 每步的延迟时间

  for (int i = 0; i <= steps; i++) {
    int r = r1 + ((r2 - r1) * i) / steps;
    int g = g1 + ((g2 - g1) * i) / steps;
    int b = b1 + ((b2 - b1) * i) / steps;

    setAllPixels(r, g, b);  // 将颜色设置到所有灯珠
    delay(delayTime);       // 延迟以控制渐变速度
  }
}

// 呼吸效果函数
void breatheEffect(int r, int g, int b, int cycleTime) {
  int fadeSteps = 150;  // 呼吸效果的渐变步骤数
  int stepDelay = cycleTime / (2 * fadeSteps); // 每步的延迟时间

  // 渐亮
  for (int i = 0; i <= fadeSteps; i++) {
    setAllPixels(r * i / fadeSteps, g * i / fadeSteps, b * i / fadeSteps);
    delay(stepDelay);
  }

  // 渐暗
  for (int i = fadeSteps; i >= 0; i--) {
    setAllPixels(r * i / fadeSteps, g * i / fadeSteps, b * i / fadeSteps);
    delay(stepDelay);
  }
}

// 常亮2秒后进行5秒褪色效果,持续30秒
void staticLightAndFade(int r, int g, int b) {
  unsigned long startTime = millis();

  while (millis() - startTime < 15000) { // 持续30秒
    // 常亮2秒
    setAllPixels(r, g, b);
    delay(2000);

    // 5秒的褪色效果
    fadeEffect(r, g, b, 5000);
  }
}

// 设置所有像素为指定颜色
void setAllPixels(int r, int g, int b) {
  for (int i = 0; i < NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
  }
  pixels.show();
}

This is the complete loop video。

More photos:

East meets West in magic.

Shot with FIMO CStill 800T.

World-breaking dimensions

The little kitty also loves today’s god-demon.

Discover more from Making Studio

Subscribe now to keep reading and get access to the full archive.

Continue reading