DIY 팩맨 LED 픽셀 패널 코스튬: 어둠 속 빛나는 할로윈 클래식 제작기
부품 및 소모품
| 부품 |
수량 |
| 아두이노 우노 (Arduino UNO) |
1 |
필수 도구 및 기계
| 도구 |
비고 |
| 레이저 커터 (범용) |
필수는 아님 but 매우 유용 |
프로젝트 개요
시작하며
우선 명백한 사실부터: 저희 가족은 패밀리 코스튬을 좋아합니다.
작년엔 우주 '외계인' 컨셉으로 은색 옷에 LED 스트립 포인트를 줬는데, 사실 그냥 은색 입은 사람들 같았죠. 그래도 LED가 마음에 들었습니다. 독특하고 밤에 아이들이 뛰어다닐 때 잘 보이니까요. 올해도 빛나는 걸로 하기로 했는데 뭘 할지 정하지 못했죠. 딸아이가 볼링장에서 남코 팩맨을 해보더니 팩맨 하자고 제안했습니다. 그 제안 하나면 충분했습니다. 멋진 아이디어였고 무조건 실현해야 했죠.
기획
첫 단계는 항상 기획입니다. 다른 8비트 캐릭터를 만들고 싶다면 특히 중요합니다.
가족용으로 팩맨 캐릭터들을 정했습니다: 팩맨, 미즈 팩맨, 고스트 블링키와 핑키. 원작 8비트 캐릭터에 충실하고 싶어서 오리지널 게임 스프라이트를 찾아봤습니다.

이 스프라이트들로 정확한 픽셀 레이아웃과 각 애니메이션 프레임을 확인할 수 있습니다. 예를 들어 고스트 다리 움직임과 팩맨 입 움직임의 각 프레임을 볼 수 있습니다.
엑셀에 각 애니메이션 프레임을 레이아웃해 LED 개수와 전체 보드 레이아웃을 파악했습니다. 개별 LED 번호는 나중에 아두이노 프로그래밍에 필요합니다.

레이아웃상 고스트는 14×14 픽셀, 팩맨은 13×13, 미즈 팩맨은 14×14 픽셀입니다.
픽셀 단위 너비/높이가 정해지면 패널 크기를 결정합니다. 모든 패널을 서로 상대적으로 같은 크기로 만들고 싶었습니다. 즉, 각 픽셀 크기가 모든 코스튬에서 동일해야 합니다. 가장 작은 가족 구성원인 아들에게 맞춰 스케일을 정했습니다. 아들을 재보고 골판지 컷아웃을 대보며 팔을 올리고 걷기 편한 크기를 찾았습니다. 고스트 높이/너비 22인치(약 56cm)로 결정했습니다.
스케일: 22인치 / 14픽셀 = 픽셀당 1.57인치(약 4cm).
재료

입고 다니기 편하게 가벼운 재료를 선택했습니다.
- 베이스: 1/8인치(약 3mm) 골판지, 레이저 커팅 후 핫글루 접착
- 투명 커버: 드래프팅 페이퍼 (벨럼 또는 마일라)
- LED: 12mm DC5V WS2811 개별 제어 가능 LED (50개짜리 스트링)
- 보드: 아두이노 우노 R3
- 전원: 5V DC 휴대용 파워뱅크 (휴대폰 충전기) + USB-DC 어댑터
- 기타: DC 파워 잭 어댑터, 브레드보드 점퍼 와이어, DC5V 파워 서플라이(테스트용), 벨크로, 우븐 덕트 스트랩, 덕트 테이프
베이스 제작
레이저 커터 접근 권한이 있어 활용했습니다. 직접 레이저 커터가 없어도 커팅 업체에 맡기거나, PDF를 출력해 골판지에 대고 수작업으로 잘라도 됩니다.
베이스는 지역 미술/취미 매장에서 구한 1/8인치 골판지 시트로 만들었습니다. 우리 레이저 커터 베드 크기가 32×18인치라 시트를 이 크기로 잘랐습니다.
패널 구조 패널은 베이스, 슬랫(격자), 커버, 스트랩으로 구성됩니다. 베이스가 LED를 고정하고, 슬랫이 픽셀을 형성하며 전체 안정성을 줍니다. 커버는 빛을 확산시키고, 스트랩으로 휴대합니다.



모델링
라이노(Rhino)로 레이저 커터용 파일을 모델링했습니다. 패널 모든 부품을 매우 정밀하게 모델링해 레이저 커터가 최대한 많은 작업을 하도록 했습니다.
각 수직/수평 핀이 서로 끼워지고 베이스에도 노치로 고정되게 설계했습니다. 이렇게 하면 접착제 없이도 부품들이 끼워져 조립되면서 완벽한 정사각형 픽셀을 보장합니다.



레이저 커팅
우리 레이저 커터가 32×18인치라 CAD 파일도 이 크기로 맞췄습니다. 고스트 코스튬용 .dwg 파일을 첨부했습니다. 직접 레이저 커팅하거나 출력해 템플릿으로 수작업해도 됩니다.
수작업 시 골판지 스트립을 잘라 쉽게 배치할 수 있습니다. 직접 치수 재도 됩니다: 픽셀 약 1.5인치 정사각형, 깊이 2인치, 중앙에 LED용 12mm 구멍. 제공된 PDF를 템플릿으로 써도 됩니다.
조립
베이스는 레이저 커터 베드 작은 쪽(18인치)이 22×22인치 베이스보다 작아 두 조각으로 잘랐습니다. 덕트 테이프로 임시 고정 후 LED와 슬랫 장착. 슬랫이 장착되면 진짜 지지대가 됩니다.
각 슬랫은 'H#' 또는 'V#'로 레이저 각인돼 있습니다(H=수평, V=수직, #=순번). 여유분 1/32인치로 잘 끼워집니다. 초기 테스트 땐 여유가 적어 조립 불가능했었습니다. 모두 끼운 뒤 베이스에 올리고 노치(십자 구멍)에 맞췄습니다. 슬랫 끝을 덕트 테이프로 베이스에 고정하고, 좌상단 모서리 핫글루로 고정해 미끄러짐 방지.
슬랫 노치가 베이스를 관통해 뒷면에 작은 탭이 생깁니다. 여기에도 핫글루로 고정.
사후 깨달음
3D 모델 다이어그램에서 패널 뒷면 인클로저를 계획하지 않았습니다. 이게 실수였습니다! 트릭오어트릿 몇 시간 전에 깨닫고 급히 2.5인치 골판지 스트립을 뒷면에 덧댔습니다. LED 와이어 외에 기댈 게 생기게 하려고요.
도움은 됐지만 완전 밀폐됐어야 했습니다. 뒷면이 열려 있으면 걷는 동안 LED가 밀려 들어갑니다. 사진에서 픽셀이 아닌 포인트 라이트로 보이는 부분이 LED가 밀리거나 빠진 곳입니다. 트릭오어트릿 중 몇 번 멈춰 고쳤습니다.
LED 레이아웃

LED 스트링 배치는 엑셀에서 0~169번 170개 LED를 순서대로 배치하며 계획했습니다.
엑셀 레이아웃
중요한 건 픽셀 간 거리와 시작점입니다.
기성 LED 스트링을 자르고 다시 배선하지 않으려면 순서상 한 구멍에서 다음 구멍까지 도달 가능해야 합니다. 제 경우 기성 스트링의 LED 간 거리가 약 3인치(75mm)라 인접한 픽셀끼리 배치해야 했습니다.
시작점은 아두이노와 파워 서플라이 장착 위치 근처여야 합니다.

LED 장착
엑셀 패턴대로 뒤에서 LED를 구멍에 밀어 넣습니다. LED에 작은 플랜지가 있어 고정됩니다. 골판지가 약해 걷다 보면 LED가 좀 움직입니다. 더 단단히 고정하려면 핫글루로 살짝 발라주세요.
배선


전원 공급
최적 전원은 5V DC 충전식 배터리(휴대용 파워뱅크)입니다. USB-DC 어댑터로 LED 스트링(암 DC 잭 어댑터 배선)에 연결하세요.
작년 코스튬용 8×AA 배터리 팩(온오프 스위치 포함)을 썼는데, 쓰면서 12V 출력인 줄 몰랐습니다. 5V LED에 12V라니 안 좋죠. (전 로켓 과학자는 아니지만 나쁘단 건 압니다) 그냥 꽂았는데 다 작동해서 의심 안 했습니다.
재미있게도 이 배터리 팩으론 약 2시간만 켜졌습니다. 올바른 5V 파워뱅크로 테스트하니 훨씬 오래 갔습니다. 2200mAh와 7800mAh 파워뱅크 테스트 결과 각각 3시간 40분, 12시간 43분 지속. 싼 '립스틱 충전기'로도 거의 4시간 사용 가능합니다.
LED
옥외 간판용 12mm WS2811 개별 제어 LED 스트링을 썼습니다. 아마존에서 50개짜리 스트링으로 구매.
배선 방법
첫 번째 스트링의 첫 LED에 전원 입력 연결(빨강/흰색 두 선)이 있습니다. 이를 암 파워 잭 어댑터에 연결하고 파워뱅크를 꽂습니다. 첫 LED의 전원, 그라운드, 데이터 선을 아두이노 5V, GND, 3번 핀(DATA)에 연결합니다.
배선 전 본인 LED 스트링 선 색상 확인하세요. 제 건 빨강(5V), 녹색(DATA), 흰색(GND)인데 다를 수 있습니다.
코드

배선 완료 후 전원 연결해도 LED는 아무 동작 안 합니다. 잠깐 깜빡일 순 있지만 뭘 할지 알려줘야 합니다.
아두이노 프로그래밍 방법은 다루지 않습니다. 모르신다면 아두이노 공식 시작하기를 보세요.
FastLED 라이브러리
FastLED 라이브러리를 사용했습니다. 사용하기 쉽고 예제가 많아 도움됩니다. 웹 컬러명을 써서 아무 LED나 색상 이름으로 지정할 수 있어 편리합니다.
내 코드
아래는 고스트용 코드입니다. 미즈 팩맨, 팩맨 코드도 업로드했습니다.
기본 로직: 모든 LED를 메인 색으로 채운 뒤, 개별 LED 집합의 색을 바꾸고 끄며 각 '프레임'을 만듭니다. 애니메이션 각 프레임 세트가 준비되면 X속도로 X번 반복해 전체 애니메이션을 만듭니다. 더 똑똑한 방법이 있을 수 있지만 이 방식이 저에겐 잘 작동했습니다.
커버 부착


커버는 마지막에 붙이세요. 종이라 찢어질 수 있고, LED 장착/조정 시 앞면에서 접근하기 편합니다.
굴러다니는 드래프팅 페이퍼 롤을 썼습니다. 벨럼일 수도, 마일라일 수도 있습니다. 어쨌든 전체를 커버할 만큼 넓은 반투명 종이가 필요합니다. 심 재료상이나 온라인에서 롤로 구매 가능.
수직 핀 상단 가장자리에 일반 엘머스 글루를 줄로 바릅니다. 수평 핀엔 안 발라도 됩니다. 종이가 제자리에만 있으면 됩니다. 글루로 고정하면 그리드 라인이 비쳐 패널이 개별 픽셀처럼 보입니다.
글루 바른 후 종이 올리고 책 같은 무거운 걸로 눌러 말립니다.
마르면 여분 종이 잘라냅니다. 가장자리 조금 남겨 접어 테이핑하면 가장자리가 들뜨지 않습니다.
외출 준비


마무리 단계로 실전 투입 준비를 합니다.
- 아두이노와 파워 서플라이를 벨크로로 뒷면에 고정
- 연결부 테이핑해 풀림 방지
- 휴대용 스트랩 추가. 우븐 덕트 스트랩 사용 (드래프팅 페이퍼처럼 굴러다님)
- 팩맨 음악과 효과음 폰에 담고 블루투스 스피커 챙기기
수확의 시간


모든 게 끝났습니다. 8비트 가족을 동네로 내보내 이웃들을 감탄시키고 달콤한 사탕 수익을 거두세요!
읽어주셔서 감사합니다. 질문 있으시면 편히 물어보세요!
코드
고스트 LED 애니메이션 코드
// ANIMATED PACMAN GHOST LED
#include "FastLED.h"
#define NUM_LEDS 170
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
delay(2000);
FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
FastLED.setBrightness(40); // 0-255
FastLED.clear();
}
// 왼쪽 보기
int eyes_pixels_l[] = {17, 20, 21, 24, 37, 38, 39, 40, 41, 47, 48, 49, 94, 102, 103, 106, 119, 120, 121, 122, 123, 129, 130, 131};
int pupils_pixels_l[] = {15, 16, 22, 23, 92, 93, 104, 105};
// 오른쪽 보기
int eyes_pixels_r[] = {38, 39, 40, 46, 47, 48, 49, 50, 63, 66, 67, 75, 120, 121, 122, 128, 129, 130, 131, 132, 145, 148, 149, 152};
int pupils_pixels_r[] = {64, 65, 76, 77, 146, 147, 153, 154};
// 발 주변 픽셀 제거
int void_pixels_1[] = {8, 29, 30, 31, 83, 84, 85, 86, 138, 139, 140, 161};
int void_pixels_2[] = {7, 31, 55, 56, 57, 112, 113, 114, 138, 162};
// 슬픈 고스트 (파워펠릿 먹힌 상태)
int sad_ghost[] = {11, 26, 35, 48, 49, 53, 60, 64, 65, 80, 89, 104, 105, 109, 116, 120, 121, 134, 143, 158};
int eyes_seconds = 2;
int reg_ghost_seconds = 10;
int feet_delay = 120; // 발 버전 간 딜레이 ms
int eye_loop = (eyes_seconds*1000)/feet_delay; // 좌우 보기 반복 횟수
int reg_ghost_loop = reg_ghost_seconds/eyes_seconds;
int sad_ghost_loop = 50;
int sad_ghost_blink_loop = 10;
void loop() {
// 일반 고스트 애니메이션 (눈 움직임)
for(int i = 0; i < reg_ghost_loop; i++){
for(int i = 0; i < eye_loop; i++){
// 몸체 채우기
fill_solid(leds, NUM_LEDS, CRGB::Red);
// 눈 설정
for (int i = 0; i < 24; i++){
leds[eyes_pixels_l[i]] = CRGB::White;
}
// 동공 설정
for (int i = 0; i < 8; i++){
leds[pupils_pixels_l[i]] = CRGB::DarkBlue;
}
// 발 주변 제거 (v1)
for (int i = 0; i < 12; i++){
leds[void_pixels_1[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
// 몸체 채우기
fill_solid(leds, NUM_LEDS, CRGB::Red);
// 눈 설정
for (int i = 0; i < 24; i++){
leds[eyes_pixels_l[i]] = CRGB::White;
}
// 동공 설정
for (int i = 0; i < 8; i++){
leds[pupils_pixels_l[i]] = CRGB::DarkBlue;
}
// 발 주변 제거 (v2)
for (int i = 0; i < 10; i++){
leds[void_pixels_2[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
}
for(int i = 0; i < eye_loop; i++){
// 몸체 채우기
fill_solid(leds, NUM_LEDS, CRGB::Red);
// 눈 설정 (오른쪽)
for (int i = 0; i < 24; i++){
leds[eyes_pixels_r[i]] = CRGB::White;
}
// 동공 설정
for (int i = 0; i < 8; i++){
leds[pupils_pixels_r[i]] = CRGB::DarkBlue;
}
// 발 주변 제거 (v1)
for (int i = 0; i < 12; i++){
leds[void_pixels_1[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
// 몸체 채우기
fill_solid(leds, NUM_LEDS, CRGB::Red);
// 눈 설정
for (int i = 0; i < 24; i++){
leds[eyes_pixels_r[i]] = CRGB::White;
}
// 동공 설정
for (int i = 0; i < 8; i++){
leds[pupils_pixels_r[i]] = CRGB::DarkBlue;
}
// 발 주변 제거 (v2)
for (int i = 0; i < 10; i++){
leds[void_pixels_2[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
}
}
// 슬픈 고스트 일반
for(int i = 0; i < sad_ghost_loop; i++){
fill_solid(leds, NUM_LEDS, CRGB::Blue);
for (int i = 0; i < 20; i++){
leds[sad_ghost[i]] = CRGB::Yellow;
}
for (int i = 0; i < 12; i++){
leds[void_pixels_1[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
fill_solid(leds, NUM_LEDS, CRGB::Blue);
for (int i = 0; i < 20; i++){
leds[sad_ghost[i]] = CRGB::Yellow;
}
for (int i = 0; i < 10; i++){
leds[void_pixels_2[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
}
// 슬픈 고스트 깜빡임
for(int i = 0; i < sad_ghost_blink_loop; i++){
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 20; i++){
leds[sad_ghost[i]] = CRGB::Red;
}
for (int i = 0; i < 12; i++){
leds[void_pixels_1[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
fill_solid(leds, NUM_LEDS, CRGB::Blue);
for (int i = 0; i < 20; i++){
leds[sad_ghost[i]] = CRGB::Yellow;
}
for (int i = 0; i < 10; i++){
leds[void_pixels_2[i]] = CRGB::Black;
}
FastLED.show();
delay(feet_delay);
}
}
미즈 팩맨 (Ms. Pac-Man)
// ANIMATED MS PACMAN LED
#include "FastLED.h"
#define NUM_LEDS 151
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
delay(2000);
FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
FastLED.setBrightness(40);
FastLED.clear();
}
int void_pixels_opened[] = {6,7,22,23,24,25,26,27,28,29,30,43,44,45,46,47,48,49,50, 51,52,53,54,55,56,57,58,68,69,70,71,72,73,74,75,76,77, 78,79,80,81,82,83,84,85,86,98,99,100,101,102,103,104, 105,106,107,108,109,127,128,129,130,131,132};
int void_pixels_mid[] = {5,150,45,46,47,48,49,50,51,52,53,54,55,56,57,58,68,69,70,71,72, 73,74,75,76,77,78,79,80,81,82,83,84,85,100,101,102,103,104};
int void_pixels_closed[] = {5,150};
int red_opened[] = {92,93,115,116,117,118,119,121,137,138,140,141,142,143,144,145,5,8,133,150};
int red_mid[] = {92,93,115,116,117,118,119,121,137,138,140,141,142,143,144,145,27,26,105,106};
int red_closed[] = {92,93,115,116,117,118,119,121,137,138,140,141,142,143,144,145,50,76,77,78};
int blue_opened[] = {120,139,96};
int blue_mid[] = {120,139,97};
int blue_closed[] = {120,139};
int black_opened[] = {40,95,111,112,124};
int black_mid[] = {40,96,110,111};
int black_closed[] = {40,95,96,97,98};
int frame_rate_ms = 100;
void loop() {
// 입 열림
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 64; i++){
leds[void_pixels_opened[i]] = CRGB::Black;
}
for (int i = 0; i < 20; i++){
leds[red_opened[i]] = CRGB::Red;
}
for (int i = 0; i < 3; i++){
leds[blue_opened[i]] = CRGB::Blue;
}
for (int i = 0; i < 5; i++){
leds[black_opened[i]] = CRGB::Purple;
}
FastLED.show();
delay(frame_rate_ms);
// 입 반쯤 열림
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 39; i++){
leds[void_pixels_mid[i]] = CRGB::Black;
}
for (int i = 0; i < 20; i++){
leds[red_mid[i]] = CRGB::Red;
}
for (int i = 0; i < 3; i++){
leds[blue_mid[i]] = CRGB::Blue;
}
for (int i = 0; i < 4; i++){
leds[black_mid[i]] = CRGB::Purple;
}
FastLED.show();
delay(frame_rate_ms);
// 입 닫힘
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 2; i++){
leds[void_pixels_closed[i]] = CRGB::Black;
}
for (int i = 0; i < 20; i++){
leds[red_closed[i]] = CRGB::Red;
}
for (int i = 0; i < 2; i++){
leds[blue_closed[i]] = CRGB::Blue;
}
for (int i = 0; i < 5; i++){
leds[black_closed[i]] = CRGB::Purple;
}
FastLED.show();
delay(frame_rate_ms);
// 입 반쯤 열림 (반복)
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 39; i++){
leds[void_pixels_mid[i]] = CRGB::Black;
}
for (int i = 0; i < 20; i++){
leds[red_mid[i]] = CRGB::Red;
}
for (int i = 0; i < 3; i++){
leds[blue_mid[i]] = CRGB::Blue;
}
for (int i = 0; i < 4; i++){
leds[black_mid[i]] = CRGB::Purple;
}
FastLED.show();
delay(frame_rate_ms);
}
팩맨 (Pac-Man)
// ANIMATED PACMAN LED
#include "FastLED.h"
#define NUM_LEDS 137
#define DATA_PIN 3
CRGB leds[NUM_LEDS];
void setup() {
delay(2000);
FastLED.addLeds<WS2811, DATA_PIN, RGB>(leds, NUM_LEDS);
FastLED.setBrightness(40);
FastLED.clear();
}
int void_pixels_opened[] = {19,20,21,22,23,24,25,26,27,28,29,42,43,44,45,46,47,48,49,50,51,52,53,54,55, 56,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,94,95,96,97,98,99,100, 101,102,103,104,105,121,122,123,124,125,126};
int void_pixels_mid[] = {46,47,48,49,50,51,52,53,54,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,98,99,100};
int frame_rate_ms = 100;
void loop() {
// 입 열림
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 61; i++){
leds[void_pixels_opened[i]] = CRGB::Black;
}
FastLED.show();
delay(frame_rate_ms);
// 입 반쯤 열림
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 27; i++){
leds[void_pixels_mid[i]] = CRGB::Black;
}
FastLED.show();
delay(frame_rate_ms);
// 입 닫힘
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
FastLED.show();
delay(frame_rate_ms);
// 입 반쯤 열림 (반복)
fill_solid(leds, NUM_LEDS, CRGB::Yellow);
for (int i = 0; i < 27; i++){
leds[void_pixels_mid[i]] = CRGB::Black;
}
FastLED.show();
delay(frame_rate_ms);
}
커스텀 파트 및 인클로저
1/8인치 골판지로 고스트 코스튬 레이저 커팅용 파일. 32×18인치 레이저 커터 베드 크기 기준.
ghostlaser-1_8th_v5_feTmMAM4r0.dwg
미즈 팩맨과 팩맨 레이저 커터용 DWG 파일
pacman-mspacman-laser_KVFskMrPk4.dwg
회로도
LED, 아두이노, 휴대용 파워 연결 방법
