Lập trình game với libGDX - Tìm hiểu về Animation




Chào mừng các bạn đã quay trở lại với blog, mấy hôm nay bận dọn phòng để chuẩn bị năm học mới với cả lười nên hôm nay mình mới có thời gian viết bài (thực ra thì 90% là do lười chứ không phải là không có thời gian :v).
Thôi không xàm nữa, chúng ta sẽ cùng điểm cả một số ý chính của bài hôm nay:

  • Tìm hiểu về Animation
  • Cách sử dụng trọng libGDX.
Ok, chúng ta bắt đầu tìm hiểu nhé :D.


I, Tìm hiểu về Animation:

Chắc hẳn ai trong chúng ta đều trải qua 1 tuổi thơ, có thể êm đềm, hoặc cũng có thể rất dữ dội =)), và dù êm đềm hay dữ dội đi nữa thì chắc hẳn phim hoạt hình cũng là một phần không thể thiếu trong tuổi thơ của chúng ta (nói vậy thôi chứ bây giờ lớn vẫn xem :v).
Ủa? Mình có lạc đề không ta =))

Thực ra mình đưa ra ví dụ về phim hoạt hình để cho các bạn dễ dàng liên tưởng nhất đến khái niệm Animation mà thôi.
Như các bạn đã biết, người ta làm phim hoạt hình bằng cách sử dụng nhiều khung hình và cho chạy liên tiếp để thu được các chuyển động của nhân vật. Và trong game thì khái niệm Animation (hoạt ảnh) cũng hoàn toàn tương tự thôi :D

Mình sẽ trích ra tư wiki trên github, khái niệm Animation cực kỳ đơn giản và dễ hiều :D

An animation consists of multiple frames which are shown in a sequence at set intervals.
 Các bạn có thể vào xem trên wiki, bài viết khá cụ thể, có ví dụ đi kèm
https://github.com/libgdx/libgdx/wiki/2D-Animation

Bây giờ chúng ta sẽ cùng nhau đi sang phần tiếp theo.

II, Cách sử dụng Animation trong libGDX:


Để có thể tạo animation thì libGDX cung cấp cho chúng ta một lớp có tên là Animtion, các bạn có thể đọc document của nó tại tại đây, khá đầy đủ và chi tiết:
https://libgdx.badlogicgames.com/nightlies/docs/api/com/badlogic/gdx/graphics/g2d/Animation.html

class này bao gồm 3 contructors, nhưng chúng ta sẽ sử dụng contructor thứ 2 (cái có nhiều tham số nhất ấy)

Animation(float frameDuration, Array<? extends TextureRegion> keyFrames)

Như các bạn thấy nó có chứa 2 tham số, ý nghĩa của từng tham số như sau:

  • frameDuration: đây chính là khoảng thời gian nghỉ giữ mỗi khung hình và được tính bằng giây.
  • keyFrames: mảng lưu các khung hình cần hiển thị, ở đây được vào bộ nhớ bởi các đối tượng TextureRegion mà chúng ta đã học ở bài trước.
Như đã thấy ở contructor trên thì chúng ta cần có một mảng các frame cần hiển thị, chúng ta có thể lấy tường bức ảnh đại diện mỗi frame rồi load vào mảng các frames đó, nhưng cách này không tối ưu, và cũng chẳng ai xài cách này đâu.
Nhớ lại bài hôm trước, chúng ta đã học cách sử dugnj Texture Packer để đóng gói ảnh lai đúng không nào, thông thương người ta sẽ dùng Texture Packer để đóng gói các frame nhỏ lại thành một tấm lớn, hoặc có thể dùng trình chỉnh sửa ảnh như Photoshop để làm điều này.

Và cái "bức ảnh lớn" mà mình thu được sau khi đóng gói lại sẽ được gọi là Sptite Sheet nha các bạn.

Rồi, bây giờ để mà test thử thì chúng ta cần có một sprite sheet, các bạn lên google gõ "sprite sheet" ra một đống luôn. Nhưng mình chắc là các bạn sẽ lười nên mình đã chuẩn bị sẵn một tấm ở đây rồi :)).
Ảnh này mình lấy trên github luôn.

Các bạn thấy ở sprite sheet trên thì các frame được sắp xếp giống như các phần tử trên một ma trân vậy, và việc duyệt các frame này cũng y chang việc các bạn duyệt các phần tử của một ma trân thôi. các bạn xem hình sau để hiểu rõ hơn:

Các frame sẽ được duyệt theo dòng từ trái qua phải và trên xuống, nếu sử dụng chế độ lặp lại thì khi chạy đến frame cuối cùng nó sẽ quay về và tiếp tục hiển thị frame đầu.

Bây giờ chúng ta sẽ code để cho nó ra chuyển động nha, việc này khá thú vị :)).
Các bạn nhớ nắm rõ cách thức hoạt động của nó vì animation trong game sử dụng khá nhiều đấy.

Không lan man nữa :)), bây giờ các bạn mở file code ra, rồi code như sau:


package com.blogspot.gameiter;

import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;

public class libGDXAnimation extends ApplicationAdapter {
 private static final int FRAME_COLS = 6;
 private static final int FRAME_ROWS = 5;

 Animation walkAnimation;
 Texture walkSheet;
 TextureRegion[] walkFrames;
 SpriteBatch spriteBatch;
 TextureRegion currentFrame;

 float stateTime;

 @Override
 public void create() {
  walkSheet = new Texture(Gdx.files.internal("animation.png"));
  TextureRegion[][] tmp = TextureRegion.split(walkSheet,
    walkSheet.getWidth() / FRAME_COLS, walkSheet.getHeight()
      / FRAME_ROWS);
  walkFrames = new TextureRegion[FRAME_COLS * FRAME_ROWS];
  int index = 0;
  for (int i = 0; i < FRAME_ROWS; i++) {
   for (int j = 0; j < FRAME_COLS; j++) {
    walkFrames[index++] = tmp[i][j];
   }
  }
  walkAnimation = new Animation(0.025f, walkFrames);
  spriteBatch = new SpriteBatch(); 
  stateTime = 0f; 
 }

 @Override
 public void render() {
  Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
  stateTime += Gdx.graphics.getDeltaTime();
  currentFrame = walkAnimation.getKeyFrame(stateTime, true);
  spriteBatch.begin();
  spriteBatch.draw(currentFrame, 50, 50);
  spriteBatch.end();
 }
}


  • 2 hằng số đầu tiên: FRAME_COLS và FRAME_ROWS chính là số hàng và số cột của sprite sheet, ơ đây bao gồm FRAME_COLS * FRAME_ROWS = 6 * 5 = 30 frame.
  • Tiếp theo là khai báo các đối tượng cần sử dụng, bao gồm: walkAnimation để tạo hoạt ảnh, walkSheet là Texture dùng để load tấm ảnh sprite sheet vào bộ nhớ.
  • walkFrames là mảng lưu các frame sau khi được cắt ra từ walkSheet.
  • currentFrame là frame hiện tại để hiển thị theo mỗi khoảng thời gian.
  • stateTime: tạm gọi là các mốc trạng thái, chúng ta sẽ cần dựa vào nó để lấy ra frame từ mảng frames của chúng ta.
Trong phương thức create() các bạn thấy có một biến tmp, biến này là một mảng 2 chiều có chức năng lưu các frame được cắt ra từ sprite sheet.
Tiếp đó, trong vòng lặp ta sẽ "đổ" lần lượt các frame đã cắt ra vào mảng một chiều walkFrames.
Cuối cùng chúng ta tạo mới đối tượng Animation với hai tham số như đã nói lúc đầu và gán stateTime lúc đầu bằng 0.

Trong phương thức render() thì chúng ta sẽ cộng vào stateTime một khoảng thời gian bằng thời gian nghỉ giữa các frame.
Sau đó gán currentFrame với frame tương ứng stateTime bằng phương thức getKeyFrame(float stateTime, boolean looping), tham số thứ 2 biểu diễn việc lặp lại frame hay không.
Cuối cùng ta vẽ currentFrame lên màn hình, do currentFrame liên tục thay đổi nên ta sẽ thấy được chuyển động của nhân vật.
Kêt quả chạy lên như sau:


Cũng không mấy khó khăn đúng không các bạn :D.
Chúc các bạn thành công, bài hôm nay mình xin dừng ở đây, hẹn gặp lại các bạn trong các bài sau.

SHARE

Xuho

  • Image
  • Image
  • Image
  • Image
  • Image

0 comments:

Post a Comment