#include <Adafruit_GFX.h> // 核心圖形庫
#include <Adafruit_ILI9341.h> // ILI9341硬體專用庫
#include <SPI.h>
#include "mlimage.h"
#include "background.h"
#define TFT_CS 15 // GPIO15 (D8)
#define TFT_RST 4 // GPIO4 (D4)
#define TFT_DC 2 // GPIO2 (D2)
#define JOY_VRX 34 // X軸類比輸入
#define JOY_BTN 35 // 開始按鈕數位輸入
#define JUMP_BTN 32 // 跳躍按鈕數位輸入
#define ATTACK_BTN 33 // 攻擊按鈕數位輸入
#define RESTART_BTN 26 // 重啟按鈕數位輸入
#define TFT_MOSI 23 // 數據針腳
#define TFT_CLK 18 // 時鐘針腳
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// 玩家變數
int joystickX = analogRead(JOY_VRX);
int a = 0;
int c = 0;
int playerX = 120; // 螢幕中央水平位置
int playerY = 0;
int playerWidth = 10;
int playerHeight = 20;
int groundY;
int lastPlayerX; // 清除軌跡用的上一個X位置
int lastPlayerY; // 清除軌跡用的上一個Y位置
bool isJumping = false;
int jumpVelocity = -12; // 跳躍初速度
float gravity = 0.8; // 重力效應
float currentVelocity = 0;
bool jumpButtonPressed = false;
bool lastJumpButtonState = LOW; // 跳躍按鈕未按下
bool lastAttackButtonState = LOW; // 攻擊按鈕未按下
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // 消抖延遲毫秒數
int playerHealth = 100; // 玩家初始血量
bool attackButtonPressed = false; // 攻擊按鈕按下狀態
// 子彈變數
const int maxBullets = 5;
struct Bullet {
int x, y;
int lastX, lastY;
bool isActive;
int direction; // 1表示右,-1表示左
};
Bullet bullets[maxBullets];
const int bulletSpeed = 5;
const int bulletWidth = 5;
const int bulletHeight = 2;
// 方向變數
int playerDirection = 1; // 1表示右,-1表示左
unsigned long lastDirectionChangeTime = 0; // 上一次方向變化的時間
unsigned long sameDirectionDuration = 0; // 持續同一方向的時間
const unsigned long directionThreshold = 2000; // 持續同一方向的閾值(毫秒)
bool enemySlowed = false; // 用於標記敵人是否變慢
// 敵人變數
int enemyX = 0;
int enemyY = 0;
int enemyWidth = 10;
int enemyHeight = 20;
float enemySpeed = 0.05; // 放慢敵人速度
bool enemyIsJumping = false;
float enemyJumpVelocity = -12;
float enemyCurrentVelocity = 0;
int enemyHealth = 3; // 敵人血量
const float easingFactor = 0.05; // 平滑運動係數
const int imageWidth = 640; // 圖像寬度(螢幕寬度的兩倍)
const int imageHeight = 240; // 圖像高度
// 滾動變數
int scrollX = 0; // 初始滾動位置
int scrollSpeed = 1; // 減少滾動速度
// 背景變數
int bgX = 0; // 背景滾動位置
const int gridSize = 20; // 網格方塊大小
uint16_t buffer[256];
int BMP_WIDTH = 16;
int BMP_HEIGHT = 16;
int TILES_HEIGHT = 1;
int TILES_WIDTH = 1;
int backgroundX = 200;
int backgroundY = 80;
// 平台變數
const int numPlatforms = 6;
int platformX[numPlatforms] = { 150, 250, 300,390,485,585}; // 平台X位置
int platformY[numPlatforms] = { 120, 180, 120,100,80,180}; // 平台Y位置
int platformWidth[numPlatforms] = {30,50,50,40,60,35};
int platformHeight = 10;
int lastPlatformX[numPlatforms]; // 存儲平台上次的位置
// 關卡選擇變數
int selectedLevel = 0;
const int numLevels = 3;
String levelNames[numLevels] = {"ZEUS", "POSEIDON", "HADES"};
// 敵人扣血變數
unsigned long lastDamageTime = 0;
const unsigned long damageInterval = 1000; // 每秒扣血一次
void setup() {
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK); // 清除屏幕
// 測試顯示一些基本圖形
tft.setTextColor(ILI9341_WHITE);
// 初始化搖桿引腳
pinMode(JOY_VRX, INPUT);
pinMode(JOY_BTN, INPUT_PULLUP);
pinMode(JUMP_BTN, INPUT_PULLUP);
pinMode(ATTACK_BTN, INPUT_PULLUP);
pinMode(RESTART_BTN, INPUT_PULLUP);
// 初始化玩家和地面位置
groundY = tft.height() - 30;
playerY = groundY - playerHeight;
lastPlayerX = playerX;
lastPlayerY = playerY;
enemyX = tft.width() - enemyWidth; // 初始敵人位置
enemyY = groundY - enemyHeight; // 與地面同高
// 初始化子彈
for (int i = 0; i < maxBullets; i++) {
bullets[i].isActive = false;
}
// 初始化平台位置
for (int i = 0; i < numPlatforms; i++) {
lastPlatformX[i] = platformX[i];
}
// 顯示開機畫面
//drawOpeningScreen();
//waitForStartButton();
drawLevelSelection();
waitForLevelSelection();
tft.fillScreen(ILI9341_BLACK);
// 繪製地形和平台
drawTerrain();
processInput();
updatePhysics();
redrawGraphics();
}
void loop() {
while (true) {
if (a == 1) {
drawLevelSelection();
waitForLevelSelection();
tft.fillScreen(ILI9341_BLACK);
// 繪製地形和平台
drawTerrain();
processInput();
updatePhysics();
redrawGraphics();
a = 0;
} else {
processInput();
updatePhysics();
updateBullets();
redrawGraphics();
delay(10); // 調整此延遲以響應遊戲
if (playerHealth <= 0) {
drawGameOverScreen();
waitForRestartButton();
restartGame();
a = 1;
}
}
}
}
void drawOpeningScreen() {
int x = -bgX;
int drawX = (x % tft.width());
tft.fillScreen(ILI9341_BLACK);
tft.setCursor(10, 10);
tft.drawRGBBitmap(0, 0, background, 320, 240);
}
void waitForStartButton() {
while (true) {
if (digitalRead(JOY_BTN) == LOW) {
delay(50); // Debounce delay
if (digitalRead(JOY_BTN) == HIGH) {
break;
}
}
}
}
void waitForRestartButton() {
while (true) {
if (digitalRead(RESTART_BTN) == LOW) {
delay(50); // Debounce delay
if (digitalRead(RESTART_BTN) == HIGH) {
break;
}
}
}
}
void drawLevelSelection() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.setTextColor(ILI9341_WHITE);
tft.println("SELECT LEVEL:");
for (int i = 0; i < numLevels; i++) {
if (i == selectedLevel) {
tft.setTextColor(ILI9341_YELLOW);
} else {
tft.setTextColor(ILI9341_WHITE);
}
tft.setCursor(10, 40 + i * 30);
tft.println(levelNames[i]);
}
}
void waitForLevelSelection() {
while (true) {
int joystickX = analogRead(JOY_VRX);
if (joystickX < 1500) {
if (selectedLevel > 0) {
selectedLevel--;
drawLevelSelection();
delay(200); // 防止過快重複輸入
}
} else if (joystickX > 2500) {
if (selectedLevel < numLevels - 1) {
selectedLevel++;
drawLevelSelection();
delay(200); // 防止過快重複輸入
}
}
if (digitalRead(JOY_BTN) == LOW) {
delay(50); // Debounce delay
if (digitalRead(JOY_BTN) == HIGH) {
break;
}
}
}
}
void processInput() {
bool moved = false;
int joystickX = analogRead(JOY_VRX);
float reducedEnemySpeed = 0.005; // 當玩家在邊緣時降低敵人速度
// 搖桿控制角色移動和背景滾動
if (joystickX < 1500) {
if (playerX < tft.width() - 110) { // 恢復原範圍
playerX += 2; // 向右移動玩家
playerDirection = 1; // 面向右
} else {
bgX += scrollSpeed; // 向右移動背景
enemySpeed = reducedEnemySpeed; // 降低敵人速度
}
moved = true;
} else if (joystickX > 2500) {
if (playerX > 110) { // 恢復原範圍
playerX -= 2; // 向左移動玩家
playerDirection = -1; // 面向左
} else {
bgX -= scrollSpeed; // 向左移動背景
enemySpeed = reducedEnemySpeed; // 降低敵人速度
}
moved = true;
} else {
enemySpeed = 0.05; // 恢復正常敵人速度
}
int jumpButtonState = digitalRead(JUMP_BTN);
if (jumpButtonState != lastJumpButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (jumpButtonState == LOW && !isJumping && !jumpButtonPressed) {
isJumping = true;
currentVelocity = jumpVelocity;
jumpButtonPressed = true;
moved = true;
} else if (jumpButtonState == HIGH) {
jumpButtonPressed = false;
}
}
lastJumpButtonState = jumpButtonState;
int attackButtonState = digitalRead(ATTACK_BTN);
if (attackButtonState != lastAttackButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (attackButtonState == LOW && !attackButtonPressed) {
shootBullet();
attackButtonPressed = true;
moved = true;
} else if (attackButtonState == HIGH) {
attackButtonPressed = false;
}
}
lastAttackButtonState = attackButtonState;
// 檢查是否持續朝一個方向移動
if (moved) {
unsigned long currentTime = millis();
if ((playerDirection == 1 && joystickX < 1500) || (playerDirection == -1 && joystickX > 2500)) {
sameDirectionDuration = currentTime - lastDirectionChangeTime;
if (sameDirectionDuration > directionThreshold) {
enemySpeed = reducedEnemySpeed;
enemySlowed = true; // 设置敌人变慢标志
} else {
enemySpeed = 0.05;
enemySlowed = false; // 重置敌人变慢标志
}
} else {
sameDirectionDuration = 0;
lastDirectionChangeTime = currentTime;
enemySlowed = false; // 重置敌人变慢标志
}
}
}
bool debounceButton(int pin, bool& lastButtonState, void (*callback)()) {
bool currentButtonState = digitalRead(pin);
if (currentButtonState != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (currentButtonState == LOW && lastButtonState == HIGH) {
callback();
}
}
lastButtonState = currentButtonState;
return currentButtonState == LOW;
}
void shootBullet() {
for (int i = 0; i < maxBullets; i++) {
if (!bullets[i].isActive) {
bullets[i].x = playerX + (playerDirection == 1 ? playerWidth : -bulletWidth);
bullets[i].y = playerY + playerHeight / 2 - bulletHeight / 2;
bullets[i].lastX = bullets[i].x;
bullets[i].lastY = bullets[i].y;
bullets[i].isActive = true;
bullets[i].direction = playerDirection;
break;
}
}
}
void updatePhysics() {
if (isJumping) {
lastPlayerY = playerY; // 更新前儲存之前的Y
playerY += currentVelocity;
currentVelocity += gravity;
// 檢查是否與平台碰撞
for (int i = 0; i < numPlatforms; i++) {
int platformLeft = platformX[i] - bgX;
int platformRight = platformLeft + platformWidth[i];
int platformTop = platformY[i];
int platformBottom = platformTop + platformHeight;
// 檢查玩家是否落在平台上
if (playerX + playerWidth > platformLeft && playerX < platformRight &&
lastPlayerY + playerHeight <= platformTop && playerY + playerHeight >= platformTop) {
playerY = platformTop - playerHeight;
isJumping = false;
currentVelocity = 0;
}
}
if (playerY >= groundY - playerHeight) {
playerY = groundY - playerHeight;
isJumping = false;
}
} else {
// 確保在角色不跳躍時仍然檢測平台碰撞
bool onPlatform = false;
for (int i = 0; i < numPlatforms; i++) {
int platformLeft = platformX[i] - bgX;
int platformRight = platformLeft + platformWidth[i];
int platformTop = platformY[i];
int platformBottom = platformTop + platformHeight;
if (playerX + playerWidth > platformLeft && playerX < platformRight &&
playerY + playerHeight >= platformTop && playerY + playerHeight <= platformBottom) {
playerY = platformTop - playerHeight;
onPlatform = true;
break;
}
}
if (!onPlatform && playerY < groundY - playerHeight) {
isJumping = true;
currentVelocity = 0;
} else if (!onPlatform && playerY >= groundY - playerHeight) {
playerY = groundY - playerHeight;
}
// 更新敵人追擊玩家
if (abs(playerX - enemyX) < tft.width() / 2) {
enemySpeed = 0.1;
} else {
enemySpeed = 0.05;
}
// 檢查敵人是否撞擊玩家
unsigned long currentTime = millis();
if (playerX + playerWidth > enemyX && playerX < enemyX + enemyWidth &&
playerY + playerHeight > enemyY && playerY < enemyY + enemyHeight) {
if (currentTime - lastDamageTime > damageInterval) {
playerHealth -= random(0, 16); // 隨機減少0到15之間
lastDamageTime = currentTime;
if (playerHealth <= 0) {
playerHealth = 0;
drawGameOverScreen();
waitForRestartButton();
return;
}
}
}
}
// 更新敵人位置以跟隨玩家,使用平滑運動效果
enemyX += (playerX - enemyX) * enemySpeed;
// 更新敵人Y位置,使用平滑運動效果
if (enemyIsJumping) {
enemyY += enemyCurrentVelocity;
enemyCurrentVelocity += gravity;
if (enemyY >= groundY - enemyHeight) {
enemyY = groundY - enemyHeight;
enemyIsJumping = false;
}
} else {
bool enemyOnPlatform = false;
for (int i = 0; i < numPlatforms; i++) {
int platformLeft = platformX[i] - bgX;
int platformRight = platformLeft + platformWidth[i];
int platformTop = platformY[i];
int platformBottom = platformTop + platformHeight;
if (enemyX + enemyWidth > platformLeft && enemyX < platformRight &&
enemyY + enemyHeight >= platformTop && enemyY + enemyHeight <= platformBottom) {
enemyY = platformTop - enemyHeight;
enemyOnPlatform = true;
break;
}
}
if (!enemyOnPlatform && enemyY < groundY - enemyHeight) {
enemyIsJumping = true;
enemyCurrentVelocity = 0;
} else if (!enemyOnPlatform && enemyY >= groundY - enemyHeight) {
enemyY = groundY - enemyHeight;
}
}
// 如果玩家跳躍則觸發敵人跳躍
if (isJumping && !enemyIsJumping) {
enemyIsJumping = true;
enemyCurrentVelocity = jumpVelocity;
}
}
bool checkBulletCollision(int bulletX, int bulletY) {
// 檢查子彈是否與平台或地面碰撞
if (bulletY >= groundY || bulletX <= 0 || bulletX >= tft.width()) {
return true;
}
for (int i = 0; i < numPlatforms; i++) {
int platformLeft = platformX[i] - bgX;
int platformRight = platformLeft + platformWidth[i];
int platformTop = platformY[i];
int platformBottom = platformTop + platformHeight;
if (bulletX + bulletWidth > platformLeft && bulletX < platformRight &&
bulletY + bulletHeight > platformTop && bulletY < platformBottom) {
return true;
}
}
return false;
}
void updateBullets() {
for (int i = 0; i < maxBullets; i++) {
if (bullets[i].isActive) {
// 清除之前的子彈位置
tft.fillRect(bullets[i].lastX, bullets[i].lastY, bulletWidth, bulletHeight, ILI9341_BLACK);
// 更新子彈位置
bullets[i].lastX = bullets[i].x;
bullets[i].lastY = bullets[i].y;
bullets[i].x += bulletSpeed * bullets[i].direction;
// 檢查子彈是否碰撞牆壁、平台或超出屏幕
if (checkBulletCollision(bullets[i].x, bullets[i].y)) {
bullets[i].isActive = false;
} else {
// 檢查是否與敵人碰撞
if (bullets[i].x + bulletWidth > enemyX && bullets[i].x < enemyX + enemyWidth &&
bullets[i].y + bulletHeight > enemyY && bullets[i].y < enemyY + enemyHeight) {
bullets[i].isActive = false;
if (enemyHealth > 0) {
enemyHealth--;
}
if (enemyHealth == 0) {
enemyX = tft.width() + enemyWidth; // 將敵人移到屏幕右邊
enemyHealth = 3; // 重置敵人血量
}
}
}
}
}
}
void redrawGraphics() {
drawImage();
drawPlatforms(); // 確保平台隨角色和背景重繪
drawCharacter();
drawEnemy();
drawBullets();
drawHealthBar();
}
void drawCharacter() {
// 僅清除角色的上一個區域以避免畫在角色上
tft.fillRect(lastPlayerX, lastPlayerY, playerWidth, playerHeight, ILI9341_BLACK);
tft.fillRect(playerX, playerY, playerWidth, playerHeight, ILI9341_WHITE);
lastPlayerX = playerX;
lastPlayerY = playerY;
}
void drawTerrain() {
// 畫一條簡單的水平線作為地形
tft.drawFastHLine(0, groundY, tft.width(), ILI9341_WHITE); // 畫白色地形線
}
void drawImage() {
int x = -bgX;
int drawX = (x % tft.width());
tft.drawRGBBitmap(144 + x, 96, mlimage, 16, 16);
}
void drawPlatforms() {
int platformWidth = 60; // 統一平台寬度
int platformHeight = 10; // 統一平台高度
// 預定的固定高度
for (int i = 0; i < numPlatforms; i++) {
// 清除之前的平台位置
tft.fillRect(lastPlatformX[i], platformY[i], platformWidth, platformHeight, ILI9341_BLACK);
// 根據背景滾動計算當前平台位置
lastPlatformX[i] = platformX[i] - bgX;
tft.fillRect(lastPlatformX[i], platformY[i], platformWidth, platformHeight, ILI9341_YELLOW);
}
}
void drawEnemy() {
// 僅清除敵人的上一個區域以避免畫在敵人上
static int lastEnemyX = enemyX;
static int lastEnemyY = enemyY;
tft.fillRect(lastEnemyX, lastEnemyY, enemyWidth, enemyHeight, ILI9341_BLACK);
if (enemyHealth > 0) {
tft.fillRect(enemyX, enemyY, enemyWidth, enemyHeight, ILI9341_RED);
}
lastEnemyX = enemyX;
lastEnemyY = enemyY;
}
void drawBullets() {
for (int i = 0; i < maxBullets; i++) {
if (bullets[i].isActive) {
tft.fillRect(bullets[i].x, bullets[i].y, bulletWidth, bulletHeight, ILI9341_GREEN);
}
}
}
void drawHealthBar() {
int healthBarWidth = map(playerHealth, 0, 100, 0, tft.width() - 20);
tft.fillRect(10, 10, tft.width() - 20, 10, ILI9341_BLACK); // 清除之前的血條
tft.fillRect(10, 10, healthBarWidth, 10, ILI9341_GREEN); // 畫新的血條
tft.fillRect(10, 25, 120, 20, ILI9341_BLACK); // 清除之前的血量數字
tft.setCursor(10, 25);
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(2);
tft.print("Blood: ");
tft.print(playerHealth);
tft.print("/100");
}
void drawGameOverScreen() {
tft.fillScreen(ILI9341_BLACK);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(4);
tft.setCursor(30, tft.height() / 2 - 15);
tft.println("GAME OVER");
tft.setTextSize(1.7);
tft.setCursor(30, tft.height() / 2 + 20); // 調整游標位置
tft.println("Push the yellow button to restart");
}
void restartGame() {
a = 1;
tft.fillScreen(ILI9341_BLACK);
}