#include <U8glib.h>
#include <math.h>
// OLED 객체
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast
// --- 핀 정의 ---
const int JOYSTICK_X_PIN = A1;
const int JOYSTICK_Y_PIN = A2;
const int BUZZER_PIN = 8; // 부저 핀 D8 추가
// --- 부저 음계 정의 ---
const int NOTE_UP = 700; // 위쪽 이동 시 소리
const int NOTE_DOWN = 500; // 아래쪽 이동 시 소리
const int NOTE_LEFT = 600; // 왼쪽 이동 시 소리
const int NOTE_RIGHT = 800; // 오른쪽 이동 시 소리
const int SOUND_DURATION = 50; // 소리 지속 시간 (밀리초)
// --- 조이스틱 설정 ---
const int JOYSTICK_CENTER = 512;
const int JOYSTICK_DEADZONE = 150;
const int JOYSTICK_RETURN_DEADZONE = 50; // Deadzone for returning to center
// --- 메뉴 아이템 ---
const char *menuItems[] = {"Start Game", "High Scores", "Settings", "About", "Exit"};
const int NUM_MENU_ITEMS = sizeof(menuItems) / sizeof(menuItems[0]);
// --- 메뉴 상태 변수 ---
int currentMenuIndex = 0;
int previousMenuIndex = 0; // Needed for animation
bool joystickMoved = false; // Tracks if joystick has moved from center since last action
bool lockInput = false; // Locks input during animation/after movement until center
// --- 디스플레이 설정 ---
const int MENU_TEXT_CENTER_Y = 32; // 세로 중앙
const int FONT_HEIGHT = 10; // u8g_font_unifont 기준 대략적인 높이
const int SCREEN_WIDTH = 128;
const int SCREEN_HEIGHT = 64;
// --- 타이밍 ---
unsigned long lastMoveTime = 0;
const long moveDelay = 150; // Delay between consecutive menu moves
// --- 애니메이션 상태 ---
bool isAnimating = false;
unsigned long animationStartTime = 0;
const long animationDuration = 250;
int animationDirection = 0; // 1 for down/right, -1 for up/left
char animationAxis = ' '; // 'X' or 'Y'
const int ANIMATION_DISTANCE_Y = FONT_HEIGHT + 15;
const int ANIMATION_DISTANCE_X = 40;
// --- 최적화 플래그 ---
bool needsRedraw = true; // Flag to indicate if the display needs updating. Start true for initial draw.
// 텍스트 가로 중앙 위치 계산 함수
int getCenteredX(const char *text)
{
return (SCREEN_WIDTH - u8g.getStrWidth(text)) / 2;
}
// 부저 소리 재생 함수
void playSound(int note) {
tone(BUZZER_PIN, note, SOUND_DURATION);
}
// setup() 함수
void setup()
{
Serial.begin(9600);
pinMode(JOYSTICK_X_PIN, INPUT);
pinMode(JOYSTICK_Y_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT); // 부저 핀 설정 추가
if (u8g.getMode() == U8G_MODE_BW)
{
u8g.setColorIndex(1); // Set color for monochrome display
}
Serial.println("Setup complete. Ready.");
// needsRedraw is already true, so the first loop() will draw.
}
// loop() 함수 - 최적화 적용
void loop()
{
unsigned long currentTime = millis();
handleInput(currentTime);
// --- Optimized Display Update ---
// Only update the display if a redraw is needed (menu changed, animation running)
if (needsRedraw || isAnimating)
{
updateDisplay();
needsRedraw = false; // Reset the flag after drawing. If animating, it will be drawn again next loop anyway.
}
}
// handleInput() 함수 - 부저 소리 기능 추가
void handleInput(unsigned long currentTime)
{
// Check for joystick return to center first, to unlock input
int joyX_check = analogRead(JOYSTICK_X_PIN);
int joyY_check = analogRead(JOYSTICK_Y_PIN);
bool isInsideCenter = (abs(joyX_check - JOYSTICK_CENTER) < JOYSTICK_RETURN_DEADZONE &&
abs(joyY_check - JOYSTICK_CENTER) < JOYSTICK_RETURN_DEADZONE);
if (isInsideCenter)
{
lockInput = false; // Unlock input when joystick returns to center
joystickMoved = false; // Reset moved flag only when centered
}
// If animating or input is locked (waiting for center return), don't process new movement
if (isAnimating || lockInput)
{
return;
}
// Read joystick values for movement detection
int joystickX = joyX_check; // Use already read value
int joystickY = joyY_check; // Use already read value
// Calculate deltas
int xDelta = joystickX - JOYSTICK_CENTER;
int yDelta = joystickY - JOYSTICK_CENTER;
// --- Movement Detection ---
bool movedUpY = yDelta < -JOYSTICK_DEADZONE;
bool movedDownY = yDelta > JOYSTICK_DEADZONE;
bool movedLeftX = xDelta < -JOYSTICK_DEADZONE;
bool movedRightX = xDelta > JOYSTICK_DEADZONE;
// --- Process Movement ---
// Check if enough time has passed and the joystick wasn't already considered 'moved' in this deflection
if (currentTime - lastMoveTime > moveDelay && !joystickMoved)
{
int nextIndex = currentMenuIndex;
int direction = 0;
char axis = ' ';
bool moveTriggered = false;
// Prioritize Y-axis movement if both detected? (Optional, current logic handles one at a time)
if (movedUpY)
{
nextIndex = (currentMenuIndex + 1) % NUM_MENU_ITEMS;
direction = 1;
axis = 'Y';
moveTriggered = true;
playSound(NOTE_UP); // 위쪽 이동 소리 추가
Serial.println("Joystick Moved UP -> Menu Down (Vertical)");
}
else if (movedDownY)
{
nextIndex = (currentMenuIndex - 1 + NUM_MENU_ITEMS) % NUM_MENU_ITEMS;
direction = -1;
axis = 'Y';
moveTriggered = true;
playSound(NOTE_DOWN); // 아래쪽 이동 소리 추가
Serial.println("Joystick Moved DOWN -> Menu Up (Vertical)");
}
else if (movedRightX)
{ // Check X only if Y didn't move
nextIndex = (currentMenuIndex + 1) % NUM_MENU_ITEMS;
direction = 1;
axis = 'X';
moveTriggered = true;
playSound(NOTE_RIGHT); // 오른쪽 이동 소리 추가
Serial.println("Joystick Moved RIGHT -> Menu Down (Horizontal)");
}
else if (movedLeftX)
{ // Check X only if Y didn't move
nextIndex = (currentMenuIndex - 1 + NUM_MENU_ITEMS) % NUM_MENU_ITEMS;
direction = -1;
axis = 'X';
moveTriggered = true;
playSound(NOTE_LEFT); // 왼쪽 이동 소리 추가
Serial.println("Joystick Moved LEFT -> Menu Up (Horizontal)");
}
// --- Trigger State Change & Animation ---
if (moveTriggered)
{
joystickMoved = true; // Mark that the joystick caused an action in this deflection
lastMoveTime = currentTime; // Update time of last processed move
lockInput = true; // Lock input until joystick returns to center
if (nextIndex != currentMenuIndex)
{ // Check if the index actually changed
previousMenuIndex = currentMenuIndex;
currentMenuIndex = nextIndex;
animationDirection = direction;
animationAxis = axis;
isAnimating = true; // Start animation
animationStartTime = currentTime;
needsRedraw = true; // <<<=== !!! SET FLAG: Signal that a redraw is required !!!
Serial.print(" Menu Index Changed to: ");
Serial.println(menuItems[currentMenuIndex]);
}
else
{
// Optional: Handle case where joystick moved but index didn't wrap around (e.g., only one item)
// In this case, no animation or redraw is needed, but input is still locked until center.
Serial.println(" Joystick moved, but index remained the same.");
}
}
}
// Note: joystickMoved flag is reset only when joystick returns to the center deadzone (handled at the top)
}
// updateDisplay() 함수 - 중앙 정렬 기능 추가 (최소한의 변경)
void updateDisplay()
{
u8g.firstPage();
do
{
u8g.setFont(u8g_font_unifont);
unsigned long currentTime = millis();
float progress = 0.0f;
if (isAnimating)
{
unsigned long elapsedTime = currentTime - animationStartTime;
if (elapsedTime >= animationDuration)
{
// --- Animation Ends ---
isAnimating = false; // Stop animating state
progress = 1.0f; // Ensure final position is drawn
animationAxis = ' '; // Reset animation axis
// 최종 메뉴 아이템 중앙 정렬하여 표시
int centerX = getCenteredX(menuItems[currentMenuIndex]);
u8g.drawStr(centerX, MENU_TEXT_CENTER_Y, menuItems[currentMenuIndex]);
}
else
{
// --- Animation In Progress ---
progress = (float)elapsedTime / animationDuration;
// Optional Easing (Smooth start/end)
progress = progress < 0.5f ? 2.0f * progress * progress : 1.0f - powf(-2.0f * progress + 2.0f, 2.0f) / 2.0f;
// 현재 및 이전 메뉴 아이템의 중앙 X 좌표 계산
int currentX = getCenteredX(menuItems[currentMenuIndex]);
int previousX = getCenteredX(menuItems[previousMenuIndex]);
int currentY = MENU_TEXT_CENTER_Y;
int previousY = MENU_TEXT_CENTER_Y;
// Calculate positions based on axis and progress
if (animationAxis == 'Y')
{
// Y축 이동 시 X는 각 항목별 중앙 유지
previousY = MENU_TEXT_CENTER_Y - (int)(animationDirection * SCREEN_HEIGHT * progress);
currentY = MENU_TEXT_CENTER_Y + (int)(animationDirection * SCREEN_HEIGHT * (1.0f - progress));
}
else if (animationAxis == 'X')
{
// X축 이동 시 각 항목별 중앙에서부터 이동
previousX = previousX - (int)(animationDirection * SCREEN_WIDTH * progress);
currentX = currentX + (int)(animationDirection * SCREEN_WIDTH * (1.0f - progress));
}
// Draw both items during transition
u8g.drawStr(previousX, previousY, menuItems[previousMenuIndex]);
u8g.drawStr(currentX, currentY, menuItems[currentMenuIndex]);
}
}
else
{
// --- Static Display (Not Animating) ---
// 메뉴 아이템 중앙 정렬하여 표시
int centerX = getCenteredX(menuItems[currentMenuIndex]);
u8g.drawStr(centerX, MENU_TEXT_CENTER_Y, menuItems[currentMenuIndex]);
}
} while (u8g.nextPage());
}