// sorry for messy connections
//GND: ILI9341 GND to ESP32 GND.
//VCC: ILI9341 VCC to ESP32 VIN.
//CS: ILI9341 CS to ESP32 D15.
//RST: ILI9341 RST to ESP32 D4.
//D/C: ILI9341 D/C to ESP32 D2.
//SCK: ILI9341 SCK to ESP32 D18.
//MISO: ILI9341 MISO to ESP32 D19.
//MOSI: ILI9341 MOSI to ESP32 D23.
//GND: 74HC165 GND to ESP32 GND.
//VCC: 74HC165 VCC to ESP32 3V3.
//Q7: 74HC165 Q7 to ESP32 D16.
//CP: 74HC165 CP to ESP32 D17.
//PL: 74HC165 PL to ESP32 D22.
//!CE: 74HC165 !CE to ESP32 D5.
//Button 1: Connected to 74HC165 D1 through resistor .
//Button 2: Connected to 74HC165 D2 through resistor .
//Button 3: Connected to 74HC165 D3 through resistor .
//Button 4: Connected to 74HC165 D4 through resistor .
//Button 5: Connected to 74HC165 D5 through resistor .
//Button 6: Connected to 74HC165 D6 through resistor .
//Use 1k resistor
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <Arduino.h>
#include <SPI.h>
#include <vector>
#include "M16.h"
#include "Glock.h"
#include "Glock_item.h"
#define TFT_CS 15
#define TFT_RST 4
#define TFT_DC 2
#define TFT_WIDTH 320
#define TFT_HEIGHT 240
#define MAP_WIDTH 30
#define MAP_HEIGHT 30
#define TILE_SIZE 20
#define MINIMAP_SIZE 100
#define MINIMAP_X (TFT_WIDTH - MINIMAP_SIZE - 10)
#define MINIMAP_Y (TFT_HEIGHT - MINIMAP_SIZE - 10)
bool showMinimap = false;
const int dataPin = 13; // Replace with the actual pin number for SERIAL_DATA
const int clockPin = 17; // Replace with the actual pin number for SHIFT_CLK
const int latchPin = 22; // Replace with the actual pin number for STORAGE_CLK
const int numBits = 6;
#define GRADIENT_STEPS 100 // Adjust the value as needed
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
const int worldMap[MAP_WIDTH][MAP_HEIGHT] PROGMEM = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
struct Enemy {
double x;
double y;
double width;
double height;
bool defeated;
};
std::vector<Enemy> enemies;
double posX = 13.0, posY = 7.0, posZ = 0;
double dirX = -1.0, dirY = 0.0, planeX = 0.0, planeY = 0.66;
double speedVar = 0.50, rotSpeedVar = 0.1;
int enemyTexture[5][5] = {
{0, 1, 1, 1, 1},
{1, 1, 0, 1, 1},
{1, 1, 1, 1, 1},
{0, 1, 0, 1, 1},
{0, 1, 0, 1, 1}
};
int deadEnemyTexture[5][5] = {
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 1, 1, 1, 0},
{0, 0, 1, 0, 0}
};
void setup() {
tft.begin();
tft.setRotation(1);
Serial.begin(115200);
setCpuFrequencyMhz(240);
pinMode(dataPin, INPUT);
pinMode(clockPin, OUTPUT);
pinMode(latchPin, OUTPUT);
Serial.begin(115200);
// Spawn enemies based on the worldMap
for (int x = 0; x < MAP_WIDTH; x++) {
for (int y = 0; y < MAP_HEIGHT; y++) {
int mapValue = pgm_read_word(&worldMap[x][y]);
if (mapValue == 2) {
Enemy enemy = {static_cast<double>(x), static_cast<double>(y), 0.5, 0.5, false};
enemies.push_back(enemy);
}
}
}
// Find the initial player spawn location
bool foundSpawn = false;
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
int mapValue = pgm_read_word(&worldMap[x][y]);
if (mapValue == 3) {
posX = x + 0.5;
posY = y + 0.5;
foundSpawn = true;
break;
}
}
if (foundSpawn) break;
}
if (!foundSpawn) {
// No spawn point found, set default position
posX = 13.0;
posY = 7.0;
}
}
void loop() {
handleInput();
// Clear the areas for rendering walls, enemies, aim, and minimap
tft.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, ILI9341_BLACK);
renderWalls();
renderEnemies();
renderAim();
Glock();
if (showMinimap) {
renderMiniMap();
}
}
uint8_t readShiftRegister() {
uint8_t data = 0;
// Step 1: Sample
digitalWrite(latchPin, LOW);
// Step 2: Shift
for (int i = 0; i < numBits; i++) {
int bit = digitalRead(dataPin);
bitWrite(data, i, bit);
digitalWrite(clockPin, HIGH); // Shift out the next bit
digitalWrite(clockPin, LOW);
}
// Step 3: Update
digitalWrite(latchPin, HIGH);
return data;
}
double moveX = 0.0; // Initialize with a default value
double moveY = 0.0;
void handleInput() {
uint8_t shiftRegisterData = readShiftRegister();
// Reset rotation variables
double rotateX = 0.0;
double rotateY = 0.0;
moveX = 0.0; // Reset or update moveX as needed
moveY -= speedVar; // Up button
moveY += speedVar; // Down button
// Update movement variables based on button presses
if (shiftRegisterData & 0b00000001) {
moveY -= speedVar; // Up button
Serial.print("Up");
}
if (shiftRegisterData & 0b00000010) {
moveY += speedVar; // Down button
Serial.print("Down");
}
if (shiftRegisterData & 0b00000100) {
rotateX = -rotSpeedVar; // Left button
}
if (shiftRegisterData & 0b00001000) {
rotateX = rotSpeedVar; // Right button
}
if (shiftRegisterData & 0b00010000) {
shootEnemies(); // Fire button
}
if (shiftRegisterData & 0b00100000) {
showMinimap = !showMinimap; // Toggle minimap visibility
}
// Update player position and rotation based on movement
double newX = posX + dirX * moveY + planeX * moveX;
double newY = posY + dirY * moveY + planeY * moveX;
int mapValue = pgm_read_word(&worldMap[int(newX)][int(newY)]);
if (mapValue == 0) {
posX = newX;
posY = newY;
}
// Update player direction based on rotation
double oldDirX = dirX;
dirX = dirX * cos(rotateX) - dirY * sin(rotateX);
dirY = oldDirX * sin(rotateX) + dirY * cos(rotateX);
double oldPlaneX = planeX;
planeX = planeX * cos(rotateX) - planeY * sin(rotateX);
planeY = oldPlaneX * sin(rotateX) + planeY * cos(rotateX);
}
void shootEnemies() {
double cameraX = 2 * (TFT_WIDTH / 2) / double(TFT_WIDTH) - 1;
double rayDirX = dirX + planeX * cameraX;
double rayDirY = dirY + planeY * cameraX;
int mapX = int(posX);
int mapY = int(posY);
double sideDistX;
double sideDistY;
double deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
double deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
int stepX, stepY;
int hit = 0;
int side;
if (rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
while (!hit) {
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
bool isEnemy = false;
for (auto it = enemies.begin(); it != enemies.end(); ++it) {
if (int(it->x) == mapX && int(it->y) == mapY) {
// Calculate the angle between the ray direction and the vector from the player to the enemy
double enemyAngle = angleBetween(posX, posY, it->x, it->y);
double rayAngle = atan2(rayDirY, rayDirX);
double angleDiff = fabs(enemyAngle - rayAngle);
// If the angle difference is small enough, mark the enemy as defeated
const double aimThreshold = 0.1; // Adjust this value to change the aim tolerance
if (angleDiff < aimThreshold) {
it->defeated = true;
}
break;
}
}
int mapValue = pgm_read_word(&worldMap[mapX][mapY]);
if (mapValue > 0) {
hit = 1;
}
}
}
double angleBetween(double x1, double y1, double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
return atan2(dy, dx);
}
void renderWalls() {
const int WALL_HEIGHT = 200; // Set the desired height of the buildings
for (int x = 0; x < TFT_WIDTH; ++x) {
double cameraX = 2 * x / double(TFT_WIDTH) - 1;
double rayDirX = dirX + planeX * cameraX;
double rayDirY = dirY + planeY * cameraX;
int mapX = int(posX);
int mapY = int(posY);
double sideDistX;
double sideDistY;
double deltaDistX = sqrt(1 + (rayDirY * rayDirY) / (rayDirX * rayDirX));
double deltaDistY = sqrt(1 + (rayDirX * rayDirX) / (rayDirY * rayDirY));
int stepX, stepY;
if (rayDirX < 0) {
stepX = -1;
sideDistX = (posX - mapX) * deltaDistX;
} else {
stepX = 1;
sideDistX = (mapX + 1.0 - posX) * deltaDistX;
}
if (rayDirY < 0) {
stepY = -1;
sideDistY = (posY - mapY) * deltaDistY;
} else {
stepY = 1;
sideDistY = (mapY + 1.0 - posY) * deltaDistY;
}
bool hit = false;
int side;
while (!hit) {
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
mapX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
mapY += stepY;
side = 1;
}
bool isEnemy = false;
for (const auto& enemy : enemies) {
if (int(enemy.x) == mapX && int(enemy.y) == mapY) {
isEnemy = true;
break;
}
}
int mapValue = pgm_read_word(&worldMap[mapX][mapY]);
if (!isEnemy && mapValue > 0) {
hit = true;
}
}
double perpWallDist;
if (side == 0) {
perpWallDist = fabs((mapX - posX + (1 - stepX) / 2) / rayDirX);
} else {
perpWallDist = fabs((mapY - posY + (1 - stepY) / 2) / rayDirY);
}
int lineHeight = int(WALL_HEIGHT / perpWallDist);
int drawStart = TFT_HEIGHT / 2 - lineHeight / 2;
int drawEnd = drawStart + lineHeight;
int color;
int mapValue = pgm_read_word(&worldMap[mapX][mapY]);
if (mapValue == 1) {
color = ILI9341_BLUE;
tft.fillRect(x, drawStart, 1, drawEnd - drawStart, color); // Draw a rectangular building
} else if (mapValue == 2) {
color = ILI9341_RED;
} else {
color = ILI9341_RED;
}
}
}
void renderEnemies() {
for (const auto& enemy : enemies) {
// Calculate enemy position relative to the player
double relX = enemy.x - posX;
double relY = enemy.y - posY;
// Calculate the inverse of the player's transformation matrix
double invDet = 1.0 / (planeX * dirY - dirX * planeY);
double transformX = invDet * (dirY * relX - dirX * relY);
double transformY = invDet * (-planeY * relX + planeX * relY);
// Calculate screen coordinates of the enemy
int enemyScreenX = int((TFT_WIDTH / 2) * (1 + transformX / transformY));
int enemyScreenHeight = abs(int(TFT_HEIGHT / transformY));
// Adjust enemy size and position based on distance from the player
int enemyTileY = int(enemy.y);
int enemyScreenY = TFT_HEIGHT / 2 - enemyScreenHeight / 2;
int enemyScreenWidth = int(enemy.width * TFT_WIDTH / MAP_WIDTH);
// Limit the maximum size of the enemy sprite
int maxEnemySize = 50; // Adjust this value as needed
enemyScreenWidth = min(enemyScreenWidth, maxEnemySize);
enemyScreenHeight = min(enemyScreenHeight, maxEnemySize);
// Render the enemy using the appropriate texture map
int (*texture)[5];
if (enemy.defeated) {
for (int y = 0; y < enemyScreenHeight; y++) {
for (int x = 0; x < enemyScreenWidth; x++) {
int textureX = int(x * 4.0 / enemyScreenWidth);
int textureY = int(y * 4.0 / enemyScreenHeight);
if (deadEnemyTexture[textureY][textureX] == 1) {
tft.drawPixel(enemyScreenX - enemyScreenWidth / 2 + x, enemyScreenY + y, ILI9341_RED);
}
}
}
// Draw a small circle at the enemy's position
int itemX = enemyScreenX - enemyScreenWidth / 2;
int itemY = enemyScreenY + enemyScreenHeight / 2;
int circleRadius = 7; // Adjust this value to change the size of the circle
tft.drawCircle(itemX, itemY, circleRadius, ILI9341_WHITE);
} else {
for (int y = 0; y < enemyScreenHeight; y++) {
for (int x = 0; x < enemyScreenWidth; x++) {
int textureX = int(x * 4.0 / enemyScreenWidth);
int textureY = int(y * 4.0 / enemyScreenHeight);
if (enemyTexture[textureY][textureX] == 1) {
tft.drawPixel(enemyScreenX - enemyScreenWidth / 2 + x, enemyScreenY + y, ILI9341_RED);
}
}
}
}
}
}
void renderAim() {
int aimX = TFT_WIDTH / 2;
int aimY = TFT_HEIGHT / 2;
int aimRadius = 3;
tft.fillCircle(aimX, aimY, aimRadius, ILI9341_GREEN);
}
void renderMiniMap() {
// Clear the screen
tft.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, ILI9341_BLACK);
for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) {
int mapValue = pgm_read_word(&worldMap[x][y]);
int screenX = x * TFT_WIDTH / MAP_WIDTH;
int screenY = y * TFT_HEIGHT / MAP_HEIGHT;
int tileSize = min(TFT_WIDTH / MAP_WIDTH, TFT_HEIGHT / MAP_HEIGHT);
if (mapValue == 1) {
// Draw walls as blue rectangles
tft.fillRect(screenX, screenY, tileSize, tileSize, ILI9341_BLUE);
} else if (mapValue == 2) {
// Draw enemies as red rectangles
tft.fillRect(screenX, screenY, tileSize, tileSize, ILI9341_RED);
}
}
}
// Draw the player's position and orientation
int playerX = int(posX * TFT_WIDTH / MAP_WIDTH);
int playerY = int(posY * TFT_HEIGHT / MAP_HEIGHT);
tft.fillCircle(playerX, playerY, 2, ILI9341_GREEN);
double angleRad = atan2(dirY, dirX);
int lineX = playerX + 5 * cos(angleRad);
int lineY = playerY + 5 * sin(angleRad);
tft.drawLine(playerX, playerY, lineX, lineY, ILI9341_GREEN);
}
void fadeOutEffect() {
static uint8_t fadeStep = 0;
if (fadeStep <= GRADIENT_STEPS) {
uint8_t alpha = map(fadeStep, 0, GRADIENT_STEPS, 255, 0); // Map step to alpha value
// Calculate semi-transparent color (RGB with alpha)
uint16_t semiTransparentColor = tft.color565(0, 0, 0); // Black color
semiTransparentColor |= ((alpha / 8) << 11); // Adjust alpha level
tft.fillScreen(semiTransparentColor); // Fill screen with semi-transparent black
delay(10); // Adjust delay for desired fade speed
fadeStep++;
} else {
// Reset variables or set flags as needed when fade out completes
fadeStep = 0;
// Additional actions after fade out completes
}
}
void Glock(){
tft.drawBitmap(10,180,Glock_item,50,50,ILI9341_WHITE);
}