เขียนเกมด้วย 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.pngCredit: 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.BitmapFontimport 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.BitmapFontimport 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.015866747LOG: Delta Time: 0.01686707LOG: Delta Time: 0.016484985LOG: 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
 -  
 Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust