#include <TFT_eSPI.h> // Configure for ILI9341 in User_Setup.h
#include <SPI.h>
#include <Adafruit_FT6206.h> // Capacitive touch controller (e.g., FT6206)
// -------------------- Display & Touch --------------------
TFT_eSPI tft = TFT_eSPI();
Adafruit_FT6206 ctp = Adafruit_FT6206();
const int16_t SCREEN_W = 240;
const int16_t SCREEN_H = 320;
// -------------------- Game constants --------------------
const int16_t TRACK_MARGIN = 20;
const int16_t TRACK_WIDTH = SCREEN_W - 2 * TRACK_MARGIN;
const int16_t CAR_WIDTH = 20;
const int16_t CAR_HEIGHT = 30;
const int16_t OBSTACLE_WIDTH = 20;
const int16_t OBSTACLE_HEIGHT = 30;
const uint16_t COLOR_BG = TFT_DARKGREY;
const uint16_t COLOR_TRACK = TFT_BLACK;
const uint16_t COLOR_CAR = TFT_RED;
const uint16_t COLOR_OBSTACLE = TFT_YELLOW;
const uint16_t COLOR_TEXT = TFT_WHITE;
const uint16_t COLOR_LINE = TFT_WHITE;
const int16_t MAX_OBSTACLES = 6;
// Game tuning
float carSpeed = 3.0; // vertical speed baseline
float obstacleSpeed = 3.0;
float steeringSpeed = 4.0;
uint16_t spawnDelay = 600; // ms between obstacle spawns
uint8_t maxLives = 3;
// -------------------- Game state --------------------
struct Car {
float x;
float y;
int16_t w;
int16_t h;
};
struct Obstacle {
float x;
float y;
bool active;
};
Car player;
Obstacle obstacles[MAX_OBSTACLES];
unsigned long lastSpawnTime = 0;
unsigned long lastFrameTime = 0;
unsigned long lastScoreTime = 0;
uint32_t score = 0;
uint8_t lives = 3;
bool gameOver = false;
// For lane lines scrolling
float laneOffset = 0;
// -------------------- Utility --------------------
bool rectOverlap(float x1, float y1, int16_t w1, int16_t h1,
float x2, float y2, int16_t w2, int16_t h2) {
return !(x1 + w1 < x2 ||
x2 + w2 < x1 ||
y1 + h1 < y2 ||
y2 + h2 < y1);
}
// -------------------- Game functions --------------------
void resetObstacles() {
for (int i = 0; i < MAX_OBSTACLES; i++) {
obstacles[i].active = false;
}
}
void resetGame() {
tft.fillScreen(COLOR_BG);
tft.setTextColor(COLOR_TEXT, COLOR_BG);
tft.setTextSize(2);
// Player car at bottom center
player.w = CAR_WIDTH;
player.h = CAR_HEIGHT;
player.x = SCREEN_W / 2 - CAR_WIDTH / 2;
player.y = SCREEN_H - CAR_HEIGHT - 10;
resetObstacles();
laneOffset = 0;
score = 0;
lives = maxLives;
gameOver = false;
lastSpawnTime = millis();
lastFrameTime = millis();
lastScoreTime = millis();
drawStaticBackground();
}
void drawStaticBackground() {
// Background
tft.fillScreen(COLOR_BG);
// Track
tft.fillRect(TRACK_MARGIN, 0, TRACK_WIDTH, SCREEN_H, COLOR_TRACK);
// Side borders
tft.drawRect(TRACK_MARGIN, 0, TRACK_WIDTH, SCREEN_H, TFT_WHITE);
}
void drawLaneLines() {
// Clear track area first
tft.fillRect(TRACK_MARGIN + 1, 0, TRACK_WIDTH - 2, SCREEN_H, COLOR_TRACK);
// Draw dashed center line
int16_t lineX = SCREEN_W / 2;
int16_t dashH = 20;
int16_t gap = 20;
for (int16_t y = -dashH; y < SCREEN_H + dashH; y += dashH + gap) {
int16_t yy = y + (int16_t)laneOffset;
if (yy + dashH < 0 || yy > SCREEN_H) continue;
tft.fillRect(lineX - 2, yy, 4, dashH, COLOR_LINE);
}
}
void drawCar(const Car &c, uint16_t color) {
tft.fillRect((int16_t)c.x, (int16_t)c.y, c.w, c.h, color);
}
void drawObstacle(const Obstacle &o, uint16_t color) {
tft.fillRect((int16_t)o.x, (int16_t)o.y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT, color);
}
void spawnObstacle() {
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacles[i].active) {
obstacles[i].active = true;
// Random lane within track
int16_t minX = TRACK_MARGIN + 5;
int16_t maxX = TRACK_MARGIN + TRACK_WIDTH - OBSTACLE_WIDTH - 5;
obstacles[i].x = random(minX, maxX);
obstacles[i].y = -OBSTACLE_HEIGHT;
break;
}
}
}
void updateObstacles(float dt) {
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacles[i].active) continue;
// Erase old
drawObstacle(obstacles[i], COLOR_TRACK);
obstacles[i].y += obstacleSpeed * dt;
// Off-screen?
if (obstacles[i].y > SCREEN_H) {
obstacles[i].active = false;
continue;
}
// Draw new
drawObstacle(obstacles[i], COLOR_OBSTACLE);
}
}
void checkCollisions() {
for (int i = 0; i < MAX_OBSTACLES; i++) {
if (!obstacles[i].active) continue;
if (rectOverlap(player.x, player.y, player.w, player.h,
obstacles[i].x, obstacles[i].y, OBSTACLE_WIDTH, OBSTACLE_HEIGHT)) {
// Collision
obstacles[i].active = false;
// Flash car
drawCar(player, TFT_WHITE);
delay(80);
drawCar(player, COLOR_CAR);
if (lives > 0) lives--;
if (lives == 0) {
gameOver = true;
}
}
}
}
void drawHUD() {
// Score & lives at top
tft.setTextColor(COLOR_TEXT, COLOR_BG);
tft.setTextSize(2);
char buf[32];
// Score
sprintf(buf, "Score:%lu", (unsigned long)score);
tft.fillRect(0, 0, 120, 20, COLOR_BG);
tft.setCursor(2, 2);
tft.print(buf);
// Lives
sprintf(buf, "Lives:%d", lives);
tft.fillRect(120, 0, 120, 20, COLOR_BG);
tft.setCursor(130, 2);
tft.print(buf);
}
void drawGameOver() {
tft.fillScreen(COLOR_BG);
tft.setTextColor(COLOR_TEXT, COLOR_BG);
tft.setTextSize(3);
tft.setCursor(40, 100);
tft.print("GAME OVER");
tft.setTextSize(2);
tft.setCursor(40, 150);
tft.print("Score: ");
tft.print(score);
tft.setCursor(20, 200);
tft.print("Tap to restart");
}
// -------------------- Input handling --------------------
void handleTouch(float dt) {
if (!ctp.touched()) return;
TS_Point p = ctp.getPoint();
// Depending on your wiring/orientation, you may need to swap/flip X/Y
int16_t tx = p.x;
int16_t ty = p.y;
// Map touch to screen coordinates if needed
// Example for a 320x240 CTP rotated to match TFT:
// (You may need to experiment with these mappings)
int16_t sx = map(tx, 0, 240, 0, SCREEN_W);
int16_t sy = map(ty, 0, 320, 0, SCREEN_H);
// Simple steering: left/right half of screen
if (sx < SCREEN_W / 2) {
// Move left
player.x -= steeringSpeed * dt * 20.0;
} else {
// Move right
player.x += steeringSpeed * dt * 20.0;
}
// Clamp to track
float minX = TRACK_MARGIN + 2;
float maxX = TRACK_MARGIN + TRACK_WIDTH - player.w - 2;
if (player.x < minX) player.x = minX;
if (player.x > maxX) player.x = maxX;
}
// -------------------- Setup & loop --------------------
void setup() {
Serial.begin(115200);
delay(1000);
tft.init();
tft.setRotation(1); // Landscape: 240x320 or 320x240 depending on setup
if (!ctp.begin(40)) { // Sensitivity threshold
Serial.println("Couldn't start capacitive touch controller");
}
randomSeed(analogRead(0));
resetGame();
}
void loop() {
unsigned long now = millis();
float dt = (now - lastFrameTime) / 1000.0;
if (dt <= 0) dt = 0.001;
lastFrameTime = now;
if (gameOver) {
drawGameOver();
// Wait for tap to restart
if (ctp.touched()) {
delay(300); // debounce
resetGame();
}
return;
}
// Scroll lane lines
laneOffset += carSpeed * dt * 20.0;
if (laneOffset > 40) laneOffset -= 40;
drawLaneLines();
// Erase old car
drawCar(player, COLOR_TRACK);
// Input
handleTouch(dt);
// Draw car
drawCar(player, COLOR_CAR);
// Spawn obstacles
if (now - lastSpawnTime > spawnDelay) {
spawnObstacle();
lastSpawnTime = now;
}
// Update obstacles
updateObstacles(dt);
// Collisions
checkCollisions();
// Score over time
if (now - lastScoreTime > 200) {
score += 1;
lastScoreTime = now;
}
// HUD
drawHUD();
}
Loading
ili9341-cap-touch
ili9341-cap-touch