#include <U8glib.h>
#include <math.h>
// OLED 객체
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
// --- 핀 정의 ---
const int JOYSTICK_X_PIN = A1;
const int JOYSTICK_Y_PIN = A2;
// --- 조이스틱 설정 ---
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_REST_X = 5;
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.
// setup() 함수
void setup()
{
Serial.begin(9600);
pinMode(JOYSTICK_X_PIN, INPUT);
pinMode(JOYSTICK_Y_PIN, INPUT);
if (u8g.getMode() == U8G_MODE_BW) {
u8g.setColorIndex(1); // Set color for monochrome display
}
// previousMenuIndex is not needed here, will be set on first move
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() 함수 - Redraw 플래그 설정 추가
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;
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;
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;
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;
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
// lockInput is handled by handleInput when joystick returns to center
animationAxis = ' '; // Reset animation axis
// Draw only the final current item
u8g.drawStr(MENU_TEXT_REST_X, 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;
int currentX = MENU_TEXT_REST_X;
int currentY = MENU_TEXT_CENTER_Y;
int previousX = MENU_TEXT_REST_X;
int previousY = MENU_TEXT_CENTER_Y;
// Calculate positions based on axis and progress
if (animationAxis == 'Y') {
// Previous item moves OUT (based on direction and progress)
previousY = MENU_TEXT_CENTER_Y - (int)(animationDirection * ANIMATION_DISTANCE_Y * progress);
// Current item moves IN (opposite direction, based on remaining progress)
currentY = MENU_TEXT_CENTER_Y + (int)(animationDirection * ANIMATION_DISTANCE_Y * (1.0f - progress));
} else if (animationAxis == 'X') {
// Previous item moves OUT
previousX = MENU_TEXT_REST_X - (int)(animationDirection * ANIMATION_DISTANCE_X * progress);
// Current item moves IN
currentX = MENU_TEXT_REST_X + (int)(animationDirection * ANIMATION_DISTANCE_X * (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) ---
// This block is reached when needsRedraw was true but isAnimating was false,
// OR it's the very first frame after animation ends.
u8g.drawStr(MENU_TEXT_REST_X, MENU_TEXT_CENTER_Y, menuItems[currentMenuIndex]);
}
} while (u8g.nextPage());
}