Hướng dẫn làm game java đơn giản năm 2024

Breakout là một tựa game khá quen thuộc với nhiều người. Ngày hôm nay mình sẽ hướng dẫn các bạn làm game này với ngôn ngữ lập trình Java

Chuẩn bị

Để làm game này, các bạn cần chuẩn bị những thứ như sau:

  • Một cái máy tính (Windows, MacOS hoặc Ubuntu)
  • Một IDE được cài sẵn (khuyến khích dùng IDE Eclipse)
  • Asset được vẽ sẵn cho game.

Lưu ý: để làm được các bạn cần dùng JDK10

Các bước thực hiện

Bước 1: Tạo một Project mới

Bước 2: Tạo class cho game:

Trong bài viết này, chúng ta sẽ dùng 7 class:

  1. Commons.java: Chứa biến và các số lượng gạch.
  2. Sprite.java: Để lấy hình ảnh và màu sắc.
  3. Brick.java: Để code cho những viên gạch biến mất sau khi bị bóng chạm vào.
  4. Ball.java: Setup cho bóng.
  5. Paddle.java: Đặt cho con nảy ở dưới game.
  6. Board.java: Ở đây chúng ta để luật và cách chơi game này.
  7. Breakout.java: Đây là class chính cho game của chúng ta.

Bước 3: Bắt đầu code game!

Ok, giờ chúng ta tạo file Commons.java trước. Ở đây là khai báo các biến cơ bản cho game. Không có gì khó ở đây cả nên các bạn đừng sợ vội nha :)

Các bạn viết đoạn code sau vào:

public interface Commons {
    int WIDTH = 300;
    int HEIGHT = 400;
    int BOTTOM_EDGE = 390;
    int N_OF_BRICKS = 30;
    int INIT_PADDLE_X = 200;
    int INIT_PADDLE_Y = 360;
    int INIT_BALL_X = 230;
    int INIT_BALL_Y = 355;
    int PERIOD = 10;
}

Tiếp đến là file Sprite.java, file này để chứa các đối tượng (Paddle, Brick,....). Code file như sau:

import java.awt.Image;
import java.awt.Rectangle;
public class Sprite {
    int x;
    int y;
    int imageWidth;
    int imageHeight;
    Image image;
    protected void setX(int x) {
        this.x = x;
    }
    int getX() {
        return x;
    }
    protected void setY(int y) {
        this.y = y;
    }
    int getY() {
        return y;
    }
    int getImageWidth() {
        return imageWidth;
    }
    int getImageHeight() {
        return imageHeight;
    }
    Image getImage() {
        return image;
    }
    Rectangle getRect() {
        return new Rectangle(x, y,
                image.getWidth(null), image.getHeight(null));
    }
    void getImageDimensions() {
        imageWidth = image.getWidth(null);
        imageHeight = image.getHeight(null);
    }
}

File này hơi dài và khó nên các bạn kiên trì viết code nhaaa

Sau đó là file Brick.java để đặt các hòn gạch. File này dùng để đặt chức năng cho các viên gạch. Code của file này như sau:

import javax.swing.ImageIcon;
public class Brick extends Sprite {
    private boolean destroyed;
    public Brick(int x, int y) {
        initBrick(x, y);
    }
    private void initBrick(int x, int y) {
        this.x = x;
        this.y = y;
        destroyed = false;
        loadImage();
        getImageDimensions();
    }
    private void loadImage() {
        var ii = new ImageIcon("src/resources/brick.png");
        image = ii.getImage();
    }
    boolean isDestroyed() {
        return destroyed;
    }
    void setDestroyed(boolean val) {
        destroyed = val;
    }
}

Sau đó là file

import java.awt.Image;
import java.awt.Rectangle;
public class Sprite {
    int x;
    int y;
    int imageWidth;
    int imageHeight;
    Image image;
    protected void setX(int x) {
        this.x = x;
    }
    int getX() {
        return x;
    }
    protected void setY(int y) {
        this.y = y;
    }
    int getY() {
        return y;
    }
    int getImageWidth() {
        return imageWidth;
    }
    int getImageHeight() {
        return imageHeight;
    }
    Image getImage() {
        return image;
    }
    Rectangle getRect() {
        return new Rectangle(x, y,
                image.getWidth(null), image.getHeight(null));
    }
    void getImageDimensions() {
        imageWidth = image.getWidth(null);
        imageHeight = image.getHeight(null);
    }
}

0:

package com.zetcode;
import javax.swing.ImageIcon;
public class Ball extends Sprite {
    private int xdir;
    private int ydir;
    public Ball() {
        initBall();
    }
    private void initBall() {
        xdir = 1;
        ydir = -1;
        loadImage();
        getImageDimensions();
        resetState();
    }
    private void loadImage() {
        var ii = new ImageIcon("src/resources/ball.png");
        image = ii.getImage();
    }
    void move() {
        x += xdir;
        y += ydir;
        if (x == 0) {
            setXDir(1);
        }
        if (x == Commons.WIDTH - imageWidth) {
            System.out.println(imageWidth);
            setXDir(-1);
        }
        if (y == 0) {
            setYDir(1);
        }
    }
    private void resetState() {
        x = Commons.INIT_BALL_X;
        y = Commons.INIT_BALL_Y;
    }
    void setXDir(int x) {
        xdir = x;
    }
    void setYDir(int y) {
        ydir = y;
    }
    int getYDir() {
        return ydir;
    }
}

Tiếp theo đó là tới Paddle.java

Code file như sau:

package com.zetcode;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
public class Paddle extends Sprite  {
    private int dx;
    public Paddle() {
        initPaddle();
    }
    private void initPaddle() {
        loadImage();
        getImageDimensions();
        resetState();
    }
    private void loadImage() {
        var ii = new ImageIcon("src/resources/paddle.png");
        image = ii.getImage();
    }
    void move() {
        x += dx;
        if (x <= 0) {
            x = 0;
        }
        if (x >= Commons.WIDTH - imageWidth) {
            x = Commons.WIDTH - imageWidth;
        }
    }
    void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            dx = -1;
        }
        if (key == KeyEvent.VK_RIGHT) {
            dx = 1;
        }
    }
    void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        if (key == KeyEvent.VK_LEFT) {
            dx = 0;
        }
        if (key == KeyEvent.VK_RIGHT) {
            dx = 0;
        }
    }
    private void resetState() {
        x = Commons.INIT_PADDLE_X;
        y = Commons.INIT_PADDLE_Y;
    }
}

Chúng ta cùng chuyển tới

import java.awt.Image;
import java.awt.Rectangle;
public class Sprite {
    int x;
    int y;
    int imageWidth;
    int imageHeight;
    Image image;
    protected void setX(int x) {
        this.x = x;
    }
    int getX() {
        return x;
    }
    protected void setY(int y) {
        this.y = y;
    }
    int getY() {
        return y;
    }
    int getImageWidth() {
        return imageWidth;
    }
    int getImageHeight() {
        return imageHeight;
    }
    Image getImage() {
        return image;
    }
    Rectangle getRect() {
        return new Rectangle(x, y,
                image.getWidth(null), image.getHeight(null));
    }
    void getImageDimensions() {
        imageWidth = image.getWidth(null);
        imageHeight = image.getHeight(null);
    }
}

1 nhé

Code:

import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class Board extends JPanel {
    private Timer timer;
    private String message = "Game Over";
    private Ball ball;
    private Paddle paddle;
    private Brick[] bricks;
    private boolean inGame = true;
    public Board() {
        initBoard();
    }
    private void initBoard() {
        addKeyListener(new TAdapter());
        setFocusable(true);
        setPreferredSize(new Dimension(Commons.WIDTH, Commons.HEIGHT));
        gameInit();
    }
    private void gameInit() {
        bricks = new Brick[Commons.N_OF_BRICKS];
        ball = new Ball();
        paddle = new Paddle();
        int k = 0;
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 6; j++) {
                bricks[k] = new Brick(j * 40 + 30, i * 10 + 50);
                k++;
            }
        }
        timer = new Timer(Commons.PERIOD, new GameCycle());
        timer.start();
    }
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        var g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);
        if (inGame) {
            drawObjects(g2d);
        } else {
            gameFinished(g2d);
        }
        Toolkit.getDefaultToolkit().sync();
    }
    private void drawObjects(Graphics2D g2d) {
        g2d.drawImage(ball.getImage(), ball.getX(), ball.getY(),
                ball.getImageWidth(), ball.getImageHeight(), this);
        g2d.drawImage(paddle.getImage(), paddle.getX(), paddle.getY(),
                paddle.getImageWidth(), paddle.getImageHeight(), this);
        for (int i = 0; i < Commons.N_OF_BRICKS; i++) {
            if (!bricks[i].isDestroyed()) {
                g2d.drawImage(bricks[i].getImage(), bricks[i].getX(),
                        bricks[i].getY(), bricks[i].getImageWidth(),
                        bricks[i].getImageHeight(), this);
            }
        }
    }
    private void gameFinished(Graphics2D g2d) {
        var font = new Font("Verdana", Font.BOLD, 18);
        FontMetrics fontMetrics = this.getFontMetrics(font);
        g2d.setColor(Color.BLACK);
        g2d.setFont(font);
        g2d.drawString(message,
                (Commons.WIDTH - fontMetrics.stringWidth(message)) / 2,
                Commons.WIDTH / 2);
    }
    private class TAdapter extends KeyAdapter {
        @Override
        public void keyReleased(KeyEvent e) {
            paddle.keyReleased(e);
        }
        @Override
        public void keyPressed(KeyEvent e) {
            paddle.keyPressed(e);
        }
    }
    private class GameCycle implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            doGameCycle();
        }
    }
    private void doGameCycle() {
        ball.move();
        paddle.move();
        checkCollision();
        repaint();
    }
    private void stopGame() {
        inGame = false;
        timer.stop();
    }
    private void checkCollision() {
        if (ball.getRect().getMaxY() > Commons.BOTTOM_EDGE) {
            stopGame();
        }
        for (int i = 0, j = 0; i < Commons.N_OF_BRICKS; i++) {
            if (bricks[i].isDestroyed()) {
                j++;
            }
            if (j == Commons.N_OF_BRICKS) {
                message = "Victory";
                stopGame();
            }
        }
        if ((ball.getRect()).intersects(paddle.getRect())) {
            int paddleLPos = (int) paddle.getRect().getMinX();
            int ballLPos = (int) ball.getRect().getMinX();
            int first = paddleLPos + 8;
            int second = paddleLPos + 16;
            int third = paddleLPos + 24;
            int fourth = paddleLPos + 32;
            if (ballLPos < first) {
                ball.setXDir(-1);
                ball.setYDir(-1);
            }
            if (ballLPos >= first && ballLPos < second) {
                ball.setXDir(-1);
                ball.setYDir(-1 * ball.getYDir());
            }
            if (ballLPos >= second && ballLPos < third) {
                ball.setXDir(0);
                ball.setYDir(-1);
            }
            if (ballLPos >= third && ballLPos < fourth) {
                ball.setXDir(1);
                ball.setYDir(-1 * ball.getYDir());
            }
            if (ballLPos > fourth) {
                ball.setXDir(1);
                ball.setYDir(-1);
            }
        }
        for (int i = 0; i < Commons.N_OF_BRICKS; i++) {
            if ((ball.getRect()).intersects(bricks[i].getRect())) {
                int ballLeft = (int) ball.getRect().getMinX();
                int ballHeight = (int) ball.getRect().getHeight();
                int ballWidth = (int) ball.getRect().getWidth();
                int ballTop = (int) ball.getRect().getMinY();
                var pointRight = new Point(ballLeft + ballWidth + 1, ballTop);
                var pointLeft = new Point(ballLeft - 1, ballTop);
                var pointTop = new Point(ballLeft, ballTop - 1);
                var pointBottom = new Point(ballLeft, ballTop + ballHeight + 1);
                if (!bricks[i].isDestroyed()) {
                    if (bricks[i].getRect().contains(pointRight)) {
                        ball.setXDir(-1);
                    } else if (bricks[i].getRect().contains(pointLeft)) {
                        ball.setXDir(1);
                    }
                    if (bricks[i].getRect().contains(pointTop)) {
                        ball.setYDir(1);
                    } else if (bricks[i].getRect().contains(pointBottom)) {
                        ball.setYDir(-1);
                    }
                    bricks[i].setDestroyed(true);
                }
            }
        }
    }
}

Giải thích:

Ở chức năng gameInit() chúng ta sẽ tạo 1 bóng, 1 Paddle và 30 viên gạch

-----------

Ok, tới class chính thôi nào. Class này là

import java.awt.Image;
import java.awt.Rectangle;
public class Sprite {
    int x;
    int y;
    int imageWidth;
    int imageHeight;
    Image image;
    protected void setX(int x) {
        this.x = x;
    }
    int getX() {
        return x;
    }
    protected void setY(int y) {
        this.y = y;
    }
    int getY() {
        return y;
    }
    int getImageWidth() {
        return imageWidth;
    }
    int getImageHeight() {
        return imageHeight;
    }
    Image getImage() {
        return image;
    }
    Rectangle getRect() {
        return new Rectangle(x, y,
                image.getWidth(null), image.getHeight(null));
    }
    void getImageDimensions() {
        imageWidth = image.getWidth(null);
        imageHeight = image.getHeight(null);
    }
}

2

Code như sau:

package com.zetcode;
import javax.swing.JFrame;
import java.awt.EventQueue;
public class Breakout extends JFrame {
    public Breakout() {
        initUI();
    }
    private void initUI() {
        add(new Board());
        setTitle("Breakout");
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        setResizable(false);
        pack();
    }
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            var game = new Breakout();
            game.setVisible(true);
        });
    }
}

Thành phẩm

Vậy là các bạn đã hoàn thành hết Code của game rồi! Giờ mình sẽ trích link các Resources tại đây . Các bạn vui lòng vào link để lấy các Assets.

Cảm ơn các bạn đã đọc bài viết của mình và sau khi hoàn thiện hãy khoe thành phẩm của các bạn tại comment nhé.