Rabu, 06 Mei 2026

JAVA - Draw Submarine 2

 




prompt

java netbeans swing gui canvas jpanel draw small submarine using 2d graphics geom and 4 stokes spinning animation and moving using key press WASD and space key launch missile

Gemini said

This implementation uses Java Swing, Graphics2D, and KeyBindings (which are more robust than KeyListeners for GUI applications). It features a Submarine class to handle the geometry and a Missile class for the projectiles.

The Submarine Code

Copy this code into a file named SubmarineGame.java.

Java
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.geom.*;
import java.util.ArrayList;
import java.util.List;

public class SubmarineGame extends JPanel {
    // Submarine position
    private double subX = 100, subY = 200;
    private double speed = 5.0;
    
    // Animation state
    private double propAngle = 0;
    private List<Missile> missiles = new ArrayList<>();

    public SubmarineGame() {
        setBackground(new Color(0, 105, 148)); // Deep Sea Blue
        setFocusable(true);
        setupControls();

        // Animation Timer (~60 FPS)
        Timer timer = new Timer(16, e -> {
            propAngle += 0.3; // Spin the propeller
            updateMissiles();
            repaint();
        });
        timer.start();
    }

    private void setupControls() {
        InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
        ActionMap am = getActionMap();

        // WASD Movement
        im.put(KeyStroke.getKeyStroke('w'), "up");
        im.put(KeyStroke.getKeyStroke('s'), "down");
        im.put(KeyStroke.getKeyStroke('a'), "left");
        im.put(KeyStroke.getKeyStroke('d'), "right");
        im.put(KeyStroke.getKeyStroke("SPACE"), "fire");

        am.put("up", new AbstractAction() { public void actionPerformed(ActionEvent e) { subY -= speed; } });
        am.put("down", new AbstractAction() { public void actionPerformed(ActionEvent e) { subY += speed; } });
        am.put("left", new AbstractAction() { public void actionPerformed(ActionEvent e) { subX -= speed; } });
        am.put("right", new AbstractAction() { public void actionPerformed(ActionEvent e) { subX += speed; } });
        am.put("fire", new AbstractAction() { public void actionPerformed(ActionEvent e) { launchMissile(); } });
    }

    private void launchMissile() {
        missiles.add(new Missile(subX + 60, subY + 15));
    }

    private void updateMissiles() {
        for (int i = missiles.size() - 1; i >= 0; i--) {
            missiles.get(i).move();
            if (missiles.get(i).x > getWidth()) missiles.remove(i);
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        drawSubmarine(g2);
        drawMissiles(g2);
    }

    private void drawSubmarine(Graphics2D g2) {
        AffineTransform old = g2.getTransform();
        g2.translate(subX, subY);

        // 1. Draw Body (Ellipse)
        g2.setColor(Color.DARK_GRAY);
        Ellipse2D body = new Ellipse2D.Double(0, 0, 80, 30);
        g2.fill(body);

        // 2. Draw Tower (Rectangle)
        Rectangle2D tower = new Rectangle2D.Double(30, -10, 20, 15);
        g2.fill(tower);

        // 3. Draw Propeller (The "4 strokes" spinning)
        g2.setColor(Color.BLACK);
        g2.setStroke(new BasicStroke(3));
        g2.translate(0, 15); // Move to back-center of sub
        g2.rotate(propAngle);
        
        for (int i = 0; i < 4; i++) {
            g2.rotate(Math.PI / 2); // 90 degree increments
            g2.draw(new Line2D.Double(0, 0, 10, 0));
        }

        g2.setTransform(old);
    }

    private void drawMissiles(Graphics2D g2) {
        g2.setColor(Color.YELLOW);
        for (Missile m : missiles) {
            g2.fill(new Ellipse2D.Double(m.x, m.y, 15, 5));
        }
    }

    // Missile Helper Class
    class Missile {
        double x, y;
        Missile(double x, double y) { this.x = x; this.y = y; }
        void move() { x += 10; }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Submarine 2D Graphics");
        frame.add(new SubmarineGame());
        frame.setSize(800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
}

Key Features Explained

  • java.awt.geom:

    • We use Ellipse2D for the hull and Rectangle2D for the conning tower.

    • The propeller is created using Line2D inside a loop that rotates the coordinate system.

  • The "4-Stroke" Animation:

    • In the drawSubmarine method, I use g2.rotate(propAngle). By looping 4 times and rotating by Math.PI / 2 (90 degrees) each time, we draw four distinct blades. Because propAngle increases in the Timer, it creates a spinning effect.

  • Movement (WASD):

    • I used KeyBindings (getInputMap and getActionMap). Unlike a KeyListener, KeyBindings don't require the component to have focus manually and are less prone to "glitching" when multiple keys are pressed.

  • Missile Logic:

    • An ArrayList stores active missiles. Every frame, they move to the right. To prevent memory leaks, they are removed from the list once they exit the screen boundaries.

How to run it in NetBeans

  1. Create a new Java Project.

  2. Create a new Java Class named SubmarineGame.

  3. Paste the code above.

  4. Right-click the file and select Run File.

Tidak ada komentar: