Lập trình game java cơ bản - Game LuckyBox (setup cơ bản)

Chào mừng các bạn đã quay trở lại với blog, mấy bài hôm trước mình đã giới thiệu qua một số kiến thức căn bản để áp dụng cho việc tạo ra một game cơ bản, bài hôm nay chúng ta sẽ cùng nhau tạo ra một trò chơi của riêng bản thân mình :D.
Trò chơi này sẽ áp dụng tất cả những giờ chúng ta đã học được trong những bài trước, và nó cũng là một trờ chơi rất đơn giản, haha =)).

Đầu tiên mình sẽ giới thiệu qua trò chơi của chúng ta, nó sẽ kiểu như này:


Ý tưởng và cách chơi rất đơn giản: nhiệm vụ của bạn là ăn cho thật nhiều hình trái tim đó, mỗi lần ăn trái tim sẽ xuất hiện ra một vật cản, chạm vào nó thì "tèo" :v. Chỉ vậy thôi.


Rồi, bây giờ làm sao thực hiện đây ta?

Nhìn vào game ta sẽ thấy sơ sơ có 3 đối tượng chính, đó: nhân vật chơi của chúng ta - chính là cái hộp - box(xin lỗi mình ko có hoa tay nên chỉ vẽ đồ họa 8-bit kiểu như trong video mà thôi :v). một đối tượng vật phẩm - item, một đối tượng quân địch - enemy.

Chúng ta nhận thấy rằng là: 3 đối tượng này sẽ có một số thuộc tính cơ bản giống nhau bao gồm:

  • Hình ảnh của đối tượng - sử dụng lớp Image trong java.
  • Tọa độ của đối tượng gồm hoành độ x và tung độ y.
  • Kích thước đối tượng gồm chiều dài (width) và chiều cao (height), thực ra lúc vẽ hình thì mình ko cần 2 thuộc tính này, nhưng chúng ta sẽ sử dụng nó để xét va chạm.
Nói về việc xét va chạm thì ta sẽ sử dụng cách dễ nhất đó là sử dụng vùng bao hình chữ nhật luôn cho tiện :D.

Ok, Vậy chúng ta sẽ đi xây dụng một lớp chung để cho các đối tượng trong game kế thừa lại nhằm tối ưu code của ta.
Ta sẽ đặt tên lớp này là Sprite, lớp Sprite sẽ được code như sau:


import java.awt.Image;
import java.awt.Rectangle;

import javax.swing.ImageIcon;

public class Sprite {
 protected int x;
 protected int y;
 protected int width;
 protected int height;
 protected boolean visible;
 protected Image image;
 
 public Sprite(int x, int y) {
  this.x = x;
  this.y = y;
  visible = true;
 }
 
 public void loadImage(String fileName) {
  ImageIcon ii = new ImageIcon(fileName);
  image = ii.getImage();
 }
 public void getImageDimention() {
  width = image.getWidth(null);
  height = image.getHeight(null);
 }

 public boolean isVisible() {
  return visible;
 }

 public void setVisible(boolean visible) {
  this.visible = visible;
 }

 public int getX() {
  return x;
 }

 public int getY() {
  return y;
 }

 public Image getImage() {
  return image;
 }
 
 public Rectangle getBound() {
  return new Rectangle(x, y, width, height);
 }
}

Phương thức getImageDimention() chúng ta sử dụng để lấy kích thước (width và height) của đối tượng và gán vào thuộc tính width, height luôn.
Phương thức getBound() sẽ dùng để trả về hình chữ nhật bao quan nhân vật chúng ta, ta sẽ dùng nó để xét va chạm.

Bây giờ ta sẽ đi xây dựng lớp Box - đây sẽ nhân vật chúng ta điều khiển :)


import java.awt.event.MouseEvent;

public class Box extends Sprite {
 private int dx;
 private int dy;

 public Box(int x, int y) {
  super(x, y);
  initBox();
 }

 private void initBox() {
  loadImage("images/box.png");
  getImageDimention();
 }
 
 public void move() {
  x = dx;
  y = dy;
 }
 
 public void mouseMoved(MouseEvent e) {
  dx = e.getX();
  dy = e.getY();
 }
}

Chúng ta kế thừa lại lớp Sprite để thừa hưởng các thuộc tính chung. Chúng ta sẽ điều khiển nhân vật bằng chuột nên sẽ set giá trị tọa độ của nhân vật bằng tọa độ chuột (dx, dy là tọa độ chuột).

Tiếp đến là lớp Item để biểu diễn vật phẩm mà nhân vật của ta cần thư thập, lớp này thì không có gì khó khăn cả:


public class Item extends Sprite {

 public Item(int x, int y) {
  super(x, y);
  initItem();
 }

 private void initItem() {
  loadImage("images/item.png");
  getImageDimention();
 }
 
}

Bây giờ là lớp Enemy để biểu diễn quân địch, ở video demo ở trên các bạn thấy quân địch của chúng ta tự động di chuyển theo phương ngang hoặc dọc, cái này mấy bài trước mình quên nói, bây giờ mình sẽ giới thiệu luôn.
Để quân địch tự di chuyển, thì trong mỗi vòng lặp ta sẽ tăng hoặc giảm tọa độ x hoặc y (tùy thuộc phương di chuyển, tăng giảm x nếu theo phương ngang và y nếu theo phương dọc).
Tiếp theo là phương di chuyển của nhân vật, thuật toán rất đơn giản:
- Lúc bạn khởi tạo nhân vật ra thì bạn sẽ gán cho nó một hướng di chuyển ban đầu, nếu phương ngang thì nhân vật sẽ có hai hướng di chuyển là LEFT hoặc RIGHT, nếu dọc thì sẽ có hai hướng di chuyển là UP hoặc DOWN.

Đễ dễ dàng quản lý thì ta sẽ tạo một lớp hoặc giao diện chứa các dữ liệu dùng trong game, ở đây mình sẽ tạo một giao diện Common để chứa nó:


public interface Common {
 public enum Status {UP, DOWN, LEFT, RIGHT}; // di chuyển ngang hoặc dọc
 public final int WIDTH = 800;
 public final int HEIGHT = 600;
 public final int ENEMY_SPEED = 2;
 public final int BOX_SPEED = 2;
 public final int DELAY = 10;
 public final int INIT_BOX_X = 50;
 public final int INIT_BOX_Y = 50;
}

Các bạn lưu ý cái enum Status, enum này sẽ là kiểu lưu các trạng thái của quân địch.

  • UP: di chuyển lên trên (giảm giá trị tung độ y)
  • DOWN: di chuyển xuống (tăng giá trị y)
  • LEFT: di chuyển qua trái (giảm hoành độ x)
  • RIGHT: di chuyển qua phải (tăng giá trị x)
Ngoài ra để quân địch không "chạy" luôn ra khỏi màn hình thì ta phải xét điều kiện chạm biên cho nó:

Các bạn xem hình sau sẽ rõ:
Tọa độ của nhân vật như ở mũi tên mình đã vẽ, muốn xem nó có chạm biên bên phải không thì ta phải xem hoành độ x của nó có lớn hơn hoặc bằng chiều rộng của màn hình trừ đi chiều rộng của nó không, tương tự với các biến khác.

Vậy ta sẽ có code lớp Enemy như sau:

public class Enemy extends Sprite implements Common {
 private Status status;

 public Enemy(int x, int y) {
  super(x, y);
  initEnemy();
 }

 private void initEnemy() {
  loadImage("images/enemy.png");
  getImageDimention();
  int rand = (int) (Math.random() * 4 + 1);
  switch (rand) {
  case 1:
   status = Status.UP;
   break;
  case 2:
   status = Status.DOWN;
   break;
  case 3:
   status = Status.LEFT;
   break;
  case 4:
   status = Status.RIGHT;
   break;
  }
 }

 public void move() {
  if (x <= 0)
   status = Status.RIGHT;
  if (x >= WIDTH - width)
   status = Status.LEFT;
  if (y <= 0)
   status = Status.DOWN;
  if (y >= HEIGHT - height)
   status = Status.UP;

  switch (status) {
  case UP:
   y -= ENEMY_SPEED;
   break;
  case DOWN:
   y += ENEMY_SPEED;
   break;
  case LEFT:
   x -= ENEMY_SPEED;
   break;
  case RIGHT:
   x += ENEMY_SPEED;
   break;
  }
 }
}
Để sử dụng kiểu Status ta phải implements Common.
Ok, vậy la ta đã setup xong cơ bản các đối tượng trong game rồi, bài hôm nay mình xin dưng ở đây, bài hôm sau ta sẽ hoàn thiện game này.
Cám ơn các bạn đã theo dõi. :)
SHARE

Xuho

  • Image
  • Image
  • Image
  • Image
  • Image

3 comments:

  1. protected boolean visible;

    public boolean isVisible() {
    return visible;
    }


    public void setVisible(boolean visible) {
    this.visible = visible;
    }

    Cho mình hỏi là khởi tạo thuộc tính boolean này nhằm mục đích gì vậy?

    ReplyDelete
    Replies
    1. Ngoài ra mình thấy bên lớp MainBoard có hàm này
      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);
      }
      }
      Mình thật sự không hiểu cơ chế hoạt động cho lắm, chỉ biết có liên quan tới hàm boolean phía trên, bạn có thể giải thích cặn cẽ về chỗ này dùm mình được không. Mình đang muốn hiểu rõ đến hoàn thiện game này, cảm ơn.

      Delete