prompt
Gemini said
Creating a full 2D game in Java Swing requires a game loop (usually via a javax.swing.Timer), key event handling, and custom painting within a JPanel.
Below is a complete, single-file implementation. You can copy this into a file named SubmarineGame.java in your NetBeans project.
SubmarineGame.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
public class SubmarineGame extends JFrame {
public SubmarineGame() {
setTitle("Submarine Strike");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
add(new GamePanel());
pack();
setLocationRelativeTo(null);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new SubmarineGame().setVisible(true);
});
}
}
class GamePanel extends JPanel implements ActionListener, KeyListener {
private Timer timer;
private Player player;
private ArrayList<Enemy> enemies;
private ArrayList<Missile> playerMissiles;
private ArrayList<Missile> enemyMissiles;
private boolean[] keys = new boolean[256];
private int score = 0;
private boolean gameOver = false;
private Random rand = new Random();
public GamePanel() {
setPreferredSize(new Dimension(800, 600));
setBackground(new Color(0, 105, 148)); // Deep Sea Blue
setFocusable(true);
addKeyListener(this);
player = new Player(100, 300);
enemies = new ArrayList<>();
playerMissiles = new ArrayList<>();
enemyMissiles = new ArrayList<>();
// Initialize 20 enemies
for (int i = 0; i < 20; i++) {
spawnEnemy();
}
timer = new Timer(16, this); // ~60 FPS
timer.start();
}
private void spawnEnemy() {
enemies.add(new Enemy(800 + rand.nextInt(1000), rand.nextInt(500) + 50));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (gameOver) {
g2d.setColor(Color.WHITE);
g2d.setFont(new Font("Arial", Font.BOLD, 50));
g2d.drawString("GAME OVER", 250, 300);
g2d.setFont(new Font("Arial", Font.PLAIN, 20));
g2d.drawString("Final Score: " + score, 330, 340);
return;
}
player.draw(g2d);
for (Enemy e : enemies) e.draw(g2d);
g2d.setColor(Color.YELLOW);
for (Missile m : playerMissiles) m.draw(g2d);
g2d.setColor(Color.ORANGE);
for (Missile m : enemyMissiles) m.draw(g2d);
// UI
g2d.setColor(Color.WHITE);
g2d.setFont(new Font("Arial", Font.BOLD, 18));
g2d.drawString("Score: " + score, 20, 30);
}
@Override
public void actionPerformed(ActionEvent e) {
if (gameOver) return;
updatePlayer();
updateEnemies();
updateMissiles();
checkCollisions();
repaint();
}
private void updatePlayer() {
if (keys[KeyEvent.VK_W]) player.y -= 4;
if (keys[KeyEvent.VK_S]) player.y += 4;
if (keys[KeyEvent.VK_A]) player.x -= 4;
if (keys[KeyEvent.VK_D]) player.x += 4;
player.updateAnimation();
}
private void updateEnemies() {
for (Enemy en : enemies) {
en.update();
if (en.shouldFire()) {
enemyMissiles.add(new Missile(en.x, en.y + 10, -5));
}
}
}
private void updateMissiles() {
playerMissiles.removeIf(m -> { m.update(); return m.x > 800; });
enemyMissiles.removeIf(m -> { m.update(); return m.x < 0; });
}
private void checkCollisions() {
// Player missiles hitting enemies
Iterator<Missile> mIt = playerMissiles.iterator();
while (mIt.hasNext()) {
Missile m = mIt.next();
Iterator<Enemy> eIt = enemies.iterator();
while (eIt.hasNext()) {
Enemy en = eIt.next();
if (en.getBounds().intersects(m.getBounds())) {
eIt.remove();
mIt.remove();
score += 100;
spawnEnemy(); // Keep the count at 20
break;
}
}
}
// Enemy missiles hitting player
for (Missile m : enemyMissiles) {
if (player.getBounds().intersects(m.getBounds())) {
gameOver = true;
}
}
}
@Override
public void keyPressed(KeyEvent e) {
keys[e.getKeyCode()] = true;
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
playerMissiles.add(new Missile(player.x + 40, player.y + 15, 7));
}
}
@Override
public void keyReleased(KeyEvent e) { keys[e.getKeyCode()] = false; }
@Override public void keyTyped(KeyEvent e) {}
}
// --- Game Objects ---
class Player {
double x, y, propAngle = 0;
public Player(double x, double y) { this.x = x; this.y = y; }
public void updateAnimation() { propAngle += 0.5; }
public void draw(Graphics2D g2d) {
AffineTransform old = g2d.getTransform();
g2d.translate(x, y);
// Submarine Body
g2d.setColor(Color.GRAY);
g2d.fill(new Ellipse2D.Double(0, 0, 50, 25));
g2d.fill(new Rectangle2D.Double(15, -10, 15, 15)); // Conning Tower
// Propeller (4 strokes spinning)
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
for (int i = 0; i < 4; i++) {
double angle = propAngle + (i * Math.PI / 2);
g2d.draw(new Line2D.Double(-5, 12, -5 + Math.cos(angle) * 10, 12 + Math.sin(angle) * 10));
}
g2d.setTransform(old);
}
public Rectangle2D getBounds() { return new Rectangle2D.Double(x, y, 50, 25); }
}
class Enemy {
double x, y, startY;
long lastFireTime;
Random rand = new Random();
public Enemy(double x, double y) {
this.x = x; this.y = y; this.startY = y;
this.lastFireTime = System.currentTimeMillis() + rand.nextInt(4000);
}
public void update() {
x -= 2; // Move left
y = startY + Math.sin(x * 0.05) * 20; // Wavy animation
if (x < -60) x = 900; // Wrap around
}
public boolean shouldFire() {
if (System.currentTimeMillis() - lastFireTime > 4000) {
lastFireTime = System.currentTimeMillis();
return true;
}
return false;
}
public void draw(Graphics2D g2d) {
g2d.setColor(new Color(200, 0, 0));
g2d.fill(new Ellipse2D.Double(x, y, 40, 20));
g2d.fill(new Rectangle2D.Double(x + 10, y - 8, 12, 10));
// Face left (eye)
g2d.setColor(Color.WHITE);
g2d.fill(new Ellipse2D.Double(x + 5, y + 5, 5, 5));
}
public Rectangle2D getBounds() { return new Rectangle2D.Double(x, y, 40, 20); }
}
class Missile {
double x, y, speed;
public Missile(double x, double y, double speed) { this.x = x; this.y = y; this.speed = speed; }
public void update() { x += speed; }
public void draw(Graphics2D g2d) { g2d.fill(new Ellipse2D.Double(x, y, 10, 5)); }
public Rectangle2D getBounds() { return new Rectangle2D.Double(x, y, 10, 5); }
}
Key Technical Details:
Graphics2D & Geom: I used
Ellipse2DandRectangle2Dfor the submarine bodies. These are more flexible than standarddrawRectbecause they allow for precise collision checking using the.intersects()method.4-Stroke Animation: The propeller logic in the
Playerclass uses and to rotate four lines around a pivot point on the back of the sub.Key Polling: Instead of relying on the jerky nature of
KeyListenerauto-repeat, I use aboolean[] keysarray to track which buttons are held down. This allows for smooth diagonal movement.Game Loop: The
javax.swing.Timerruns every 16ms, triggeringactionPerformed, which updates the positions of all entities and callsrepaint().Enemy Logic: Enemies are stored in an
ArrayList. When one is hit, it is removed and a new one is spawned off-screen to maintain the count of 20. They use alastFireTimevariable to ensure they only shoot every 4 seconds.

Tidak ada komentar:
Posting Komentar