เขียนเกมด้วย LibGDX #2 – Hello World

เขียนเกมด้วย LibGDX #2 – Hello World Cover Image

หลังจากนำเสนอ เขียนเกมด้วย LibGDX #1 - สร้างโปรเจ็ค ในบทความที่แล้ว ทำให้ตอนนี้สามารถที่จะตั้งค่า เซ็ตอัพโปรเจ็คด้วย LibGDX ได้แล้ว ต่อไปจะเป็นการเริ่มต้นใช้งาน LibGDX กันเลย โปรแกรมแรกที่จะแสดงก็ต้องเป็นแบบคลาสสิคสุดๆ นั้นก็คือ แสดงคำว่า Hello World ก่อนอื่นนั้น เราต้องมาเข้าใจภาพรวม เข้าใจคอนเซปของ LibGDX กันก่อนครับ

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


Life Cycle

การทำงานของ LibGDX เราจะมาพูดถึงขั้นตอนการ create , pause , start กันครับ ว่ามันเริ่มต้นยังไง คลาสที่สำคัญเลย คือ ApplicationListener อย่างเช่น เราตั้งชื่อคลาสเกมของเราว่า MyGame และทำการ implements ApplicationListener เมธอดทั้งหมดที่เรา override จะได้ดังนี้

import com.badlogic.gdx.ApplicationListener;

public class MyGame implements ApplicationListener {
    @Override
    public void create() {

    }

    @Override
    public void resize(int width, int height) {

    }

    @Override
    public void render() {

    }

    @Override
    public void pause() {

    }

    @Override
    public void resume() {

    }

    @Override
    public void dispose() {

    }
}

เมธอดแต่ละเมธอดจะถูกเรียกใช้งาน เรียงตามลำดับ ดังนี้ครับ

  • create() : เมธอดนี้จะถูกเรียกครั้งเดียว คือตอนที่ Application ถูกสร้าง

  • resize(int width, int height) : เมธอดนี้จะถูกเรียกทุกครั้งเมื่อมีการเปลี่ยนขนาดหน้าจอ เช่น ปรับหน้าจอวินโดว์ หรือบนมือถือ เช่นเปลี่ยนจากแนวตั้งเป็นแนวนอน และเมธอดนี้จะถูกเรียกครั้งแรก หลังจาก create() เช่นกัน parameter witdh, height คือขนาดกว้าง ยาว ของหน้าจออันใหม่ที่มีการเปลี่ยนแปลง

  • render() : เมธอดนี้คือหัวใจของ Application เลยก็ว่าได้ เพราะมันคือ Game Loop ครับ เมธอดนี้จะถูกรันตลอดเวลา นึกถึงเวลา render เกมครับ เคยเห็นเกมที่มี FPS (Frame Per Second) มั้ยครับ บางเกมมี FPS 50-60 เช่น เมธอดนี้จะถูกเรียก 50-60 ครั้งต่อวินาที รูปภาพ หรือ ฉากต่างๆของเกมส์ จะถูกวาดที่เมธอดนี้

  • pause() : บน Android เมธอดนี้จะถูกเรียกเมื่อมีสายเข้า หรือว่าเวลายูเซอร์กดปุ่ม Home แต่ใน Desktop จะถูกเรียกก่อน เมธอด dispose() เมื่อออกจากโปรแกรม เมธอดนี้เหมาะสำหรับเก็บค่า state ต่างๆ เช่นเซฟตำแหน่งปัจจุบันผู้เล่น บันทึกแต้มต่างๆ

  • resume() : เมธอดนี้จะถูกเรียกเฉพาะบน Android เมื่อแอพพลิเคชันถูกเรียกกลับจาก pause state.

  • dispose() : เมธอดนี้จะถูกเรียกเมื่อกำลังจะปิดแอพพลิเคชัน สิ้นสุดโปรแกรม เป็นเมธอดที่ถูกเรียกหลังสุด

โอเคเมื่อเข้าใจว่าเมธอดแต่ละตัว ทำงานอย่างไรแล้ว คราวนี้กลับไปดูตัวเกมที่ได้ import มา จาก บทความบทแรกครับ ที่โฟลเดอร์ AhoyBoxBox-core จะเห็นไฟล์ชื่อ 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.SpriteBatch;

public class MyGame extends ApplicationAdapter {
    SpriteBatch batch;
    Texture img;

    @Override
    public void create () {
        batch = new SpriteBatch();
        img = new Texture("badlogic.jpg");
    }

    @Override
    public void render () {
        Gdx.gl.glClearColor(1, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.begin();
        batch.draw(img, 0, 0);
        batch.end();
    }
}

extends ApplicationAdapter ทำไมถึงเป็นคลาส ApplicationAdapter จริงๆ คลาสนี้ implements มาจากอินเตอร์เฟซ ApplicationListener อีกทีนึง ตอนนี้เรา extends คลาส ApplicationAdapter เพราะเราสนใจแค่เมธอด สองตัว นั่นก็คือ create() กับ render() เท่านั้นครับ เวลาทำงานมันก็จะไปเรียกเมธอด ApplicationListener.create() และ ApplicationListener.render() ครับ

ที่เมธอด create() มีการสร้าง SpriteBatch และ Texture เพื่อที่จะวาดรูป ให้เราลองจินตนาการ Texture ก็เปรียบเสมือนคลาสที่ทำการโหลดรูปมาเก็บไว้ใน memory แต่มันไม่สามารถแสดงรูปได้ จำเป็นต้องใช้ SpriteBatch มาช่วยในการวาดรูปนั่นเอง

batch = new SpriteBatch();
img = new Texture("badlogic.jpg");

ต่อไปดูที่เมธอด render() เมธอดนี้เป็น Game Loop จะถูกเรียกเรื่อยๆ วินาทีละหลายสิบครั้ง จนกว่าจะปิดแอพพลิเคชัน

Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

2 คำสั่งด้านบน เป็นการเคลียร์หน้าจอ ให้เป็นสีแดง โดยใช้เมธอด Gdx.gl.glClearColor(red, green, blue, opacity) ซึ่งค่าสีก็เป็น rgba มีค่าตั้งแต่ 0 - 1 หน่วยเป็น float ส่วนอีกบรรทัดเป็นการฟังค์ชั่นของ OpenGL สำหรับเคลียร์หน้าจอครับ

หรือจะใช้แทนรหัสสีแบบนี้ก็ได้ Gdx.gl.glClearColor(255 /255.0f, 0 /255.0f, 0 / 255.0f, 1)

ทุกครั้งที่เราจะสั่งให้ SpriteBatch วาดรูปนั้น เราต้องให้วาดระหว่างเมธอด SpriteBatch.begin() และ SpriteBatch.end() ดังตัวอย่างนี้

batch.begin(); batch.draw(img, 0, 0); batch.end();

จะเห็นได้ว่า SpriteBatch ทำการวาดรูป Texture ที่โหลดมาจาก `badlogic.jpg’ ที่ตำแหน่ง x = 0 และ y = 0

Note: ตำแหน่ง x และ y เริ่มที่มุมซ้ายล่างนะครับ

ทีนี้พอกด Run มันก็จะได้รูปแบบ แบบเดียวกับบทความที่แล้วแบบนี้แหละครับ

libgdx

ทีนี้ลองเปลี่ยนสีหน้าจอครับ เป็นสีเขียว ทำยังไง? (ผมลดสีเขียวลงหน่อยนึง รู้สึกแสบตาเกินครับ )

Learn LibGDX

ง่ายมากครับ แค่เปลี่ยนเป็น Gdx.gl.glClearColor(0, 1, 0, 1);

แล้วเราจะแสดง Hello World ยังไงละเนี้ย?

LibGDX มีให้คุณอีกแล้วครับ LibGDX ได้เตรียม com.badlogic.gdx.graphics.g2d.BitmapFont ไว้ให้แล้ว สำหรับแสดงตัวอักษรบนหน้าจอ

เริ่มแรก ผมทำการประกาศสร้างตัวแปร font จากคลาส BitmapFont เป็น private member ดังนี้

BitmapFont font;

จากนั้น ก็สร้างออปเจ็คขึ้นใหม่ที่เมธอด create() และตั้งค่าสีเป็นสีขาว

 @Override
public void create() {
...
    font = new BitmapFont();
    font.setColor(Color.WHITE);
}

ที่เมธอด render() จะให้แสดงข้อความแทนรูปภาพ จะเปลี่ยนยังไง ?

BitmapFont จะมีเมธอด draw() ครับ โดยรับพารามิเตอร์เป็น SpriteBatch, ข้อความ, ตำแหน่งแกนx, ตำแหน่งแกน y ตามลำดับครับ

batch.begin();
font.draw(batch, "Hello World", 300, 300);
batch.end();

ก็จะได้ผลลัพธ์ดังนี้ครับ

Ahoy LibGDX

สุดท้ายโค๊ดใน 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();
    }
}

ทีนี้มาดูที่คลาส Starter ของ Desktop และ Android กันครับ

ลองเปิด DesktopLauncher ของ AhoyBoxBox-desktop ก่อนครับ ก็คือ เราเขียนตัวเกมทุกอย่างไว้ที่ AhoyBoxBox-core แล้ว ชื่อว่า MyGame ส่วน Starter คลาสจะเป็นเหมือนแค่ สื่อกลางที่จะเปิดเกมใน Platform นั้นๆเอง ดังนี้

public class DesktopLauncher {
    public static void main (String[] arg) {
        LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
        new LwjglApplication(new MyGame(), config);
    }
}

คือข้างบน เราสามารถ คอนฟิคค่าต่างๆ ของ หน้าตา Desktop ได้ เช่น ชื่อไตเติ้ล ขนาดความกว้าง ความยาว เช่น

LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = "Learn LibGDX #2 - Hello World ";
config.width = 500;
config.height = 500;
new LwjglApplication(new MyGame(), config);

ทีนี้มาดู Starter คลาสของแอนดรอยส์กันบ้าง ไปที่ AndroidLauncher ในโฟลเดอร์ AhoxBoxBox-android จะเห็นดังนี้ เข้าใจตรงกันนะ :)

public class AndroidLauncher extends AndroidApplication {
    @Override
    protected void onCreate (Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
        initialize(new MyGame(), config);
    }
}

โดยปกติแล้ว เวลาเราเขียนแอนดรอยส์ เราจะทำการ extends Activity ใช่มั้ยครับ แต่ว่าอันนี้เราจะทำการ extends AndroidApplication ซึ่งเป็นคลาสของ LibGDX อันนี้ก็สามารถ config ค่าต่างๆได้เช่นกันครับ เช่น พวกเข็มทิศ Accelerometer ครับ ส่วนเมธอด initialize(Game, config); ก็คือการเปิดแอพแอนดรอยส์ โดยใช้ตัวเกมที่เราได้สร้างไว้ครับ

สำหรับบทความนี้ จบเพียงเท่านี้ก่อนครับ วันนี้พวกคุณได้รู้จัก Life Cycle ของ LibGDX ได้รู้จัก ApplicationListener และ method ต่างๆของ ApplicationListener รวมถึงรู้จัก SpriteBatch, Texture, BitmapFont และการแสดงผลลัพธ์ผ่าน เมธอด ApplicationListener.render() ไปแล้ว ก็ลองนำบทความไปประยุกต์ใช้ดูกันครับ เช่นต้องการโหลดรูปอื่นขึ้นมา ต้องการเปลี่ยนตำแหน่งรูป หรือโหลดรูปพร้อมแสดงข้อความ เป็นต้นครับ แล้วพบกันบทความต่อไปครับ ขอบคุณครับ

Chai Chai Phonbopit : Web Developer @Nimbl3 • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด