Files
american-identity-project/Platformer.java
2026-04-19 14:41:10 -05:00

475 lines
14 KiB
Java

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.*;
public class Platformer extends JPanel implements KeyListener, ActionListener {
// constants
static final int GRAVITY = 1;
static final Color playerColor = Color.RED;
static final Color tileColor = Color.BLUE;
static final Color SKY = new Color(135, 206, 235);
static final int FRICTION = 1;
static final int MAXYVELO = 15;
static final int MAXXVELO = 5;
static final int totalLevels = 10;
static int[] numAm = {4, 5, 0, 0, 0, 0, 0, 0, 0, 0};
// game objects
Player player;
ArrayList<Collidable> collidables;
ArrayList<Collectable> collectables;
Flag flag;
ArrayList<Enemy> enemies;
ArrayList<Projectile> projectiles;
// game vars
int boardWidth;
int boardHeight;
int tileSize;
Timer gameTimer;
HashMap<Integer, Boolean> pressedKeys;
boolean jumpPressed;
int cameraX, cameraY;
int currentLevel;
boolean allCollected;
boolean gameOver;
boolean gameStarted;
Image heart, emptyHeart, slash, amendmentImg, powerImg, pressRImg, endImg, winImg, titleImg;
ArrayList<Image> numbers;
public Platformer(int boardWidth, int boardHeight, int tileSize) {
// setup game
this.boardWidth = boardWidth;
this.boardHeight = boardHeight;
this.tileSize = tileSize;
setPreferredSize(new Dimension(this.boardWidth, this.boardHeight));
addKeyListener(this);
this.setFocusable(true);
this.setLayout(null);
pressedKeys = new HashMap<>();
jumpPressed = false;
gameOver = false;
gameStarted = false;
setBackground(SKY);
// setup objects
heart = new ImageIcon("Sprites/Hearts/heart.png").getImage();
titleImg = new ImageIcon("Sprites/Title.png").getImage();
emptyHeart = new ImageIcon("Sprites/Hearts/emptyHeart.png").getImage();
endImg = new ImageIcon("Sprites/end.png").getImage();
pressRImg = new ImageIcon("Sprites/PressR.png").getImage();
winImg = new ImageIcon("Sprites/win.png").getImage();
gameTimer = new Timer(15, this);
player = new Player(-20, 0, tileSize, tileSize);
collidables = new ArrayList<>();
collectables = new ArrayList<>();
enemies = new ArrayList<>();
projectiles = new ArrayList<>();
flag = new Flag(-20, 0, tileSize, tileSize);
cameraX = 0;
cameraY = 0;
currentLevel = 0;
numbers = new ArrayList<>();
for (int i = 0; i < 10; i++)
numbers.add((new ImageIcon("Sprites/Numbers/" + i + ".png")).getImage());
slash = new ImageIcon("Sprites/Numbers/Slash.png").getImage();
amendmentImg = new ImageIcon("Sprites/Amendment.png").getImage();
powerImg = new ImageIcon("Sprites/Powerup1.png").getImage();
gameTimer.start();
// if i wanna add a button
/*JButton gameStart = new JButton("Start Game");
gameStart.addActionListener(e -> {
loadLevel(currentLevel);
gameTimer.start();
this.remove(gameStart);
this.revalidate();
this.repaint();
});
gameStart.setBorderPainted(false);
gameStart.setFocusPainted(false);
gameStart.setBounds(193,200,114,15);
gameStart.setForeground(new Color(52, 152, 219));
this.add(gameStart);*/
}
// gameloop
public void gameLoop() {
if (currentLevel > totalLevels) return;
// camera
cameraX = player.x - boardWidth / 2;
cameraX = Math.max(0, cameraX);
cameraY = player.y - boardHeight / 2;
// cameraY = Math.max(0, cameraY);
// win
allCollected = player.numAmendments >= numAm[currentLevel - 1];
if (player.collidesWith(flag) && allCollected) {
currentLevel++;
player.health = 3;
if (currentLevel > totalLevels) {
gameTimer.stop();
System.out.println("You win!");
return;
} else {
loadLevel(currentLevel);
}
return;
}
// keys
if (Math.abs(player.xVelo) < MAXXVELO) {
if (isKeyPressed(KeyEvent.VK_D) || isKeyPressed(KeyEvent.VK_RIGHT)) {
player.xVelo += 1;
} else if (isKeyPressed(KeyEvent.VK_A) || isKeyPressed(KeyEvent.VK_LEFT)) {
player.xVelo -= 1;
}
}
if (!isKeyPressed(KeyEvent.VK_D)
&& !isKeyPressed(KeyEvent.VK_RIGHT)
&& !isKeyPressed(KeyEvent.VK_A)
&& !isKeyPressed(KeyEvent.VK_LEFT)) { // friction
if (player.xVelo > 0) {
player.xVelo = Math.max(0, player.xVelo - FRICTION);
} else if (player.xVelo < 0) {
player.xVelo = Math.min(0, player.xVelo + FRICTION);
}
}
// jump
boolean jumpKeyDown = (isKeyPressed(KeyEvent.VK_W) || isKeyPressed(KeyEvent.VK_UP));
if (jumpKeyDown && !jumpPressed) {
if (player.onGround) {
player.yVelo = -15;
player.onGround = false;
jumpPressed = true;
player.airJumps = 0;
} else if (player.curPower == 1 && player.airJumps < 1) {
player.yVelo = -15;
player.airJumps++;
jumpPressed = true;
}
}
if (!jumpKeyDown) {
jumpPressed = false;
}
// gravity
if (player.yVelo < MAXYVELO) {
player.yVelo += GRAVITY;
}
// fall out of world
if (player.y > 2000) {
loadLevel(currentLevel);
player.health--;
}
for (Collectable c : collectables) {
if (c instanceof Powerup) {
Powerup pu = (Powerup) c;
if (pu.yVelo < MAXYVELO && !pu.onGround) {
pu.yVelo += GRAVITY;
}
pu.moveY(pu.yVelo);
for (Collidable col : collidables) {
if (pu.collidesWith(col)) {
pu.yVelo = 0;
pu.onGround = true;
pu.y = ((Tile) col).y - pu.height;
pu.rect.y = pu.y;
}
}
if (player.collidesWith(pu)) {
player.curPower = pu.id;
player.powerTimer = Player.POWER_DURATION;
}
} else if (c instanceof Amendment) {
Amendment am = (Amendment) c;
if (player.collidesWith(am)) {
player.numAmendments++;
}
}
}
collectables.removeIf(c -> player.collidesWith(c));
// update x
player.moveX(player.xVelo);
// collision with all tiles x
for (Collidable c : collidables) {
if (player.collidesWith(c)) {
player.onCollideX(c);
}
}
// update y
player.moveY(player.yVelo);
// assume not on ground
player.onGround = false;
// collision with all tiles y
for (Collidable c : collidables) {
if (player.collidesWith(c)) {
player.onCollideY(c, collectables);
}
}
// Powerup timer
if (player.curPower > 0) {
player.powerTimer--;
if (player.powerTimer <= 0) {
player.curPower = 0;
}
}
// update facing
if (player.xVelo > 0) player.facing = 1;
else if (player.xVelo < 0) player.facing = -1;
// shoot cooldown
if (player.shootCooldown > 0) player.shootCooldown--;
// invincibility timer
if (player.invincibleTimer > 0) player.invincibleTimer--;
// shoot
// projectiles
if (isKeyPressed(KeyEvent.VK_SPACE) && player.shootCooldown == 0) {
int projX = player.facing == 1 ? player.x + player.width : player.x - 10;
projectiles.add(new Projectile(projX, player.y, tileSize, 10, currentLevel, player.facing));
player.shootCooldown = Player.SHOOT_COOLDOWN;
}
projectiles.removeIf(p -> p.x < -50 + cameraX || p.x > boardWidth + cameraX + 200);
for (Projectile p : new ArrayList<>(projectiles)) {
p.move();
// projectile hits tile
for (Collidable c : collidables) {
if (p.collidesWith(c)) {
projectiles.remove(p);
break;
}
}
}
// update enemies
for (Enemy e : enemies) {
e.patrol(collidables);
// enemy hits player
if (player.collidesWith(e)) {
player.takeDamage();
}
}
// die
if (player.health <= 0) {
System.out.print("Game Over - You Died!");
gameOver = true;
gameTimer.stop();
}
// projectile hits enemy
for (Projectile p : new ArrayList<>(projectiles)) {
for (Enemy e : new ArrayList<>(enemies)) {
if (p.collidesWith(e)) {
enemies.remove(e);
projectiles.remove(p);
break;
}
}
}
enemies.removeIf(e -> !e.alive);
}
public void loadLevel(int level) {
projectiles.clear();
try {
LevelLoader.load(tileSize, collidables, collectables, enemies, flag, player, level);
player.reset();
player.setLevel(level);
cameraX = 0;
cameraY = 0;
} catch (IOException e) {
System.out.println("Could not load level " + level);
}
}
// paintComponent
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
// draw function
public void draw(Graphics g) {
// gameover screen
if (gameOver) {
g.drawImage(endImg, boardWidth / 2 - 100, boardHeight / 2 - 150, null);
g.drawImage(pressRImg, boardWidth / 2 - 80, boardHeight / 2, null);
return;
}
if (currentLevel > totalLevels) {
g.drawImage(winImg, boardWidth / 2 - 100, boardHeight / 2 - 50, null);
g.drawImage(pressRImg, boardWidth / 2 - 80, boardHeight / 2 + 60, null);
return;
}
g.translate(-cameraX, -cameraY);
player.draw(g);
for (Collidable c : collidables) c.draw(g);
for (Collectable c : collectables) c.draw(g);
for (Enemy e : enemies) e.draw(g);
for (Projectile p : projectiles) p.draw(g);
flag.draw(g);
// flag counter
int amOnes = player.numAmendments % 10;
int amTens = player.numAmendments / 10;
if (currentLevel > 0 && player.numAmendments < numAm[currentLevel - 1]) {
if (amTens > 0) g.drawImage(numbers.get(amTens), flag.x - 20, flag.y - 30, null);
g.drawImage(numbers.get(amOnes), flag.x + 5, flag.y - 30, null);
g.drawImage(slash, flag.x + 28, flag.y - 32, null);
g.drawImage(numbers.get(numAm[currentLevel - 1]), flag.x + 55, flag.y - 30, null);
g.drawImage(amendmentImg, flag.x + 85, flag.y - 30, null);
}
g.translate(cameraX, cameraY);
int modAmt = 2000;
int curTime = (int) System.currentTimeMillis() % modAmt;
curTime = Math.abs(curTime);
// start screen
int startTime = 0;
if (curTime >= 0 && curTime <= modAmt / 4 - 1) startTime = 0;
else if (curTime >= modAmt / 4 && curTime <= modAmt / 2 - 1) startTime = 1;
else if (curTime >= modAmt / 2 && curTime <= modAmt * 3 / 4 - 1) startTime = 2;
else if (curTime >= modAmt * 3 / 4 && curTime <= modAmt - 1) startTime = 3;
if (currentLevel == 0) {
g.drawImage(titleImg, boardWidth / 2 - 150, boardHeight / 2 - 200, null);
g.drawString("An American Identity Project", boardWidth / 2 - 75, boardHeight / 2 - 40);
String text = "Press P to Start!";
int xBase = 340;
int yBase = 400;
int spacing = 8;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
// x moves linearly
int x = xBase + (i * spacing);
// y uses a sine wave
// Math.sin takes radians. We use (startTime + i) to give each letter a different phase.
double waveOffset = Math.sin(startTime + i * 0.5) * 10;
int y = yBase + (int) waveOffset;
g.drawString(String.valueOf(c), x, y);
}
}
if (currentLevel > 0) {
// draw hearts:
int heartTime = 0;
if (curTime >= 0 && curTime <= modAmt / 2 - 1) heartTime = 1;
else if (curTime >= modAmt / 2 && curTime <= modAmt - 1) heartTime = 0;
for (int i = 0; i < player.health; i++) {
g.drawImage(heart, (((i + 1) * 20) - 10) + heartTime, 10 + heartTime * 2, null);
}
for (int i = 0; i < 3 - player.health; i++) {
g.drawImage(emptyHeart, (50 - (i * 20)) + heartTime, 10 + heartTime * 2, null);
}
// draw amendments counter in top right
if (amTens > 0) g.drawImage(numbers.get(amTens), 315, 10, null);
g.drawImage(numbers.get(amOnes), 340, 10, null);
g.drawImage(slash, 363, 12, null);
g.drawImage(numbers.get(numAm[currentLevel - 1]), 390, 10, null);
g.drawImage(amendmentImg, 420, 10, null);
// draw powerup timer
if (player.curPower == 1) {
int secs = player.powerTimer / 66;
int tens = secs / 10;
int ones = secs % 10;
if (tens > 0) {
g.drawImage(numbers.get(tens), 184, 10, null);
}
g.drawImage(numbers.get(ones), 205, 10, null);
g.drawImage(powerImg, 220, 10, null);
}
}
}
// is key pressed
public boolean isKeyPressed(int key) {
return pressedKeys.getOrDefault(key, false);
}
// every tick
@Override
public void actionPerformed(ActionEvent e) {
if (gameStarted) {
gameLoop();
}
repaint();
}
// check for key presses
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_P) {
if (currentLevel == 0) {
currentLevel = 1;
loadLevel(currentLevel);
gameStarted = true;
}
gameTimer.start();
return;
}
if (e.getKeyCode() == KeyEvent.VK_R) {
if (gameOver) {
gameTimer.stop();
loadLevel(1);
currentLevel = 1;
jumpPressed = false;
player.health = 3;
gameOver = false;
repaint();
gameTimer.start();
gameStarted = true;
}
}
if (e.getKeyCode() == KeyEvent.VK_O) {
currentLevel++;
loadLevel(currentLevel);
}
pressedKeys.put(e.getKeyCode(), true);
}
@Override
public void keyReleased(KeyEvent e) {
pressedKeys.put(e.getKeyCode(), false);
}
// dont need
@Override
public void keyTyped(KeyEvent e) {}
}