เขียนเกมด้วย LibGDX :3 – Render และการรับ input

Published on
Game Development
2014/04/libgdx-tutorial-render-and-input-handling
Discord

สวัสดีครับ บทความนี้เป็นบทความที่ 3 ครับ จากซีรีส์ สอน LibGDX ครับ

บทความตอนอื่นๆ ในซีรีย์ เขียนเกมด้วย LibGDX ติดตามอ่านได้ตามลิงค์ข้างล่างเลยครับ


สำหรับบทความนี้จะมานำเสนอ วิธีการทำให้รูปภาพเคลื่อนที่ได้ และการรับ input ต่างๆ เช่นเมาท์คลิ๊ก กดคีย์บอร์ด หรือว่า ทัชสกรีนบนหน้าจอมือถือแอนดรอยส์กันครับ

ตัวอย่างนี้ผมใช้วิธีง่ายๆ คือโหลดรูปมาแสดงบนหน้าจอ จากนั้น ก็จะให้มันขยับไปทางขวา จนสุดจอ แล้วก็วนมาเริ่มต้นไป วนแบบนี้ไปเรื่อยๆครับ

ก็อบปี้รูปของท่านเอง ไปไว้ที่ project-android/assets/ หรืออยากจะโหลดรูปตัวอย่าง ก็ตามนี้ครับ

chai.png

ไฟล์ 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 จะได้ดังนี้

Example

ต่อไป ผมต้องการให้รูปภาพที่แสดงมันขยับไปด้านขวาของหน้าจอเรื่อยๆ ผมจะทำอย่างไร? ก็ต้องเปลี่ยนค่า ที่เมธอด render() ตรง batch.draw(img, x, y) นั่นเองครับ ซึ่งค่า x และ y ก็คือ ตำแหน่งของแกน x และแกน y ที่ต้องการจะให้รูปแสดง โดยนับจากมุมซ้ายล่างของรูป

อันนี้เป็นตัวอย่างตำแหน่งของรูป และแกน x, y คร่าวๆครับ ฮ่าๆ รีบทำ ภาพไม่สวย ^^

Screen

ทีนี้ เราจะให้รูปมันขยับ เราก็ต้องทำเปลี่ยนค่า 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 ที่ตำแหน่งกึ่งกลางของความสูงหน้าจอ และจะตกลงเรื่อยๆ ต้องใช้เมาท์กดที่หน้าจอ หรือว่าใช้มือแตะที่หน้าจอมือถือ หากรันบนแอนดรอยส์ ก็จะทำให้รูป มีการลอยตัวขึ้นครับ ลองๆ นำไปประยุกต์ปรับใช้ หรือลองเล่นกันดูครับ หากใครมีข้อสงสัย สอบถามได้ครับ

Buy Me A Coffee
Authors
Discord