import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.Random; 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 Color NIGHT_SKY = new Color(4, 26, 64); 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, 10, 18, 6, 6, 0, 0, 0, 0}; // game objects Player player; ArrayList collidables; ArrayList collectables; Flag flag; ArrayList enemies; ArrayList projectiles; // game vars int boardWidth; int boardHeight; int tileSize; int enemiesKilled; Timer gameTimer; HashMap 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 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; enemiesKilled = 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; // lvl 3 arena if (currentLevel == 4) { if (enemiesKilled < 100 && enemies.isEmpty()) { Random rand = new Random(); int xOff = rand.nextInt(700); for (int i = 0; i < 10; i++) { xOff = rand.nextInt(700); if (xOff + 80 <= player.x && player.x <= xOff + 140) xOff += 60; enemies.add(new Enemy(100 + xOff, 280, 20, 20, currentLevel)); } } else if (enemiesKilled >= 100) { collidables.removeIf(c -> c.y == 300 && c.x >= 440 && c.x <= 520); } } // 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 > 1400) { // 1400/20 = 70 rows to work with per level 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); enemiesKilled++; projectiles.remove(p); break; } } } enemies.removeIf(e -> !e.alive); } public void loadLevel(int level) { projectiles.clear(); enemiesKilled = 0; 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; } if (currentLevel == 6) { this.setBackground(NIGHT_SKY); } if (currentLevel == 7) { this.setBackground(SKY); } 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); if (currentLevel == 2) { g.drawString("Take a leap of faith....", flag.x - 25, flag.y - 400); } if (currentLevel == 4) { g.drawString("Kill 100 of them...", 220, 200); } // flag counter int amOnes = player.numAmendments % 10; int amTens = player.numAmendments / 10; if (currentLevel > 0 && player.numAmendments < numAm[currentLevel - 1]) { int lvlAmOnes = numAm[currentLevel - 1] % 10; int lvlAmTens = numAm[currentLevel - 1] / 10; 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); if (lvlAmTens > 0) { g.drawImage(numbers.get(lvlAmTens), flag.x + 55, flag.y - 30, null); g.drawImage(numbers.get(lvlAmOnes), flag.x + 80, flag.y - 30, null); g.drawImage(amendmentImg, flag.x + 110, flag.y - 30, null); } else { g.drawImage(numbers.get(lvlAmOnes), flag.x + 55, flag.y - 30, null); g.drawImage(amendmentImg, flag.x + 85, flag.y - 30, null); } } g.translate(cameraX, cameraY); if (currentLevel == 8) { BufferedImage darkness = new BufferedImage(boardWidth, boardHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = darkness.createGraphics(); // fill entire overlay with fully opaque black g2.setColor(new Color(0, 0, 0, 255)); g2.fillRect(0, 0, boardWidth + 1000, boardHeight + 1000); // player's position in screen coordinates int screenX = (player.x + player.width / 2) - cameraX; int screenY = (player.y + player.height / 2) - cameraY; // cut circle centered on player g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)); g2.fillOval(screenX - 80, screenY - 100, 160, 160); g2.dispose(); // draw overlay in screen space (after translate reset) g.drawImage(darkness, 0, 0, null); } 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); int lvlAmOnes = numAm[currentLevel - 1] % 10; int lvlAmTens = numAm[currentLevel - 1] / 10; g.drawImage(slash, 363, 12, null); if (lvlAmTens > 0) { g.drawImage(numbers.get(lvlAmTens), 390, 10, null); g.drawImage(numbers.get(lvlAmOnes), 415, 10, null); g.drawImage(amendmentImg, 445, 10, null); } else { g.drawImage(numbers.get(lvlAmOnes), 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) {} }