Kamis, 27 November 2025

Pertemuan 15 - Pemrograman Game

 Nama    : Safa Mashita

 NRP      : 5025241022


 Pertemuan 15 - Pemrograman Game

Pada pertemuan ini, saya mempelajari cara membuat permainan klasik Pong menggunakan bahasa pemrograman Java dengan library Java Swing dan OOP (Object-Oriented Programming). Pong merupakan game sederhana yang meniru permainan tenis meja, terdiri dari dua paddle, satu bola, dan sistem skor.

A. Struktur Kelas dan Hubungan Antar Kelas


Program ini terdiri dari beberapa kelas utama:

  1. Pong

    • Class utama, mengelola game state, input, dan update logic.

  2. Ball

    • Mengatur pergerakan bola, deteksi tumbukan, dan sistem skor.

  3. Paddle

    • Objek pemain (player 1 dan player 2/bot).

  4. Renderer

    • Class untuk menggambar objek ke layar menggunakan paintComponent.

Hubungan antar kelas:

  • Pong membuat objek Ball, Paddle, dan Renderer.

  • Renderer memanggil method render() pada class Pong.

  • Ball berinteraksi langsung dengan objek Paddle untuk memeriksa tabrakan.

  • Seluruh kelas saling bekerja sesuai prinsip OOP untuk membentuk game lengkap.


B. Alur Program
  1. Program dimulai dari main() pada class Pong.

  2. Window game dibuat menggunakan JFrame.

  3. Timer berjalan setiap 20ms untuk meng-update game (game loop).

  4. Ketika pemain menekan Space, permainan dimulai.

  5. Setiap frame:

    • Bola bergerak sesuai kecepatan (motionX, motionY).

    • Paddle bergerak mengikuti input keyboard (W/S atau Up/Down).

    • Jika menggunakan mode bot, paddle kanan digerakkan otomatis.

  6. Bola memantul ketika menyentuh batas atas/bawah atau paddle.

  7. Jika bola melewati paddle, pemain lawan mendapat skor.

  8. Game berakhir ketika skor mencapai limit.


C. Penjelasan Kode

1. Class Ball

package pong;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class Ball
{

public int x, y, width = 25, height = 25;

public int motionX, motionY;

public Random random;

private Pong pong;

public int amountOfHits;

public Ball(Pong pong)
{
this.pong = pong;

this.random = new Random();

spawn();
}

public void update(Paddle paddle1, Paddle paddle2)
{
int speed = 5;

this.x += motionX * speed;
this.y += motionY * speed;

if (this.y + height - motionY > pong.height || this.y + motionY < 0)
{
if (this.motionY < 0)
{
this.y = 0;
this.motionY = random.nextInt(4);

if (motionY == 0)
{
motionY = 1;
}
}
else
{
this.motionY = -random.nextInt(4);
this.y = pong.height - height;

if (motionY == 0)
{
motionY = -1;
}
}
}

if (checkCollision(paddle1) == 1)
{
this.motionX = 1 + (amountOfHits / 5);
this.motionY = -2 + random.nextInt(4);

if (motionY == 0)
{
motionY = 1;
}

amountOfHits++;
}
else if (checkCollision(paddle2) == 1)
{
this.motionX = -1 - (amountOfHits / 5);
this.motionY = -2 + random.nextInt(4);

if (motionY == 0)
{
motionY = 1;
}

amountOfHits++;
}

if (checkCollision(paddle1) == 2)
{
paddle2.score++;
spawn();
}
else if (checkCollision(paddle2) == 2)
{
paddle1.score++;
spawn();
}
}

public void spawn()
{
this.amountOfHits = 0;
this.x = pong.width / 2 - this.width / 2;
this.y = pong.height / 2 - this.height / 2;

this.motionY = -2 + random.nextInt(4);

if (motionY == 0)
{
motionY = 1;
}

if (random.nextBoolean())
{
motionX = 1;
}
else
{
motionX = -1;
}
}

public int checkCollision(Paddle paddle)
{
if (this.x < paddle.x + paddle.width && this.x + width > paddle.x && this.y < paddle.y + paddle.height && this.y + height > paddle.y)
{
return 1; //bounce
}
else if ((paddle.x > x && paddle.paddleNumber == 1) || (paddle.x < x - width && paddle.paddleNumber == 2))
{
return 2; //score
}

return 0; //nothing
}

public void render(Graphics g)
{
g.setColor(Color.WHITE);
g.fillOval(x, y, width, height);
}

}

Kegunaan :

Mengatur bola dalam permainan: posisi, pergerakan, pantulan, tabrakan, dan kondisi ketika mencetak skor.

Atribut:

  • x, y : posisi bola di layar.

  • width, height : ukuran bola.

  • motionX, motionY : arah gerakan bola secara horizontal dan vertikal.

  • random : untuk menghasilkan arah acak ketika memantul atau spawn.

  • pong : mengakses ukuran layar dan objek utama.

  • amountOfHits : menghitung jumlah pantulan agar bola makin cepat.

Method:

  • Ball(Pong pong) : constructor, menyimpan referensi ke kelas Pong dan memanggil spawn().

  • update(paddle1, paddle2) : menggerakkan bola, mengecek tabrakan, mengecek skor, memberi pantulan.

  • spawn() : mereset posisi bola ke tengah dan memberi arah acak.

  • checkCollision(paddle) : mendeteksi tabrakan atau bola lewat paddle.

  • render(Graphics g) : menggambar bola sebagai lingkaran putih.


2. Class Paddle

package pong;

import java.awt.Color;
import java.awt.Graphics;

public class Paddle
{

public int paddleNumber;

public int x, y, width = 50, height = 250;

public int score;

public Paddle(Pong pong, int paddleNumber)
{
this.paddleNumber = paddleNumber;

if (paddleNumber == 1)
{
this.x = 0;
}

if (paddleNumber == 2)
{
this.x = pong.width - width;
}

this.y = pong.height / 2 - this.height / 2;
}

public void render(Graphics g)
{
g.setColor(Color.WHITE);
g.fillRect(x, y, width, height);
}

public void move(boolean up)
{
int speed = 15;

if (up)
{
if (y - speed > 0)
{
y -= speed;
}
else
{
y = 0;
}
}
else
{
if (y + height + speed < Pong.pong.height)
{
y += speed;
}
else
{
y = Pong.pong.height - height;
}
}
}

}


Kegunaan :

Mengatur paddle pemain, posisinya, gerakannya, dan tampilannya.

Atribut:

  • paddleNumber : menentukan paddle kiri (1) atau kanan (2).

  • x, y : posisi paddle.

  • width, height : ukuran paddle.

  • score : skor pemain tersebut.

Method:

  • Paddle(Pong, int) → constructor, menentukan posisi awal paddle berdasarkan nomor.

  • render(Graphics g) → menggambar paddle sebagai kotak putih.

  • move(boolean up) → menggerakkan paddle ke atas atau ke bawah dengan batas layar.

3. Class Pong

package pong;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.Timer;

public class Pong implements ActionListener, KeyListener
{

public static Pong pong;

public int width = 700, height = 700;

public Renderer renderer;

public Paddle player1;

public Paddle player2;

public Ball ball;

public boolean bot = false, selectingDifficulty;

public boolean w, s, up, down;

public int gameStatus = 0, scoreLimit = 7, playerWon; //0 = Menu, 1 = Paused, 2 = Playing, 3 = Over

public int botDifficulty, botMoves, botCooldown = 0;

public Random random;

public JFrame jframe;

public Pong()
{
Timer timer = new Timer(20, this);
random = new Random();

jframe = new JFrame("Pong");

renderer = new Renderer();

jframe.setSize(width + 15, height + 35);
jframe.setVisible(true);
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.add(renderer);
jframe.addKeyListener(this);

timer.start();
}

public void start()
{
gameStatus = 2;
player1 = new Paddle(this, 1);
player2 = new Paddle(this, 2);
ball = new Ball(this);
}

public void update()
{
if (player1.score >= scoreLimit)
{
playerWon = 1;
gameStatus = 3;
}

if (player2.score >= scoreLimit)
{
gameStatus = 3;
playerWon = 2;
}

if (w)
{
player1.move(true);
}
if (s)
{
player1.move(false);
}

if (!bot)
{
if (up)
{
player2.move(true);
}
if (down)
{
player2.move(false);
}
}
else
{
if (botCooldown > 0)
{
botCooldown--;

if (botCooldown == 0)
{
botMoves = 0;
}
}

if (botMoves < 10)
{
if (player2.y + player2.height / 2 < ball.y)
{
player2.move(false);
botMoves++;
}

if (player2.y + player2.height / 2 > ball.y)
{
player2.move(true);
botMoves++;
}

if (botDifficulty == 0)
{
botCooldown = 20;
}
if (botDifficulty == 1)
{
botCooldown = 15;
}
if (botDifficulty == 2)
{
botCooldown = 10;
}
}
}

ball.update(player1, player2);
}

public void render(Graphics2D g)
{
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

if (gameStatus == 0)
{
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", 1, 50));

g.drawString("PONG", width / 2 - 75, 50);

if (!selectingDifficulty)
{
g.setFont(new Font("Arial", 1, 30));

g.drawString("Press Space to Play", width / 2 - 150, height / 2 - 25);
g.drawString("Press Shift to Play with Bot", width / 2 - 200, height / 2 + 25);
g.drawString("<< Score Limit: " + scoreLimit + " >>", width / 2 - 150, height / 2 + 75);
}
}

if (selectingDifficulty)
{
String string = botDifficulty == 0 ? "Easy" : (botDifficulty == 1 ? "Medium" : "Hard");

g.setFont(new Font("Arial", 1, 30));

g.drawString("<< Bot Difficulty: " + string + " >>", width / 2 - 180, height / 2 - 25);
g.drawString("Press Space to Play", width / 2 - 150, height / 2 + 25);
}

if (gameStatus == 1)
{
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", 1, 50));
g.drawString("PAUSED", width / 2 - 103, height / 2 - 25);
}

if (gameStatus == 1 || gameStatus == 2)
{
g.setColor(Color.WHITE);

g.setStroke(new BasicStroke(5f));

g.drawLine(width / 2, 0, width / 2, height);

g.setStroke(new BasicStroke(2f));

g.drawOval(width / 2 - 150, height / 2 - 150, 300, 300);

g.setFont(new Font("Arial", 1, 50));

g.drawString(String.valueOf(player1.score), width / 2 - 90, 50);
g.drawString(String.valueOf(player2.score), width / 2 + 65, 50);

player1.render(g);
player2.render(g);
ball.render(g);
}

if (gameStatus == 3)
{
g.setColor(Color.WHITE);
g.setFont(new Font("Arial", 1, 50));

g.drawString("PONG", width / 2 - 75, 50);

if (bot && playerWon == 2)
{
g.drawString("The Bot Wins!", width / 2 - 170, 200);
}
else
{
g.drawString("Player " + playerWon + " Wins!", width / 2 - 165, 200);
}

g.setFont(new Font("Arial", 1, 30));

g.drawString("Press Space to Play Again", width / 2 - 185, height / 2 - 25);
g.drawString("Press ESC for Menu", width / 2 - 140, height / 2 + 25);
}
}

@Override
public void actionPerformed(ActionEvent e)
{
if (gameStatus == 2)
{
update();
}

renderer.repaint();
}

public static void main(String[] args)
{
pong = new Pong();
}

@Override
public void keyPressed(KeyEvent e)
{
int id = e.getKeyCode();

if (id == KeyEvent.VK_W)
{
w = true;
}
else if (id == KeyEvent.VK_S)
{
s = true;
}
else if (id == KeyEvent.VK_UP)
{
up = true;
}
else if (id == KeyEvent.VK_DOWN)
{
down = true;
}
else if (id == KeyEvent.VK_RIGHT)
{
if (selectingDifficulty)
{
if (botDifficulty < 2)
{
botDifficulty++;
}
else
{
botDifficulty = 0;
}
}
else if (gameStatus == 0)
{
scoreLimit++;
}
}
else if (id == KeyEvent.VK_LEFT)
{
if (selectingDifficulty)
{
if (botDifficulty > 0)
{
botDifficulty--;
}
else
{
botDifficulty = 2;
}
}
else if (gameStatus == 0 && scoreLimit > 1)
{
scoreLimit--;
}
}
else if (id == KeyEvent.VK_ESCAPE && (gameStatus == 2 || gameStatus == 3))
{
gameStatus = 0;
}
else if (id == KeyEvent.VK_SHIFT && gameStatus == 0)
{
bot = true;
selectingDifficulty = true;
}
else if (id == KeyEvent.VK_SPACE)
{
if (gameStatus == 0 || gameStatus == 3)
{
if (!selectingDifficulty)
{
bot = false;
}
else
{
selectingDifficulty = false;
}

start();
}
else if (gameStatus == 1)
{
gameStatus = 2;
}
else if (gameStatus == 2)
{
gameStatus = 1;
}
}
}

@Override
public void keyReleased(KeyEvent e)
{
int id = e.getKeyCode();

if (id == KeyEvent.VK_W)
{
w = false;
}
else if (id == KeyEvent.VK_S)
{
s = false;
}
else if (id == KeyEvent.VK_UP)
{
up = false;
}
else if (id == KeyEvent.VK_DOWN)
{
down = false;
}
}

@Override
public void keyTyped(KeyEvent e)
{

}
}


Kegunaan :

Menjadi inti game. Mengatur jendela permainan, update logika, input pemain, mode bot, scoring, dan menggambar semua objek.

Atribut:

  • pong : instance global

  • width, height : ukuran layar game

  • renderer : panel gambar

  • player1, player2 : paddle pemain

  • ball : bola

  • bot : apakah mode bot aktif

  • selectingDifficulty : apakah sedang memilih tingkat bot

  • w, s, up, down : status tombol keyboard

  • gameStatus : status game (menu, pause, main, selesai)

  • scoreLimit : batas skor untuk menang

  • playerWon : menyimpan nomor pemain yang menang

  • botDifficulty : tingkat kesulitan bot

  • botMoves, botCooldown : pengaturan perilaku bot

  • random : generator angka random

  • jframe : jendela game

Method:

  • Pong() : constructor, membuat window, timer, dan input listener

  • start() : memulai ulang game (buat objek paddle dan bola baru)

  • update() : logika game seperti gerakan pemain, bot, dan bola

  • render(Graphics2D g) : menggambar semua elemen game sesuai statusnya

  • actionPerformed() : dipanggil oleh timer untuk update dan repaint

  • keyPressed() / keyReleased() / keyTyped() : menangani input keyboard

  • main() : menjalankan game

4. Class Renderer

package pong;

import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class Renderer extends JPanel
{

private static final long serialVersionUID = 1L;

@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);

Pong.pong.render((Graphics2D) g);
}

}

Kegunaan :

Menggambar ulang tampilan game di layar setiap frame.

Atribut:

  • Tidak ada yang penting selain serialVersionUID.

Method:

  • paintComponent(Graphics g) : membersihkan layar dan memanggil pong.render().


D. Tampilan Game


Jika memencet space, maka bermain dengan lawan.




Jika memencet shift, maka bermain dengan bot.





E. Kesimpulan

Dari tugas ini, saya mempelajari konsep dasar pemrograman game menggunakan Java, yaitu:

  1. Game Loop (update dan render) menggunakan Timer.

  2. Pemodelan objek game dengan OOP, seperti Ball dan Paddle.

  3. Collision detection antara bola dan paddle.

  4. Handling keyboard input untuk kontrol permainan.

  5. AI sederhana untuk bot yang mengikuti posisi bola.

  6. Pembuatan antarmuka grafik menggunakan Java Swing.


Tidak ada komentar:

Posting Komentar

Pertemuan 16 - Membuat Aplikasi CRUD Java

 Nama          : Safa Mashita  NRP            : 5025241022 Membuat Aplikasi CRUD Java - Manajemen Buku Perpustakaan Pada pertemuan ini, saya...