Rabu, 03 Desember 2025

PBO A - Pertemuan 15 - Pong!

Nama : Erlangga Rizqi Dwi Raswanto

NRP : 5025241179

Kelas : PBO A


1. Pendahuluan

Pada pertemuan 15, kita mempraktikkan pembuatan game arcade sederhana “Pong” menggunakan Java GUI (AWT & Swing).

Secara singkat, Pong adalah salah satu game video generasi awal yang terinspirasi dari permainan tenis meja (ping-pong). Secara visual, game ini sangat sederhana: hanya terdiri dari dua paddle (kayu pemukul) dan satu bola yang memantul ke kanan dan kiri. 

Di pertemuan ini, saya mengimplementasikan game Pong dengan Java Swing yang terdiri dari beberapa kelas: PongGame, GameFrame, GamePanel, Paddle, dan Ball.


2. Tujuan Praktikum

Tujuan dari tugas pertemuan 15 adalah:

  1. Menerapkan konsep GUI pada Java menggunakan Swing dan AWT.

  2. Memahami cara kerja game loop menggunakan Thread dan interface Runnable.

  3. Menggunakan event listener (KeyListener) untuk menangani input keyboard.

  4. Menerapkan pemodelan objek (OOP) ke dalam game (memecah game jadi beberapa class).

  5. Menerapkan logika sederhana collision detection dan scoring.


3. Perancangan Program

3.1 Skema Kelas

Secara garis besar, struktur program:

  • PongGame
    Kelas dengan main(). Tugasnya hanya membuat jendela game (GameFrame).

  • GameFrame
    Turunan dari JFrame. Ini adalah window utama yang berisi GamePanel.

  • GamePanel
    Turunan dari JPanel, sekaligus Runnable dan KeyListener.
    Bertanggung jawab untuk:

    • Menjalankan game loop (update & repaint)

    • Menggambar semua objek (paddle, ball, score, garis tengah)

    • Mengatur collision dan scoring

  • Paddle
    Mewakili objek paddle (kayu pemukul) milik Player 1 dan Player 2. Masing-masing bisa digerakkan dengan keyboard:

    • Player 1: W (naik), S (turun)

    • Player 2: UP dan DOWN

  • Ball
    Mewakili bola game. Bola memiliki posisi (x, y) dan kecepatan (xVelocity, yVelocity) yang di-update di setiap frame.

3.2 Alur Program

  1. Program dimulai dari PongGame.main(), yang membuat objek GameFrame.

  2. GameFrame membuat GamePanel dan menampilkannya.

  3. Di dalam GamePanel, constructor:

    • Membuat dua paddle dan satu bola.

    • Mengatur ukuran panel dan background.

    • Menambahkan KeyListener.

    • Memulai Thread game.

  4. Thread menjalankan method run(), yang berisi game loop:

    • Panggil move() → update posisi paddle & bola.

    • Panggil checkCollision() → cek pantulan & scoring.

    • Panggil repaint() → memicu paint() dan menggambar ulang layar.

  5. Saat user menekan/melepas tombol, event akan diteruskan ke Paddle melalui keyPressed() dan keyReleased().


4. Implementasi Program

4.1 PongGame.java

/**
 * Kelas utama untuk menjalankan game Pong.
 * Memanggil GameFrame sebagai window utama.
 */
public class PongGame {
    public static void main(String[] args) {
        new GameFrame();
    }
}

4.2 GameFrame.java

import javax.swing.JFrame;

/**
 * GameFrame
 * Window utama untuk game Pong.
 * Berisi satu GamePanel di dalamnya.
 */
public class GameFrame extends JFrame {

    public GameFrame() {
        this.add(new GamePanel());
        this.setTitle("Pong - Erlangga");
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.pack();                // ukuran frame mengikuti GamePanel
        this.setLocationRelativeTo(null); // tampil di tengah layar
        this.setVisible(true);
    }
}

4.3 GamePanel.java

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

/**
 * GamePanel
 * Panel utama yang menangani:
 * - Gambar objek (paddle, bola, score)
 * - Game loop (Runnable)
 * - Input keyboard (KeyListener)
 */
public class GamePanel extends JPanel implements Runnable, KeyListener {

    static final int GAME_WIDTH = 800;
    static final int GAME_HEIGHT = 600;
    static final Dimension SCREEN_SIZE = new Dimension(GAME_WIDTH, GAME_HEIGHT);

    Thread gameThread;
    Image image;
    Graphics graphics;

    Paddle paddle1;
    Paddle paddle2;
    Ball ball;

    int player1Score = 0;
    int player2Score = 0;

    boolean running = true;

