Rabu, 06 Mei 2026

JAVA - Draw Submarine 3

 


prompt

Gemini said

This is a complete, single-file Java solution using Swing and Graphics2D. It utilizes Path2D, Ellipse2D, and AffineTransform for the geometry and animations.

To run this, simply create a class named SubmarineGame in NetBeans and paste the code below.

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("Deep Sea Strike");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(new GamePanel());
        pack();
        setLocationRelativeTo(null);
        setResizable(false);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new SubmarineGame().setVisible(true));
    }
}

class GamePanel extends JPanel implements ActionListener, KeyListener {
    private final int WIDTH = 800;
    private final int HEIGHT = 600;
    private Timer timer;
    private Player player;
    private ArrayList<Enemy> enemies;
    private ArrayList<Missile> missiles;
    private boolean gameOver = false;
    private Random rand = new Random();
    private long lastEnemyFire = 0;

    public GamePanel() {
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBackground(new Color(0, 50, 100));
        setFocusable(true);
        addKeyListener(this);

        initGame();
        timer = new Timer(16, this); // ~60 FPS
        timer.start();
    }

    private void initGame() {
        player = new Player(100, 300);
        enemies = new ArrayList<>();
        missiles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            enemies.add(new Enemy(400 + rand.nextInt(350), rand.nextInt(550)));
        }
    }

    @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", WIDTH / 2 - 150, HEIGHT / 2);
            g2d.setFont(new Font("Arial", Font.PLAIN, 20));
            g2d.drawString("Press 'R' to Restart", WIDTH / 2 - 80, HEIGHT / 2 + 50);
            return;
        }

        player.draw(g2d);
        for (Enemy e : enemies) e.draw(g2d);
        for (Missile m : missiles) m.draw(g2d);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (!gameOver) {
            update();
        }
        repaint();
    }

    private void update() {
        player.update();

        // Enemy firing logic (every 2 seconds)
        if (System.currentTimeMillis() - lastEnemyFire > 2000) {
            for (Enemy en : enemies) {
                missiles.add(new Missile(en.x, en.y + 15, -5, Color.RED, false));
            }
            lastEnemyFire = System.currentTimeMillis();
        }

        // Move missiles and check collisions
        Iterator<Missile> mIter = missiles.iterator();
        while (mIter.hasNext()) {
            Missile m = mIter.next();
            m.update();
            if (m.x < 0 || m.x > WIDTH) {
                mIter.remove();
                continue;
            }

            if (m.fromPlayer) {
                Iterator<Enemy> eIter = enemies.iterator();
                while (eIter.hasNext()) {
                    Enemy en = eIter.next();
                    if (m.getBounds().intersects(en.getBounds())) {
                        eIter.remove();
                        mIter.remove();
                        break;
                    }
                }
            } else {
                if (m.getBounds().intersects(player.getBounds())) {
                    gameOver = true;
                }
            }
        }

        for (Enemy en : enemies) en.update(WIDTH, HEIGHT);
    }

    @Override
    public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_W) player.dy = -4;
        if (key == KeyEvent.VK_S) player.dy = 4;
        if (key == KeyEvent.VK_A) player.dx = -4;
        if (key == KeyEvent.VK_D) player.dx = 4;
        if (key == KeyEvent.VK_SPACE && !gameOver) {
            missiles.add(new Missile(player.x + 40, player.y + 15, 7, Color.YELLOW, true));
        }
        if (key == KeyEvent.VK_R && gameOver) {
            gameOver = false;
            initGame();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_W || key == KeyEvent.VK_S) player.dy = 0;
        if (key == KeyEvent.VK_A || key == KeyEvent.VK_D) player.dx = 0;
    }

    @Override public void keyTyped(KeyEvent e) {}
}

abstract class Submarine {
    double x, y;
    double rotation = 0;
    Color color;

    public Submarine(double x, double y, Color color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }

    public void draw(Graphics2D g2d) {
        AffineTransform old = g2d.getTransform();
        g2d.translate(x, y);

        // Submarine Body
        g2d.setColor(color);
        g2d.fill(new Ellipse2D.Double(0, 0, 50, 25)); // Hull
        g2d.fill(new Rectangle2D.Double(15, -10, 15, 15)); // Conning tower
        
        // Propeller (4 spokes spinning)
        g2d.setColor(Color.BLACK);
        g2d.translate(0, 12.5); // Move to back center
        rotation += 0.2;
        g2d.rotate(rotation);
        for (int i = 0; i < 4; i++) {
            g2d.rotate(Math.PI / 2);
            g2d.draw(new Line2D.Double(0, 0, 0, 10));
        }

        g2d.setTransform(old);
    }

    public Rectangle2D getBounds() {
        return new Rectangle2D.Double(x, y - 10, 50, 35);
    }
}

class Player extends Submarine {
    double dx, dy;
    public Player(double x, double y) { super(x, y, Color.GRAY); }
    public void update() {
        x += dx;
        y += dy;
    }
}

class Enemy extends Submarine {
    double vx, vy;
    private Random r = new Random();

    public Enemy(double x, double y) {
        super(x, y, Color.RED);
        vx = (r.nextDouble() - 0.5) * 2;
        vy = (r.nextDouble() - 0.5) * 2;
    }

    public void update(int w, int h) {
        x += vx;
        y += vy;
        // Bounce off walls
        if (x < 300 || x > w - 60) vx *= -1;
        if (y < 50 || y > h - 50) vy *= -1;
    }
}

class Missile {
    double x, y, speed;
    Color color;
    boolean fromPlayer;

    public Missile(double x, double y, double speed, Color color, boolean fromPlayer) {
        this.x = x;
        this.y = y;
        this.speed = speed;
        this.color = color;
        this.fromPlayer = fromPlayer;
    }

    public void update() { x += speed; }

    public void draw(Graphics2D g2d) {
        g2d.setColor(color);
        g2d.fill(new Rectangle2D.Double(x, y, 10, 4));
    }

    public Rectangle2D getBounds() { return new Rectangle2D.Double(x, y, 10, 4); }
}

Key Logic Features:

  • Graphics2D & Geom: The submarine uses Ellipse2D for the hull and Rectangle2D for the tower. The propeller is animated using AffineTransform to rotate 4 Line2D "spokes" around the rear of the sub.

  • Game Loop: Uses a javax.swing.Timer set to 16ms to achieve roughly 60 frames per second.

  • Movement: KeyListener captures WASD inputs to modify the player's delta values (, ).

  • Enemies: 20 red submarines are instantiated with random velocities. They "bounce" within a defined boundary on the right side of the screen.

  • Collision Detection: Every frame, the game iterates through missiles and checks if their bounding box (Rectangle2D) intersects with a submarine's bounding box.

  • Game Over: If a missile from an enemy hits the player's bounds, the gameOver flag stops the update logic and displays the end screen.

How are you planning to expand this—maybe adding a scoring system or different levels?

Tidak ada komentar: