Lập trình game java cơ bản - Hoàn thành game Lucky Box

Xin chào các bạn, chào mừng các bạn quay trở lại với blog.
Tiếp tục bài hôm trước thì hôm nay mình sẽ cùng các bạn làm "hoàn chỉnh" game hôm trước.
Bài hôm trước chúng ta đã cùng nhau setup các lớp cơ bản trong game, bài hôm nay chúng ta sẽ cài đặt lớp MainBoard - lớp này có chức năng xử lý và render hình ảnh.

Bây giờ chúng ta sẽ bắt tay vào làm nhé.
Giống những bài hôm trước, game của chúng ta sẽ sử dụng Timer, do đó chúng ta cần implements lại ActionListener, ngoài ra chúng ta cần sử dụng các thuộc tính chung nằm trong interface Common, nên ta sẽ implements nó luôn.

Trong game của ta sẽ có nhân vật chính là đối tượng Box, một danh sách các Enemy và danh sách các Item nên chúng ta sẽ có các thuộc tính cơ bản sau:


public class MainBoard extends JPanel implements ActionListener, Common {
 private ArrayList < item > items;
 private ArrayList < enemy > enemies;
 private Box box;
 private Timer timer;
 private boolean inGame;
 private int point;

 public MainBoard() {
  initBoard();
 }
}

Chúng ta thêm thuộc tính inGame để kiểm tra xem game đã kết thúc hay chưa và thêm thuộc tính point để tính điểm cho người chơi.

Phương thức khởi tạo ta sẽ tạo ra một phương thức initBoard() để cho code dễ nhìn hơn (về cơ bản thì nếu một công việc bạn chia ra cho nhiều người làm thì nó sẽ phần nào nhanh hơn đúng không) và sau này sửa code sẽ dễ dàng hơn :).

Ok, bây giờ chúng ta sẽ tạo ra phương thức initBoard() - phương thức này sẽ khởi tạo các đối tượng mà game của chúng ta sử dụng.

private void initBoard() {
  inGame = true;
  point = 0;
  addMouseMotionListener(new TAdapter());
  setFocusable(true);
  setBackground(Color.lightGray);
  setDoubleBuffered(true);
  box = new Box(INIT_BOX_X, INIT_BOX_Y);
  enemies = new ArrayList < enemy >();
  items = new ArrayList <item >();
  initEnemy();
  initItem();
  inGame = true;
  timer = new Timer(DELAY, this);
  timer.start();
 }
Mọi thứ đã quá quen thuộc rồi đúng không nào (Bạn nào chưa xem các bài trước thì có thể xem lại nhé) :D, ở đây tạm thời sẽ báo lỗi do bạn chưa tạo ra phương thức initEnemy(), initItem() và lớp TAdapter, chúng ta sẽ tạo lớp này nằm trong lớp MainBoard luôn ngay bây giờ.

Đầu tiền là phương thức initEnemy()

private void initEnemy() {
  int initX = (int) (Math.random() * (Common.WIDTH - 50));
  int initY = (int) (Math.random() * (Common.HEIGHT - 50));
  Enemy enemy = new Enemy(initX, initY);
  enemies.add(enemy);
 }
Đầu tiên là random tọa độ của enemy sau đó tạo mới và add nó vào danh sách các enemies.

private void initItem() {
  int initX = (int) (Math.random() * (Common.WIDTH - 50));
  int initY = (int) (Math.random() * (Common.HEIGHT - 50));
  Item item = new Item(initX, initY);
  items.add(item);
 }
Tương tự cho phương thức initEnemy().

Bây giờ là phương thức paintComponent(...) có chức năng render hình ảnh. phương thức này ta cần cài đặt như sau:

@Override
 protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  if (inGame) {
   drawObject(g);
  } else {
   drawGameOver(g);
  }
 }
Hiểu đơn giản là: nếu game đang còn được chơi (Nhân vật chính chưa chết) thì ta sẽ vẽ lại các nhân vật trong trò chơi, ngược lại chúng ta sẽ vẽ ra màn hình Game Over :).
Bây giờ ta tiếp tục tạo 2 phương thức drawObject(Graphics g) và drawGameOver(Graphics g).

private void drawObject(Graphics g) {
  // Draw Box
  g.drawImage(box.getImage(), box.getX(), box.getY(), this);
  // Draw Enemies
  for (Enemy enemy : enemies) {
   g.drawImage(enemy.getImage(), enemy.getX(), enemy.getY(), this);
  }
  // Draw Item
  for (Item item : items) {
   g.drawImage(item.getImage(), item.getX(), item.getY(), this);
  }

  // Draw Point
  Font font = new Font("Arial", Font.BOLD, 12);
  g.setColor(Color.BLACK);
  g.setFont(font);
  g.drawString("Your point: " + point, 5, 15);
 }
Mình đã comment ở code rồi nên không cần giải thích nữa :))
Tương tự phương thức drawGameOver(Graphics g):

 
 private void drawGameOver(Graphics g) {
  Font font = new Font("Helvetica", Font.BOLD, 20);
  g.setColor(Color.BLACK);
  g.setFont(font);
  g.drawString("Game over!", 350, Common.HEIGHT / 2);
  g.drawString("Score: " + point, 370, Common.HEIGHT / 2 + 30);
 }
Lúc game over ta sẽ vẽ ra màn hình chữ "Game over!" cùng với điểm mà người chơi đạt được.

Do chúng ta implements ActionListener nên bây giờ chúng ta cần Override lại phương thức actionPerformed(...)

 
@Override
 public void actionPerformed(ActionEvent e) {
  inGame();
  // Update Object
  updateBox();
  updateEnemy();
  updateItem();
  // Check collision
  checkCollision();
  // Repaint
  repaint();
 }
Các phương thức ở trong chúng ta sẽ tiến hành cài đặt ngay bên dưới:

 
private void inGame() {
  if (!inGame) {
   timer.stop();
  }
 }

Nếu game over rồi thì ta sẽ dừng timer lại.
Tiếp đến là phương thức updateBox()
 
 private void updateBox() {
  box.move();
 }

Việc update đơn giản chỉ là di chuyển nhân vật chính của chúng ta.
 
private void updateEnemy() {
  for (int i = 0; i < enemies.size(); i++) {
   Enemy enemy = enemies.get(i);
   if (enemy.isVisible()) {
    enemy.move();
   } else {
    enemies.remove(i);
   }
  }
 }

Ở đây cái isVisible cũng không cần thiết lắm, vì nếu enemy chạm nhân vật chính thì game over rồi nên cũng không cần xóa enemy khỏi List nữa. Các bạn có thể nâng cấp game chẳng hạn như: ăn item nào đấy thì sẽ cho nổ vài enemy chẳng hạn :)). ở đây game sẽ làm cơ bản nên không quan tâm nha :).
 
private void updateItem() {
  for (int i = 0; i < items.size(); i++) {
   Item item = items.get(i);
   if (!item.isVisible()) {
    items.remove(i);
   }
  }
 }

Ở đây mỗi lần chỉ tạo 1 item, còn nếu bạn muốn nâng cấp có thể cho thêm nhiều item vào nữa :).
Và cuối cùng là phương lớp TAdapter như đã nói ban đầu:
private class TAdapter extends MouseMotionAdapter {
  @Override
  public void mouseMoved(MouseEvent e) {
   box.mouseMoved(e);
  }
 }
Và để test thử game thì chúng ta cần tạo thêm một lớp LuckBox nữa nha:

 
public class LuckyBox extends JFrame implements Common{

 public LuckyBox() {
  setTitle("My first game");
  add(new MainBoard());
  setSize(Common.WIDTH, Common.HEIGHT);
  setResizable(false);
  setLocationRelativeTo(null);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
 }
 
 public static void main(String[] args) {
  EventQueue.invokeLater(new Runnable() {
   
   @Override
   public void run() {
    new LuckyBox().setVisible(true);
   }
  });
 }

}
Vậy là hoàn thành xong cái game cơ bản :)), chúc các bạn vui vẻ, những bài tới mình sẽ giới thiệu qua một số engine/framework làm game và nếu có khả năng thì mình sẽ làm một series về lập trinhf game sử dụng libGDX framework (bạn có thể tìm hiểu trước cái này)
Chào và hẹn gặp lại các bạn :).
SHARE

Xuho

  • Image
  • Image
  • Image
  • Image
  • Image

1 comments: