#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// ===== ENUM =====
enum Personality { CUTE, LAZY, TSUNDERE, CURIOUS };
enum Expression { IDLE, HAPPY, ANGRY, SLEEPY, SURPRISED, LOOK_LEFT, LOOK_RIGHT };
enum EmotionState { STATE_IDLE, STATE_REACT, STATE_HOLD, STATE_DECAY };
Personality currentPersonality = CUTE;
Expression currentExp = IDLE;
Expression targetExp = IDLE;
EmotionState emoState = STATE_IDLE;
unsigned long emoTimer = 0;
// ===== EYE STRUCT =====
struct Eye {
float baseX, baseY;
float x, y;
float w, h;
float pupilX, pupilY;
float targetX, targetY;
float vx, vy;
float squish;
};
Eye leftEye = {40, 32, 40, 32, 30, 20, 0, 0, 40, 32, 0, 0, 0};
Eye rightEye = {88, 32, 88, 32, 30, 20, 0, 0, 88, 32, 0, 0, 0};
// ===== PHYSICS PARAM =====
float getStiffness() {
switch(currentPersonality) {
case CUTE: return 0.12;
case LAZY: return 0.05;
case TSUNDERE: return 0.15;
case CURIOUS: return 0.2;
}
return 0.1;
}
float getDamping() {
switch(currentPersonality) {
case CUTE: return 0.85;
case LAZY: return 0.9;
case TSUNDERE: return 0.75;
case CURIOUS: return 0.7;
}
return 0.8;
}
// ===== UPDATE EYE =====
void updateEye(Eye &e) {
float stiffness = getStiffness();
float damping = getDamping();
float ax = (e.targetX - e.x) * stiffness;
float ay = (e.targetY - e.y) * stiffness;
e.vx = (e.vx + ax) * damping;
e.vy = (e.vy + ay) * damping;
float centerForce = 0.02;
e.vx += (e.baseX - e.x) * centerForce;
e.vy += (e.baseY - e.y) * centerForce;
e.x += e.vx;
e.y += e.vy;
e.x = constrain(e.x, e.baseX - 10, e.baseX + 10);
e.y = constrain(e.y, e.baseY - 6, e.baseY + 6);
float speed = abs(e.vx) + abs(e.vy);
e.squish = speed * 0.3;
if (e.squish > 3) e.squish = 3;
}
// ===== EXPRESSION =====
void applyExpression(Expression exp) {
leftEye.w = rightEye.w = 30;
leftEye.h = rightEye.h = 20;
leftEye.pupilX = rightEye.pupilX = 0;
leftEye.pupilY = rightEye.pupilY = 0;
switch(exp) {
case HAPPY:
leftEye.h = rightEye.h = 14;
break;
case ANGRY:
leftEye.pupilX = -4;
rightEye.pupilX = 4;
leftEye.h = rightEye.h = 12;
break;
case SLEEPY:
leftEye.h = rightEye.h = 6;
leftEye.pupilY = 2;
rightEye.pupilY = 2;
break;
case SURPRISED:
leftEye.w = rightEye.w = 36;
leftEye.h = rightEye.h = 30;
break;
case LOOK_LEFT:
leftEye.pupilX = rightEye.pupilX = -6;
break;
case LOOK_RIGHT:
leftEye.pupilX = rightEye.pupilX = 6;
break;
default:
break;
}
}
// ===== EXPRESSION ENGINE =====
void triggerExpression(Expression exp) {
targetExp = exp;
emoState = STATE_REACT;
emoTimer = millis();
}
void updateExpression() {
switch(emoState) {
case STATE_REACT:
applyExpression(targetExp);
if (millis() - emoTimer > 200) {
emoState = STATE_HOLD;
emoTimer = millis();
}
break;
case STATE_HOLD:
if (millis() - emoTimer > 800) {
emoState = STATE_DECAY;
emoTimer = millis();
}
break;
case STATE_DECAY:
applyExpression(IDLE);
if (millis() - emoTimer > 500) {
emoState = STATE_IDLE;
}
break;
case STATE_IDLE:
applyExpression(IDLE);
break;
}
}
// ===== IDLE BEHAVIOR =====
void idleBehavior(Eye &e) {
int rangeX = 8;
int rangeY = 4;
if (random(0,100) < 3) {
e.targetX = e.baseX + random(-rangeX, rangeX);
e.targetY = e.baseY + random(-rangeY, rangeY);
}
}
// ===== MICRO EXPRESSION =====
void microExpression() {
if (random(0,1000) < 5) {
triggerExpression(SURPRISED);
}
}
// ===== DRAW =====
void drawEye(Eye e) {
int w = e.w + e.squish;
int h = e.h - e.squish;
display.fillRoundRect(e.x - w/2, e.y - h/2, w, h, 8, WHITE);
int px = e.x + e.pupilX + e.vx * 2;
int py = e.y + e.pupilY + e.vy * 2;
display.fillCircle(px, py, 3, BLACK);
}
// ===== SETUP =====
void setup() {
delay(100);
Wire.begin(8, 9);
Wire.setClock(400000);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}
// ===== LOOP =====
unsigned long lastFrame = 0;
#define FRAME_TIME 20
void loop() {
if (millis() - lastFrame < FRAME_TIME) return;
lastFrame = millis();
updateExpression();
microExpression();
idleBehavior(leftEye);
idleBehavior(rightEye);
updateEye(leftEye);
updateEye(rightEye);
display.fillRect(0,0,128,64,BLACK);
drawEye(leftEye);
drawEye(rightEye);
display.display();
}Loading
xiao-esp32-c3
xiao-esp32-c3
Loading
ssd1306
ssd1306