#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <SPI.h>
#define TFT_CS 10
#define TFT_DC 8
#define TFT_RST 9
#define ILI9341_BLACK 0x0000 /* 0, 0, 0 */
#define ILI9341_NAVY 0x000F /* 0, 0, 128 */
#define ILI9341_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define ILI9341_DARKCYAN 0x03EF /* 0, 128, 128 */
#define ILI9341_MAROON 0x7800 /* 128, 0, 0 */
#define ILI9341_PURPLE 0x780F /* 128, 0, 128 */
#define ILI9341_OLIVE 0x7BE0 /* 128, 128, 0 */
#define ILI9341_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define ILI9341_DARKGREY 0x7BEF /* 128, 128, 128 */
#define ILI9341_BLUE 0x001F /* 0, 0, 255 */
#define ILI9341_GREEN 0x07E0 /* 0, 255, 0 */
#define ILI9341_CYAN 0x07FF /* 0, 255, 255 */
#define ILI9341_RED 0xF800 /* 255, 0, 0 */
#define ILI9341_MAGENTA 0xF81F /* 255, 0, 255 */
#define ILI9341_YELLOW 0xFFE0 /* 255, 255, 0 */
#define ILI9341_WHITE 0xFFFF /* 255, 255, 255 */
#define ILI9341_ORANGE 0xFD20 /* 255, 165, 0 */
#define ILI9341_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define ILI9341_PINK 0xF81F /* 255, 0, 255 */
#define ILI9341_GRAY 0x7BEF // 中等灰色 (128, 128, 128)
// 控制按鈕和搖桿引腳
#define JOY_X 6
#define JOY_Y 7
#define JOY_SW 40
#define BUTTON1 1
#define BUTTON2 2
#define BUTTON3 4
#define BUTTON4 5
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
unsigned long pretime = 0;
const unsigned long interval = 100;
// 遊戲參數
const int playerWidth = 30;
const int playerHeight = 40;
int playerX = 120;
int playerY = 0;
const int groundHeight = 30;
const int screenWidth = 320;
const int screenHeight = 240;
const int groundY = screenHeight - groundHeight;
// 敵人參數
const int enemyWidth = 32;
const int enemyHeight = 32;
const int enemySpeed = 3;
struct Enemy {
int x;
int y;
bool active;
bool dying; // 標記敵人是否正在死亡狀態
int health;
bool hasSword;
unsigned long swordTime;
unsigned long deathTime; // 敵人死亡時間
};
Enemy enemy;
float velocityY = 0;
const float gravity = 1.2;
const float jumpForce = -15;
bool isJumping = false;
const int moveSpeed = 8;
// 攻擊系統參數
bool isAttacking = false;
bool attackHit = false;
unsigned long lastAttackTime = 0;
unsigned long attackStartTime = 0;
const unsigned long attackCooldown = 500;
const unsigned long attackDuration = 200;
const unsigned long attackVisualDuration = 50;
const unsigned long swordStickDuration = 500;
const unsigned long enemyDeathDuration = 500; // 敵人死亡後停留時間(毫秒)
const int attackRange = 40;
const int attackDamage = 1;
// 顏色定義
#define GROUND_COLOR ILI9341_DARKGREEN
#define PLAYER_COLOR ILI9341_MAGENTA
#define ENEMY_COLOR ILI9341_CYAN
#define DEAD_ENEMY_COLOR ILI9341_GRAY // 使用新定義的灰色
#define SKY_COLOR ILI9341_BLACK
#define TEXT_COLOR ILI9341_WHITE
#define ATTACK_COLOR ILI9341_YELLOW
#define SWORD_COLOR ILI9341_WHITE
#define DEFENSE_BUTTON BUTTON4 // 使用BUTTON4作為防禦按鈕
bool isDefending = false;
unsigned long defenseStartTime = 0;
const unsigned long defenseDuration = 1000; // 防禦持續時間1秒
const unsigned long defenseCooldown = 3000; // 冷卻時間3秒
bool defenseReady = true;
unsigned long lastDefenseTime = 0;
bool gameStarted = false;
bool canShowPlayer = false;
int prevPlayerX = playerX;
int prevPlayerY = playerY;
bool needRedraw = false;
unsigned long lastEnemySpawnTime = 0;
const unsigned long enemySpawnInterval = 3000;
bool swordEffectActive = false;
unsigned long swordEffectEndTime = 0;
int swordEffectDirection = 1; // 1為右,-1為左
int swordEffectX = 0;
int swordEffectY = 0;
bool isGameOver = false;
unsigned long gameOverStartTime = 0;
const unsigned long gameOverDelay=2000;
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(3);
pinMode(JOY_SW, INPUT_PULLUP);
pinMode(BUTTON1, INPUT_PULLUP);
pinMode(BUTTON2, INPUT_PULLUP);
pinMode(BUTTON3, INPUT_PULLUP);
pinMode(BUTTON4, INPUT_PULLUP);
pinMode(DEFENSE_BUTTON, INPUT_PULLUP);
showStartScreen();
enemy.active = false;
enemy.dying = false;
enemy.hasSword = false;
}
void loop() {
if (isGameOver) {
if(millis() - gameOverStartTime >= gameOverDelay) {
finishGameOver();
}
return; // 直接返回,不執行任何遊戲更新或繪製
}
if (!gameStarted) {
unsigned long currenttime = millis();
if (checkStartInput()) {
if(currenttime - pretime >= interval){
pretime = currenttime;
gameStarted = true;
initGame();
canShowPlayer = true;
drawPlayer();
}
}
return;
}
static uint32_t lastUpdate = 0;
uint32_t now = millis();
if (now - lastUpdate >= 20) {
lastUpdate = now;
prevPlayerX = playerX;
prevPlayerY = playerY;
handleInput();
updatePhysics();
updateEnemy();
checkCollision();
updateSwordEffect();
updateDeathEffect();
if (needRedraw) {
noInterrupts();
erasePlayer();
if (enemy.active || enemy.dying) eraseEnemy();
drawPlayer();
if (enemy.active || enemy.dying) drawEnemy();
needRedraw = false;
interrupts();
}
}
}
void updateDeathEffect() {
if (enemy.dying && millis() - enemy.deathTime > enemyDeathDuration) {
// Completely remove the enemy
eraseEnemy();
enemy.dying = false;
enemy.active = false;
needRedraw = true;
}
}
bool checkStartInput() {
return (digitalRead(JOY_SW) == LOW ||
analogRead(JOY_X) < 1000 ||
analogRead(JOY_X) > 3000 ||
analogRead(JOY_Y) < 1000 ||
analogRead(JOY_Y) > 3000 ||
digitalRead(BUTTON1) == LOW ||
digitalRead(BUTTON2) == LOW ||
digitalRead(BUTTON3) == LOW ||
digitalRead(BUTTON4) == LOW);
}
void showStartScreen() {
tft.fillScreen(SKY_COLOR);
tft.setTextColor(TEXT_COLOR);
tft.setTextSize(3);
tft.setCursor(90, 80);
tft.println("JUMP GAME");
tft.setTextSize(2);
tft.setCursor(40, 150);
tft.println("Press any button");
tft.setCursor(80, 180);
tft.println("to start");
tft.fillRoundRect(140, 210, playerWidth, playerHeight, 5, PLAYER_COLOR);
}
void initGame() {
playerX = 120;
playerY = groundY - playerHeight;
velocityY = 0;
isJumping = false;
isAttacking = false;
enemy.active = false;
enemy.dying = false;
enemy.health = 1;
enemy.hasSword = false;
canShowPlayer = false;
drawScene();
}
void drawScene() {
tft.fillScreen(SKY_COLOR);
tft.fillRect(0, groundY, screenWidth, groundHeight, GROUND_COLOR);
tft.drawFastHLine(0, groundY, screenWidth, ILI9341_WHITE);
}
void handleInput() {
int xValue = analogRead(JOY_X);
int yValue = analogRead(JOY_Y);
int moveX = 0;
if (xValue < 1000) moveX = -moveSpeed;
else if (xValue > 3000) moveX = moveSpeed;
if (moveX != 0) {
playerX = constrain(playerX + moveX, 0, screenWidth - playerWidth);
if (playerX != prevPlayerX) needRedraw = true;
}
// 下搖桿跳躍
if (yValue > 3000 && playerY >= groundY - playerHeight && !isJumping) {
velocityY = jumpForce;
isJumping = true;
delay(100);
}
// 搖桿中間按鈕攻擊
if (digitalRead(JOY_SW) == LOW && millis() - lastAttackTime > attackCooldown) {
isAttacking = true;
attackHit = false;
lastAttackTime = millis();
attackStartTime = millis();
checkAttack();
needRedraw = true;
} else if (millis() - attackStartTime > attackDuration || (isAttacking && attackHit)) {
isAttacking = false;
}
if (digitalRead(DEFENSE_BUTTON) == LOW && defenseReady && millis() - lastDefenseTime > defenseCooldown) {
isDefending = true;
defenseStartTime = millis();
defenseReady = false;
needRedraw = true;
// 如果正在被攻擊,則消滅敵人
if (enemy.active && checkEnemyCollision()) {
enemy.health = 0;
enemy.active = false;
enemy.dying = true;
enemy.deathTime = millis();
}
}
// 更新防禦狀態
if (isDefending && millis() - defenseStartTime > defenseDuration) {
isDefending = false;
lastDefenseTime = millis();
needRedraw = true;
}
// 防禦冷卻結束
if (!defenseReady && millis() - lastDefenseTime > defenseCooldown) {
defenseReady = true;
}
}
void checkAttack() {
if ((!enemy.active && !enemy.dying) || !isAttacking) return;
bool facingLeft = (analogRead(JOY_X) < 2000);
swordEffectDirection = facingLeft ? -1 : 1;
bool inXRange = (swordEffectDirection == -1)
? (enemy.x + enemyWidth > playerX - attackRange && enemy.x < playerX)
: (enemy.x < playerX + playerWidth + attackRange && enemy.x + enemyWidth > playerX);
bool inYRange = (playerY < enemy.y + enemyHeight) && (playerY + playerHeight > enemy.y);
if (inXRange && inYRange && enemy.active) {
enemy.health -= attackDamage;
attackHit = true;
enemy.hasSword = true;
enemy.swordTime = millis();
// 啟動劍氣效果
swordEffectActive = true;
swordEffectEndTime = millis() + 500;
swordEffectX = playerX + (swordEffectDirection == -1 ? -5 : playerWidth + 5);
swordEffectY = playerY;
if (enemy.health <= 0) {
// First erase the active enemy
eraseEnemy();
// Then set death state
enemy.active = false;
enemy.dying = true;
enemy.deathTime = millis();
enemy.hasSword = false; // Clear sword when dying
}
needRedraw = true;
}
}
// 新增更新劍氣效果的函數
void updateSwordEffect() {
if ((enemy.active || enemy.dying) && enemy.hasSword && millis() - enemy.swordTime > swordStickDuration) {
enemy.hasSword = false;
needRedraw = true;
}
// 更新劍氣效果
if (swordEffectActive && millis() > swordEffectEndTime) {
swordEffectActive = false;
needRedraw = true;
}
}
void updatePhysics() {
if (playerY < groundY - playerHeight || velocityY < 0) {
velocityY += gravity;
playerY += velocityY;
if (playerY >= groundY - playerHeight) {
playerY = groundY - playerHeight;
velocityY = 0;
isJumping = false;
}
if (playerY != prevPlayerY) needRedraw = true;
}
if (playerY < 0) {
playerY = 0;
velocityY = 0;
}
}
void spawnEnemy() {
if (!enemy.active && !enemy.dying && millis() - lastEnemySpawnTime > enemySpawnInterval) {
enemy.x = screenWidth;
enemy.y = groundY - enemyHeight;
enemy.active = true;
enemy.dying = false;
enemy.health = 1;
enemy.hasSword = false;
lastEnemySpawnTime = millis();
needRedraw = true;
}
}
void updateEnemy() {
spawnEnemy();
if (enemy.active && !enemy.dying) { // Only move active, non-dying enemies
eraseEnemy();
enemy.x -= enemySpeed;
if (enemy.x < -enemyWidth) {
enemy.active = false;
} else {
drawEnemy();
}
}
}
void drawEnemy() {
if (isGameOver) {
tft.fillRect(enemy.x - 10, enemy.y - 10,
enemyWidth + 20, enemyHeight + 20, ILI9341_RED);
return;
}
// 根據敵人狀態選擇顏色
uint16_t enemyColor = enemy.dying ? DEAD_ENEMY_COLOR : ENEMY_COLOR;
tft.fillRoundRect(enemy.x, enemy.y, enemyWidth, enemyHeight, 4, enemyColor);
// 只有活著的敵人有眼睛
if (!enemy.dying) {
tft.fillCircle(enemy.x + 8, enemy.y + 8, 3, ILI9341_BLACK);
tft.fillCircle(enemy.x + 24, enemy.y + 8, 3, ILI9341_BLACK);
}
// 繪製插在敵人身上的劍
if (enemy.hasSword) {
int swordX = enemy.x + enemyWidth / 2;
int swordY1 = enemy.y + 10;
int swordY2 = enemy.y + enemyHeight - 5;
// 劍身
tft.drawFastVLine(swordX, swordY1, swordY2 - swordY1, SWORD_COLOR);
// 劍柄
tft.drawFastHLine(swordX - 3, swordY2 - 5, 6, SWORD_COLOR);
// 劍尖
tft.fillTriangle(
swordX, swordY1,
swordX - 2, swordY1 + 6,
swordX + 2, swordY1 + 6,
SWORD_COLOR
);
}
}
void eraseEnemy() {
// 遊戲結束時不需要單獨清除,因為已經有fillScreen
if (isGameOver) return;
// 原始清除邏輯
tft.fillRect(enemy.x, enemy.y, enemyWidth, enemyHeight, SKY_COLOR);
// Erase the entire enemy area
tft.fillRect(enemy.x, enemy.y, enemyWidth, enemyHeight, SKY_COLOR);
// Redraw any ground that was covered
if (enemy.y + enemyHeight > groundY) {
int overlap = (enemy.y + enemyHeight) - groundY;
tft.fillRect(enemy.x, groundY, enemyWidth, overlap, GROUND_COLOR);
tft.drawFastHLine(enemy.x, groundY, enemyWidth, ILI9341_WHITE);
}
// Also erase any sword effects that might remain
if (enemy.hasSword) {
int swordClearWidth = 10; // Wider than the actual sword
int swordClearX = enemy.x + enemyWidth/2 - swordClearWidth/2;
tft.fillRect(swordClearX, enemy.y, swordClearWidth, enemyHeight,
(enemy.y + enemyHeight > groundY) ? GROUND_COLOR : SKY_COLOR);
}
}
void checkCollision() {
if ((enemy.active || enemy.dying) && checkEnemyCollision()) {
// 檢查是否是從上方踩踏敵人
if (velocityY > 0 && playerY + playerHeight < enemy.y + enemyHeight / 2 && enemy.active) {
enemy.health--;
velocityY = jumpForce / 2;
if (enemy.health <= 0) {
enemy.active = false;
enemy.dying = true;
enemy.deathTime = millis();
}
}
// 如果是其他方向的碰撞且敵人還活著
else if (enemy.active && !isDefending) { // 防禦狀態下免疫傷害
startGameOver();
}
else if (enemy.active && isDefending) { // 防禦狀態下消滅敵人
enemy.health = 0;
enemy.active = false;
enemy.dying = true;
enemy.deathTime = millis();
}
}
}
// 新增輔助函數檢查碰撞
bool checkEnemyCollision() {
return (playerX < enemy.x + enemyWidth &&
playerX + playerWidth > enemy.x &&
playerY < enemy.y + enemyHeight &&
playerY + playerHeight > enemy.y);
}
// 修改drawPlayer()函數以顯示防禦狀態
void drawPlayer() {
if (!canShowPlayer) return;
// 根據防禦狀態選擇顏色
uint16_t playerColor = isDefending ? ILI9341_BLUE : PLAYER_COLOR;
tft.fillRoundRect(playerX, playerY, playerWidth, playerHeight, 5, playerColor);
tft.drawRoundRect(playerX, playerY, playerWidth, playerHeight, 5, ILI9341_WHITE);
// 繪製眼睛(根據移動方向)
int eyeX = (analogRead(JOY_X) < 2000 ? 3 : -3);
tft.fillCircle(playerX + playerWidth/3 + eyeX, playerY + 10, 4, ILI9341_WHITE);
tft.fillCircle(playerX + playerWidth*2/3 + eyeX, playerY + 10, 4, ILI9341_WHITE);
// 繪製攻擊劍氣
if ((isAttacking && !attackHit && millis() - attackStartTime <= attackVisualDuration) ||
(swordEffectActive)) {
int direction = swordEffectDirection;
int attackStartX = swordEffectActive ? swordEffectX :
(direction == -1 ? playerX - 5 : playerX + playerWidth + 5);
int attackEndX = attackStartX + (direction * 35);
int drawY = swordEffectActive ? swordEffectY : playerY;
// 主劍氣
tft.fillRect(attackStartX, drawY + 10, direction * 35, 20, ILI9341_WHITE);
// 劍氣尖端
tft.fillTriangle(
attackEndX, drawY + 10,
attackEndX + (direction * 8), drawY + 20,
attackEndX, drawY + 30,
ILI9341_WHITE
);
}
}
void startGameOver() {
noInterrupts();
// 1. 先完全清除整個螢幕(包括任何殘留的黑色方框)
tft.fillScreen(ILI9341_RED);
// 2. 強制清除所有遊戲物件
erasePlayer(); // 清除玩家
eraseEnemy(); // 清除敵人
// 3. 重新繪製GAME OVER文字(確保在最上層)
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(3);
tft.setCursor(100, 100);
tft.print("GAME OVER");
// 4. 重置所有遊戲狀態
gameOverStartTime = millis();
isGameOver = true;
canShowPlayer = false;
interrupts();
// 5. 額外確保畫面完全覆蓋
delay(10); // 小延遲確保所有操作完成
tft.fillScreen(ILI9341_RED);
tft.setCursor(100, 100);
tft.print("GAME OVER");
}
void erasePlayer() {
if (isGameOver) {
tft.fillRect(prevPlayerX - 10, prevPlayerY - 10,
playerWidth + 20, playerHeight + 20, ILI9341_RED);
return;
}
// 清除玩家主體
if (prevPlayerY + playerHeight > groundY) {
int groundOverlap = (prevPlayerY + playerHeight) - groundY;
tft.fillRect(prevPlayerX, groundY, playerWidth, groundOverlap, GROUND_COLOR);
tft.drawFastHLine(prevPlayerX, groundY, playerWidth, ILI9341_WHITE);
}
if (prevPlayerY < groundY) {
int skyPartHeight = min(playerHeight, groundY - prevPlayerY);
tft.fillRect(prevPlayerX, prevPlayerY, playerWidth, skyPartHeight, SKY_COLOR);
}
// 清除劍氣特效區域
int direction = (analogRead(JOY_X) < 2000 ? -1 : 1);
int clearStartX = (direction == -1) ? prevPlayerX - 45 : prevPlayerX + playerWidth;
int clearEndX = (direction == -1) ? prevPlayerX + 15 : prevPlayerX + playerWidth + 45;
tft.fillRect(clearStartX, prevPlayerY, abs(clearEndX - clearStartX), playerHeight,
(prevPlayerY + playerHeight > groundY) ? GROUND_COLOR : SKY_COLOR);
}
void finishGameOver(){
gameStarted = false;
canShowPlayer= false;
isGameOver =false;
showStartScreen();
}