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.
SHARE

Xuho

  • Image
  • Image
  • Image
  • Image
  • Image

5 comments:

  1. sao t run nó chỉ cho 1 màn hình trắng k có đối tượng

    ReplyDelete
  2. không hiển thị đối tượng mà chỉ có 1 frame trắng là sao ạ ?

    ReplyDelete
  3. sao t chỉ in ra đc 1 frame trắng nhỉ, ai giải thích giúp t đc ko?

    ReplyDelete