    public GamePanel() {
        newPaddles();
        newBall();

        this.setPreferredSize(SCREEN_SIZE);
        this.setBackground(Color.BLACK);
        this.setFocusable(true);
        this.addKeyListener(this);

        gameThread = new Thread(this);
        gameThread.start();
    }

    /**
     * Membuat bola baru di tengah.
     */
    public void newBall() {
        ball = new Ball(GAME_WIDTH / 2 - Ball.DIAMETER / 2,
                        GAME_HEIGHT / 2 - Ball.DIAMETER / 2);
    }

    /**
     * Membuat paddle baru (reset posisi).
     */
    public void newPaddles() {
        paddle1 = new Paddle(
                20,
                GAME_HEIGHT / 2 - Paddle.PADDLE_HEIGHT / 2,
                1
        );
        paddle2 = new Paddle(
                GAME_WIDTH - 20 - Paddle.PADDLE_WIDTH,
                GAME_HEIGHT / 2 - Paddle.PADDLE_HEIGHT / 2,
                2
        );
    }

    @Override
    public void paint(Graphics g) {
        // Double buffering: gambar ke image dulu
        image = createImage(getWidth(), getHeight());
        graphics = image.getGraphics();
        draw(graphics);
        g.drawImage(image, 0, 0, this);
    }

    /**
     * Menggambar semua objek game.
     */
    public void draw(Graphics g) {
        // Background
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);

        // Garis tengah putus-putus
        g.setColor(Color.GRAY);
        for (int i = 0; i < GAME_HEIGHT; i += 30) {
            g.fillRect(GAME_WIDTH / 2 - 2, i, 4, 20);
        }

        // Score
        g.setColor(Color.WHITE);
        g.setFont(new Font("Consolas", Font.BOLD, 32));
        g.drawString(String.valueOf(player1Score), GAME_WIDTH / 2 - 60, 40);
        g.drawString(String.valueOf(player2Score), GAME_WIDTH / 2 + 40, 40);

        // Paddle & bola
        paddle1.draw(g);
        paddle2.draw(g);
        ball.draw(g);
    }

    /**
     * Update posisi paddle dan bola.
     */
    public void move() {
        paddle1.move();
        paddle2.move();
        ball.move();
    }

    /**
     * Cek benturan (collision) dan scoring.
     */
    public void checkCollision() {
        // Pantulan bola di tepi atas & bawah
        if (ball.y <= 0) {
            ball.setYDirection(-ball.yVelocity);
        }
        if (ball.y >= GAME_HEIGHT - Ball.DIAMETER) {
            ball.setYDirection(-ball.yVelocity);
        }

        // Batas gerak paddle (tidak boleh keluar layar)
        if (paddle1.y < 0) {
            paddle1.y = 0;
        }
        if (paddle1.y > GAME_HEIGHT - Paddle.PADDLE_HEIGHT) {
            paddle1.y = GAME_HEIGHT - Paddle.PADDLE_HEIGHT;
        }
        if (paddle2.y < 0) {
            paddle2.y = 0;
        }
        if (paddle2.y > GAME_HEIGHT - Paddle.PADDLE_HEIGHT) {
            paddle2.y = GAME_HEIGHT - Paddle.PADDLE_HEIGHT;
        }

        // Bola menabrak paddle kiri
        if (ball.getBounds().intersects(paddle1.getBounds())) {
            ball.x = paddle1.x + Paddle.PADDLE_WIDTH;
            ball.setXDirection(Math.abs(ball.xVelocity)); // pastikan ke kanan
        }

        // Bola menabrak paddle kanan
        if (ball.getBounds().intersects(paddle2.getBounds())) {
            ball.x = paddle2.x - Ball.DIAMETER;
            ball.setXDirection(-Math.abs(ball.xVelocity)); // pastikan ke kiri
        }

        // Bola keluar di sisi kiri (poin untuk Player 2)
        if (ball.x <= 0) {
            player2Score++;
            newBall();
            newPaddles();
        }

        // Bola keluar di sisi kanan (poin untuk Player 1)
        if (ball.x >= GAME_WIDTH - Ball.DIAMETER) {
            player1Score++;
            newBall();
            newPaddles();
        }
    }

    @Override
    public void run() {
        // Game loop ~60 FPS
        long lastTime = System.nanoTime();
        double ticksPerSecond = 60.0;
        double ns = 1000000000 / ticksPerSecond;
        double delta = 0;

        while (running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;

            while (delta >= 1) {
                move();
                checkCollision();
                repaint();
                delta--;
            }
        }
    }

    // ==== KeyListener ====

    @Override
    public void keyPressed(KeyEvent e) {
        paddle1.keyPressed(e);
        paddle2.keyPressed(e);
    }

    @Override
    public void keyReleased(KeyEvent e) {
        paddle1.keyReleased(e);
        paddle2.keyReleased(e);
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // tidak digunakan
    }
}

4.4 Paddle.java

import java.awt.*;
import java.awt.event.KeyEvent;

/**
 * Paddle
 * Mewakili paddle kiri dan kanan.
 * Kontrol:
 * - Paddle 1 (kiri) : W / S
 * - Paddle 2 (kanan): UP / DOWN
 */
public class Paddle {

    public static final int PADDLE_WIDTH = 20;
    public static final int PADDLE_HEIGHT = 100;

    int x;
    int y;
    int yVelocity;
    int speed = 10;

    int paddleNumber; // 1 = kiri, 2 = kanan

    public Paddle(int x, int y, int paddleNumber) {
        this.x = x;
        this.y = y;
        this.paddleNumber = paddleNumber;
    }

    public void draw(Graphics g) {
        g.setColor(Color.WHITE);
        g.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
    }

    public void move() {
        y += yVelocity;
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
    }

    /**
     * Mengatur kecepatan paddle saat tombol ditekan.
     */
    public void keyPressed(KeyEvent e) {
        switch (e.getKeyCode()) {
            // Paddle 1 (kiri): W/S
            case KeyEvent.VK_W:
                if (paddleNumber == 1) {
                    yVelocity = -speed;
                }
                break;
            case KeyEvent.VK_S:
                if (paddleNumber == 1) {
                    yVelocity = speed;
                }
                break;

            // Paddle 2 (kanan): UP/DOWN
            case KeyEvent.VK_UP:
                if (paddleNumber == 2) {
                    yVelocity = -speed;
                }
                break;
            case KeyEvent.VK_DOWN:
                if (paddleNumber == 2) {
                    yVelocity = speed;
                }
                break;
        }
    }

    /**
     * Menghentikan paddle saat tombol dilepas.
     */
    public void keyReleased(KeyEvent e) {
        switch (e.getKeyCode()) {
            case KeyEvent.VK_W:
            case KeyEvent.VK_S:
                if (paddleNumber == 1) {
                    yVelocity = 0;
                }
                break;
            case KeyEvent.VK_UP:
            case KeyEvent.VK_DOWN:
                if (paddleNumber == 2) {
                    yVelocity = 0;
                }
                break;
        }
    }
}

4.5 Ball.java

import java.awt.*;
import java.util.Random;

/**
 * Ball
 * Mewakili bola pada game Pong.
 * Memiliki posisi, kecepatan, dan bisa dipantulkan.
 */
public class Ball {

    public static final int DIAMETER = 30;

    int x;
    int y;
    int xVelocity;
    int yVelocity;
    int initialSpeed = 5;

    Random random;

    public Ball(int x, int y) {
        this.x = x;
        this.y = y;
        this.random = new Random();

        // Arah awal bola random (kanan/kiri & atas/bawah)
        int randomXDirection = random.nextBoolean() ? 1 : -1;
        int randomYDirection = random.nextBoolean() ? 1 : -1;

        xVelocity = initialSpeed * randomXDirection;
        yVelocity = initialSpeed * randomYDirection;
    }

    public void setXDirection(int xDirection) {
        xVelocity = xDirection;
    }

    public void setYDirection(int yDirection) {
        yVelocity = yDirection;
    }

    /**
     * Update posisi bola berdasarkan velocity.
     */
    public void move() {
        x += xVelocity;
        y += yVelocity;
    }

    public void draw(Graphics g) {
        g.setColor(Color.GREEN);
        g.fillOval(x, y, DIAMETER, DIAMETER);
    }

    public Rectangle getBounds() {
        return new Rectangle(x, y, DIAMETER, DIAMETER);
    }
}

5. Cara Menjalankan Program

  1. Buat project Java baru

  2. Buat 5 file dengan nama:

    • PongGame.java

    • GameFrame.java

    • GamePanel.java

    • Paddle.java

    • Ball.java
      lalu copy–paste kode di atas sesuai nama kelasnya.

  3. Compile semua class.

  4. Jalankan program dari PongGame.main().

Kontrol Permainan

  • Paddle kiri (Player 1)

    • W → naik

    • S → turun

  • Paddle kanan (Player 2)

    • ↑ (UP) → naik

    • ↓ (DOWN) → turun

Setiap kali bola keluar dari sisi kiri/kanan layar, pemain lawan akan mendapatkan skor dan permainan otomatis mereset posisi bola dan paddle ke tengah.


Tidak ada komentar:

Posting Komentar

PBO A - Pertemuan 15 - Pong!

Nama : Erlangga Rizqi Dwi Raswanto NRP : 5025241179 Kelas : PBO A 1. Pendahuluan Pada pertemuan 15, kita mempraktikkan pembuatan game...