Загрузка данных
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- ПИНЫ ---
#define JOY_X A0
#define JUMP_BTN 2
#define BUZZER 8
// ============================================================
// МИР (карта уровня)
// ============================================================
const int WORLD_WIDTH = 512;
// --- ИГРОК ---
struct Player {
float x, y, vx, vy;
int w, h;
bool onGround;
int hp;
bool hasSword;
bool facingRight;
int attackTimer;
bool attacking;
};
Player player;
// --- ПЛАТФОРМЫ ---
struct Platform { int x, y, w; int speed; };
const int NUM_PLATFORMS = 20;
Platform platforms[NUM_PLATFORMS];
// --- ШИПЫ ---
struct Spike { int x, y; bool active; };
const int NUM_SPIKES = 12;
Spike spikes[NUM_SPIKES];
// --- МОНЕТЫ ---
struct Coin { int x, y; bool collected; };
const int NUM_COINS = 12;
Coin coins[NUM_COINS];
// --- МЕЧ ---
struct Sword { int x, y; bool taken; };
Sword sword;
// --- ВРАГИ ---
struct Enemy { float x, y, vx; int hp; bool alive; int timer; };
const int NUM_ENEMIES = 4;
Enemy enemies[NUM_ENEMIES];
// --- БОСС ---
struct Boss {
float x, y;
int w, h;
int hp, maxHp;
bool alive;
int moveDir;
unsigned long lastMove;
};
Boss boss;
// --- ФИЗИКА ---
const float GRAVITY = 0.4;
const float JUMP_FORCE = -6.0;
const float MOVE_SPEED = 2.0;
const float FRICTION = 0.85;
// --- КАМЕРА ---
float cameraX = 0;
// --- СОСТОЯНИЯ ---
enum GameState { STATE_PLAYING, STATE_DEAD, STATE_WIN };
GameState currentState = STATE_PLAYING;
// --- ТАЙМЕРЫ ---
unsigned long lastUpdate = 0;
int lastJumpState = HIGH;
// --- КНОПКА АТАКИ ---
#define ATTACK_BTN A1
int lastAttackState = HIGH;
void setup() {
Serial.begin(9600);
pinMode(JUMP_BTN, INPUT_PULLUP);
pinMode(ATTACK_BTN, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
for (;;);
}
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(F("OPEN WORLD"));
display.println(F("Find the sword"));
display.println(F("Defeat the boss!"));
display.display();
delay(2000);
loadWorld();
}
void loop() {
if (millis() - lastUpdate < 16) return;
lastUpdate = millis();
switch (currentState) {
case STATE_PLAYING:
handleInput();
updatePlatforms();
updateEnemies();
updateBoss();
updatePhysics();
checkCollisions();
checkSpikes();
checkCoins();
checkSwordPickup();
checkEnemyCollisions();
checkBossCombat();
updateCamera();
checkWinLose();
render();
break;
case STATE_DEAD:
display.clearDisplay();
display.setCursor(25, 25);
display.setTextSize(2);
display.print(F("YOU DIED"));
display.display();
delay(2000);
loadWorld();
break;
case STATE_WIN:
display.clearDisplay();
display.setCursor(20, 25);
display.setTextSize(2);
display.print(F("BOSS DOWN"));
display.display();
delay(2000);
loadWorld();
break;
}
}
// ============================================================
// СОЗДАНИЕ МИРА
// ============================================================
void loadWorld() {
currentState = STATE_PLAYING;
player.x = 10;
player.y = 40;
player.vx = 0;
player.vy = 0;
player.w = 6;
player.h = 8;
player.onGround = false;
player.hp = 5;
player.hasSword = false;
player.facingRight = true;
player.attacking = false;
player.attackTimer = 0;
cameraX = 0;
for (int i = 0; i < NUM_PLATFORMS; i++) platforms[i] = {0, 0, 0, 0};
for (int i = 0; i < NUM_SPIKES; i++) spikes[i] = {0, 0, false};
for (int i = 0; i < NUM_COINS; i++) coins[i] = {0, 0, false};
for (int i = 0; i < NUM_ENEMIES; i++) enemies[i] = {0, 0, 0, 0, false, 0};
// --- ПЛАТФОРМЫ (открытый мир 512 пикселей) ---
platforms[0] = {0, 56, 60, 0};
platforms[1] = {80, 48, 40, 0};
platforms[2] = {140, 44, 40, 0};
platforms[3] = {200, 40, 30, 0};
platforms[4] = {240, 52, 40, 0};
platforms[5] = {300, 44, 30, 1}; // движется
platforms[6] = {350, 36, 40, 0};
platforms[7] = {410, 48, 60, 0}; // арена босса
platforms[8] = {480, 40, 32, 0};
// --- ШИПЫ ---
spikes[0] = {62, 52, true};
spikes[1] = {122, 52, true};
spikes[2] = {182, 52, true};
spikes[3] = {282, 52, true};
spikes[4] = {335, 52, true};
spikes[5] = {395, 44, true};
// --- МОНЕТЫ ---
coins[0] = {85, 44, true};
coins[1] = {145, 40, true};
coins[2] = {205, 36, true};
coins[3] = {245, 48, true};
coins[4] = {305, 40, true};
coins[5] = {355, 32, true};
coins[6] = {415, 44, true};
coins[7] = {485, 36, true};
// --- МЕЧ ---
sword = {260, 44, false}; // Меч в середине уровня
// --- ВРАГИ ---
enemies[0] = {100, 40, 0.5, 1, true, 0};
enemies[1] = {220, 32, -0.5, 1, true, 0};
enemies[2] = {360, 28, 0.5, 2, true, 0};
// --- БОСС ---
boss = {440, 32, 16, 16, 10, 10, true, 1, 0};
Serial.println(F("WORLD LOADED"));
}
// ============================================================
// УПРАВЛЕНИЕ
// ============================================================
void handleInput() {
// --- ДВИЖЕНИЕ ---
int x = analogRead(JOY_X);
if (x < 300) { player.vx = -MOVE_SPEED; player.facingRight = false; }
else if (x > 700) { player.vx = MOVE_SPEED; player.facingRight = true; }
else player.vx *= FRICTION;
// --- ПРЫЖОК ---
int jumpReading = digitalRead(JUMP_BTN);
if (jumpReading == LOW && lastJumpState == HIGH && player.onGround) {
player.vy = JUMP_FORCE;
player.onGround = false;
tone(BUZZER, 800, 30);
}
lastJumpState = jumpReading;
// --- АТАКА (только если есть меч) ---
int attackReading = digitalRead(ATTACK_BTN);
if (attackReading == LOW && lastAttackState == HIGH && player.hasSword) {
player.attacking = true;
player.attackTimer = 15; // 15 кадров атаки
tone(BUZZER, 1000, 50);
}
lastAttackState = attackReading;
if (player.attacking) {
player.attackTimer--;
if (player.attackTimer <= 0) player.attacking = false;
}
}
// ============================================================
// ФИЗИКА
// ============================================================
void updatePhysics() {
player.vy += GRAVITY;
player.x += player.vx;
player.y += player.vy;
if (player.x < 0) player.x = 0;
if (player.x + player.w > WORLD_WIDTH) player.x = WORLD_WIDTH - player.w;
if (player.y > SCREEN_HEIGHT) {
player.hp = 0;
currentState = STATE_DEAD;
tone(BUZZER, 200, 500);
}
}
void updatePlatforms() {
for (int i = 0; i < NUM_PLATFORMS; i++) {
if (platforms[i].speed != 0) {
platforms[i].x += platforms[i].speed;
if (platforms[i].x < 280 || platforms[i].x + platforms[i].w > 340) {
platforms[i].speed = -platforms[i].speed;
}
}
}
}
void updateEnemies() {
for (int i = 0; i < NUM_ENEMIES; i++) {
if (!enemies[i].alive) continue;
enemies[i].x += enemies[i].vx;
if (enemies[i].x < 0 || enemies[i].x > WORLD_WIDTH) enemies[i].vx = -enemies[i].vx;
// Простая проверка «от края платформы»
if (enemies[i].x > 95 && enemies[i].x < 105 && enemies[i].vx > 0) enemies[i].vx = -enemies[i].vx;
if (enemies[i].x < 95 && enemies[i].vx < 0) enemies[i].vx = -enemies[i].vx;
}
}
void updateBoss() {
if (!boss.alive) return;
if (millis() - boss.lastMove > 40) {
boss.lastMove = millis();
boss.x += boss.moveDir * 1.5;
if (boss.x < 410 || boss.x > 470) boss.moveDir = -boss.moveDir;
}
}
void updateCamera() {
cameraX = player.x - SCREEN_WIDTH / 2;
if (cameraX < 0) cameraX = 0;
if (cameraX > WORLD_WIDTH - SCREEN_WIDTH) cameraX = WORLD_WIDTH - SCREEN_WIDTH;
}
// ============================================================
// КОЛЛИЗИИ
// ============================================================
void checkCollisions() {
player.onGround = false;
for (int i = 0; i < NUM_PLATFORMS; i++) {
if (platforms[i].w == 0) continue;
if (player.x + player.w > platforms[i].x &&
player.x < platforms[i].x + platforms[i].w) {
int top = platforms[i].y;
if (player.vy >= 0 && player.y + player.h >= top && player.y + player.h <= top + 10) {
player.y = top - player.h;
player.vy = 0;
player.onGround = true;
}
}
}
}
void checkSpikes() {
for (int i = 0; i < NUM_SPIKES; i++) {
if (!spikes[i].active) continue;
if (player.x + player.w > spikes[i].x &&
player.x < spikes[i].x + 5 &&
player.y + player.h > spikes[i].y &&
player.y < spikes[i].y + 4) {
player.hp--;
if (player.hp <= 0) {
currentState = STATE_DEAD;
tone(BUZZER, 200, 500);
} else {
// Отбрасываем игрока
player.vy = -4;
player.vx = 0;
tone(BUZZER, 300, 100);
spikes[i].active = false; // шип ломается
}
}
}
}
void checkCoins() {
for (int i = 0; i < NUM_COINS; i++) {
if (!coins[i].collected) continue;
if (player.x + player.w > coins[i].x &&
player.x < coins[i].x + 4 &&
player.y + player.h > coins[i].y &&
player.y < coins[i].y + 4) {
coins[i].collected = false;
tone(BUZZER, 1200, 30);
}
}
}
void checkSwordPickup() {
if (sword.taken) return;
if (player.x + player.w > sword.x &&
player.x < sword.x + 6 &&
player.y + player.h > sword.y &&
player.y < sword.y + 6) {
sword.taken = true;
player.hasSword = true;
tone(BUZZER, 1500, 100);
delay(100);
tone(BUZZER, 2000, 150);
Serial.println(F("SWORD TAKEN"));
}
}
void checkEnemyCollisions() {
for (int i = 0; i < NUM_ENEMIES; i++) {
if (!enemies[i].alive) continue;
// Игрок коснулся врага
if (player.x + player.w > enemies[i].x &&
player.x < enemies[i].x + 8 &&
player.y + player.h > enemies[i].y &&
player.y < enemies[i].y + 8) {
// Если игрок атакует — враг получает урон
if (player.attacking) {
enemies[i].hp--;
if (enemies[i].hp <= 0) {
enemies[i].alive = false;
tone(BUZZER, 400, 100);
}
} else {
// Иначе игрок получает урон
player.hp--;
player.vx = -player.vx * 2;
player.vy = -3;
if (player.hp <= 0) {
currentState = STATE_DEAD;
tone(BUZZER, 200, 500);
}
}
}
}
}
void checkBossCombat() {
if (!boss.alive) return;
if (player.x + player.w > boss.x &&
player.x < boss.x + boss.w &&
player.y + player.h > boss.y &&
player.y < boss.y + boss.h) {
if (player.attacking) {
boss.hp--;
tone(BUZZER, 500, 100);
if (boss.hp <= 0) {
boss.alive = false;
currentState = STATE_WIN;
tone(BUZZER, 1500, 300);
}
} else {
player.hp--;
player.vx = -player.vx * 2;
player.vy = -4;
if (player.hp <= 0) {
currentState = STATE_DEAD;
tone(BUZZER, 200, 500);
}
}
}
}
void checkWinLose() {
// Условия победы/поражения проверяются выше
}
// ============================================================
// ОТРИСОВКА (с учётом камеры)
// ============================================================
void render() {
display.clearDisplay();
// Платформы
for (int i = 0; i < NUM_PLATFORMS; i++) {
if (platforms[i].w == 0) continue;
int sx = platforms[i].x - cameraX;
if (sx + platforms[i].w < 0 || sx > SCREEN_WIDTH) continue;
display.fillRect(sx, platforms[i].y, platforms[i].w, 2, SSD1306_WHITE);
}
// Шипы
for (int i = 0; i < NUM_SPIKES; i++) {
if (!spikes[i].active) continue;
int sx = spikes[i].x - cameraX;
if (sx < -10 || sx > SCREEN_WIDTH) continue;
display.fillTriangle(sx, spikes[i].y + 4,
sx + 5, spikes[i].y + 4,
sx + 2, spikes[i].y, SSD1306_WHITE);
}
// Монеты
for (int i = 0; i < NUM_COINS; i++) {
if (!coins[i].collected) continue;
int sx = coins[i].x - cameraX;
if (sx < -10 || sx > SCREEN_WIDTH) continue;
display.drawCircle(sx + 2, coins[i].y + 2, 2, SSD1306_WHITE);
}
// Меч
if (!sword.taken) {
int sx = sword.x - cameraX;
display.drawLine(sx, sword.y, sx + 4, sword.y + 4, SSD1306_WHITE);
display.drawLine(sx, sword.y + 4, sx + 4, sword.y, SSD1306_WHITE);
}
// Враги
for (int i = 0; i < NUM_ENEMIES; i++) {
if (!enemies[i].alive) continue;
int sx = enemies[i].x - cameraX;
if (sx < -10 || sx > SCREEN_WIDTH) continue;
display.fillRect(sx, enemies[i].y, 8, 8, SSD1306_WHITE);
display.fillRect(sx + 2, enemies[i].y + 2, 2, 2, SSD1306_BLACK);
display.fillRect(sx + 4, enemies[i].y + 2, 2, 2, SSD1306_BLACK);
}
// Босс
if (boss.alive) {
int sx = boss.x - cameraX;
if (sx > -20 && sx < SCREEN_WIDTH) {
display.fillRect(sx, boss.y, boss.w, boss.h, SSD1306_WHITE);
display.fillRect(sx + 3, boss.y + 3, 3, 3, SSD1306_BLACK);
display.fillRect(sx + 10, boss.y + 3, 3, 3, SSD1306_BLACK);
// Полоска HP босса
display.fillRect(sx, boss.y - 4, boss.w, 2, SSD1306_WHITE);
display.fillRect(sx, boss.y - 4, boss.w * boss.hp / boss.maxHp, 2, SSD1306_BLACK);
}
}
// Игрок
int sx = player.x - cameraX;
display.fillRect(sx, player.y, player.w, player.h, SSD1306_WHITE);
// Меч в руках игрока (если атакует)
if (player.attacking && player.hasSword) {
if (player.facingRight) {
display.drawLine(sx + player.w, player.y + 2, sx + player.w + 6, player.y + 2, SSD1306_WHITE);
} else {
display.drawLine(sx - 6, player.y + 2, sx, player.y + 2, SSD1306_WHITE);
}
}
// --- HUD ---
display.setTextSize(1);
display.setCursor(0, 0);
display.print(F("HP:"));
for (int i = 0; i < player.hp; i++) display.print(F("#"));
if (player.hasSword) display.print(F(" SWD"));
display.setCursor(SCREEN_WIDTH - 20, 0);
display.print(F("L1"));
display.display();
}