#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
// =========== 顯示設定 ===========
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
// =========== TFT 腳位定義 ===========
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1
#define TFT_MOSI 11
#define TFT_SCK 36
#define TFT_MISO 37
// =========== 控制腳位定義 ===========
#define BULLET_JOYSTICK_X_PIN 12
#define BULLET_JOYSTICK_Y_PIN 10
#define JOYSTICK_X_PIN 14
#define JOYSTICK_Y_PIN 13
#define SKILL1_PIN 7 // 暈眩
#define SKILL2_PIN 8 // 範圍攻擊
#define SKILL3_PIN 9 // 無敵
// =========== 遊戲常數 ===========
#define ENTITY_RADIUS 10 // 統一玩家和敵人的半徑 (原 CENTER_RADIUS 和 RED_RADIUS)
#define BULLET_RADIUS 3
#define BULLET_SIZE 4
#define BOSS_RADIUS (ENTITY_RADIUS * 3)
#define FIRE_ZONE_RADIUS 40
// =========== 碰撞檢測常量 ===========
#define ENTITY_RADIUS_SQ (ENTITY_RADIUS * ENTITY_RADIUS)
#define COLLISION_THRESHOLD_SQ ((ENTITY_RADIUS * 2) * (ENTITY_RADIUS * 2))
// =========== 實體數量限制 ===========
#define MAX_BULLETS 50
#define MAX_BOSS_BULLETS 20
#define MAX_ENEMIES 20
#define MAX_ENEMIES_EASY 3
#define MAX_ENEMIES_NORMAL 5
#define MAX_ENEMIES_HARD 8
#define MAX_FIRE_ZONES 3
// =========== 生命值設定 ===========
#define PLAYER_MAX_HP 10
#define BOSS_MAX_HP 10
// =========== 敵人子彈 ===========
#define MAX_ENEMY_BULLETS 10
#define ENEMY_BULLET_RADIUS 3
#define ENEMY_BULLET_SPEED 6.0
#define ENEMY_BULLET_INTERVAL 2000
// =========== 時間常量(毫秒) ===========
#define FRAME_INTERVAL 50
#define BULLET_INTERVAL 200
#define ENEMY_SPAWN_INTERVAL 2000
#define HP_UPDATE_INTERVAL 100
#define TIMER_UPDATE_INTERVAL 250
#define STAGE_DURATION 15000
#define COOLDOWN_UPDATE_INTERVAL 1000
#define INVINCIBILITY_DURATION 1500 // 一般受傷後的無敵時間
#define LAST_STAND_DURATION 3000 // 大破保護的無敵時間
// =========== 技能相關常量 ===========
#define SKILL1_COOLDOWN 2000
#define SKILL2_COOLDOWN 3000
#define SKILL3_COOLDOWN 5000
#define MAX_SKILL1_LEVEL 12
#define BASE_STUN_RADIUS 60
#define BASE_STUN_DURATION 3
#define BASE_SKILL1_COOLDOWN 2000
#define MAX_SKILL2_LEVEL 12
#define BASE_BOMB_RADIUS 40
#define BASE_BOMB_DAMAGE 1
#define BASE_SKILL2_COOLDOWN 3000
#define MAX_SKILL3_LEVEL 10
#define BASE_INVINCIBLE_DURATION 1.0
#define BASE_SKILL3_COOLDOWN 5000
// =========== Boss 相關常量 ===========
#define BOSS_BAR_MARGIN 0
#define BOSS_BAR_HEIGHT 8
#define BATTLES_BEFORE_BOSS 3
#define STAGES_BEFORE_BOSS 2
// =========== 射線攻擊狀態 ===========
#define BEAM_STATE_WARNING 0
#define BEAM_STATE_ATTACK 1
#define BEAM_STATE_FADE 2
// =========== 遊戲類型枚舉 ===========
enum GameType {
STORY_MODE,
ENDLESS_MODE
};
enum EnemyType {
ENEMY_RED,
ENEMY_BLUE
};
enum GameState {
MAIN_MENU,
IN_BATTLE,
IN_SHOP,
GAME_OVER,
VICTORY_SCREEN
};
enum GameMode {
NORMAL_BATTLE,
BOSS_BATTLE
};
enum GameDifficulty {
EASY,
NORMAL,
HARD,
WTF
};
// =========== 遊戲結構定義 ===========
struct FireZone {
int x;
int y;
bool active;
bool isWarning;
};
struct BossBullet {
float x, y;
float nx, ny;
bool active;
};
struct Bullet {
int16_t x, y;
int8_t dx, dy;
bool active;
int16_t lastX, lastY;
};
struct EnemyBullet {
int16_t x, y;
float dx, dy;
bool active;
int16_t lastX, lastY;
};
struct Boss {
int16_t x, y;
uint16_t currentHP;
uint16_t maxHP;
};
struct Circle {
int16_t x, y;
int8_t dx, dy;
bool active;
bool visible;
bool stunned;
int16_t lastX, lastY;
uint8_t currentHP;
uint8_t maxHP;
uint8_t type; // Add enemy type: 0 for red, 1 for blue
unsigned long lastShotTime; // For blue enemies to track shooting cooldown
};
// =========== 全域變數 ===========
// 顯示物件
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// =========== 遊戲物件陣列 ===========
Bullet bullets[MAX_BULLETS];
Circle enemies[MAX_ENEMIES];
Boss boss;
FireZone fireZones[MAX_FIRE_ZONES];
BossBullet bossBullets[MAX_BOSS_BULLETS];
EnemyBullet enemyBullets[MAX_ENEMY_BULLETS];
// =========== 遊戲狀態 ===========
GameType currentGameType;
GameState currentGameState = MAIN_MENU;
GameMode currentGameMode;
GameDifficulty currentDifficulty;
// =========== 玩家狀態 ===========
int16_t centerX = SCREEN_WIDTH / 2;
int16_t centerY = SCREEN_HEIGHT / 2;
int16_t lastCenterX = SCREEN_WIDTH / 2;
int16_t lastCenterY = SCREEN_HEIGHT / 2;
uint8_t playerHP = 3;
uint8_t maxHP = 3;
uint16_t currency = 0;
// =========== 遊戲進度 ===========
uint16_t currentStage = 1;
int normalBattlesCompleted = 0;
int currentMaxEnemies = MAX_ENEMIES_NORMAL;
int baseHP = 0;
int shopshowed = 0;
// =========== 射線相關 ===========
int beamState = BEAM_STATE_WARNING;
float warningNX = 0;
float warningNY = 0;
float warningSpeed = 0.05f;
float beamNX = 0;
float beamNY = 0;
float beamLength = 0;
int lastBeamX = 0;
int lastBeamY = 0;
unsigned long beamStateChangeTime = 0;
// =========== 實體半徑相關 ===========
#define ENTITY_RADIUS 10 // 統一的實體半徑
#define CENTER_RADIUS ENTITY_RADIUS // 與 ENTITY_RADIUS 相等
#define RED_RADIUS ENTITY_RADIUS // 與 ENTITY_RADIUS 相等
// =========== 技能狀態 ===========
bool isInvincible = false;
bool skill1OnCooldown = false;
bool skill2OnCooldown = false;
bool skill3OnCooldown = false;
bool skill3Active = false;
bool urStrongEnabled = false;
bool bulletMaxEnabled = false;
bool noCDEnabled = false;
unsigned long invincibilityStartTime = 0;
bool playerBlinking = false;
bool skillMaxEnabled = false;
// =========== 技能等級與效果 ===========
int skill1Level = 0;
int currentStunRadius = BASE_STUN_RADIUS;
int currentStunDuration = BASE_STUN_DURATION;
int currentSkill1Cooldown = BASE_SKILL1_COOLDOWN;
int skill2Level = 0;
int currentBombRadius = BASE_BOMB_RADIUS;
int currentBombDamage = BASE_BOMB_DAMAGE;
int currentSkill2Cooldown = BASE_SKILL2_COOLDOWN;
int skill3Level = 0;
float currentInvincibleDuration = BASE_INVINCIBLE_DURATION;
int currentSkill3Cooldown = BASE_SKILL3_COOLDOWN;
// =========== 時間追蹤 ===========
uint32_t stageStartTime = 0;
uint32_t lastBulletFire = 0;
uint32_t lastEnemySpawn = 0;
uint32_t lastHPUpdate = 0;
uint32_t lastFrameTime = 0;
uint32_t lastTimerUpdate = 0;
uint32_t lastCooldownUpdate = 0;
uint32_t skill1LastUsed = 0;
uint32_t skill2LastUsed = 0;
uint32_t skill3LastUsed = 0;
uint32_t lastBossRedrawTime = 0;
uint32_t lastPlayerRedrawTime = 0;
uint32_t confirmationStartTime = 0;
// =========== 時間間隔常量(毫秒) ===========
#define FRAME_INTERVAL 50
const uint16_t frameInterval = FRAME_INTERVAL; // 用於向後相容
#define TIMER_UPDATE_INTERVAL 250
const uint16_t timerUpdateInterval = TIMER_UPDATE_INTERVAL; // 用於向後相容
#define ENEMY_SPAWN_INTERVAL 2000
const uint16_t enemySpawnInterval = ENEMY_SPAWN_INTERVAL; // 用於向後相容
#define COOLDOWN_UPDATE_INTERVAL 1000
const uint32_t cooldownUpdateInterval = COOLDOWN_UPDATE_INTERVAL; // 用於向後相容
// =========== 遊戲設定 ===========
uint16_t bulletInterval = 200;
uint32_t skill1Cooldown = 2000;
uint32_t skill2Cooldown = 3000;
uint32_t skill3Cooldown = 5000;
// =========== 狀態標記 ===========
bool gameStarted = false;
bool stageCompleted = false;
bool isFirstStart = true;
bool centerNeedsRedraw = true;
bool hpNeedsUpdate = true;
bool timerNeedsUpdate = true;
bool skillCooldownNeedsUpdate = true;
bool bossDefeated = false;
bool storymoderestart = false;
bool needRedrawBoss = true;
bool needRedrawPlayer = true;
bool isShowingConfirmation = false;
// =========== 敵人狀態 ===========
bool enemyStunned[MAX_ENEMIES] = {false};
uint32_t enemyStunEndTime[MAX_ENEMIES] = {0};
//=========================================
// ===================== 初始化遊戲元素 =====================
void initializePlayer() {
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
playerHP = maxHP;
}
void initializeBullets() {
for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].active = false;
}
}
void initializeEnemies() {
for (int i = 0; i < MAX_ENEMIES; i++) {
enemies[i].active = false;
}
}
void initializeUI() {
tft.fillScreen(ILI9341_BLACK); // 清空畫面
// 可以在這裡添加其他 UI 初始化邏輯,例如繪製血量條
}
//============================= 顯示血條 ===============================
void drawHPBar() {
static uint8_t lastHP = 255;
static uint16_t lastCurrency = 65535;
if (lastHP != playerHP || lastCurrency != currency || hpNeedsUpdate) {
// 清除整個UI區域
tft.fillRect(0, 0, SCREEN_WIDTH, 22, ILI9341_BLACK);
// 繪製HP長方形
const int rectWidth = 4; // 長方形的寬度
const int rectHeight = 10; // 長方形的高度 (5:2比例)
const int rectSpacing = 3; // 長方形之間的間距
const int startX = 6; // 起始X座標
const int startY = 5; // 起始Y座標
// 繪製剩餘血量 (紫色)
for (int i = 0; i < playerHP; i++) {
tft.fillRect(
startX + i * (rectWidth + rectSpacing),
startY,
rectWidth,
rectHeight,
ILI9341_RED
);
}
// 繪製失去的血量 (白色)
for (int i = playerHP; i < maxHP; i++) {
tft.fillRect(
startX + i * (rectWidth + rectSpacing),
startY,
rectWidth,
rectHeight,
ILI9341_WHITE
);
}
// 繪製貨幣(置中)
tft.setTextSize(2);
tft.setTextColor(ILI9341_YELLOW);
// 計算貨幣文字的位置使其置中
String currencyText = "$: " + String(currency);
int16_t textWidth = currencyText.length() * 12; // 估算文字寬度(每個字符約12像素)
int16_t centerX = (SCREEN_WIDTH - textWidth) / 2;
tft.setCursor(centerX, 2);
tft.print(currencyText);
lastHP = playerHP;
lastCurrency = currency;
hpNeedsUpdate = false;
}
}
//===================== 繪製倒數計時器 ====================
void drawCountdownTimer() {
// 檢查遊戲狀態
if (!gameStarted || currentGameMode != NORMAL_BATTLE) {
return;
}
uint32_t currentTime = millis();
uint32_t elapsed = currentTime - stageStartTime;
uint32_t remainingTime = (STAGE_DURATION > elapsed) ? (STAGE_DURATION - elapsed + 999) / 1000 : 0;
// 清除計時器顯示區域
tft.fillRect(SCREEN_WIDTH - 80, 0, 70, 20, ILI9341_BLACK);
// 繪製碼表圖示
tft.drawCircle(SCREEN_WIDTH - 70, 10, 6, ILI9341_WHITE);
tft.drawLine(SCREEN_WIDTH - 70, 10, SCREEN_WIDTH - 65, 6, ILI9341_WHITE);
// 顯示冒號和時間
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH - 60, 2);
tft.print(":");
tft.setCursor(SCREEN_WIDTH - 50, 2);
tft.print(remainingTime);
// 檢查是否需要結束關卡
if (remainingTime == 0 && !stageCompleted) {
stageCompleted = true;
showStageCompleteScreen();
}
timerNeedsUpdate = true; // 確保下一幀會更新
}
//=================== 背景 =================
void drawGalaxyBackground() {
// Clear the screen
tft.fillScreen(ILI9341_BLACK);
// Draw the spiral galaxy
const int centerX = SCREEN_WIDTH / 2;
const int centerY = SCREEN_HEIGHT / 2;
const int numArms = 4; // Number of spiral arms
const float armSeparation = 2 * PI / numArms;
const int numStars = 200; // Total number of stars
const int maxRadius = min(SCREEN_WIDTH, SCREEN_HEIGHT) / 2;
for (int i = 0; i < numStars; i++) {
float angle = random(0, 360) * DEG_TO_RAD; // Random angle in radians
float radius = random(0, maxRadius); // Random radius
int arm = random(0, numArms); // Random spiral arm
// Add a slight offset to the angle for the spiral effect
angle += arm * armSeparation + (radius / maxRadius) * PI / 8;
// Calculate star position
int x = centerX + cos(angle) * radius;
int y = centerY + sin(angle) * radius;
// Randomly alternate red and blue stars
uint16_t starColor = (random(0, 2) == 0) ? ILI9341_RED : ILI9341_BLUE;
// Draw the star
tft.drawPixel(x, y, starColor);
}
// Add random white stars
for (int i = 0; i < 50; i++) {
int x = random(SCREEN_WIDTH);
int y = random(SCREEN_HEIGHT);
tft.drawPixel(x, y, ILI9341_WHITE);
}
}
// Update the initial screen setup to remove background during battle
void initialScreenSetup() {
// Clear the screen to black for battle
tft.fillScreen(ILI9341_BLACK);
// Draw initial UI elements
hpNeedsUpdate = true;
timerNeedsUpdate = true;
drawHPBar();
drawCountdownTimer();
// Draw initial player
centerNeedsRedraw = true;
drawPlayerCircle();
}
// ===================== 初始化 =====================
void setup() {
Serial.begin(115200);
pinMode(SKILL1_PIN, INPUT_PULLUP);
pinMode(SKILL2_PIN, INPUT_PULLUP);
pinMode(SKILL3_PIN, INPUT_PULLUP);
SPI.begin(TFT_SCK, TFT_MISO, TFT_MOSI, TFT_CS);
tft.begin();
tft.setRotation(3);
// Set SPI to maximum possible speed for faster drawing
SPI.setFrequency(80000000);
tft.setAddrWindow(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1);
// Initialize timing variables
lastFrameTime = 0;
lastTimerUpdate = 0;
showStartScreen();
}
// ===================== 起始畫面 ===========================
void showStartScreen() {
const char* options[] = {"Main Story", "Endless", "Setting"};
const uint8_t numOptions = sizeof(options) / sizeof(options[0]);
uint8_t selectedIndex = 0;
// Draw background
drawGalaxyBackground();
// Display title
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(3);
tft.setCursor(60, 40);
tft.print("Aether Escape");
while (true) {
// Draw options
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextSize(2);
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(80, 100 + i * 30);
tft.print(options[i]);
// Add an indicator for the currently selected option
if (i == selectedIndex) {
tft.fillCircle(60, 108 + i * 30, 5, ILI9341_YELLOW);
} else {
tft.fillCircle(60, 108 + i * 30, 5, ILI9341_DARKGREY);
}
}
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
enemyBullets[i].active = false;
}
// Display button guides
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
// Handle button input
if (digitalRead(SKILL1_PIN) == LOW) { // Move up
if (selectedIndex > 0) {
selectedIndex--;
} else {
selectedIndex = numOptions - 1;
}
delay(200); // Debounce delay
}
if (digitalRead(SKILL2_PIN) == LOW) { // Move down
selectedIndex = (selectedIndex + 1) % numOptions;
delay(200); // Debounce delay
}
if (digitalRead(SKILL3_PIN) == LOW) { // Confirm selection
delay(200);
if (selectedIndex == 0) { // Main Story
resetStoryMode(); // 重置故事模式狀態,確保可以重新開始
showDifficultyScreen(true);
return;
} else if (selectedIndex == 1) {
showDifficultyScreen(false); // Endless Mode
return;
} else if (selectedIndex == 2) {
showSettingsScreen(); // Settings
return;
}
}
}
}
// ===================== 設定畫面 =====================
void drawSettingsTitle() {
tft.setCursor(0, 0); // 設定游標至左上角
tft.setTextColor(ILI9341_WHITE); // 設定文字顏色為白色
tft.print("Setting"); // 顯示 "Set" 作為標題
}
void showSettingsScreen() {
const char* settingsOptions[] = {"U R STRONG", "Bullet Max", "Skill MAX", "Back"};
const uint8_t numSettingsOptions = sizeof(settingsOptions) / sizeof(settingsOptions[0]);
uint8_t selectedSettingIndex = 0;
uint8_t lastSelectedIndex = 255;
// 繪畫背景
drawGalaxyBackground();
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(0, 0);
tft.print("Setting");
// Draw the settings options
for (uint8_t i = 0; i < numSettingsOptions; i++) {
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(40, 40 + i * 30);
tft.print(settingsOptions[i]);
tft.fillCircle(20, 48 + i * 30, 5, ILI9341_DARKGREY);
// Display the initial state of each option
if (i < numSettingsOptions - 1) {
bool isEnabled = false;
switch (i) {
case 0: isEnabled = urStrongEnabled; break;
case 1: isEnabled = bulletMaxEnabled; break;
case 2: isEnabled = skillMaxEnabled; break;
}
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH - 60, 40 + i * 30);
tft.setTextColor(isEnabled ? ILI9341_GREEN : ILI9341_RED);
tft.print(isEnabled ? "ON" : "OFF");
}
}
// Draw descriptions for Bullet Max
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(5, SCREEN_HEIGHT - 45);
tft.print("Bullet Max: Triple spread shot!");
// Draw button guides
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
while (true) {
// Only update the display when the selection changes
if (selectedSettingIndex != lastSelectedIndex) {
// Clear the old selection indicator
if (lastSelectedIndex < numSettingsOptions) {
tft.fillCircle(20, 48 + lastSelectedIndex * 30, 5, ILI9341_DARKGREY);
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(40, 40 + lastSelectedIndex * 30);
tft.print(settingsOptions[lastSelectedIndex]);
}
// Draw the new selection indicator
tft.fillCircle(20, 48 + selectedSettingIndex * 30, 5, ILI9341_YELLOW);
tft.setTextSize(2);
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor(40, 40 + selectedSettingIndex * 30);
tft.print(settingsOptions[selectedSettingIndex]);
// Update the description based on selection
tft.fillRect(5, SCREEN_HEIGHT - 45, SCREEN_WIDTH - 10, 20, ILI9341_BLACK);
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(5, SCREEN_HEIGHT - 45);
switch (selectedSettingIndex) {
case 0:
tft.print("U R STRONG: Maximum HP and full heal");
break;
case 1:
tft.print("Bullet Max: Triple spread shot!");
break;
case 2:
tft.print("Skill MAX: All skills at maximum level");
break;
case 3:
tft.print("Back to main menu");
break;
}
lastSelectedIndex = selectedSettingIndex;
}
// Handle button input
if (digitalRead(SKILL1_PIN) == LOW) {
if (selectedSettingIndex > 0) {
selectedSettingIndex--;
} else {
selectedSettingIndex = numSettingsOptions - 1;
}
delay(200);
}
if (digitalRead(SKILL2_PIN) == LOW) {
selectedSettingIndex = (selectedSettingIndex + 1) % numSettingsOptions;
delay(200);
}
if (digitalRead(SKILL3_PIN) == LOW) {
delay(200);
switch (selectedSettingIndex) {
case 0: // U R STRONG
urStrongEnabled = !urStrongEnabled;
tft.fillRect(SCREEN_WIDTH - 65, 40, 60, 20, ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH - 60, 40);
tft.setTextColor(urStrongEnabled ? ILI9341_GREEN : ILI9341_RED);
tft.print(urStrongEnabled ? "ON" : "OFF");
if (urStrongEnabled) {
maxHP = 10;
playerHP = maxHP;
}
break;
case 1: // Bullet Max
bulletMaxEnabled = !bulletMaxEnabled;
tft.fillRect(SCREEN_WIDTH - 65, 70, 60, 20, ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH - 60, 70);
tft.setTextColor(bulletMaxEnabled ? ILI9341_GREEN : ILI9341_RED);
tft.print(bulletMaxEnabled ? "ON" : "OFF");
// Additional visual effect when turning on Bullet Max
if (bulletMaxEnabled) {
// Optional: Show a brief animation or effect when enabling triple shot
tft.fillRect(5, SCREEN_HEIGHT - 65, SCREEN_WIDTH - 10, 15, ILI9341_BLACK);
tft.setTextSize(1);
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor(5, SCREEN_HEIGHT - 65);
tft.print("Triple shot activated!");
delay(500);
tft.fillRect(5, SCREEN_HEIGHT - 65, SCREEN_WIDTH - 10, 15, ILI9341_BLACK);
}
break;
case 2: // Skill MAX
skillMaxEnabled = !skillMaxEnabled;
tft.fillRect(SCREEN_WIDTH - 65, 100, 60, 20, ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH - 60, 100);
tft.setTextColor(skillMaxEnabled ? ILI9341_GREEN : ILI9341_RED);
tft.print(skillMaxEnabled ? "ON" : "OFF");
// 當開啟 Skill MAX 時,設定所有技能等級為最大
if (skillMaxEnabled) {
// 技能1 (暈眩)
skill1Level = MAX_SKILL1_LEVEL;
currentStunRadius = BASE_STUN_RADIUS * 2.4; // 12次升級後的最大範圍
currentStunDuration = BASE_STUN_DURATION + 4; // 最大持續時間
skill1Cooldown = 500; // 最小冷卻時間
// 技能2 (爆炸)
skill2Level = MAX_SKILL2_LEVEL;
currentBombRadius = BASE_BOMB_RADIUS * 2.4; // 12次升級後的最大範圍
currentBombDamage = BASE_BOMB_DAMAGE + 4; // 最大傷害
currentSkill2Cooldown = 500; // 最小冷卻時間
// 技能3 (無敵)
skill3Level = MAX_SKILL3_LEVEL;
currentInvincibleDuration = BASE_INVINCIBLE_DURATION + 2.5; // 最大持續時間
currentSkill3Cooldown = 500; // 最小冷卻時間
}
break;
case 3: // Back
tft.fillScreen(ILI9341_BLACK);
showStartScreen();
return;
}
}
}
}
// ===================== 設定畫面選項繪製 =====================
void drawSettingsOptions(const char* options[], uint8_t numOptions, uint8_t selectedIndex) {
// 清除設定選項區域
tft.fillRect(0, 30, SCREEN_WIDTH, SCREEN_HEIGHT - 30, ILI9341_BLACK);
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextSize(2); // 確保字體大小一致
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(40, 40 + i * 30); // 調整間距
tft.print(options[i]);
// 顯示 "Max HP+7" 的開關狀態
if (i == 0) {
tft.setCursor(SCREEN_WIDTH - 60, 40 + i * 30);
tft.setTextColor(urStrongEnabled ? ILI9341_GREEN : ILI9341_RED);
tft.print(urStrongEnabled ? "ON" : "OFF");
}
}
}
// ===================== 選項顯示 =====================
void drawSelectionOptions(const char* options[], uint8_t numOptions, uint8_t selectedIndex) {
// Clear the options area
tft.fillRect(0, 80, SCREEN_WIDTH, 140, ILI9341_BLACK);
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(60, 100 + i * 30);
tft.print(options[i]);
}
}
// ======================= 難度 ==========================
void showDifficultyScreen(bool isMainStory) {
const char* difficulties[] = {"EASY", "NORMAL", "HARD", "WTF"};
const uint8_t numOptions = sizeof(difficulties) / sizeof(difficulties[0]);
uint8_t selectedIndex = 1; // Default to NORMAL
// Draw background
drawGalaxyBackground();
// Display mode title
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(40, 30);
tft.print(isMainStory ? "Main Story-" : "Endless-");
// Display selection prompt
tft.setCursor(40, 60);
tft.print("Select Difficulty:");
while (true) {
// Draw difficulty options
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextSize(2);
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(80, 100 + i * 30);
tft.print(difficulties[i]);
// Add a special color indicator for the currently selected difficulty
if (i == selectedIndex) {
tft.fillCircle(60, 108 + i * 30, 5, ILI9341_YELLOW);
} else {
tft.fillCircle(60, 108 + i * 30, 5, ILI9341_DARKGREY);
}
}
// Display button guides
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
// Handle button input
if (digitalRead(SKILL1_PIN) == LOW) { // Move up
if (selectedIndex > 0) {
selectedIndex--;
} else {
selectedIndex = numOptions - 1;
}
delay(200); // Debounce delay
}
if (digitalRead(SKILL2_PIN) == LOW) { // Move down
selectedIndex = (selectedIndex + 1) % numOptions;
delay(200); // Debounce delay
}
if (digitalRead(SKILL3_PIN) == LOW) { // Confirm selection
currentDifficulty = static_cast<GameDifficulty>(selectedIndex);
delay(200);
// Display confirmation message
tft.fillRect(40, SCREEN_HEIGHT / 2 - 20, 240, 40, ILI9341_GREEN);
tft.setTextSize(2);
tft.setTextColor(ILI9341_BLACK);
tft.setCursor(50, SCREEN_HEIGHT / 2 - 10);
tft.print(difficulties[selectedIndex]);
tft.print(" SELECTED!");
delay(1000);
if (isMainStory) {
// 確保完全重置故事模式
resetStoryMode();
startStoryMode();
} else {
startEndlessMode();
}
return;
}
}
}
// ===================== 按鈕標籤 =====================
void drawButtonLabels() {
// 清除按鈕區域,避免疊加的文字導致混亂
tft.fillRect(0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, 20, ILI9341_BLACK);
// 設定文字大小
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
// UP 按鈕
tft.setCursor(5, SCREEN_HEIGHT - 15);
tft.print("UP");
// DOWN 按鈕
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 15);
tft.print("DOWN");
// USE/CONFIRM 按鈕
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 15);
tft.print("select");
}
// ===================== 故事模式 ===================
void startStoryMode() {
// 先重置所有狀態
resetStoryMode();
clearAllEffects();
clearAllEntities();
// 設置初始遊戲狀態
currentGameMode = NORMAL_BATTLE;
currentGameType = STORY_MODE;
currentStage = 1;
normalBattlesCompleted = 0;
bossDefeated = false;
shopshowed = 0;
storymoderestart = false;
// 重置玩家狀態
playerHP = maxHP;
currency = 0;
// 初始化遊戲
gameStarted = true;
isFirstStart = true;
// 開始遊戲
startGame();
}
// ======================= 無盡模式 =========================
void startEndlessMode() {
gameStarted = true;
stageCompleted = false;
currentGameType = ENDLESS_MODE; // 明確設置為無盡模式
currentGameMode = NORMAL_BATTLE;
normalBattlesCompleted = 0; // 重置關卡計數
if (isFirstStart) {
currentStage = 1;
if (urStrongEnabled) {
maxHP = 10;
} else {
maxHP = 3;
}
isFirstStart = false;
}
playerHP = maxHP;
// 清理所有子彈狀態
for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].active = false;
}
// 清理所有敵人狀態
for (int i = 0; i < MAX_ENEMIES; i++) {
enemies[i].active = false;
enemies[i].stunned = false;
enemyStunned[i] = false;
enemyStunEndTime[i] = 0;
}
// 重置技能冷卻
skill1LastUsed = 0;
skill2LastUsed = 0;
skill3LastUsed = 0;
skill1OnCooldown = false;
skill2OnCooldown = false;
skill3OnCooldown = false;
skillCooldownNeedsUpdate = true;
// 設置玩家位置
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
// 更新時間戳
stageStartTime = millis();
lastTimerUpdate = stageStartTime;
timerNeedsUpdate = true;
// 遊戲參數初始化
currency = 0;
bulletInterval = 200;
currentMaxEnemies = MAX_ENEMIES_NORMAL;
initialScreenSetup();
}
// ===================== 開始遊戲 =====================
void startGame() {
// 如果是故事模式,確保完全重置
if (currentGameType == STORY_MODE) {
bossDefeated = false; // 確保Boss狀態被重置
currentGameMode = NORMAL_BATTLE;
}
gameStarted = true;
stageCompleted = false;
// 如果是第一次開始或重新開始故事模式
if (isFirstStart || currentGameType == STORY_MODE) {
currentStage = 1;
normalBattlesCompleted = 0;
if (urStrongEnabled) {
maxHP = 10;
} else {
maxHP = 3;
}
isFirstStart = false;
}
// 重置玩家狀態
playerHP = maxHP;
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
// 初始化彈藥
for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].active = false;
}
// 初始化敵人
for (int i = 0; i < MAX_ENEMIES; i++) {
enemies[i].active = false;
enemies[i].stunned = false;
enemyStunned[i] = false;
enemyStunEndTime[i] = 0;
}
// 重置技能冷卻
skill1LastUsed = 0;
skill2LastUsed = 0;
skill3LastUsed = 0;
skill1OnCooldown = false;
skill2OnCooldown = false;
skill3OnCooldown = false;
skillCooldownNeedsUpdate = true;
// 更新時間戳
stageStartTime = millis();
lastTimerUpdate = stageStartTime;
timerNeedsUpdate = true;
// 根據難度設置敵人數量
switch (currentDifficulty) {
case EASY: currentMaxEnemies = MAX_ENEMIES_EASY; break;
case NORMAL: currentMaxEnemies = MAX_ENEMIES_NORMAL; break;
case HARD: currentMaxEnemies = MAX_ENEMIES_HARD; break;
case WTF: currentMaxEnemies = MAX_ENEMIES; break;
}
// 設置起始遊戲模式
currentGameMode = NORMAL_BATTLE;
initialScreenSetup();
}
// ===================== 通關畫面 =====================
void showStageCompleteScreen() {
currentStage++;
normalBattlesCompleted++; // 增加完成的戰鬥數
// 重置遊戲狀態
playerHP = maxHP;
hpNeedsUpdate = true;
stageStartTime = millis();
lastTimerUpdate = stageStartTime;
timerNeedsUpdate = true;
stageCompleted = false;
// 確保正確的遊戲模式轉換
if (currentGameType == STORY_MODE) {
if (normalBattlesCompleted >= STAGES_BEFORE_BOSS && !bossDefeated) {
currentGameMode = BOSS_BATTLE;
initializeBoss();
return;
}
}
// 只在非Boss戰時顯示商店
if (currentGameMode != BOSS_BATTLE && shopshowed == 0) {
showMainShopScreen();
}
}
//============== 商店 =================
void showMainShopScreen() {
gameStarted = false;
shopshowed = 1;
const char* options[] = {"MAX HP+1 (MAX=10) (3$)", "Main Weapon UP (5$)", "Skill upgrade", "continue", "Back Main Screen"};
const uint8_t numOptions = sizeof(options) / sizeof(options[0]);
uint8_t selectedIndex = 0;
drawGalaxyBackground();
drawHPAndCurrency();
while (true) {
// 繪製選項
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextSize(2);
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(40, 40 + i * 30);
tft.print(options[i]);
if (i == selectedIndex) {
tft.fillCircle(20, 48 + i * 30, 5, ILI9341_YELLOW);
} else {
tft.fillCircle(20, 48 + i * 30, 5, ILI9341_DARKGREY);
}
}
// 顯示按鈕指引
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
// 顯示目前武器狀態
tft.setTextSize(1);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(5, SCREEN_HEIGHT - 40);
tft.print("Weapon speed: ");
tft.print(1000 / bulletInterval);
tft.print(" shots/sec");
if (bulletMaxEnabled) {
tft.setCursor(5, SCREEN_HEIGHT - 55);
tft.print("Triple shot enabled!");
}
if (digitalRead(SKILL1_PIN) == LOW) { // 上移
if (selectedIndex > 0) {
selectedIndex--;
} else {
selectedIndex = numOptions - 1;
}
delay(200);
}
if (digitalRead(SKILL2_PIN) == LOW) { // 下移
selectedIndex = (selectedIndex + 1) % numOptions;
delay(200);
}
if (digitalRead(SKILL3_PIN) == LOW) { // 確認選擇
if (selectedIndex == 0 && currency >= 3 && playerHP <= 9) {
maxHP++;
playerHP = min((int)playerHP + 1, (int)maxHP);
currency -= 3;
showConfirmation("MAX HP+1");
drawHPAndCurrency();
} else if (selectedIndex == 0 && playerHP == 10){
showConfirmation("HP MAX");
} else if (selectedIndex == 1 && currency >= 5) {
// Weapon upgrade affects both normal and Bullet Max modes
bulletInterval = max(100, bulletInterval - 50);
currency -= 5;
// Display special message if Bullet Max is enabled
if (bulletMaxEnabled) {
showConfirmation("Triple Shot Boosted!");
} else {
showConfirmation("Main Weapon Boost!");
}
drawHPAndCurrency();
} else if (selectedIndex == 2) {
showSkillUpgradeScreen();
drawGalaxyBackground();
drawHPAndCurrency();
} else if (selectedIndex == 3) { // continue 選項
tft.fillScreen(ILI9341_BLACK);
playerHP = maxHP;
hpNeedsUpdate = true;
drawPlayerCircle();
stageStartTime = millis();
lastTimerUpdate = stageStartTime;
timerNeedsUpdate = true;
stageCompleted = false;
gameStarted = true;
if (currentGameType == ENDLESS_MODE) {
currentGameMode = NORMAL_BATTLE;
} else {
if (normalBattlesCompleted >= STAGES_BEFORE_BOSS) {
currentGameMode = BOSS_BATTLE;
initializeBoss();
} else {
currentGameMode = NORMAL_BATTLE;
}
}
return;
} else if (selectedIndex == 4) { // Back Main Screen 選項
if (showBackConfirmationDialog()) {
tft.fillScreen(ILI9341_BLACK);
showStartScreen();
return;
} else {
// 返回商店選單
drawGalaxyBackground();
drawHPAndCurrency();
}
} else {
showConfirmation("Not enough $");
drawHPAndCurrency();
}
delay(200);
}
}
}
// 確認是否要返回主畫面的對話框
bool showBackConfirmationDialog() {
// 繪製紅色背景
tft.fillScreen(ILI9341_BLACK);
// 顯示提示文字
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(3);
tft.setCursor(20, 80);
tft.print("ARE YOU SURE?");
// 顯示選項
tft.setTextSize(3);
// 初始選擇為否
bool yesSelected = false;
while (true) {
// 繪製選項
tft.fillRect(60, 150, 200, 60, ILI9341_BLACK); // 清除選項區域
tft.setTextColor(yesSelected ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(80, 150);
tft.print("YES");
tft.setTextColor(!yesSelected ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(180, 150);
tft.print("NO");
// 繪製選擇指示器
tft.fillCircle(yesSelected ? 60 : 160, 158, 5, ILI9341_YELLOW);
// 按鈕控制
if (digitalRead(SKILL1_PIN) == LOW || digitalRead(SKILL2_PIN) == LOW) { // 切換選項
yesSelected = !yesSelected;
delay(200);
}
if (digitalRead(SKILL3_PIN) == LOW) { // 確認選擇
return yesSelected; // 返回選擇結果
}
}
}
// ===================== 技能升級畫面 =====================
void showSkillUpgradeScreen() {
const char* skillOptions[] = {"Dizzy (5$)", "Bomb (5$)", "MAX (5$)", "Continue Game", "Back", };
const uint8_t numSkillOptions = sizeof(skillOptions) / sizeof(skillOptions[0]);
uint8_t selectedSkillIndex = 0;
uint8_t lastSelectedIndex = 255;
bool firstDraw = true;
// 調整選項間距和位置
const int OPTION_HEIGHT = 35; // 選項高度
const int OPTION_START_Y = 40; // 起始Y座標
const int OPTION_TEXT_OFFSET = 8; // 文字Y軸偏移量
const int UPGRADE_INFO_X = 180; // 升級資訊的X座標
// 初始化畫面
drawGalaxyBackground();
drawHPAndCurrency();
// 初始繪製按鈕指引
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
// 繪製選項的函數
auto drawOption = [&](uint8_t index, bool selected) {
int yPos = OPTION_START_Y + index * OPTION_HEIGHT;
tft.fillRect(15, yPos - 5, SCREEN_WIDTH - 30, OPTION_HEIGHT + 2, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(selected ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(40, yPos + OPTION_TEXT_OFFSET);
if (index == 0) { // 技能1
if (skill1Level >= MAX_SKILL1_LEVEL) {
tft.print("Dizzy full");
} else {
tft.print(skillOptions[index]);
tft.setCursor(UPGRADE_INFO_X, yPos + OPTION_TEXT_OFFSET);
int upgradeType = skill1Level % 3;
switch (upgradeType) {
case 0:
tft.print("Range +20%");
break;
case 1:
tft.print("Duration +1s");
break;
case 2:
tft.print("Cooldown -10%");
break;
}
}
} else if (index == 1) { // 技能2
if (skill2Level >= MAX_SKILL2_LEVEL) {
tft.print("Bomb full");
} else {
tft.print(skillOptions[index]);
tft.setCursor(UPGRADE_INFO_X, yPos + OPTION_TEXT_OFFSET);
int upgradeType = skill2Level % 3;
switch (upgradeType) {
case 0:
tft.print("Area +20%");
break;
case 1:
tft.print("Damage +1");
break;
case 2:
tft.print("Cooldown -10%");
break;
}
}
} else if (index == 2) { // 技能3
if (skill3Level >= MAX_SKILL3_LEVEL) {
tft.print("MAX full");
} else {
tft.print(skillOptions[index]);
tft.setCursor(UPGRADE_INFO_X, yPos + OPTION_TEXT_OFFSET);
int upgradeType = skill3Level % 2;
switch (upgradeType) {
case 0:
tft.print("Time +0.5s");
break;
case 1:
tft.print("Cooldown -10%");
break;
}
}
} else {
tft.print(skillOptions[index]);
}
tft.fillCircle(20, yPos + OPTION_HEIGHT / 2, 5, selected ? ILI9341_YELLOW : ILI9341_DARKGREY);
};
// 初次繪製所有選項
for (uint8_t i = 0; i < numSkillOptions; i++) {
drawOption(i, i == selectedSkillIndex);
}
firstDraw = false;
while (true) {
uint32_t currentTime = millis();
if (isShowingConfirmation && currentTime - confirmationStartTime >= 2000) {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
isShowingConfirmation = false;
}
if (selectedSkillIndex != lastSelectedIndex) {
if (lastSelectedIndex != 255) {
drawOption(lastSelectedIndex, false);
}
drawOption(selectedSkillIndex, true);
lastSelectedIndex = selectedSkillIndex;
}
if (digitalRead(SKILL1_PIN) == LOW) {
if (selectedSkillIndex > 0) {
selectedSkillIndex--;
} else {
selectedSkillIndex = numSkillOptions - 1;
}
delay(200);
}
if (digitalRead(SKILL2_PIN) == LOW) {
selectedSkillIndex = (selectedSkillIndex + 1) % numSkillOptions;
delay(200);
}
if (digitalRead(SKILL3_PIN) == LOW) {
if (selectedSkillIndex == 0) { // 技能1升級
if (skill1Level < MAX_SKILL1_LEVEL && currency >= 5) {
currency -= 5;
switch (skill1Level % 3) {
case 0:
currentStunRadius += BASE_STUN_RADIUS * 0.2;
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Range Up!");
break;
case 1:
currentStunDuration++;
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Duration Up!");
break;
case 2:
skill1Cooldown = max(500, (int)(skill1Cooldown * 0.9));
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Cooldown Down!");
break;
}
isShowingConfirmation = true;
confirmationStartTime = currentTime;
skill1Level++;
drawHPAndCurrency();
drawOption(0, true);
} else if (skill1Level >= MAX_SKILL1_LEVEL) {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Max Level!");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
} else {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Not enough $");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
}
} else if (selectedSkillIndex == 1) { // 技能2升級
if (skill2Level < MAX_SKILL2_LEVEL && currency >= 5) {
currency -= 5;
switch (skill2Level % 3) {
case 0:
currentBombRadius += BASE_BOMB_RADIUS * 0.2;
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Area Up!");
break;
case 1:
currentBombDamage++;
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Damage Up!");
break;
case 2:
currentSkill2Cooldown = max(500, (int)(currentSkill2Cooldown * 0.9));
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Cooldown Down!");
break;
}
isShowingConfirmation = true;
confirmationStartTime = currentTime;
skill2Level++;
drawHPAndCurrency();
drawOption(1, true);
} else if (skill2Level >= MAX_SKILL2_LEVEL) {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Max Level!");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
} else {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Not enough $");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
}
} else if (selectedSkillIndex == 2) { // 技能3升級
if (skill3Level < MAX_SKILL3_LEVEL && currency >= 5) {
currency -= 5;
switch (skill3Level % 2) {
case 0:
currentInvincibleDuration += 0.5;
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Time Up!");
break;
case 1:
currentSkill3Cooldown = max(500, (int)(currentSkill3Cooldown * 0.9));
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(40, 180);
tft.print("Cooldown Down!");
break;
}
isShowingConfirmation = true;
confirmationStartTime = currentTime;
skill3Level++;
drawHPAndCurrency();
drawOption(2, true);
} else if (skill3Level >= MAX_SKILL3_LEVEL) {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Max Level!");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
} else {
tft.fillRect(40, 180, 240, 30, ILI9341_BLACK);
tft.setTextSize(2);
tft.setTextColor(ILI9341_RED);
tft.setCursor(40, 180);
tft.print("Not enough $");
isShowingConfirmation = true;
confirmationStartTime = currentTime;
}
} else if (selectedSkillIndex == 3) { // Continue Game 選項
tft.fillScreen(ILI9341_BLACK);
playerHP = maxHP;
hpNeedsUpdate = true;
drawPlayerCircle();
stageStartTime = millis();
lastTimerUpdate = stageStartTime;
timerNeedsUpdate = true;
stageCompleted = false;
gameStarted = true;
if (currentGameType == ENDLESS_MODE) {
currentGameMode = NORMAL_BATTLE;
} else {
if (normalBattlesCompleted >= STAGES_BEFORE_BOSS) {
currentGameMode = BOSS_BATTLE;
initializeBoss();
} else {
currentGameMode = NORMAL_BATTLE;
}
}
return;
} else if (selectedSkillIndex == 4) { // Back 選項
tft.fillScreen(ILI9341_BLACK);
showMainShopScreen();
return;
}
}
}
delay(200);
}
// ===================== 畫面更新函數 =====================
void drawHPAndCurrency() {
// 清除頂部區域
tft.fillRect(0, 0, SCREEN_WIDTH, 22, ILI9341_BLACK);
// 使用與遊戲中相同的長方形設定
const int rectWidth = 4; // 長方形的寬度
const int rectHeight = 10; // 長方形的高度
const int rectSpacing = 3; // 長方形之間的間距
const int startX = 6; // 起始X座標
const int startY = 5; // 起始Y座標
// 繪製血量長方形(全部顯示為滿血狀態)
for (int i = 0; i < maxHP; i++) {
tft.fillRect(
startX + i * (rectWidth + rectSpacing),
startY,
rectWidth,
rectHeight,
ILI9341_RED // 使用紅色表示滿血狀態
);
}
// 顯示金額
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor(SCREEN_WIDTH - 100, 2);
tft.print(" $: ");
tft.print(currency);
}
// 修改商店選項的顯示間距
void drawShopOptions(const char* options[], uint8_t numOptions, uint8_t selectedIndex) {
// 清除選項區域
tft.fillRect(0, 30, SCREEN_WIDTH, SCREEN_HEIGHT - 30, ILI9341_BLACK);
// 顯示選項
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(40, 40 + i * 30); // 減小選項之間的間距
tft.print(options[i]);
}
// 底部按鈕指引
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20); // 調整按鈕位置以更均勻
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20); // 防止 "continue" 被切割
tft.print("USE");
}
// 顯示確認字幕的函數
void showConfirmation(const char* message) {
// 顯示訊息
tft.fillRect(0, SCREEN_HEIGHT - 40, SCREEN_WIDTH, 20, ILI9341_CYAN); // 水藍色背景表示確認
tft.setTextColor(ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH / 2 - (strlen(message) * 3), SCREEN_HEIGHT - 38); // 居中顯示
tft.print(message);
// 記錄開始顯示的時間
confirmationStartTime = millis();
isShowingConfirmation = true;
}
void updateConfirmation() {
// 如果正在顯示訊息,並且已經超過 3 秒
if (isShowingConfirmation && millis() - confirmationStartTime >= 3000) {
// 清除訊息(恢復為黑色背景)
tft.fillRect(0, SCREEN_HEIGHT - 40, SCREEN_WIDTH, 20, ILI9341_BLACK);
isShowingConfirmation = false; // 重置狀態
}
}
// Fast approximation of inverse square root (for normalization)
// Based on Quake III's fast inverse square root
float Q_rsqrt(float number) {
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = *(long*)&y;
i = 0x5f3759df - (i >> 1);
y = *(float*)&i;
y = y * (threehalfs - (x2 * y * y)); // 1st iteration
return y;
}
// ===================== 子彈發射 =====================
void fireBullet() {
int16_t joystickX = -(analogRead(BULLET_JOYSTICK_X_PIN) - 2048);
int16_t joystickY = -(analogRead(BULLET_JOYSTICK_Y_PIN) - 2048);
// Fast magnitude check without using sqrt
int32_t magnitudeSq = joystickX * joystickX + joystickY * joystickY;
if (magnitudeSq > 262144 && millis() - lastBulletFire >= bulletInterval) { // 512^2 = 262144
lastBulletFire = millis();
// Calculate bullet direction using fast normalization
float invMag = Q_rsqrt(magnitudeSq);
float normalizedX = joystickX * invMag;
float normalizedY = joystickY * invMag;
if (bulletMaxEnabled) {
// Fire three bullets in a spread pattern
fireBulletSpread(normalizedX, normalizedY);
} else {
// Original single bullet firing system
fireSingleBullet(normalizedX, normalizedY);
}
}
}
// 發射單一子彈的函數(原始方式)
void fireSingleBullet(float normalizedX, float normalizedY) {
// Find inactive bullet
for (int i = 0; i < MAX_BULLETS; i++) {
if (!bullets[i].active) {
// Set bullet start position farther from the player circle
float distanceMultiplier = 1.5; // Adjust this to control how far from the player the bullet spawns
bullets[i].x = centerX + (normalizedX * CENTER_RADIUS * distanceMultiplier);
bullets[i].y = centerY + (normalizedY * CENTER_RADIUS * distanceMultiplier);
bullets[i].lastX = bullets[i].x; // Initialize last position
bullets[i].lastY = bullets[i].y;
// Set bullet velocity
bullets[i].dx = (normalizedX * 4.0) * 2; // Scale by 2 for fixed-point
bullets[i].dy = (normalizedY * 4.0) * 2;
bullets[i].active = true;
break;
}
}
}
// 發射三發子彈的函數(Bullet Max 模式)
void fireBulletSpread(float normalizedX, float normalizedY) {
// Calculate perpendicular direction for spread
float perpX = -normalizedY; // Perpendicular to the direction vector
float perpY = normalizedX;
// Spread angle (small angle for slight spread)
float spreadFactor = 0.2; // Adjust this value to control the spread width
// Find inactive bullets and fire in spread pattern
int bulletsFound = 0;
// Create array to track bullet indices we want to use
int bulletIndices[3] = {-1, -1, -1};
// First, find 3 available bullet slots
for (int i = 0; i < MAX_BULLETS && bulletsFound < 3; i++) {
if (!bullets[i].active) {
bulletIndices[bulletsFound] = i;
bulletsFound++;
}
}
// If we found all 3 bullets, set them up in a spread pattern
if (bulletsFound == 3) {
// Center bullet (same as original direction)
int centerIdx = bulletIndices[0];
float distanceMultiplier = 1.5;
bullets[centerIdx].x = centerX + (normalizedX * CENTER_RADIUS * distanceMultiplier);
bullets[centerIdx].y = centerY + (normalizedY * CENTER_RADIUS * distanceMultiplier);
bullets[centerIdx].lastX = bullets[centerIdx].x;
bullets[centerIdx].lastY = bullets[centerIdx].y;
bullets[centerIdx].dx = (normalizedX * 4.0) * 2;
bullets[centerIdx].dy = (normalizedY * 4.0) * 2;
bullets[centerIdx].active = true;
// Left bullet (slight offset)
int leftIdx = bulletIndices[1];
bullets[leftIdx].x = centerX + ((normalizedX - perpX * spreadFactor) * CENTER_RADIUS * distanceMultiplier);
bullets[leftIdx].y = centerY + ((normalizedY - perpY * spreadFactor) * CENTER_RADIUS * distanceMultiplier);
bullets[leftIdx].lastX = bullets[leftIdx].x;
bullets[leftIdx].lastY = bullets[leftIdx].y;
bullets[leftIdx].dx = ((normalizedX - perpX * spreadFactor) * 4.0) * 2;
bullets[leftIdx].dy = ((normalizedY - perpY * spreadFactor) * 4.0) * 2;
bullets[leftIdx].active = true;
// Right bullet (slight offset)
int rightIdx = bulletIndices[2];
bullets[rightIdx].x = centerX + ((normalizedX + perpX * spreadFactor) * CENTER_RADIUS * distanceMultiplier);
bullets[rightIdx].y = centerY + ((normalizedY + perpY * spreadFactor) * CENTER_RADIUS * distanceMultiplier);
bullets[rightIdx].lastX = bullets[rightIdx].x;
bullets[rightIdx].lastY = bullets[rightIdx].y;
bullets[rightIdx].dx = ((normalizedX + perpX * spreadFactor) * 4.0) * 2;
bullets[rightIdx].dy = ((normalizedY + perpY * spreadFactor) * 4.0) * 2;
bullets[rightIdx].active = true;
}
}
// ===================== 更新子彈 =====================
void updateBullets() {
// 保留原有的進度條參數
const int barWidth = 60;
const int barHeight = 10;
const int barY = SCREEN_HEIGHT - 20;
const int bar1X = 10;
const int bar2X = 130;
const int bar3X = 250;
// 讀取左搖桿以獲取推動效果
int16_t joystickX = -(analogRead(JOYSTICK_X_PIN) - 2048);
int16_t joystickY = -(analogRead(JOYSTICK_Y_PIN) - 2048);
// 計算推動偏移量
int32_t joyMagSq = joystickX * joystickX + joystickY * joystickY;
float offsetX = 0;
float offsetY = 0;
if (joyMagSq > 40000) {
float invMag = Q_rsqrt(joyMagSq);
offsetX = -(joystickX * invMag * 4.5);
offsetY = -(joystickY * invMag * 4.5);
}
for (int i = 0; i < MAX_BULLETS; i++) {
if (!bullets[i].active) continue;
int16_t oldX = bullets[i].x;
int16_t oldY = bullets[i].y;
// 清除舊的子彈位置
if (!isUIArea(oldX, oldY)) {
tft.fillCircle(oldX, oldY, BULLET_RADIUS, ILI9341_BLACK);
}
// 修改:子彈移動時加入推動偏移量,實現視覺上的推動效果
bullets[i].x += bullets[i].dx + offsetX;
bullets[i].y += bullets[i].dy + offsetY;
// 保存新位置以便下次清除
bullets[i].lastX = bullets[i].x;
bullets[i].lastY = bullets[i].y;
// 檢查子彈是否超出邊界
if (bullets[i].x < BULLET_RADIUS || bullets[i].x > SCREEN_WIDTH - BULLET_RADIUS ||
bullets[i].y < BULLET_RADIUS || bullets[i].y > SCREEN_HEIGHT - BULLET_RADIUS) {
bullets[i].active = false;
continue;
}
// 【Boss戰】檢查與Boss的碰撞 (在當前遊戲模式為Boss戰時)
if (currentGameMode == BOSS_BATTLE) {
int16_t dx = bullets[i].x - boss.x;
int16_t dy = bullets[i].y - boss.y;
uint32_t distSq = (uint32_t)dx * dx + (uint32_t)dy * dy;
uint32_t collisionDistSq = (uint32_t)(BOSS_RADIUS + BULLET_RADIUS) * (BOSS_RADIUS + BULLET_RADIUS);
if (distSq <= collisionDistSq) {
// 清除子彈
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
bullets[i].active = false;
// Boss受傷處理
if (boss.currentHP > 0) {
boss.currentHP--;
hpNeedsUpdate = true;
// Boss受傷視覺效果
tft.fillCircle(boss.x, boss.y, BOSS_RADIUS, ILI9341_RED);
delay(10);
needRedrawBoss = true;
// 更新Boss血條
drawBossHealthBar();
// 增加金錢獎勵
currency += 2;
// 檢查Boss是否被擊敗
if (boss.currentHP <= 0) {
showBossDefeatScreen();
return;
}
}
continue; // 子彈已經消失,跳過後續處理
}
}
// 技能進度條碰撞檢測
int bulletLeft = bullets[i].x - BULLET_RADIUS;
int bulletRight = bullets[i].x + BULLET_RADIUS;
int bulletTop = bullets[i].y - BULLET_RADIUS;
int bulletBottom = bullets[i].y + BULLET_RADIUS;
// 技能1進度條碰撞檢測
if (bulletRight >= bar1X && bulletLeft <= bar1X + barWidth &&
bulletBottom >= barY && bulletTop <= barY + barHeight) {
skill1LastUsed = millis();
skill1OnCooldown = true;
skillCooldownNeedsUpdate = true;
bullets[i].active = false;
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
continue;
}
// 技能2進度條
if (bulletRight >= bar2X && bulletLeft <= bar2X + barWidth &&
bulletBottom >= barY && bulletTop <= barY + barHeight) {
skill2LastUsed = millis();
skill2OnCooldown = true;
skillCooldownNeedsUpdate = true;
bullets[i].active = false;
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
continue;
}
// 技能3進度條
if (bulletRight >= bar3X && bulletLeft <= bar3X + barWidth &&
bulletBottom >= barY && bulletTop <= barY + barHeight) {
skill3LastUsed = millis();
skill3OnCooldown = true;
skillCooldownNeedsUpdate = true;
bullets[i].active = false;
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
continue;
}
// 與普通敵人的碰撞檢測
for (int j = 0; j < MAX_ENEMIES; j++) {
if (!enemies[j].active) continue;
int16_t dx = bullets[i].x - enemies[j].x;
int16_t dy = bullets[i].y - enemies[j].y;
int32_t distSq = dx * dx + dy * dy;
if (distSq <= (BULLET_RADIUS + RED_RADIUS) * (BULLET_RADIUS + RED_RADIUS)) {
enemies[j].currentHP--;
bullets[i].active = false;
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
if (enemies[j].currentHP <= 0) {
if (!isUIArea(enemies[j].x, enemies[j].y)) {
clearEnemy(&enemies[j]);
}
enemies[j].active = false;
currency++;
hpNeedsUpdate = true;
} else {
drawEnemy(&enemies[j]);
}
break;
}
}
// 如果子彈還活著且不在UI區域,繪製它
if (bullets[i].active && !isUIArea(bullets[i].x, bullets[i].y)) {
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_WHITE);
}
}
}
// ===================== 產生敵人 =====================
void spawnEnemies() {
shopshowed = 0;
storymoderestart = false;
// 檢查生成間隔
if (millis() - lastEnemySpawn < enemySpawnInterval) {
return;
}
// 根據遊戲模式設定最大敵人數量
if (currentGameMode == BOSS_BATTLE) {
switch (currentDifficulty) {
case EASY:
currentMaxEnemies = MAX_ENEMIES_EASY / 3;
break;
case NORMAL:
currentMaxEnemies = MAX_ENEMIES_NORMAL / 3;
break;
case HARD:
currentMaxEnemies = MAX_ENEMIES_HARD / 3;
break;
case WTF:
currentMaxEnemies = MAX_ENEMIES / 3;
break;
}
} else {
switch (currentDifficulty) {
case EASY:
currentMaxEnemies = MAX_ENEMIES_EASY;
break;
case NORMAL:
currentMaxEnemies = MAX_ENEMIES_NORMAL;
break;
case HARD:
currentMaxEnemies = MAX_ENEMIES_HARD;
break;
case WTF:
currentMaxEnemies = MAX_ENEMIES;
break;
}
}
// 計算當前活躍敵人數量
uint8_t activeCount = 0;
for (int i = 0; i < MAX_ENEMIES; i++) {
if (enemies[i].active) activeCount++;
}
// 如果敵人數量未達上限,嘗試生成新敵人
if (activeCount < currentMaxEnemies) {
for (int i = 0; i < MAX_ENEMIES; i++) {
if (!enemies[i].active) {
const int UI_SAFE_ZONE = 30;
int edge = random(4);
int16_t newX, newY;
// 從四個邊隨機選擇生成位置
if (edge == 0) {
newX = random(RED_RADIUS, SCREEN_WIDTH - RED_RADIUS);
newY = UI_SAFE_ZONE + RED_RADIUS;
} else if (edge == 1) {
newX = random(RED_RADIUS, SCREEN_WIDTH - RED_RADIUS);
newY = SCREEN_HEIGHT - RED_RADIUS;
} else if (edge == 2) {
newX = RED_RADIUS;
newY = random(UI_SAFE_ZONE + RED_RADIUS, SCREEN_HEIGHT - RED_RADIUS);
} else {
newX = SCREEN_WIDTH - RED_RADIUS;
newY = random(UI_SAFE_ZONE + RED_RADIUS, SCREEN_HEIGHT - RED_RADIUS);
}
// 在Boss戰中檢查與Boss的距離
if (currentGameMode == BOSS_BATTLE) {
float distToBoss = sqrt(pow(newX - boss.x, 2) + pow(newY - boss.y, 2));
if (distToBoss < BOSS_RADIUS * 2) {
float angle = atan2(newY - boss.y, newX - boss.x);
newX = boss.x + cos(angle) * BOSS_RADIUS * 3;
newY = boss.y + sin(angle) * BOSS_RADIUS * 3;
newX = constrain(newX, RED_RADIUS, SCREEN_WIDTH - RED_RADIUS);
newY = constrain(newY, UI_SAFE_ZONE + RED_RADIUS, SCREEN_HEIGHT - RED_RADIUS);
}
}
// 初始化新敵人
enemies[i].x = newX;
enemies[i].y = newY;
enemies[i].lastX = newX;
enemies[i].lastY = newY;
enemies[i].active = true;
enemies[i].dx = 0;
enemies[i].dy = 0;
enemies[i].stunned = false;
enemies[i].visible = true;
// 隨機決定敵人類型
enemies[i].type = random(2) == 0 ? ENEMY_RED : ENEMY_BLUE;
enemies[i].lastShotTime = millis();
// 根據難度和關卡設定血量
int baseHP;
switch (currentDifficulty) {
case EASY:
baseHP = (currentStage == 1) ? 1 : 1 + (currentStage - 1);
break;
case NORMAL:
baseHP = (currentStage == 1) ? 1 : 1 + (currentStage - 1) * 2;
break;
case HARD:
baseHP = (currentStage == 1) ? 3 : 3 + (currentStage - 1) * 3;
break;
case WTF:
baseHP = (currentStage == 1) ? 3 : 3 + (currentStage - 1) * 4;
break;
}
enemies[i].maxHP = baseHP;
enemies[i].currentHP = enemies[i].maxHP;
enemyStunned[i] = false;
// 繪製敵人
drawEnemy(&enemies[i]);
// 更新生成時間
lastEnemySpawn = millis();
break;
}
}
}
}
// ==================== 敵人更新 ==================
void updateEnemies() {
int16_t joystickX = (analogRead(JOYSTICK_X_PIN) - 2048);
int16_t joystickY = (analogRead(JOYSTICK_Y_PIN) - 2048);
float speedMultiplier;
switch (currentDifficulty) {
case EASY: speedMultiplier = 0.7; break;
case NORMAL: speedMultiplier = 1.0; break;
case HARD: speedMultiplier = 1.5; break;
case WTF: speedMultiplier = 2.0; break;
}
int32_t joyMagSq = joystickX * joystickX + joystickY * joystickY;
float offsetX = 0;
float offsetY = 0;
if (joyMagSq > 40000) {
float invMag = Q_rsqrt(joyMagSq);
offsetX = (joystickX * invMag * 4.5);
offsetY = (joystickY * invMag * 4.5);
}
for (int i = 0; i < MAX_ENEMIES; i++) {
if (!enemies[i].active) continue;
int16_t oldX = enemies[i].x;
int16_t oldY = enemies[i].y;
if (!isUIArea(oldX, oldY)) {
clearEnemy(&enemies[i]);
}
int16_t dx = centerX - oldX;
int16_t dy = centerY - oldY;
float dist = sqrt(dx * dx + dy * dy);
// 檢查與玩家的碰撞
if (dist <= (CENTER_RADIUS + RED_RADIUS)) {
if (!isInvincible) {
if (!isUIArea(oldX, oldY)) {
clearEnemy(&enemies[i]);
}
enemies[i].active = false;
if (playerHP > 0) {
playerHP--;
hpNeedsUpdate = true;
centerNeedsRedraw = true;
if (playerHP <= 0) {
showGameOverScreen();
return;
}
}
continue;
} else {
if (!isUIArea(oldX, oldY)) {
clearEnemy(&enemies[i]);
}
enemies[i].active = false;
centerNeedsRedraw = true;
continue;
}
}
// 藍色敵人檢查是否可以發射子彈
if (enemies[i].type == ENEMY_BLUE && !enemies[i].stunned) {
fireEnemyBullet(i);
}
float moveX = 0;
float moveY = 0;
if (!enemies[i].stunned && dist > 0) {
float normalizedDx = dx / dist;
float normalizedDy = dy / dist;
moveX = normalizedDx * speedMultiplier * 2.0;
moveY = normalizedDy * speedMultiplier * 2.0;
}
enemies[i].x = oldX + moveX + offsetX;
enemies[i].y = oldY + moveY + offsetY;
if (isUIArea(enemies[i].x, enemies[i].y)) {
if (enemies[i].visible) {
clearEnemy(&enemies[i]);
enemies[i].visible = false;
}
continue;
} else {
if (!enemies[i].visible) {
enemies[i].visible = true;
enemies[i].stunned ? tft.fillCircle(enemies[i].x, enemies[i].y, RED_RADIUS, ILI9341_GREEN) : drawEnemy(&enemies[i]);
}
}
if (enemies[i].visible) {
if (enemies[i].stunned) {
tft.fillCircle(enemies[i].x, enemies[i].y, RED_RADIUS, ILI9341_GREEN);
} else {
drawEnemy(&enemies[i]);
}
}
}
}
// ============== 敵人顯示 ==============
void drawEnemy(Circle* enemy) {
// 清除舊的位置(如果有移動)
if (enemy->lastX != enemy->x || enemy->lastY != enemy->y) {
clearEnemy(enemy);
}
// 如果在UI區域則不繪製
if (!isUIArea(enemy->x, enemy->y)) {
uint16_t mainColor;
uint16_t secondaryColor;
// 依據敵人類型決定顏色
if (enemy->type == ENEMY_RED) {
mainColor = ILI9341_RED;
secondaryColor = ILI9341_DARKGREY;
} else { // ENEMY_BLUE
mainColor = ILI9341_BLUE;
secondaryColor = ILI9341_CYAN;
}
// 繪製不穩定靈體外觀
uint16_t colors[] = {mainColor, secondaryColor, ILI9341_BLACK}; // 模擬靈體的模糊效果
int radiusOffset[] = {0, 2, 4}; // 不同半徑的模糊層
for (int i = 0; i < 3; i++) {
tft.fillCircle(enemy->x, enemy->y, RED_RADIUS - radiusOffset[i], colors[i]);
}
// 繪製血條背景
const int barWidth = RED_RADIUS * 2;
const int barHeight = 3;
const int barY = enemy->y - RED_RADIUS - 6;
const int barX = enemy->x - RED_RADIUS;
// 繪製血條背景(總血量)
tft.fillRect(barX, barY, barWidth, barHeight, ILI9341_DARKGREY);
// 計算並繪製當前血量
if (enemy->currentHP > 0) {
int currentWidth = (enemy->currentHP * barWidth) / enemy->maxHP;
tft.fillRect(barX, barY, currentWidth, barHeight, mainColor);
}
}
// 更新最後位置
enemy->lastX = enemy->x;
enemy->lastY = enemy->y;
}
// ==================== 敵人發射子彈 ====================
void fireEnemyBullet(int enemyIndex) {
// 確保只有藍色敵人才會發射子彈
if (enemies[enemyIndex].type != ENEMY_BLUE || !enemies[enemyIndex].active) return;
// 檢查發射間隔
unsigned long currentTime = millis();
if (currentTime - enemies[enemyIndex].lastShotTime < ENEMY_BULLET_INTERVAL) return;
enemies[enemyIndex].lastShotTime = currentTime;
// 尋找一個未使用的敵人子彈
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
if (!enemyBullets[i].active) {
// 計算子彈方向(指向玩家中心)
int16_t dx = centerX - enemies[enemyIndex].x;
int16_t dy = centerY - enemies[enemyIndex].y;
float dist = sqrt(dx * dx + dy * dy);
// 避免除以零
if (dist <= 0) continue;
float normalizedX = dx / dist;
float normalizedY = dy / dist;
// 設置子彈起始位置(從敵人處發射)
enemyBullets[i].x = enemies[enemyIndex].x + normalizedX * RED_RADIUS * 1.5;
enemyBullets[i].y = enemies[enemyIndex].y + normalizedY * RED_RADIUS * 1.5;
enemyBullets[i].lastX = enemyBullets[i].x;
enemyBullets[i].lastY = enemyBullets[i].y;
// 設置子彈速度
enemyBullets[i].dx = normalizedX * ENEMY_BULLET_SPEED;
enemyBullets[i].dy = normalizedY * ENEMY_BULLET_SPEED;
enemyBullets[i].active = true;
break;
}
}
}
// ==================== 更新敵人子彈 ====================
void updateEnemyBullets() {
// 讀取左搖桿以獲取推動效果
int16_t joystickX = -(analogRead(JOYSTICK_X_PIN) - 2048);
int16_t joystickY = -(analogRead(JOYSTICK_Y_PIN) - 2048);
// 計算推動偏移量
int32_t joyMagSq = joystickX * joystickX + joystickY * joystickY;
float offsetX = 0;
float offsetY = 0;
if (joyMagSq > 40000) {
float invMag = Q_rsqrt(joyMagSq);
offsetX = -(joystickX * invMag * 4.5);
offsetY = -(joystickY * invMag * 4.5);
}
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
if (!enemyBullets[i].active) continue;
int16_t oldX = enemyBullets[i].x;
int16_t oldY = enemyBullets[i].y;
// 清除舊的子彈位置
if (!isUIArea(oldX, oldY)) {
tft.fillCircle(oldX, oldY, ENEMY_BULLET_RADIUS, ILI9341_BLACK);
}
// 更新子彈位置(添加左搖桿推動效果)
enemyBullets[i].x += enemyBullets[i].dx + offsetX;
enemyBullets[i].y += enemyBullets[i].dy + offsetY;
// 檢查子彈是否超出邊界
if (enemyBullets[i].x < ENEMY_BULLET_RADIUS || enemyBullets[i].x > SCREEN_WIDTH - ENEMY_BULLET_RADIUS ||
enemyBullets[i].y < ENEMY_BULLET_RADIUS || enemyBullets[i].y > SCREEN_HEIGHT - ENEMY_BULLET_RADIUS) {
enemyBullets[i].active = false;
continue;
}
// 檢查與玩家的碰撞
int16_t dx = enemyBullets[i].x - centerX;
int16_t dy = enemyBullets[i].y - centerY;
float distSq = dx * dx + dy * dy;
if (distSq <= (CENTER_RADIUS + ENEMY_BULLET_RADIUS) * (CENTER_RADIUS + ENEMY_BULLET_RADIUS)) {
enemyBullets[i].active = false;
// 玩家被敵人子彈擊中
if (!isInvincible && playerHP > 0) {
playerHP--;
hpNeedsUpdate = true;
centerNeedsRedraw = true;
if (playerHP <= 0) {
showGameOverScreen();
return;
}
}
continue;
}
// 如果子彈還活著且不在UI區域,繪製它
if (enemyBullets[i].active && !isUIArea(enemyBullets[i].x, enemyBullets[i].y)) {
tft.fillCircle(enemyBullets[i].x, enemyBullets[i].y, ENEMY_BULLET_RADIUS, ILI9341_CYAN);
}
// 更新最後位置
enemyBullets[i].lastX = enemyBullets[i].x;
enemyBullets[i].lastY = enemyBullets[i].y;
}
}
//================= 敵人血條 ======================
void drawEnemyHealthBar(Circle* enemy) {
// 只有當最大血量大於1時才顯示血條
if (enemy->maxHP > 1) {
const int barWidth = RED_RADIUS * 2; // 血條寬度
const int barHeight = 3; // 血條高度
const int barY = enemy->y - RED_RADIUS + 6; // 血條Y座標(在敵人上方)
const int barX = enemy->x - RED_RADIUS; // 血條X座標
// 繪製背景(總血量)
tft.fillRect(barX, barY, barWidth, barHeight, ILI9341_DARKGREY);
// 計算當前血量寬度
int currentWidth = (enemy->currentHP * barWidth) / enemy->maxHP;
// 繪製當前血量
tft.fillRect(barX, barY, currentWidth, barHeight, ILI9341_RED);
}
}
//==================== 清除敵人 ==============
void clearEnemy(Circle* enemy) {
int clearRadius = RED_RADIUS + 3;
int initialTop = enemy->y - RED_RADIUS - 10;
int initialHeight = (RED_RADIUS * 2) + 12;
int clearLeft = max(0, enemy->x - clearRadius);
int clearTop = max(0, initialTop);
int clearWidth = min(SCREEN_WIDTH - clearLeft, clearRadius * 2);
int finalHeight = min(SCREEN_HEIGHT - clearTop, initialHeight);
// 檢查清除範圍是否包含主角
if (clearLeft <= centerX + CENTER_RADIUS &&
clearLeft + clearWidth >= centerX - CENTER_RADIUS &&
clearTop <= centerY + CENTER_RADIUS &&
clearTop + finalHeight >= centerY - CENTER_RADIUS) {
centerNeedsRedraw = true;
}
tft.fillRect(
clearLeft,
clearTop,
clearWidth,
finalHeight,
ILI9341_BLACK
);
}
// ===================== 判斷是否為UI區域 =====================
bool isUIArea(int16_t x, int16_t y) {
if (y - RED_RADIUS < 27 && x > SCREEN_WIDTH / 2 - 200 && x < SCREEN_WIDTH / 2 + 200) {
return true;
}
return false;
}
// ===================== 檢查敵人所在區域是否超出邊界 =====================
bool isEnemyOutOfBounds(const Circle* enemy) {
// 敵人可自由進出,無需檢查邊界,僅回傳 false 表示不會超出範圍
return false;
}
// ===================== 一般戰鬥邏輯 =====================
void normalBattleLoop() {
// 更新角色、子彈、敵人
drawPlayerCircle();
updateBullets();
updateEnemies();
spawnEnemies();
// 顯示 UI 更新(血量、技能冷卻等)
if (hpNeedsUpdate) {
void drawHPBar();
hpNeedsUpdate = false;
}
// 檢查玩家是否完成一般戰鬥,進入 Boss 模式
if (stageCompleted) {
currentGameMode = BOSS_BATTLE;
initializeBoss(); // 初始化 Boss
}
}
// ===================== 初始化遊戲 =====================
void initializeGame() {
// 初始化玩家、子彈、敵人等
initializePlayer();
initializeBullets();
initializeEnemies();
initializeUI();
resetStoryMode();
// 確保玩家位置在中心
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
needRedrawPlayer = true;
// 預設進入一般戰鬥模式
currentGameMode = NORMAL_BATTLE;
stageCompleted = false;
}
// ===================== Boss 戰鬥主迴圈修改 - 優化版本 =====================
void bossBattleLoop() {
uint32_t currentTime = millis();
// Update game state
updatePlayerPosition();
updateBullets();
updateBoss(); // Update Boss attacks
// Add enemy handling to boss battles
spawnEnemies(); // Spawn enemies during boss battle (possibly at a reduced rate)
updateEnemies(); // Update existing enemies
updateEnemyBullets(); // Update enemy bullets
updateInvincibilityState(); // Update invincibility state
// Important: Ensure collision detection is executed
checkCollisionWithBoss();
// UI updates
if (hpNeedsUpdate) {
drawBossHealthBar();
drawHPBar(); // Make sure player health bar is also updated
hpNeedsUpdate = false;
}
// Check if Boss is defeated
if (boss.currentHP <= 0) {
showBossDefeatScreen();
gameStarted = false;
return;
}
// Check if player has died
if (playerHP <= 0) {
showGameOverScreen();
return;
}
// Frame rate control
if (currentTime - lastFrameTime < 16) { // ~60FPS
delay(1);
}
lastFrameTime = currentTime;
}
// ===================== 繪製 Boss 血條 - 優化版本 =====================
void drawBossHealthBar() {
// 設定血條位置
const int BAR_Y = 25;
const int BAR_HEIGHT = 10;
const int BAR_MARGIN = 10;
// 計算血條寬度
int totalBarWidth = SCREEN_WIDTH - (BAR_MARGIN * 2);
// 確保血量計算正確
int currentHP = constrain(boss.currentHP, 0, BOSS_MAX_HP);
int currentWidth = map(currentHP, 0, BOSS_MAX_HP, 0, totalBarWidth);
// 繪製血條背景
tft.fillRect(BAR_MARGIN, BAR_Y, totalBarWidth, BAR_HEIGHT, ILI9341_MAROON);
// 繪製當前血量
if (currentWidth > 0) {
tft.fillRect(BAR_MARGIN, BAR_Y, currentWidth, BAR_HEIGHT, ILI9341_RED);
}
// 顯示血量數值
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE);
String hpText = String(currentHP) + "/" + String(BOSS_MAX_HP);
tft.setCursor(SCREEN_WIDTH / 2 - 20, BAR_Y + 2);
tft.print(hpText);
}
// ===================== 初始化 Boss - 優化版本 =====================
void initializeBoss() {
// 檢查是否應該初始化 Boss
if (bossDefeated && currentGameType == STORY_MODE) {
return; // 如果 Boss 已經被擊敗且在故事模式中,不要初始化
}
// 清除畫面
tft.fillScreen(ILI9341_BLACK);
// 設定 Boss 的位置和血量
boss.x = SCREEN_WIDTH / 2;
boss.y = SCREEN_HEIGHT - 200;
boss.maxHP = BOSS_MAX_HP; // 使用定義的常數
boss.currentHP = BOSS_MAX_HP; // 確保當前血量等於最大血量
// 設置更新標記
hpNeedsUpdate = true;
needRedrawBoss = true;
// 顯示 Boss 登場訊息
tft.setTextSize(3);
tft.setTextColor(ILI9341_RED);
tft.setCursor((SCREEN_WIDTH - 150) / 2, SCREEN_HEIGHT / 2 - 30);
tft.print("BOSS BATTLE!");
// 顯示關卡信息
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor((SCREEN_WIDTH - 140) / 2, SCREEN_HEIGHT / 2 + 10);
tft.print("STAGE " + String(currentStage));
delay(1000);
tft.fillScreen(ILI9341_BLACK);
// 繪製 Boss
drawBoss();
// 立即繪製血條確保可見
drawBossHealthBar();
}
// ===================== 繪製 Boss - 優化版本 =====================
void drawBoss() {
// 只在必要時繪製Boss
if (needRedrawBoss && !storymoderestart && currentGameMode == BOSS_BATTLE && !bossDefeated) {
// 清除舊的Boss圖像
tft.fillCircle(boss.x, boss.y, BOSS_RADIUS + 1, ILI9341_BLACK);
// 繪製新的Boss
uint16_t bossColors[] = {ILI9341_PURPLE, ILI9341_MAGENTA, ILI9341_BLUE, ILI9341_DARKGREY, ILI9341_BLACK};
int layerCount = 5;
for (int i = 0; i < layerCount; i++) {
int radius = BOSS_RADIUS - (i * (BOSS_RADIUS / layerCount));
tft.fillCircle(boss.x, boss.y, radius, bossColors[i]);
}
int coreRadius = BOSS_RADIUS / 4;
tft.fillCircle(boss.x, boss.y, coreRadius, ILI9341_YELLOW);
tft.fillCircle(boss.x, boss.y, coreRadius / 2, ILI9341_WHITE);
needRedrawBoss = false;
lastBossRedrawTime = millis();
}
// 只在Boss戰模式時更新血條
if (currentGameMode == BOSS_BATTLE && !bossDefeated) {
drawBossHealthBar();
}
}
// ===================== 更新玩家位置 - 優化版本 =====================
void updatePlayerPosition() {
// 如果在 Boss 戰中,玩家可以自由移動
if (currentGameMode == BOSS_BATTLE) {
// 讀取左搖桿數值並反向
int16_t joystickX = -(analogRead(JOYSTICK_X_PIN) - 2048); // 加上負號反向
int16_t joystickY = -(analogRead(JOYSTICK_Y_PIN) - 2048); // 加上負號反向
// 確認是否有明顯的搖桿輸入
int32_t joyMagSq = joystickX * joystickX + joystickY * joystickY;
if (needRedrawPlayer) {
tft.fillCircle(lastCenterX, lastCenterY, CENTER_RADIUS, ILI9341_BLACK);
}
float invMag = Q_rsqrt(joyMagSq);
float moveX = joystickX * invMag * 3.0; // 移動速度
float moveY = joystickY * invMag * 3.0;
// 更新位置
centerX += moveX;
centerY += moveY;
// 確保不超出畫面邊界
centerX = constrain(centerX, CENTER_RADIUS, SCREEN_WIDTH - CENTER_RADIUS);
centerY = constrain(centerY, CENTER_RADIUS + 50, SCREEN_HEIGHT - CENTER_RADIUS); // 上方留空間給 UI
needRedrawPlayer = true;
// 只在必要時重繪玩家
if (needRedrawPlayer) {
// 先確保舊位置被清除
tft.fillCircle(lastCenterX, lastCenterY, CENTER_RADIUS, ILI9341_BLACK);
// 繪製玩家
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
// 更新上次位置
lastCenterX = centerX;
lastCenterY = centerY;
needRedrawPlayer = false;
lastPlayerRedrawTime = millis();
}
} else {
// 一般戰鬥模式的原本行為
drawPlayerCircle();
}
}
// ===================== 更新 Boss 狀態 - 優化非阻塞版本 =====================
void updateBoss() {
static uint32_t lastBossAttackTime = 0;
static bool isAttacking = false;
static uint8_t attackType = 0;
static uint32_t attackStartTime = 0;
uint32_t currentTime = millis();
// 如果正在攻擊,則更新攻擊動畫
if (isAttacking) {
switch (attackType) {
case 1: // 火焰地面攻擊
updateBossFireZoneAttack(currentTime - attackStartTime);
break;
case 2: // 射線攻擊
updateBossBeamAttack(currentTime - attackStartTime);
break;
case 3: // 彈幕攻擊
updateBossBulletHellAttack(currentTime - attackStartTime);
break;
}
// 檢查攻擊是否完成 (攻擊持續3秒)
if (currentTime - attackStartTime > 3000) {
isAttacking = false;
// 清除所有攻擊效果
clearAttackEffects();
// 標記需要重繪
needRedrawBoss = true;
needRedrawPlayer = true;
}
}
// 如果不在攻擊中,且時間到了,開始新的攻擊
else if (currentTime - lastBossAttackTime > 5000) {
lastBossAttackTime = currentTime;
attackStartTime = currentTime;
isAttacking = true;
// 根據Boss血量選擇攻擊類型
float healthPercentage = (float)boss.currentHP / boss.maxHP;
if (healthPercentage > 0.7) {
attackType = 1; // 火焰地面攻擊
initBossFireZoneAttack();
} else if (healthPercentage > 0.3) {
attackType = 2; // 射線攻擊
initBossBeamAttack();
} else {
attackType = 3; // 彈幕攻擊
initBossBulletHellAttack();
}
}
}
// 初始化火焰地面攻擊
void initBossFireZoneAttack() {
// 清除現有區域
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
if (fireZones[i].active) {
tft.fillCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_BLACK);
fireZones[i].active = false;
}
}
// 創建三個不重疊的隨機區域
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
bool overlapping;
int attempts = 0;
do {
// 隨機生成位置,但避開螢幕邊緣
fireZones[i].x = random(FIRE_ZONE_RADIUS + 10, SCREEN_WIDTH - FIRE_ZONE_RADIUS - 10);
fireZones[i].y = random(FIRE_ZONE_RADIUS + 50, SCREEN_HEIGHT - FIRE_ZONE_RADIUS - 10);
// 檢查是否與其他區域重疊
overlapping = false;
for (int j = 0; j < i; j++) {
if (fireZones[j].active) {
int dx = fireZones[i].x - fireZones[j].x;
int dy = fireZones[i].y - fireZones[j].y;
float dist = sqrt(dx * dx + dy * dy);
// 如果區域重疊,重新生成
if (dist < (FIRE_ZONE_RADIUS * 2) + 20) {
overlapping = true;
break;
}
}
}
attempts++;
// 如果嘗試太多次還找不到合適位置,則縮小區域
if (attempts > 10) {
fireZones[i].x = random(FIRE_ZONE_RADIUS, SCREEN_WIDTH - FIRE_ZONE_RADIUS);
fireZones[i].y = random(FIRE_ZONE_RADIUS + 50, SCREEN_HEIGHT - FIRE_ZONE_RADIUS);
break;
}
} while (overlapping);
// 設置為警告狀態
fireZones[i].active = true;
fireZones[i].isWarning = true;
// 繪製警告區域(黃色圓圈)
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_YELLOW);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 1, ILI9341_YELLOW);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 2, ILI9341_YELLOW);
}
}
// 更新火焰地面攻擊
void updateBossFireZoneAttack(uint32_t elapsedTime) {
// 1秒後將警告區域轉為火焰區域
if (elapsedTime >= 1000 && elapsedTime < 1050) { // 只在時間點執行一次
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
if (fireZones[i].active && fireZones[i].isWarning) {
// 清除警告區域
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_BLACK);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 1, ILI9341_BLACK);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 2, ILI9341_BLACK);
// 繪製火焰區域(紅色填充)
tft.fillCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_RED);
// 更新狀態
fireZones[i].isWarning = false;
}
}
}
// 攻擊持續期間,檢查玩家是否在火焰區域內
if (elapsedTime >= 1000) {
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
if (fireZones[i].active && !fireZones[i].isWarning) {
// 檢查玩家是否在火焰區域內
int dx = centerX - fireZones[i].x;
int dy = centerY - fireZones[i].y;
float distSq = dx * dx + dy * dy;
// 如果玩家進入火焰區域且不處於無敵狀態
if (distSq <= (FIRE_ZONE_RADIUS + CENTER_RADIUS) * (FIRE_ZONE_RADIUS + CENTER_RADIUS) && !isInvincible) {
damagePlayer();
}
}
}
}
// 攻擊結束後清除火焰區域
if (elapsedTime >= 3000 && elapsedTime < 3050) { // 只在時間點執行一次
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
if (fireZones[i].active) {
tft.fillCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_BLACK);
fireZones[i].active = false;
}
}
}
}
// 清除火焰區域
void clearFireZones() {
for (int i = 0; i < MAX_FIRE_ZONES; i++) {
if (fireZones[i].active) {
if (fireZones[i].isWarning) {
// 清除警告圓圈
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_BLACK);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 1, ILI9341_BLACK);
tft.drawCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS - 2, ILI9341_BLACK);
} else {
// 清除火焰區域
tft.fillCircle(fireZones[i].x, fireZones[i].y, FIRE_ZONE_RADIUS, ILI9341_BLACK);
}
fireZones[i].active = false;
}
}
}
// 清除所有攻擊效果 - 高效率版本
void clearAttackEffects() {
// 清除火焰地面攻擊的殘留區域
clearFireZones();
// 清除射線攻擊殘留
if (beamLength > 0) {
int beamWidth = 8;
int beamEndX = boss.x + beamNX * beamLength;
int beamEndY = boss.y + beamNY * beamLength;
// 清除主射線
tft.drawLine(boss.x, boss.y, beamEndX, beamEndY, ILI9341_BLACK);
// 清除射線寬度
for (int i = 1; i <= beamWidth / 2; i++) {
// 繪製平行線進行清除
tft.drawLine(
boss.x + i * beamNY,
boss.y - i * beamNX,
beamEndX + i * beamNY,
beamEndY - i * beamNX,
ILI9341_BLACK
);
tft.drawLine(
boss.x - i * beamNY,
boss.y + i * beamNX,
beamEndX - i * beamNY,
beamEndY + i * beamNX,
ILI9341_BLACK
);
}
// 重置射線長度
beamLength = 0;
}
// 清除彈幕攻擊殘留
for (int i = 0; i < MAX_BOSS_BULLETS; i++) {
if (bossBullets[i].active) {
// 清除彈幕
tft.fillCircle(bossBullets[i].x, bossBullets[i].y, 4, ILI9341_BLACK);
bossBullets[i].active = false;
}
}
// 標記需要重繪Boss和玩家
needRedrawBoss = true;
needRedrawPlayer = true;
}
// ===================== Boss 射線攻擊 - 優化版本 =====================
// 初始化射線攻擊
void initBossBeamAttack() {
// 初始化射線為警告狀態
beamState = BEAM_STATE_WARNING;
beamStateChangeTime = millis();
beamLength = 0;
// 初始瞄準玩家的方向
int dx = centerX - boss.x;
int dy = centerY - boss.y;
float dist = sqrt(dx * dx + dy * dy);
if (dist > 0) {
warningNX = dx / dist;
warningNY = dy / dist;
} else {
warningNX = 0;
warningNY = -1; // 預設向上
}
lastBeamX = boss.x;
lastBeamY = boss.y;
}
// 更新射線攻擊
void updateBossBeamAttack(uint32_t elapsedTime) {
int beamWidth = 8;
float maxDist = SCREEN_HEIGHT * 1.5; // 最大長度
unsigned long currentTime = millis();
// 清除上一個射線或警告
if (beamLength > 0) {
uint16_t color = ILI9341_BLACK;
// 使用直線清除,而不是逐像素清除
tft.drawLine(boss.x, boss.y, lastBeamX, lastBeamY, color);
for (int i = 1; i <= beamWidth / 2; i++) {
// 繪製平行線進行清除
tft.drawLine(
boss.x + i * beamNY,
boss.y - i * beamNX,
lastBeamX + i * beamNY,
lastBeamY - i * beamNX,
color
);
tft.drawLine(
boss.x - i * beamNY,
boss.y + i * beamNX,
lastBeamX - i * beamNY,
lastBeamY + i * beamNX,
color
);
}
}
// 根據射線狀態更新
switch (beamState) {
case BEAM_STATE_WARNING:
// 警告階段 - 紅色警告線追蹤玩家但速度較慢
// 更新追蹤玩家的方向
{
int dx = centerX - boss.x;
int dy = centerY - boss.y;
float dist = sqrt(dx * dx + dy * dy);
if (dist > 0) {
// 逐漸調整方向向目標移動
float targetNX = dx / dist;
float targetNY = dy / dist;
// 平滑過渡
warningNX += (targetNX - warningNX) * warningSpeed;
warningNY += (targetNY - warningNY) * warningSpeed;
// 重新標準化方向向量
float len = sqrt(warningNX * warningNX + warningNY * warningNY);
if (len > 0) {
warningNX /= len;
warningNY /= len;
}
}
// 使用這個方向繪製警告線
beamNX = warningNX;
beamNY = warningNY;
beamLength = maxDist;
// 計算射線終點
int beamEndX = boss.x + beamNX * beamLength;
int beamEndY = boss.y + beamNY * beamLength;
// 繪製警告線 (紅色虛線) - 修復類型轉換問題
for (int i = 0; i < (int)beamLength; i += 10) {
int x1 = boss.x + beamNX * i;
int y1 = boss.y + beamNY * i;
// 使用 static_cast 確保類型匹配
int x2 = boss.x + beamNX * (i + 5 < (int)beamLength ? i + 5 : (int)beamLength);
int y2 = boss.y + beamNY * (i + 5 < (int)beamLength ? i + 5 : (int)beamLength);
tft.drawLine(x1, y1, x2, y2, ILI9341_RED);
}
// 記錄這次的終點,下次用於清除
lastBeamX = beamEndX;
lastBeamY = beamEndY;
// 警告階段持續2秒
if (elapsedTime > 2000) {
beamState = BEAM_STATE_ATTACK;
beamStateChangeTime = currentTime;
// 固定最終攻擊方向
beamNX = warningNX;
beamNY = warningNY;
}
}
break;
case BEAM_STATE_ATTACK:
// 攻擊階段 - 黃色實線射線造成傷害
{
// 計算新長度 (迅速增長到100%,持續1秒)
int progress = map(elapsedTime - 2000, 0, 1000, 0, 100);
if (progress < 100) {
beamLength = (progress * maxDist) / 100;
} else {
beamLength = maxDist;
}
// 確保長度非負
beamLength = max(0.0f, beamLength);
// 計算射線終點
int beamEndX = boss.x + beamNX * beamLength;
int beamEndY = boss.y + beamNY * beamLength;
// 繪製新射線
if (beamLength > 0) {
// 主射線
tft.drawLine(boss.x, boss.y, beamEndX, beamEndY, ILI9341_YELLOW);
// 繪製寬度
for (int i = 1; i <= beamWidth / 2; i++) {
// 繪製平行線產生寬度
tft.drawLine(
boss.x + i * beamNY,
boss.y - i * beamNX,
beamEndX + i * beamNY,
beamEndY - i * beamNX,
ILI9341_YELLOW
);
tft.drawLine(
boss.x - i * beamNY,
boss.y + i * beamNX,
beamEndX - i * beamNY,
beamEndY + i * beamNX,
ILI9341_YELLOW
);
}
// 記錄這次的終點,下次用於清除
lastBeamX = beamEndX;
lastBeamY = beamEndY;
// 檢查玩家是否被射中
if (!isInvincible) {
// 計算射線與玩家之間的最短距離
float t = (beamNX * (centerX - boss.x) + beamNY * (centerY - boss.y)) / (beamNX * beamNX + beamNY * beamNY);
// 限制t在[0,beamLength]範圍內
t = constrain(t, 0, beamLength);
float closestX = boss.x + t * beamNX;
float closestY = boss.y + t * beamNY;
float playerDist = sqrt((centerX - closestX) * (centerX - closestX) + (centerY - closestY) * (centerY - closestY));
if (playerDist <= (CENTER_RADIUS + beamWidth / 2)) {
damagePlayer();
}
}
}
// 攻擊階段持續1秒
if (elapsedTime > 3000) { // 2000(警告階段) + 1000(攻擊階段)
beamState = BEAM_STATE_FADE;
beamStateChangeTime = currentTime;
}
}
break;
case BEAM_STATE_FADE:
// 消失階段 - 射線逐漸變短消失
{
// 計算新長度 (從100%逐漸減少到0%)
int progress = map(elapsedTime - 3000, 0, 500, 0, 100);
beamLength = maxDist * (100 - progress) / 100;
// 確保長度非負
beamLength = max(0.0f, beamLength);
if (beamLength > 0) {
// 計算射線終點
int beamEndX = boss.x + beamNX * beamLength;
int beamEndY = boss.y + beamNY * beamLength;
// 繪製消失中的射線 (淺黃色)
tft.drawLine(boss.x, boss.y, beamEndX, beamEndY, ILI9341_ORANGE);
// 繪製寬度
for (int i = 1; i <= beamWidth / 2; i++) {
// 繪製平行線產生寬度
tft.drawLine(
boss.x + i * beamNY,
boss.y - i * beamNX,
beamEndX + i * beamNY,
beamEndY - i * beamNX,
ILI9341_ORANGE
);
tft.drawLine(
boss.x - i * beamNY,
boss.y + i * beamNX,
beamEndX - i * beamNY,
beamEndY + i * beamNX,
ILI9341_ORANGE
);
}
// 記錄這次的終點,下次用於清除
lastBeamX = beamEndX;
lastBeamY = beamEndY;
}
}
break;
}
}
// ===================== Boss 彈幕攻擊 - 非阻塞版本 =====================
// 初始化彈幕攻擊
void initBossBulletHellAttack() {
// 初始化所有彈幕
for (int i = 0; i < MAX_BOSS_BULLETS; i++) {
float angle = 2 * PI * i / MAX_BOSS_BULLETS;
bossBullets[i].nx = cos(angle);
bossBullets[i].ny = sin(angle);
// 起始位置在Boss邊緣
bossBullets[i].x = boss.x + bossBullets[i].nx * BOSS_RADIUS;
bossBullets[i].y = boss.y + bossBullets[i].ny * BOSS_RADIUS;
bossBullets[i].active = true;
}
}
// 更新彈幕攻擊
void updateBossBulletHellAttack(uint32_t elapsedTime) {
const int bulletSpeed = 5; // 彈幕速度
// 更新所有彈幕位置
for (int i = 0; i < MAX_BOSS_BULLETS; i++) {
if (bossBullets[i].active) {
// 清除舊位置
tft.fillCircle(bossBullets[i].x, bossBullets[i].y, 4, ILI9341_BLACK);
// 計算新位置
bossBullets[i].x += bossBullets[i].nx * bulletSpeed;
bossBullets[i].y += bossBullets[i].ny * bulletSpeed;
// 檢查是否超出屏幕
if (bossBullets[i].x < 0 || bossBullets[i].x >= SCREEN_WIDTH ||
bossBullets[i].y < 0 || bossBullets[i].y >= SCREEN_HEIGHT) {
bossBullets[i].active = false;
continue;
}
// 繪製彈幕
tft.fillCircle(bossBullets[i].x, bossBullets[i].y, 4, ILI9341_MAGENTA);
// 檢查是否擊中玩家
int dx = bossBullets[i].x - centerX;
int dy = bossBullets[i].y - centerY;
int distSq = dx * dx + dy * dy;
if (distSq <= (CENTER_RADIUS + 4) * (CENTER_RADIUS + 4) && !isInvincible) {
damagePlayer();
// 彈幕消失
tft.fillCircle(bossBullets[i].x, bossBullets[i].y, 4, ILI9341_BLACK);
bossBullets[i].active = false;
}
}
}
// 重繪Boss和玩家
drawBoss();
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
}
void damagePlayer() {
// 檢查是否處於無敵狀態
if (isInvincible) return;
// 正常受傷流程
playerHP--;
hpNeedsUpdate = true;
// 顯示傷害效果(紅色閃爍)
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_RED);
delay(50);
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
// 檢查玩家是否死亡
if (playerHP <= 0) {
showGameOverScreen();
return;
}
// 設置受傷後的無敵狀態
isInvincible = true;
invincibilityStartTime = millis();
// 檢查是否觸發大破保護(當前HP為1時)
if (playerHP == 1) {
// 顯示大破保護效果(藍色閃爍)
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_BLUE);
delay(50);
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
// 提示文字
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT / 2 - 30);
tft.print("LAST STAND!");
delay(300);
}
}
void updateInvincibilityState() {
unsigned long currentTime = millis();
// 檢查無敵狀態
if (isInvincible) {
// 檢查是否是技能三造成的無敵
if (skill3Active) {
// 技能三的無敵效果由 processSkills() 處理
return;
}
// 確定無敵時間長度(大破保護或一般受傷後的無敵)
unsigned long invincibilityTime = (playerHP == 1) ?
LAST_STAND_DURATION :
INVINCIBILITY_DURATION;
// 檢查無敵時間是否結束
if (currentTime - invincibilityStartTime > invincibilityTime) {
isInvincible = false;
// 確保玩家在無敵結束時可見
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
playerBlinking = false;
return;
}
// 無敵期間閃爍效果 (每150毫秒切換一次)
uint16_t visibleColor = (playerHP == 1) ? ILI9341_BLUE : ILI9341_CYAN;
if ((currentTime - invincibilityStartTime) / 150 % 2 == 0) {
if (!playerBlinking) {
tft.fillCircle(centerX, centerY, CENTER_RADIUS, visibleColor);
playerBlinking = true;
}
} else {
if (playerBlinking) {
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_BLACK);
playerBlinking = false;
}
}
}
}
void checkCollisionWithBoss() {
uint32_t currentTime = millis();
// 檢查所有子彈是否與 Boss 碰撞
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].active) {
// 計算子彈和Boss之間的距離
int16_t dx = bullets[i].x - boss.x;
int16_t dy = bullets[i].y - boss.y;
uint32_t distSq = (uint32_t)dx * dx + (uint32_t)dy * dy;
uint32_t collisionDistSq = (uint32_t)(BOSS_RADIUS + BULLET_RADIUS) * (BOSS_RADIUS + BULLET_RADIUS);
if (distSq <= collisionDistSq) {
// 清除子彈
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
bullets[i].active = false;
// Boss受傷處理
if (boss.currentHP > 0) {
// 扣血
boss.currentHP--;
// 更新血條
hpNeedsUpdate = true;
drawBossHealthBar();
// Boss受傷視覺效果
tft.fillCircle(boss.x, boss.y, BOSS_RADIUS, ILI9341_RED);
delay(10);
needRedrawBoss = true;
// 如果Boss被打敗
if (boss.currentHP <= 0) {
// 調用勝利處理函數
handleBossVictory();
return;
}
}
}
}
}
// 檢查玩家是否與Boss碰撞
int16_t dx = centerX - boss.x;
int16_t dy = centerY - boss.y;
uint32_t distSq = (uint32_t)dx * dx + (uint32_t)dy * dy;
uint32_t collisionDistSq = (uint32_t)(BOSS_RADIUS + CENTER_RADIUS) * (BOSS_RADIUS + CENTER_RADIUS);
if (distSq <= collisionDistSq) {
// 玩家與Boss碰撞
if (!isInvincible) {
// 玩家碰到Boss且不在無敵狀態,觸發受傷
damagePlayer();
}
}
// 如果需要重繪Boss
if (needRedrawBoss) {
drawBoss();
needRedrawBoss = false;
lastBossRedrawTime = currentTime;
}
}
// ===================== 顯示 Boss 擊敗畫面 =====================
void showBossDefeatScreen() {
// 清除畫面
tft.fillScreen(ILI9341_BLACK);
if (currentGameType == STORY_MODE) {
// 主線模式:顯示通關畫面
// 顯示勝利訊息
tft.setTextSize(3);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor((SCREEN_WIDTH - 170) / 2, SCREEN_HEIGHT / 2 - 50);
tft.print("VICTORY!");
normalBattlesCompleted = 0;
currentGameMode = NORMAL_BATTLE;
// 顯示獎勵
int rewardAmount = 100 * currentStage;
currency += rewardAmount;
tft.setTextSize(2);
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor((SCREEN_WIDTH - 200) / 2, SCREEN_HEIGHT / 2);
tft.print("FINAL SCORE: $" + String(rewardAmount));
// 顯示返回主畫面提示
tft.setTextColor(ILI9341_WHITE);
tft.setCursor((SCREEN_WIDTH - 200) / 2, SCREEN_HEIGHT / 2 + 30);
tft.print("Press any button");
tft.setCursor((SCREEN_WIDTH - 200) / 2, SCREEN_HEIGHT / 2 + 50);
tft.print("to continue...");
// 等待玩家按鍵
delay(1000); // 防止立即按鍵
while (true) {
if (digitalRead(SKILL1_PIN) == LOW ||
digitalRead(SKILL2_PIN) == LOW ||
digitalRead(SKILL3_PIN) == LOW) {
delay(200);
break;
}
}
// 重置遊戲狀態
resetGameState();
resetStoryMode();
// 返回主畫面
showStartScreen();
} else {
// 無盡模式:顯示階段勝利畫面
// 顯示勝利訊息
tft.setTextSize(3);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor((SCREEN_WIDTH - 170) / 2, SCREEN_HEIGHT / 2 - 50);
tft.print("BOSS DEFEATED!");
// 顯示獎勵
int rewardAmount = 50 * currentStage; // 無盡模式獎勵較少
currency += rewardAmount;
tft.setTextSize(2);
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor((SCREEN_WIDTH - 200) / 2, SCREEN_HEIGHT / 2);
tft.print("REWARD: $" + String(rewardAmount));
// 顯示下一關信息
tft.setTextColor(ILI9341_WHITE);
tft.setCursor((SCREEN_WIDTH - 200) / 2, SCREEN_HEIGHT / 2 + 30);
tft.print("NEXT STAGE: " + String(currentStage + 1));
delay(2000); // 顯示2秒
}
}
// 清除所有實體
void clearAllEntities() {
// 清除敵人
for (int i = 0; i < MAX_ENEMIES; i++) {
if (enemies[i].active) {
enemies[i].active = false;
enemies[i].stunned = false;
enemyStunned[i] = false;
enemyStunEndTime[i] = 0;
}
}
// 清除玩家子彈
for (int i = 0; i < MAX_BULLETS; i++) {
bullets[i].active = false;
}
// 清除Boss子彈
for (int i = 0; i < MAX_BOSS_BULLETS; i++) {
bossBullets[i].active = false;
}
// 清除敵人子彈
for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
enemyBullets[i].active = false;
}
// 清除火焰區域
clearFireZones();
// 重置生成時間
lastEnemySpawn = millis();
}
// =============== 初始化一般戰鬥 ==========
void initializeNormalBattle() {
// 清屏
tft.fillScreen(ILI9341_BLACK);
// 重置敵人生成計時器
lastEnemySpawn = millis();
// 設置遊戲模式
currentGameMode = NORMAL_BATTLE;
// 清除所有當前敵人和其他實體
clearAllEntities();
// 重置玩家位置到中心
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
// 強制更新玩家繪製標記
needRedrawPlayer = true;
centerNeedsRedraw = true;
// 更新UI
hpNeedsUpdate = true;
skillCooldownNeedsUpdate = true;
// 只在故事模式下顯示關卡信息
if (currentGameType == STORY_MODE) {
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor((SCREEN_WIDTH - 100) / 2, SCREEN_HEIGHT / 2);
tft.print("STAGE " + String(currentStage));
delay(1000);
tft.fillRect((SCREEN_WIDTH - 100) / 2, SCREEN_HEIGHT / 2, 100, 20, ILI9341_BLACK);
}
// 強制重繪玩家
drawPlayerCircle();
// 確保位置更新
lastCenterX = centerX;
lastCenterY = centerY;
needRedrawPlayer = false;
centerNeedsRedraw = false;
}
// ================= 重置遊戲狀態的函數 ==================
void resetGameState() {
// 重置基本遊戲狀態
playerHP = maxHP;
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
// 清除所有子彈
initializeBullets();
// 清除所有敵人
initializeEnemies();
// 重置技能冷卻
skill1OnCooldown = false;
skill2OnCooldown = false;
skill3OnCooldown = false;
// 重置無敵狀態
isInvincible = false;
// 重置時間相關變量
lastBulletFire = millis();
lastEnemySpawn = millis();
lastHPUpdate = millis();
lastFrameTime = millis();
lastTimerUpdate = millis();
lastCooldownUpdate = millis();
// 不重置進度相關變量,除非明確返回主選單
// currentStage 保持不變
// normalBattlesCompleted 保持不變
// currentGameMode 保持不變
// 重置畫面更新標記
centerNeedsRedraw = true;
hpNeedsUpdate = true;
timerNeedsUpdate = true;
skillCooldownNeedsUpdate = true;
}
// ========== 重製玩家位置 ===========
void resetPlayerPosition() {
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
needRedrawPlayer = true;
}
// ================ 重製變數 ===============
void resetAllFlags() {
hpNeedsUpdate = true;
timerNeedsUpdate = true;
skillCooldownNeedsUpdate = true;
needRedrawBoss = true;
needRedrawPlayer = true;
skill1OnCooldown = false;
skill2OnCooldown = false;
skill3OnCooldown = false;
skill1LastUsed = 0;
skill2LastUsed = 0;
skill3LastUsed = 0;
isInvincible = false;
}
//========= 故事模式重製 ============
void resetStoryMode() {
// 遊戲進度重置
currentStage = 1;
normalBattlesCompleted = 0;
stageCompleted = false;
shopshowed = 0;
// 遊戲模式設置
currentGameMode = NORMAL_BATTLE;
currentGameType = STORY_MODE;
gameStarted = false;
isFirstStart = true;
// 玩家狀態重置
playerHP = maxHP;
currency = 0;
// 清除所有活動實體
clearAllEntities();
// 重置所有標記
resetAllFlags();
}
// ================ 清除所有效果 =================
void clearAllEffects() {
// 清除火焰區域
clearFireZones();
// 清除射線
if (beamLength > 0) {
int beamEndX = boss.x + beamNX * beamLength;
int beamEndY = boss.y + beamNY * beamLength;
tft.drawLine(boss.x, boss.y, beamEndX, beamEndY, ILI9341_BLACK);
beamLength = 0;
}
// 清除彈幕
for (int i = 0; i < MAX_BOSS_BULLETS; i++) {
if (bossBullets[i].active) {
tft.fillCircle(bossBullets[i].x, bossBullets[i].y, 4, ILI9341_BLACK);
bossBullets[i].active = false;
}
}
}
// 清除所有子彈
void clearAllBullets() {
for (int i = 0; i < MAX_BULLETS; i++) {
if (bullets[i].active) {
tft.fillCircle(bullets[i].x, bullets[i].y, BULLET_RADIUS, ILI9341_BLACK);
bullets[i].active = false;
}
}
}
// 更新關卡進度
void updateGameProgress() {
if (currentGameMode == NORMAL_BATTLE) {
normalBattlesCompleted++;
if (normalBattlesCompleted >= BATTLES_BEFORE_BOSS) {
// 切換到Boss戰
currentGameMode = BOSS_BATTLE;
initializeBoss();
} else {
// 繼續一般戰鬥
initializeNormalBattle();
}
}
}
//============ 畫腳色 ============
void drawPlayerCircle() {
if (centerNeedsRedraw || (lastCenterX != centerX || lastCenterY != centerY) || shopshowed ) {
// 清除舊位置,填滿黃色而不是黑色
if (lastCenterX != centerX || lastCenterY != centerY) {
tft.fillCircle(lastCenterX, lastCenterY, CENTER_RADIUS + 2, ILI9341_YELLOW); // 填滿黃色背景
}
int16_t hexRadius = CENTER_RADIUS; // 六角形半徑
int16_t hexX[6], hexY[6];
// 計算六角形的頂點座標
for (int i = 0; i < 6; i++) {
hexX[i] = centerX + hexRadius * cos(PI / 3 * i);
hexY[i] = centerY + hexRadius * sin(PI / 3 * i);
}
// 繪製漸層效果的六角形
int16_t innerRadius = hexRadius; // 從外層開始縮小
uint16_t colors[] = {ILI9341_RED, ILI9341_WHITE, ILI9341_BLUE}; // 紅、白、藍漸層
for (int layer = 0; layer < 3; layer++) {
int16_t layerX[6], layerY[6];
// 計算每層六角形的頂點座標
for (int i = 0; i < 6; i++) {
layerX[i] = centerX + innerRadius * cos(PI / 3 * i);
layerY[i] = centerY + innerRadius * sin(PI / 3 * i);
}
// 填充六角形
tft.fillTriangle(layerX[0], layerY[0], layerX[1], layerY[1], centerX, centerY, colors[layer]);
tft.fillTriangle(layerX[2], layerY[2], layerX[3], layerY[3], centerX, centerY, colors[layer]);
tft.fillTriangle(layerX[4], layerY[4], layerX[5], layerY[5], centerX, centerY, colors[layer]);
// 縮小半徑,形成內層
innerRadius -= 4; // 每層縮小4像素
}
// 更新最後位置
lastCenterX = centerX;
lastCenterY = centerY;
centerNeedsRedraw = false;
}
}
// ===================== 重繪UI元素 =====================
void redrawUIElements() {
// Force redraw HP and timer
hpNeedsUpdate = true;
timerNeedsUpdate = true;
drawHPBar();
drawCountdownTimer();
}
//=================== 技能 =====================
void processSkills(uint32_t currentTime) {
static uint32_t lastSkillCheck = 0;
static uint32_t skill1ActiveUntil = 0;
static uint32_t skill1EffectEnd = 0;
static uint32_t skill2ActiveUntil = 0;
static uint32_t skill3ActiveUntil = 0;
static bool skill3Active = false;
static uint32_t textDisplayUntil = 0;
static bool skill1ButtonPressed = false;
static bool skill2ButtonPressed = false;
static bool skill3ButtonPressed = false;
static uint8_t skill1Duration = 3;
if (currentTime - lastSkillCheck < 50) return;
lastSkillCheck = currentTime;
bool skill1State = (digitalRead(SKILL1_PIN) == LOW);
bool skill2State = (digitalRead(SKILL2_PIN) == LOW);
bool skill3State = (digitalRead(SKILL3_PIN) == LOW);
// 每秒檢查冷卻狀態,觸發進度條更新
if (currentTime - lastCooldownUpdate >= cooldownUpdateInterval) {
lastCooldownUpdate = currentTime;
skillCooldownNeedsUpdate = true;
}
// 更新冷卻狀態
if (skill1OnCooldown && currentTime >= skill1LastUsed + skill1Cooldown) {
skill1OnCooldown = false;
skillCooldownNeedsUpdate = true;
}
if (skill2OnCooldown && currentTime >= skill2LastUsed + skill2Cooldown) {
skill2OnCooldown = false;
skillCooldownNeedsUpdate = true;
}
if (skill3OnCooldown && currentTime >= skill3LastUsed + skill3Cooldown) {
skill3OnCooldown = false;
skillCooldownNeedsUpdate = true;
}
// 處理技能1 - 暈眩
if (skill1State && !skill1ButtonPressed && !skill1OnCooldown) {
skill1ActiveUntil = currentTime + 1000;
skill1EffectEnd = currentTime + (currentStunDuration * 1000);
skill1ButtonPressed = true;
skill1LastUsed = currentTime;
skill1OnCooldown = true;
skillCooldownNeedsUpdate = true;
// 使用升級後的範圍繪製效果
for (int r = 0; r < currentStunRadius; r += 5) {
tft.drawCircle(centerX, centerY, r, ILI9341_GREEN);
}
// 檢查敵人是否在升級後的範圍內
for (int i = 0; i < MAX_ENEMIES; i++) {
if (enemies[i].active) {
int16_t dx = enemies[i].x - centerX;
int16_t dy = enemies[i].y - centerY;
int32_t distSq = dx * dx + dy * dy;
if (distSq <= currentStunRadius * currentStunRadius) {
enemyStunned[i] = true;
enemyStunEndTime[i] = currentTime + (currentStunDuration * 1000);
enemies[i].stunned = true;
tft.fillCircle(enemies[i].x, enemies[i].y, RED_RADIUS, ILI9341_GREEN);
}
}
}
// 清除效果圓圈
for (int r = 0; r < currentStunRadius; r += 5) {
tft.drawCircle(centerX, centerY, r, ILI9341_BLACK);
}
} else if (!skill1State) {
skill1ButtonPressed = false;
}
// 處理技能2-範圍攻擊
if (skill2State && !skill2ButtonPressed && !skill2OnCooldown) {
skill2ActiveUntil = currentTime + 500;
skill2ButtonPressed = true;
skill2LastUsed = currentTime; // 修正:使用 skill2LastUsed
skill2OnCooldown = true;
skillCooldownNeedsUpdate = true;
for (int r = 0; r < 60; r += 5) {
tft.drawCircle(centerX, centerY, r, ILI9341_YELLOW);
}
uint8_t enemiesCleared = 0;
for (int i = 0; i < MAX_ENEMIES; i++) {
if (enemies[i].active) {
int16_t dx = enemies[i].x - centerX;
int16_t dy = enemies[i].y - centerY;
int32_t distSq = dx * dx + dy * dy;
if (distSq <= 3600) {
enemyStunned[i] = false;
clearEnemy(&enemies[i]);
enemies[i].active = false;
enemiesCleared++;
}
}
}
currency += enemiesCleared;
hpNeedsUpdate = true;
for (int r = 0; r < 60; r += 5) {
tft.drawCircle(centerX, centerY, r, ILI9341_BLACK);
}
centerNeedsRedraw = true;
hpNeedsUpdate = true;
timerNeedsUpdate = true;
} else if (!skill2State) {
skill2ButtonPressed = false;
}
// 處理技能3-無敵狀態
if (skill3State && !skill3ButtonPressed && !skill3OnCooldown) {
skill3ActiveUntil = currentTime + 5000;
skill3Active = true;
isInvincible = true;
skill3ButtonPressed = true;
skill3LastUsed = currentTime;
skill3OnCooldown = true;
skillCooldownNeedsUpdate = true;
centerNeedsRedraw = true;
tft.setTextColor(ILI9341_GREEN);
tft.setTextSize(2);
tft.setCursor(SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT - 50);
tft.print("SHIELD UP");
textDisplayUntil = currentTime + 500;
}
if (textDisplayUntil > 0 && currentTime > textDisplayUntil) {
tft.fillRect(SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT - 50, 130, 20, ILI9341_BLACK);
textDisplayUntil = 0;
}
if (!skill3State) {
skill3ButtonPressed = false;
}
// Process active Skill 3
if (currentTime < skill3ActiveUntil) {
if (skill3Active && centerNeedsRedraw) {
tft.fillCircle(centerX, centerY, CENTER_RADIUS + 2, ILI9341_BLACK);
tft.fillCircle(centerX, centerY, CENTER_RADIUS, ILI9341_CYAN);
centerNeedsRedraw = false;
}
if (playerHP != maxHP) {
playerHP = maxHP;
hpNeedsUpdate = true;
}
} else if (skill3Active) {
skill3Active = false;
isInvincible = false;
tft.fillCircle(centerX, centerY, CENTER_RADIUS + 2, ILI9341_BLACK);
centerNeedsRedraw = true;
}
// 更新進度條
if (skillCooldownNeedsUpdate) {
drawSkillCooldowns(currentTime);
}
}
//======================繪製技能冷卻======================
void drawSkillCooldowns(uint32_t currentTime) {
// 進度條參數
const int barWidth = 60;
const int barHeight = 5;
const int barY = SCREEN_HEIGHT - 10;
const int bar1X = 10; // 技能1:左
const int bar2X = 130; // 技能2:中
const int bar3X = 250; // 技能3:右
// 清除整個進度條區域
tft.fillRect(0, barY - 2, SCREEN_WIDTH, barHeight + 4, ILI9341_BLACK);
// 技能1進度條
if (!skill1OnCooldown) {
tft.fillRect(bar1X, barY, barWidth, barHeight, ILI9341_GREEN);
} else {
uint32_t elapsed = currentTime - skill1LastUsed;
float progress = (float)elapsed / skill1Cooldown;
if (progress > 1.0) progress = 1.0;
int fillWidth = barWidth * progress;
tft.fillRect(bar1X, barY, fillWidth, barHeight, ILI9341_BLUE);
tft.drawRect(bar1X, barY, barWidth, barHeight, ILI9341_WHITE);
}
// 技能2進度條
if (!skill2OnCooldown) {
tft.fillRect(bar2X, barY, barWidth, barHeight, ILI9341_GREEN);
} else {
uint32_t elapsed = currentTime - skill2LastUsed;
float progress = (float)elapsed / skill2Cooldown;
if (progress > 1.0) progress = 1.0;
int fillWidth = barWidth * progress;
tft.fillRect(bar2X, barY, fillWidth, barHeight, ILI9341_BLUE);
tft.drawRect(bar2X, barY, barWidth, barHeight, ILI9341_WHITE);
}
// 技能3進度條
if (!skill3OnCooldown) {
tft.fillRect(bar3X, barY, barWidth, barHeight, ILI9341_GREEN);
} else {
uint32_t elapsed = currentTime - skill3LastUsed;
float progress = (float)elapsed / skill3Cooldown;
if (progress > 1.0) progress = 1.0;
int fillWidth = barWidth * progress;
tft.fillRect(bar3X, barY, fillWidth, barHeight, ILI9341_BLUE);
tft.drawRect(bar3X, barY, barWidth, barHeight, ILI9341_WHITE);
}
skillCooldownNeedsUpdate = false;
}
// ===================== Game Over =====================
void showGameOverScreen() {
gameStarted = false;
isFirstStart = true;
const char* options[] = {"Try Again?", "Back to Menu"};
const uint8_t numOptions = sizeof(options) / sizeof(options[0]);
uint8_t selectedIndex = 0;
// 清除畫面
tft.fillScreen(ILI9341_BLACK);
// 顯示 Game Over 文字
tft.setTextColor(ILI9341_RED);
tft.setTextSize(3);
tft.setCursor(80, 60);
tft.print("Game Over");
while (true) {
// 繪製選項
for (uint8_t i = 0; i < numOptions; i++) {
tft.setTextSize(2);
tft.setTextColor((i == selectedIndex) ? ILI9341_YELLOW : ILI9341_WHITE);
tft.setCursor(80, 120 + i * 30);
tft.print(options[i]);
// 為當前選中的選項添加指示器
if (i == selectedIndex) {
tft.fillCircle(60, 128 + i * 30, 5, ILI9341_YELLOW);
} else {
tft.fillCircle(60, 128 + i * 30, 5, ILI9341_DARKGREY);
}
}
// 顯示按鈕指引
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(5, SCREEN_HEIGHT - 20);
tft.print("UP");
tft.setCursor(SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT - 20);
tft.print("DOWN");
tft.setCursor(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 20);
tft.print("SELECT");
// 處理按鈕輸入
if (digitalRead(SKILL1_PIN) == LOW) { // 上移
if (selectedIndex > 0) {
selectedIndex--;
} else {
selectedIndex = numOptions - 1;
}
delay(200);
}
if (digitalRead(SKILL2_PIN) == LOW) { // 下移
selectedIndex = (selectedIndex + 1) % numOptions;
delay(200);
}
if (digitalRead(SKILL3_PIN) == LOW) { // 確認選擇
delay(200);
if (selectedIndex == 0) { // Try Again
// 清除畫面
tft.fillScreen(ILI9341_BLACK);
if (currentGameType == STORY_MODE && bossDefeated) {
// 如果在故事模式中且Boss已被擊敗,重置故事模式
resetStoryMode();
startStoryMode();
} else if (currentGameMode == BOSS_BATTLE) {
// Boss戰重試邏輯
resetGameState();
initializeBoss();
currentGameMode = BOSS_BATTLE;
gameStarted = true;
} else {
// 一般戰鬥重試邏輯
resetGameState();
if (currentDifficulty == GameDifficulty::WTF) {
startEndlessMode();
} else {
startGame();
}
}
return;
}
else {
// 選擇回到主選單
showStartScreen();
return;
}
}
}
}
//========== 處理Boss戰勝利 ===========
void handleBossVictory() {
// 重置玩家位置到中心
centerX = SCREEN_WIDTH / 2;
centerY = SCREEN_HEIGHT / 2;
lastCenterX = centerX;
lastCenterY = centerY;
needRedrawPlayer = true;
// 根據遊戲模式選擇不同的處理方式
if (currentGameType == STORY_MODE) {
resetStoryMode();
startGame();
} else {
normalBattlesCompleted = 0;
currentGameMode = NORMAL_BATTLE;
initializeNormalBattle();
}
}
//=========== 迴圈
void loop() {
if (!gameStarted) {
showStartScreen();
return;
}
// 添加遊戲狀態檢查
checkGameState();
updateConfirmation();
uint32_t currentTime = millis();
switch (currentGameMode) {
case NORMAL_BATTLE:
if (currentTime - lastFrameTime >= frameInterval) {
lastFrameTime = currentTime;
// UI 更新
if (hpNeedsUpdate) {
drawHPBar();
hpNeedsUpdate = false;
}
// 時間更新和關卡檢查
if (currentTime - lastTimerUpdate >= timerUpdateInterval) {
lastTimerUpdate = currentTime;
drawCountdownTimer();
// 檢查關卡是否結束
if (!stageCompleted && currentTime - stageStartTime >= STAGE_DURATION) {
stageCompleted = true;
if (currentGameType == STORY_MODE &&
normalBattlesCompleted >= STAGES_BEFORE_BOSS - 1 &&
!bossDefeated) { // 添加 !bossDefeated 檢查
normalBattlesCompleted++;
currentGameMode = BOSS_BATTLE;
initializeBoss();
return;
} else {
normalBattlesCompleted++;
showStageCompleteScreen();
return;
}
}
}
// 遊戲邏輯更新
updateEnemies();
updateBullets();
updateEnemyBullets();
drawPlayerCircle();
fireBullet();
spawnEnemies();
processSkills(currentTime);
}
break;
case BOSS_BATTLE:
if (currentTime - lastFrameTime >= frameInterval) {
lastFrameTime = currentTime;
if (hpNeedsUpdate) {
drawHPBar();
drawBossHealthBar();
hpNeedsUpdate = false;
}
updateInvincibilityState(); // 使用這個函數來處理所有的無敵狀態
updatePlayerPosition();
drawPlayerCircle();
fireBullet();
updateBullets();
updateBoss();
processSkills(currentTime);
checkCollisionWithBoss();
updateEnemies();
updateBullets();
updateEnemyBullets();
if (boss.currentHP <= 0) {
handleBossVictory();
return;
}
}
break;
}
}
// ============= 遊戲狀態檢查 ==========
void checkGameState() {
// 確保遊戲狀態正確
if (currentGameType == STORY_MODE) {
if (storymoderestart) {
resetStoryMode();
storymoderestart = false;
}
}
}