Showing posts with label Lập trình game java cơ bản. Show all posts
Showing posts with label Lập trình game java cơ bản. Show all posts

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

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 :).

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. :)

Lập trình game java cơ bản - phát hiện va chạm (P2) - hình tròn và hình chữ nhật

Xin chào các bạn đã quay trở lại với blog.
Mấy hôm nay lười quá không viết bài mới :))

Hôm nay chúng ta tiếp tục tìm hiểu về việc xử lý va chạm trong game, bài hôm trước chúng ta đã thực hiện việc xử lý va chạm giữa hai hình chữ nhật với nhau mà không tốn tí công sức nào :v (vì nó có sẵn chỉ lấy ra xài thôi).

Bài hôm nay thì sẽ cần code một chút vì chúng ta cần xét va chạm giữa một hình tròn và một hình chữ nhật, cơ mà trong java nó lại không có lớp hình tròn sẵn, cho nên chúng ta sẽ đi xây dựng lớp hình tròn ngay bây giờ, việc này thì rất đơn giản:

Một hình tròn thì sẽ có 2 thuộc tính quan trọng là: tọa độ của tâm và bán kính, tọa độ tâm ta có thể dùng lớp Point có sẵn của java hoặc tự xây dựng 1 lớp Point cho riêng mình, nhưng ở đây ta cũng không cần xây dựng làm gì, cứ để tọa độ tâm là 2 biến kiểu nguyên cũng được, bán kính ta khai báo kiểu int luôn cho nó đơn giản nhé :v.
public class Circle {
private int x;
private int y;
private int radius;

public Circle(int x, int y, int radius) {
this.x = x;
this.y = y;
this.radius = radius;
}
public int getRadius() {
return radius;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Rồi, bây giờ chúng ta sẽ vẽ ra hình tròn này thử xem nhé.
các bạn thêm đoạn code sau vào class CollisionDetection, tại phương thức paintComponent(...) nhé:

g.drawOval( circle.getX() - circle.getRadius(), circle.getY()-circle.getRadius(), circle.getRadius() * 2,circle.getRadius() * 2);


Sau đó các bạn chạy thử xem, mình giải thích thêm:
Tại sao tọa độ (x, y) lại là circle.getX() - circle.getRadius(), circle.getY() và circle.getRadius()???
Các bạn lưu ý là phương thức drawOval(...) nó sẽ vẽ hình Oval nằm trong hình chữ nhật.
Để hiểu rõ phương thức drawOval(...) vẽ như nào các bạn tạm thời comment hết đoạn code trong phương thức paintComponent(...) lại rồi thêm đoạn này vào:
g.drawOval(100, 100, 200, 100);
g.drawRect(100, 100, 200, 100);

Bây giờ các bạn thử chạy lên xem, kết quả như sau:

Vậy nên để vẽ hình tròn chính xác theo tâm, ta sẽ trừ 1 đoạn bằng chính bán kính của nó.
Hai tham số sau có giá trị circle.getRadius() * 2 bởi vì ta vẽ hình tròn nên phương thức drawOval sẽ truyền vào chiều dài và rộng bằng nhau - hình oval nội tiếp hình vuông sẽ cho ra hình tròn :D.

Nói sơ qua về cách vẽ hình tròn với java thế thôi. Bây giờ là phần chính của bài hôm nay.
Bây giờ làm sao để ta có thể xét được va chạm giữa một hình tròn và một hình chữ nhật đây ta?

Chúng ta bắt đầu suy nghĩ và tìm thuật toán nhé.
Ý tưởng: để kiểm tra va chạm giữa hình chữ nhật và hình tròn ta sẽ tìm một điểm, gọi là điểm A(xA, yA) là điểm gần tâm hình tròn nhất thuộc hình chữ nhật, sau khi đã tìm ra được điểm A này rồi thì công việc còn lại là chỉ cần tính khoảng cách từ tâm hình tròn tới điểm A và so sánh với bán kính hình tròn là ok.
Các bạn xem qua hình vẽ này (ảnh mình lấy từ google và có sửa lại để dễ hình dung hơn):
Nhìn sơ thì ta cũng chỉ có mấy trường hợp như vậy thôi.
Ta quy ước tọa độ các đỉnh hình chữ nhật giống như hình vẽ mình đã chú thích luôn nhé, tọa độ tâm sẽ là C(xC, yC).
Đầu tiên ta cho tọa độ điểm A bằng với tọa độ tâm hình tròn luôn (vì điểm gần tầm nhất thì là nó luôn rồi :D), hay tọa độ A lúc này là (xA, yA) = (xC, yC).
Tiếp theo ta sẽ xét các trường hợp sau:
Nếu xC < rectLeft thì gán xA = rectLeft (như hình vẽ), ngược lại nếu xC > rectRight thì gán xA = rectRight.
Nếu yC < rectTop thì gán yC = rectTop, ngược lại nếu yC > rectBottom thì gán yC = rectBottom.
Trường hợp nữa là rectLeft < xC < rectRight thì xA = xC, rectTop < yC < rectBottom thì yA = yC (nhưng do đầu tiên ta đã gán tọa độ của A bằng tọa độ của C rồi cho nên ta không cần code đoạn này.

Bây giờ chúng ta sẽ thực hiện thuật toán bằng code, tròn class CollisionDetection các bạn tạo thêm 1 phương thức xét va chạm giữa hình tròn và hình chữ nhật như sau:
public boolean checkColiision(Rectangle rect, Circle cir) {
int rectLeft = (int) rect.getX();
int rectRight = (int) (rect.getX() + rect.getWidth());
int rectTop = (int) rect.getY();
int rectBottom = (int) (rect.getY() + rect.getHeight());

int xC = cir.getX();
int yC = cir.getY();
int xA = xC;
int yA = yC;

if (xC < rectLeft) {
xA = rectLeft;
} else if (xC > rectRight) {
xA = rectRight;
}

if (yC < rectTop) {
yA = rectTop;
} else if (yC > rectBottom) {
yA = rectBottom;
}
int dx = xA - xC;
int dy = yA - yC;
return (dx * dx + dy * dy) <= cir.getRadius() * cir.getRadius();
}

Code trên thực hiện y chang thuật toán đã nói ở trên rồi :)
Nhưng cái câu lệnh return (dx * dx + dy * dy) <= cir.getRadius() * cir.getRadius(); là sao nhỉ?

Công thức tính độ dài đoạn thẳng khi biết tọa độ 2 điểm là:
sqrt(deltaX^2 + deltaY^2)
Ở đây để đỡ thêm 1 bước tính căn bậc hai thì ta chỉ cần bình phương 2 vế lên là xong.

Trong phương thức actionPerformed(ActionEvent e) các bạn thêm dòng này vào nữa để test:
if (checkColiision(myChar.getBound(), circle)) {
System.out.println("Hinh chu nhat va cham hinh tron");
}

Kết quả chạy được như hình dưới:


Bài hôm nay dừng tại đây, cám ơn các bạn đã theo dõi :).

Lập trình game java cơ bản - phát hiện va chạm

Xin chào các bạn :D, hôm nay chúng ta túc tiệp với loạt bài về lập trình game java nhỉ :D.

Bài hôm nay, mình sẽ giới thiệu các bạn cách phát hiện va chạm hết sức đơn giản trong game.

Với những game như mấy game chạy nhảy, bắn súng chẳng hạn, thì việc phát hiện va chạm là rất quan trọng. Vậy hôm nay chúng ta sẽ làm việc này bằng một cách đơn giản nhất: đó là sử dụng luôn lớp Rectangle (mô tả Hình chữ nhật) trong java.awt.

Bây giờ chúng ta bắt đầu luôn nhé :)


Thông thường để bắt va chạm chuẩn xác nhất thì người ta sẽ phải xác định vùng bao của vật thể cần xét va chạm, mà vùng bao này thì muôn hình vạn trạng, có thể là hình tròn, hình đa giác, hay vô số hình phức tạp, đồng thời thuật toán để xét va chạm giữ các hình này thì không phải đơn giản (hiện tại mình mới làm theo kiểu vùng bao hình tròn, hình vuông, chữ nhật mà thôi :v).
Và thật may mắn khi lớp Rectangle trong java.awt đã cung cấp cho ta phương thức intersecters(...) để có thể kiểm tra va chạm giữa 2 hình chữ nhật với nhau, việc chúng ta cần làm là gọi nó ra và dùng mà thôi =)).

Để hiểu rõ hơn mình sẽ đi vào ví dụ cụ thể:
Mình sẽ tạo 2 đối tượng: MyCharacter - tượng trưng cho nhân vật chơi của chúng ta và Enemy - tượng trưng cho quân địch, 2 đối tượng này đơn giản sẽ chỉ là 2 hình chữ nhật, do đó nó sẽ có thuộc tính chung là tọa độ, chiều dài, chiều rộng, để code ngắn gọn ta sẽ tạo 1 lớp Sprite chứa các thuộc tính chung này sau đó cho 2 đối tượng kia kế thừa lại.

Ta có lớp Sprite như sau:

import java.awt.Rectangle;
public class Sprite {
protected int x;
protected int y;
protected int width;
protected int height;

public Sprite(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Rectangle getBound() {
return new Rectangle(x, y, width, height);
}
 }


Phương thức getBound() sẽ dùng để xác định vùng bao của 2 nhân vật, ở đây vì mình làm 2 đối tượng là 2 hình chữ nhật nên vùng bao sẽ chính xác tuyệt đối.

Tiếp theo là lớp MyCharacter:

import java.awt.event.MouseEvent;
public class MyCharacter extends Sprite{
private int mx;
private int my;
public MyCharacter(int x, int y, int width, int height) {
super(x, y, width, height);
}
public void move() {
x = mx;
y = my;
}
public void mouseMoved(MouseEvent e) {
mx = e.getX();
my = e.getY();
}
}


Ta thêm 2 thuộc tính mx và my để biểu diễn tọa độ con trỏ chuột của chúng ta (bài hôm trước mình quên giới thiệu cách điều khiển nhân vật bằng chuột nên giờ mình sẽ dùng chuột điều khiển).
Phương thức mouseMoved(MouseEvent e) đơn giản là lấy tọa độ chuột gán vào cho mx và my.
Phương thức move() sẽ set lại tọa độ nhân vật bằng tọa độ chuột, hiểu đơn giản là di chuột tới đâu nhân vật sẽ chạy theo con trỏ chuột =)),  rất là chuối :)).

Tiếp đến là lớp Enemy:

public class Enemy extends Sprite {

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

}

Chả có gì hết =)).

Xong, giờ tạo thêm lớp CollisionDetection như sau:

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JPanel;
import javax.swing.Timer;
public class CollisionDetection extends JPanel implements ActionListener {
private MyCharacter myChar;
private Enemy enemy;
private final int DELAY = 10;
private Timer timer;

public CollisionDetection() {
myChar = new MyCharacter(50, 50, 50, 50);
enemy = new Enemy(100, 100, 50, 50);
addMouseMotionListener(new MyAdapter());
timer = new Timer(DELAY, this);
timer.start();
}

@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(myChar.getX(), myChar.getY(), myChar.getWidth(),
myChar.getHeight());
g.drawRect(enemy.getX(), enemy.getY(), enemy.getWidth(),
enemy.getHeight());
}

@Override
public void actionPerformed(ActionEvent e) {
checkCollision();
myChar.move();
repaint();
}
public void checkCollision() {
Rectangle myCharBound = myChar.getBound();
Rectangle enemyBound = enemy.getBound();
if(myCharBound.intersects(enemyBound)) {
System.out.println("Va cham");
}
}
private class MyAdapter extends MouseMotionAdapter {
@Override
public void mouseMoved(MouseEvent e) {
myChar.mouseMoved(e);
}
}
}

Code này thì các bạn chắc đã hiểu hết vì bài trước mình đã giải thích hết rồi, ngoại trừ chỗ class MyAdapter, bài trước mình tạo class này bên ngoài, nhưng muốn tạo bên ngoài thì cần tham chiếu đối tượng myChar qua hàm tạo nữa, ở đây ta tọa luôn inner class - có nghĩa là class bên trong 1 class, và nó dùng luôn được thuộc tính class chứa nó (ở đây là dùng luôn myChar).
Phương thức checkCollision(): lấy ra 2 hình chữ nhật bao quanh 2 đối tượng, myCharBound.intersects(enemyBound), phương thức intersects(...) nói lên công dụng của nó :), phương thức này trả về true nếu 2 hình chữ nhật giao nhau và false cho trường hợp còn lại.

Các bạn chạy thử demo bằng cách tạo thêm class MainTest như sau:
import java.awt.Color;
import javax.swing.JFrame;
@SuppressWarnings("serial")
public class MainTest extends JFrame {
public MainTest() {
add(new CollisionDetection());
setTitle("Collision detection test");
setSize(500, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBackground(Color.WHITE);
setVisible(true);
}
public static void main(String[] args) {
new MainTest();
}
}



Vậy là xong bài hôm nay, bây nhiêu đó là hôm sau ta có thể bắt tay vào làm 1 game nhỏ rồi :D. Hẹn gặp lại các bạn vào bài sau.

Lập trình game java cơ bản - Điều khiển nhân vật

Lập trình game java cơ bản - Điều khiển nhân vật
Chào mừng các bạn đã quay trở lại với loạt bài hướng dẫn lập trình game java cơ bản của blog.
Bài hôm trước chúng ta đã biết cách vẽ hình lên frame.
Bài hôm nay chúng ta sẽ tiếp tục tìm hiểu cách điều khiển nhân vật (nhân vật ở đây đơn giản là 1 hình vẽ nha).
Thì mình sẽ giới thiệu cho các bạn điều khiển nhân vật với bàn phím và với chuột.
Chúng ta bắt đầu đi vào tìm hiểu nhé.
Trước tiên, chúng ta cần chuẩn bị một bức ảnh nhỏ để mô phỏng đối tượng nhân vật mà ta sẽ điều khiển, ở đây mình đã chuẩn bị một hình (hình này sẽ dùng để làm game Lucky Box trong những bài sau luôn)



Bức ảnh này sẽ có tên là "box.png". Các bạn down về và để trong thư mục "images" mà hôm trước chúng ta đã tạo luôn nha, hoặc nếu bạn tạo mới project thì có thể tạo lại thư mục này :D.

À quên, với những bài hôm trước chúng ta chưa đả động gì tới vòng lặp game mà mới chỉ đơn giản là vẽ 1 hình ảnh hay 1 đoạn text lên thôi. Hôm nay để thực hiện vòng lặp game chúng ta sẽ phải sử dụng 1 thread (các bạn nếu chưa biết vấn đề đa tiến trình sử dụng Thread có thể lên mạng tham khảo thêm nhé) để thực hiện.
Để đơn giản cho việc này ta sẽ sử dụng lớp Timer mà java đã cung cấp cho chúng ta, mình sẽ nói rõ cách sử dụng vào các phần bên dưới.


Rồi bắt đầu.

 Đầu tiên chúng ta sẽ tạo một class mới có tên là Box, class này sẽ thể hiện đối tượng nhân vật mà ta sẽ điều khiển.


import java.awt.Image;
import javax.swing.ImageIcon;

public class Box {
 private int dx;
 private int dy;
 private int x;
 private int y;
 private Image image;
 private final int SPEED = 1;
 public Box() {
  ImageIcon ii = new ImageIcon("images/box.png");
  image = ii.getImage();
  x = 50;
  y = 50;
 }
}

Giải thích:
x, y: sẽ là tọa độ của nhân vật chơi của chúng ta.
dx, dy:hệ số delta x và delta y để thay đổi tọa độ nhân vật.
Ở trên ta khỏi tạo đối tượng box với 1 hình vẽ có tên "box.png" và tọa độ (x ,y) = (50, 50).

Sau đó chúng ta sẽ thêm vào phương thức move() để di chuyển nhân vật.

public void move() {
     x += dx;
     y += dy;
}

Để hiểu vì sao dùng như thế này các bạn tiếp tục xem phần dưới mình sẽ giải thích sau...

Tiếp đến các bạn tạo thêm 2 phương thức keyPressed(KeyEvent e) - xử lý khi người dùng nhấn phím và keyReleased(KeyEvent e) - xử lý khi người dùng nhả phím để điều khiển nhân vật của chúng ta.
2 phương thức cụ thể như sau:

  public void keyPressed(KeyEvent e) {
    int key = e.getKeyCode();
    switch (key) {
  case KeyEvent.VK_UP:
   dy = -SPEED;
   break;
  case KeyEvent.VK_DOWN:
   dy = SPEED;
   break;
  case KeyEvent.VK_LEFT:
   dx = -SPEED;
   break;
  case KeyEvent.VK_RIGHT:
   dx = SPEED;
   break;
  }
}
    public void keyReleased(KeyEvent e) {
  int key = e.getKeyCode();
  switch (key) {
  case KeyEvent.VK_UP:
   dy = 0;
   break;
  case KeyEvent.VK_DOWN:
   dy = 0;
   break;
  case KeyEvent.VK_LEFT:
   dx = 0;
   break;
  case KeyEvent.VK_RIGHT:
   dx = 0;
   break;
  }
}
Giải thích: Như các bạn thấy tại phương thức move() thì tọa độ nhân vật đươck thay đổi bởi dx và dy, nhớ lại bài trước, trục tọa độ của chúng ta sẽ có trục tung đi xuống thì giá trị tọa độ tăng. Ở đây để nhân vật di chuyển thì mình sẽ cho nó tăng giảm tọa độ tùy theo phím bấm, chẳng hạn ở phương thức keyPressed(KeyEvent e) khi người dùng nhấn phím đi lên(VK_UP) thì mình sẽ cho dy = -1, hay nói cách khác, khi phương thức move() được gọi thì tọa độ y sẽ giảm đi 1 => nhân vật sẽ di chuyển đi lên.

Cuối cùng ta thêm một số getter để lát ta có thể lấy giá trị các thuộc tính của đối tượng Box ra cho việc vẽ hình.

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public Image getImage() {
        return image;
    }
 
Sau khi hoàn thành ta sẽ có code class Box như sau:

import java.awt.Image;
import java.awt.event.KeyEvent;

import javax.swing.ImageIcon;

public class Box {
 private int dx;
 private int dy;
 private int x;
 private int y;
 private Image image;
 
 public Box() {
  ImageIcon ii = new ImageIcon("images/box.png");
  image = ii.getImage();
  x = 50;
  y = 50;
 }
 public void move() {
  x += dx;
  y += dy;
 }
 public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public Image getImage() {
        return image;
    }
    public void keyPressed(KeyEvent e) {
     int key = e.getKeyCode();
     switch (key) {
  case KeyEvent.VK_UP:
   dy = -1;
   break;
  case KeyEvent.VK_DOWN:
   dy = 1;
   break;
  case KeyEvent.VK_LEFT:
   dx = -1;
   break;
  case KeyEvent.VK_RIGHT:
   dx = 1;
   break;
  }
    }
  public void keyReleased(KeyEvent e) {
     int key = e.getKeyCode();
     switch (key) {
  case KeyEvent.VK_UP:
   dy = 0;
   break;
  case KeyEvent.VK_DOWN:
   dy = 0;
   break;
  case KeyEvent.VK_LEFT:
   dx = 0;
   break;
  case KeyEvent.VK_RIGHT:
   dx = 0;
   break;
  }
 }
}
Bây giờ ta tạo 1 class đặt tên là MyAdapter kế thừa KeyAdapter - đây là 1 lớp trừu tượng giúp nhận tín hiệu người dùng nhập từ bàn phím.

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

public class MyAdapter extends KeyAdapter {
 private Box box;
 public MyAdapter(Box box) {
  this.box = box;
 }
 @Override
 public void keyPressed(KeyEvent e) {
  box.keyPressed(e);
 }
 @Override
 public void keyReleased(KeyEvent e) {
  box.keyReleased(e);
 }
}
Ta cần có 1 đối tượng Box trong này để lát tham chiếu đến class MainGame mà ta sắp tạo:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JPanel;
import javax.swing.Timer;

@SuppressWarnings("serial")
public class MainGame extends JPanel implements ActionListener{
 private Timer timer;
 private final int DELAY = 10;
 private Box box;
 
 public MainGame() {
  setFocusable(true);
  setBackground(Color.WHITE);
  box = new Box();
  MyAdapter adapter = new MyAdapter(box);
  addKeyListener(adapter);
  timer = new Timer(DELAY, this);
  timer.start();
 }
 @Override
 protected void paintComponent(Graphics g) {
  super.paintComponent(g);
  g.drawImage(box.getImage(), box.getX(), box.getY(), this);
 }
 @Override
 public void actionPerformed(ActionEvent e) {
  box.move();
  repaint();
 }
}

Giải thích:
MainGame của chúng ta thực thi giao diện ActionListener - giao diện này có chức năng nhận các action từ người dùng, ở đây ta muốn sử dụng Timer nên ta cần thực thi giao diện này, cụ thể:
Ở hàm khởi tạo Timer:
timer = new Timer(DELAY, this);

- Tham số đầu tiên là tham số delay - đây là khoảng thời gian nghỉ giữa mỗi vòng lặp (được tính bằng ms)
- Tham số thứ 2 chính là đối tượng thực thi ActionListener hay nói cách khác là bộ lắng nghe, ở đây chính là this luôn.

timer.start();
Dùng để khởi động timer, các bạn cứ hình dùng timer là 1 thread như bình thường, thread này sẽ có thời gian sleep sau mỗi lần update là DELAY ms.

Phương thức  paintComponent(Graphics g) thì đơn giản chỉ là vẽ đối tượng Box lên màn hình :).

Cuối cùng là phương thức actionPerformed(ActionEvent e) là phương thức Override lại khi thực thi ActionListener, khi timer chạy nó sẽ chạy code trong phương thức này mỗi DELAY ms, ở đây đơn giản là gọi lại phương thức move() của đối tượng box và yêu cầu panel vẽ lại.

Cuối cùng bạn tạo mới class Test như sau để chạy thử kết quả :)

import javax.swing.JFrame;

public class Test extends JFrame{
 public Test() {
  add(new MainGame());
  setSize(480, 360);
  setVisible(true);
  setLocationRelativeTo(null);
  setDefaultCloseOperation(EXIT_ON_CLOSE);
 }
 public static void main(String[] args) {
  new Test();
 }
}

Muốn di chuyển nhanh thì bạn tăng SPEED lên và ngược lại. bài hôm nay dừng tại đây, cám ơn các bạn đã theo dõi, hẹn gặp lại trong các bài sau nhé :D.

Lập trình game java cơ bản - Vẽ hình lên frame

Xin chào các bạn, mấy ngày hôm nay bận chỉnh chu cái blog mà chưa có thời gian viết bài mới, mong các bạn thông cảm nha :p.

Hôm nay chung ta sẽ đi vào tìm hiểu cách load 1 hình ảnh lên frame (bài hôm trước chúng ta đã viết chữ lên frame được rồi đúng không nào.

Để thú vị ta sẽ vẽ hình chú chó nâu này nhé, các bạn có thể kiếm hình khác để vẽ :D
Rồi! Bài hôm trước chúng ta đã viết chữ lên frame bằng cách viết trực tiếp lên đối tượng JFrame, nhưng hôm nay chúng ta sẽ load hình lên đối tượng JPanel rồi sau đó mới add đối tượng này và JFrame của chúng ta để hiển thị. Với cách làm này thì ta sẽ vẽ lên JPanel xong xuôi đã rồi mới "dán" vào JFrame, và khi game chạy ta sẽ thấy nó "mượt" hơn :v (với game data nhiều mới thấy chứ load 1  đôi cái hình chắc cũng ko cảm nhận rõ lắm).

Chúng ta tiến hành tạo một thư mục để chứa hình ảnh, ở đây mình đặt tên là  "image ".

Bạn copy ảnh cần vẽ vào thư mục này.
Sau đó chúng ta sẽ tạo một class LoadImage kế thừa class JPanel.
Nhắc lại chút kiến thức trước: để kế thừa trong java ta dùng từ khóa extends.

Đầu tiên ở class LoadImage chúng ta sẽ khai báo 1 đối tượng:
private Image image;

Lưu ý: do Image là một lớp trừu tượng nên ta không thể tạo mới một đối tượng Image trực tiếp được.

Cho nên chúng ta sẽ cần dùng đến class ImageIcon (trong javadoc có nói qua về lớp này, bạn đọc để hiểu hơn, nôm na là nó sẽ load một hình, có thể từ đường dẫn chúng ta truyền vào, vv).

package com.blogspot.gameiter;

import java.awt.Graphics;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class LoadImage extends JPanel {
    private Image image;
    public LoadImage() {
      ImageIcon imageIcon = new ImageIcon("image/cho_nau.jpg");
      image = imageIcon.getImage();
    }
}
Đây là cách để ta tạo đối tượng Image, hiểu nôm na là thằng imageIcon sẽ load ảnh từ file name chúng ta truyền vào, sau đó thằng image sẽ lấy hình đó từ thằng imageIcon.

Tiếp theo là vẽ hình lên đối tượng JPanel.

Lớp JPanel có phương thức painComponent(Graphics g) lớp này có chức năng vẽ các thành phần lên container.

Mẹo: để gõ nhanh trên eclipse ta gõ vài chữ đầu phương thức cần override rồi  nhấn tổ hợp phím Ctrl + Space, eclipse sẽ gợi ý tất cả phương thức liên quan.
Ở đây khi nhấn Ctrl + Space các bạn sẽ thấy nó hiện ra những hai phương thức painComponent(Graphics g),nhưng các bạn lưu ý dùng phương thức có access modifier  là protected nhé. Do ở đây ta dùng JPanel, mà JPanel kế thừa từ JComponent, còn nếu ta dùng Panel thì sẽ dùng phương thức có
Tiếp tục :D...
Sau khi thêm code vẽ hình vào ta sẽ có:
package com.blogspot.gameiter;

import java.awt.Graphics;
import java.awt.Image;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

public class LoadImage extends JPanel {
    private Image image;
    public LoadImage() {
      ImageIcon imageIcon = new ImageIcon("image/cho_nau.jpg");
      image = imageIcon.getImage();
    }
    @Override
    protected void paintComponent(Graphics g) {
      g.drawImage(image, 100, 100, this); // Vẽ hình tại tọa độ (x, y) = (100, 100)
    }
    @Override
    public void paintComponents(Graphics g) {
      // Không dùng phương thức với JPanel
    }
}

Tham số this truyền vào ở đây có nghĩa là ta vẽ ngay trên JPanel LoadImage.
Xong, rất đơn giản để load hình đúng không nào các bạn. Bây giờ ta tạo 1 đối tượng JFrame và add nó vào chạy thử thôi :
Bài hôm nay kết thúc ở đây, xin chào và hẹn gặp lại các bạn :D.

Lập trình game java cơ bản - Bắt đầu với việc làm game

Xin chào các bạn đã quay trở lại với blog.
Bài hôm nay chúng ta sẽ tiếp tục series tìm hiểu về lập trình game cơ bản (với ngôn ngữ sử dụng ở đây là Java nhé).

Ok, bắt đầu nào!!!
Đầu tiên mình sẽ nói qua nội dung mà hôm nay chúng ta sẽ tìm hiểu (nói chung là mấy cái cơ bản thôi, nên khá chán)
  1. Một số thứ cần thiết để bắt đầu
  2. Nhắc lại một số khái niệm cơ bản về lập trình hướng đối tượng (OOP)
  3. Chương trình "Hello World" thần thánh :))

Lập trình game java cơ bản - Ôn lại một số câu lệnh thường sử dụng trong lập trình game

Chào mừng các bạn đã quay trở lại với blog của mình.
Hôm nay chúng ta sẽ ôn lại một số cấu trúc lệnh cơ bản trong Java để có thể bắt tay vào lập trình game.
Chúng ta sẽ ôn lại một số thứ sau:
  • Cấu trúc rẽ nhánh và câu lệnh điều kiện
  • Cấu trúc lặp

Lập trình game java cơ bản - Game là gì? Làm thế nào để tạo được một game đơn giản?

Lập trình game java cơ bản - Game là gì? Làm thế nào để tạo được một game đơn giản?
 Xin chào các bạn, mình là Hòa(tên thường gọi là Lép - tại sao mọi người gọi vậy mình cũng không rõ nữa :v), mình là một sinh viên ngành IT, trình độ của mình thì cực kỳ cùi bắp, nhưng với mong muốn chia sẻ kiến thức có được, cũng như nhận lại kiến thức từ người khác mình đã tạo blog này :D, thật ra mà nói thì đây là lần đầu tiên mình viết blog =)), văn vẻ của mình khá tệ :3 nên mong các bạn thông cảm :D. Thôi dài dòng vậy đủ rồi, ta bắt đầu vấn đề chính cái nhỉ :D, vấn đề mình muốn đề cập hôm nay đó là Game(đây là mảng mà mình thích và đang theo đuổi, không biết sau này có nên cơm cháo gì không nhưng giờ cứ làm vì đam mê đã :p).