เขียนเกมด้วย LibGDX :3 – Render และการรับ input
สวัสดีครับ บทความนี้เป็นบทความที่ 3 ครับ จากซีรีส์ สอน LibGDX ครับ
บทความตอนอื่นๆ ในซีรีย์ เขียนเกมด้วย LibGDX ติดตามอ่านได้ตามลิงค์ข้างล่างเลยครับ
- เขียนเกมด้วย libGDX #1 – สร้างโปรเจ็ค
- เขียนเกมด้วย LibGDX #2 – Hello World
- เขียนเกมด้วย LibGDX #3 – Render และการรับ input
- เขียนเกมด้วย LibGDX #4 – Simple Game ภาคแรก
- เขียนเกมด้วย LibGDX #5 – Simple Game ภาคจบ
- เขียนเกมด้วย LibGDX #6 – Simple Game ภาคพิเศษ
- เขียนเกมด้วย LibGDX #7 - Simple Game - scene2d.ui
- เขียนเกมด้วย LibGDX #8 - Simple Game - Actor
สำหรับบทความนี้จะมานำเสนอ วิธีการทำให้รูปภาพเคลื่อนที่ได้ และการรับ input ต่างๆ เช่นเมาท์คลิ๊ก กดคีย์บอร์ด หรือว่า ทัชสกรีนบนหน้าจอมือถือแอนดรอยส์กันครับ
ตัวอย่างนี้ผมใช้วิธีง่ายๆ คือโหลดรูปมาแสดงบนหน้าจอ จากนั้น ก็จะให้มันขยับไปทางขวา จนสุดจอ แล้วก็วนมาเริ่มต้นไป วนแบบนี้ไปเรื่อยๆครับ
ก็อบปี้รูปของท่านเอง ไปไว้ที่ project-android/assets/
หรืออยากจะโหลดรูปตัวอย่าง ก็ตามนี้ครับ
ไฟล์ image ผมใช้ไฟล์ของผมเอง ชื่อ
chai.png
Credit: Thaweepong on Behance
ตัวอย่างใช้คลาส MyGame จาก บทความที่ 2 ได้เลยครับ ข้างล่างคือคลาส MyGame เดิมครับ
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.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
BitmapFont font;
@Override
public void create () {
batch = new SpriteBatch();
img = new Texture("badlogic.jpg");
font = new BitmapFont();
font.setColor(Color.WHITE);
}
@Override
public void render () {
Gdx.gl.glClearColor(0, 1, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
// batch.draw(img, 0, 0);
font.draw(batch, "Hello World", 300, 300);
batch.end();
}
}
จากด้านบน ผมจะเปลี่ยนให้เป็นแบบนี้
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.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
public class MyGame extends ApplicationAdapter {
SpriteBatch batch;
Texture img;
@Override
public void create () {
batch = new SpriteBatch();
img = new Texture(Gdx.files.internal("chai.png"));
}
@Override
public void render () {
Gdx.gl.glClearColor(0, 1, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
batch.draw(img, 0, 0);
batch.end();
จากด้านบน ผมได้ทำการลบ BitmapFont
ออก และ Texture
ทำการโหลดรูปภาพใหม่เข้ามา โดยใช้ Gdx.files.internal("chai.png")
ซึ่งเป็น Module ของทาง LibGDX ครับ เป็นการโหลดไฟล์จาก โฟลเดอร์ AhoyBoxBox-android/assets/
นั่นเอง ส่วนเมธอด render()
ก็สั่งให้ SpriteBatch
ทำการวาดภาพอันใหม่ ที่ตำแหน่ง 0, 0 จะได้ดังนี้
ต่อไป ผมต้องการให้รูปภาพที่แสดงมันขยับไปด้านขวาของหน้าจอเรื่อยๆ ผมจะทำอย่างไร? ก็ต้องเปลี่ยนค่า ที่เมธอด render()
ตรง batch.draw(img, x, y)
นั่นเองครับ ซึ่งค่า x และ y ก็คือ ตำแหน่งของแกน x และแกน y ที่ต้องการจะให้รูปแสดง โดยนับจากมุมซ้ายล่างของรูป
อันนี้เป็นตัวอย่างตำแหน่งของรูป และแกน x, y คร่าวๆครับ ฮ่าๆ รีบทำ ภาพไม่สวย ^^
ทีนี้ เราจะให้รูปมันขยับ เราก็ต้องทำเปลี่ยนค่า x, y ไปเรื่อยๆ เพราะ render()
มันจะรันเป็น Game Loop อยู่แล้ว คือมันจะวนทำซ้ำไปเรื่อยๆ วินาทีอาจจะ 40 ครั้ง 50 ครั้ง หรือ 60 ครั้ง โดยเราจะเพิ่ม ค่า x และ y เข้าไปในเมธอด render()
ดังนี้ โดยประกาศ int x = 0; int y = 0
เป็น member variable ไว้ภายในคลาส
batch.begin();
batch.draw(img, x, y);
batch.end();
x += 100 * Gdx.graphics.getDeltaTime();
ทีนี้เมื่อ render()
ถูกเรียก มันก็จะ batch.draw()
ตำแหน่ง x เปลี่ยนไปทุกๆครั้ง หากลองสั่งรันโปรแกรมดู ก็จะเห็นรูปขยับกันนะครับ
Delta Time
ทีนี้มาดูทำไมถึงเป็น Gdx.graphics.getDeltaTime();
อันนี้เป็น Module ของทาง LibGDX อีกเช่นกันครับ ไว้สำหรับแสดงเวลา DeltaTime มันคือเวลา จากเฟรมล่าสุด ถึงเฟรมปัจจุบันครับ ลองจินตนาการดู ภาพการเคลื่อนไหว ปกติจะแบ่งเป็น เฟรมๆใช่มั้ยครับ เช่น เฟรม 0 - 50 เป็นภาพแสดงการก้าวเท้าขวา ทุกๆเฟรม มันก็คือการเคลื่อนไหวทีละเล็กทีละน้อย 0 - 20 อาจจะกำลังยกขา 21-50 อาจเป็นช่วงที่ขากำลังแตะพื้น DeltaTime ก็คือช่วงเวลาระหว่างเฟรมสุดท้าย กับเฟรมล่าสุด หน่วยอาจจะเป็นเสี้ยววินาที หากอยากรู้ว่า DeltaTime มีค่าเท่าไหร่ ลองเพิ่มอันนี้เข้าไปครับ
...
batch.end();
Gdx.app.log("LOG", "Delta Time: " + Gdx.graphics.getDeltaTime());
ที่ console จะได้ผลลัพธ์ประมาณนี้
...
LOG: Delta Time: 0.015866747
LOG: Delta Time: 0.01686707
LOG: Delta Time: 0.016484985
LOG: Delta Time: 0.016276209
จากด้านบน เห็นเวลามั้ยครับ เสี้ยววินาที หาก เราเพิ่มค่า x ทีละ 100 ลองจินตนาการดูครับ เราแทบมองไม่เห็น รูปเลยครับ เนื่องจาก มันถูกเพิ่ม 100 แค่ 8 ครั้ง มันก็สุดจอไปแล้ว โดยใช้เวลา ไม่ถึง 0.1 วินาทีด้วยซ้ำ ตามองไม่ทัน ฉะนั้น ก็เลยต้องใช้วิธี x += 100 * ค่า deltaTime
ครับ ก็คือ เพิ่มที 1 - 2 pixel เท่านั้น ทีนี้ก็มองทันแล้ว
Show Log
Gdx.app.log()
นั้นก็คือ Module ของ LibGDX อีกแล้วครับท่าน ไว้สำหรับแสดง Log ในโปรแกรม หากเขียนแอนดรอยส์มา ก็จะคุ้นเคยกับ Log.i, Log.e
หรือบ้านๆเลย ใครใช้ System.out.prinln()
ก็เปลี่ยนมาให้ ล็อคดีกว่าครับ ^^
จริงๆ LibGDX มี Module อีกหลายตัวที่อำนวยความสะดวกให้เรานะครับ พูดแล้วจะหาว่าคุย ลองไปศึกษาเพิ่มเติมได้ที่นี่ LibGDX Module
การรับ Input
การรับ Input ทาง LibGDX ก็มี Module ที่เตรียมมาให้ครับ เช่น หากเราต้องการรู้ว่า มีการคลิกเมาท์ หรือว่ามีการแตะที่หน้าจอมือถือหรือไม่ ก็จะใช้คำสั่งนี้ครับ
if (Gdx.input.isTouched()) {
// Touched
}
ส่วนการรับ Input จากแป้นคีย์บอร์ด ก็ใช้คำสั่งนี้ เพื่อเช็คว่ามีการพิมพ์คีย์บอร์ดหรือไม่
if (Gdx.input.isKeyPressed(Keys.SPACE)) {
// Spacebar is pressed.
}
อย่าลืม import
import com.badlogic.gdx.Input;
Note: short cut สำหรับ import Eclipse คือ Ctrl + Alt + O ส่วน Intelij IDEA มัน auto import on fly ^^
สุดท้าย ผมได้ทำการ implement ทั้งหมดข้างต้น โดยทำเป็นคล้ายๆ Flappy Bird คือ รูปจะมีการเคลื่อนที่ไปข้างหน้า (ไปด้านขวา ค่า x ค่อยๆเพิ่ม) และจะตกลงเรื่อยๆ(ค่า y ค่อยๆลด) หากคลิ๊กเมาท์หรือแตะหน้าจอ ก็จะให้รูปมันลอยขึ้น ครับ (ค่า y เพิ่มขึ้น) โค๊ดที่ได้ก็จะประมาณนี้ ลองๆนำไปเล่นกันดูนะครับ :)
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.SpriteBatch;
public class MyGame extends ApplicationAdapter {
public static final int WIDTH = 800;
public static final int HEIGHT = 480;
SpriteBatch batch;
Texture img;
int x = 0;
int y = HEIGHT / 2;
@Override
public void create () {
batch = new SpriteBatch();
img = new Texture(Gdx.files.internal("chai.png"));
}
@Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
y -= 200 * Gdx.graphics.getDeltaTime();
x += 100 * Gdx.graphics.getDeltaTime();
batch.begin();
batch.draw(img, x, y);
batch.end();
Gdx.app.log("LOG", "Delta Time: " + Gdx.graphics.getDeltaTime());
if (Gdx.input.isTouched()) {
y += 500 * Gdx.graphics.getDeltaTime();
}
// เช็คหาก รูปตกขอบทั้งด้านข้างหรือด้านล่าง ก็ให้ไปเริ่มใหม่
if (y < 0 || x > WIDTH) {
x = 0;
y = HEIGHT / 2;
}
}
}
โดยตัวเกม คือ เริ่มต้นสตาร์ทที่ตำแหน่ง x = 0 และ y ที่ตำแหน่งกึ่งกลางของความสูงหน้าจอ และจะตกลงเรื่อยๆ ต้องใช้เมาท์กดที่หน้าจอ หรือว่าใช้มือแตะที่หน้าจอมือถือ หากรันบนแอนดรอยส์ ก็จะทำให้รูป มีการลอยตัวขึ้นครับ ลองๆ นำไปประยุกต์ปรับใช้ หรือลองเล่นกันดูครับ หากใครมีข้อสงสัย สอบถามได้ครับ
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit