เขียนเกมด้วย LibGDX #7 – Simple Game - scene2d.ui

เขียนเกมด้วย LibGDX #7 – Simple Game - scene2d.ui Cover Image

วันนี้จะมาแนะนำการทำหน้า MenuScreen ด้วย scene2d.ui ครับ จากบทความที่แล้ว imple Game ภาคพิเศษ เราได้ตัวเกมที่มีหน้าเมนูก่อนเข้าเล่นเกม แต่ว่าไม่มีเมนูอะไรให้เลือกเลย เช่น กด Start กด HighScore อะไรพวกนี้ เป็นต้น วันนี้ก็เลยจะมานำเสนอวิธีการทำ Menu ดังกล่าวครับ

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


มาเริ่มกันเลยครับ ต่อจากโค๊ดที่บทความที่แล้วเลย หน้าเมนูของเรา ตอนนี้จะมีเพียงแค่นี้เอง ไม่มีอะไรให้เลือกเลย แค่รับ input สำหรับแตะหน้าจอเท่านั้น ถึงจะเริ่มเกม

Stage

Stage คือ InputProcessor เปรียบเสมือนตัวรับอินพุทต่างๆ ประมาณมันคอยตรวจจับ input เช่น หากเราต้องการสร้าง Button ปุ่มกด เราก็จำเป็นจะต้องใช้ Stage ครับ

ที่ไฟล์ MainMenuScreen.java ให้เราทำการประกาศสร้าง Stage ชื่อว่า stage ครับ

public class MainMenuScreen implements Screen {
    final Drop game;
    OrthographicCamera camera;
    private Stage stage;

   // ส่วนอื่นๆของโค๊ด

Actor

Actor มันก็ Scene graph เป็นส่วนหนึ่งของ Stage มันสามารถที่จะ draw, render รูปภาพ หรือว่ารับ Input ก็ได้ อย่าง Widget ต่างๆเช่น Button Label Checkbox มันก็คือ Actor นั่นเอง (ถ้าผมเข้าใจไม่ผิดนะ ฮ่าๆ เดี่ยวต้องกลับไปอ่านอีกรอบ)

Add Menu Button

โอเค มาถึงวิธีการเพิ่ม Button กันเลยดีกว่า สำหรับ Button LibGDX นั้น ก็มีมาให้แล้วโดยใช้ scene2d.ui ครับ ส่วน Button จำเป็นต้องมี Skins ของ Button ครับ สามารถดาวน์โหลด Skins ได้ตามข่างล้างนี้

หรือว่าโหลดซิปนี้ครับ ผมรวมไฟล์ /assets ที่จะใช้ทั้งหมด แตกซิปไปทับของเก่าได้เลยครับ assets.zip บน Google Drive

สำหรับรายละเอียดไฟล์ข้างบน ตอนนี้ยังไม่ต้องสนใจมากครับ ว่ามันไว้ทำอะไร แค่เอามาใช้ได้ก็พอ โดยรวมแล้วมันก็คือ Style ของ Button นั่นเอง บางไฟล์ก็จะระบุพิกัดตำแหน่ง ของรูป ของ Button ต่างๆครับ หากสนใจ ก็ลองๆ เปิดไฟล์ดูครับ น่าจะดูไม่ยาก ฟอแมตก็ json แล้วก็ atlas ก็คล้ายๆ css ครับ

สำหรับคนที่ยังงงเรื่อง Skin อยู่ ก็ขออธิบายเพิ่มเติมแล้วกันครับ ว่า Skin มันก็เปรียบเสมือนหน้าตาของ Widget นั้นๆนั่นเอง นึกถึงเวลาคุณเขียนเว็บไซต์ ก็ต้องมี stylesheet กำหนด button ให้มีสีนั้นสีนี้ กำหนด background ให้มัน หรือว่าใน Android ก็จะมี Button โดยกำหนด attribute ให้มันเช่น ใส่ background, textSize, padding ต่างๆ เป็นต้นครับ

ใน LibGDX หากเราต้องการสร้าง Button เราจำเป็นต้องมี Skin ด้วยครับ ทำการประกาศสร้างตัวแปร skin ไว้ในคลาส MainMenuScreen ครับ

public class MainMenuScreen implements Screen {

    final Drop game;
    OrthographicCamera camera;
    private Stage stage;
    private Skin skin;
    // ส่วนอื่นๆ

จากนั้นในส่วน constructor ก็ทำการสร้างออปเจ็คใหม่ขึ้นมา ดังนี้

    public MainMenuScreen(final MainGame game) {
        this.game = game;

        stage = new Stage();
        Gdx.input.setInputProcessor(stage);

        skin = new Skin(Gdx.files.internal("uiskin.json"));

จากโค๊ดด้านบน ผมได้ทำการสร้างออปเจ็ค Stage ขึ้นมาใหม่ จากนั้นก็ใช้ Gdx.input.setInputProcessor(stage) เพื่อทำให้ stage เป็นตัวรับ handler ต่างๆครับ คือคอยรับ input จากผู้เล่น

ต่อมาก็สร้างออปเจ็ค Skin ขึ้นมาใหม่ โดยโหลดจาก /assets/uiskin.json สุดท้ายที่ เมธอด render() ก็ได้เปลี่ยนให้เหลือเพียงแค่ใช้ Stage#act() และ Stage#draw() สำหรับเรนเดอร์ครับ เพราะว่าเราจะใช้ Stage ในการเรนเดอร์แล้วครับ ไม่ใช้ SpriteBatch แล้ว

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
    }

ต่อมา สร้าง TextButton สมมติผมสร้าง Button ชื่อ ‘START’ เพื่อเอาไว้สำหรับเมื่อกดปุ่มนี้ ก็จะไปที่หน้า MainGame เลย (แต่ว่าตอนนี้ทำให้โชว์ก่อนครับ ยังไม่ได้ให้รับ event ใดๆ) สร้างต่อจาก Skin เลย

skin = new Skin(Gdx.files.internal("uiskin.json"));

TextButton buttonStart = new TextButton("START", skin);
buttonStart.setWidth(200);
buttonStart.setHeight(50);
buttonStart.setPosition(800 / 2 - 200 / 2, 300);

จากด้านบน ผมทำการสร้าง TextButton โดยจะให้มันชื่อว่า ‘STARTและโหลด Style ต่างๆ จาก Skin ครับ ไอ้ตัว Skin มันก็ไปโหลดuiskin.json` มาอีกทอดนึง แล้ว uiskin.json ก็จะไปโหลด uiskin.atlats แล้ว …….. พอๆ ฮ่าๆ ก็แค่รู้ว่า TextButton มันโหลด Skin มาครับ :)

จากนั้น เซตค่าให้มัน ความกว้าง ความสูง แล้วก็ตำแหน่งครับ ผมจัดให้มันอยู่กึ่งกลางหน้าจอ โดยแกน y = 300 ส่วน buttonStart.setPosition(800 / 2 - 200 / 2, 300); คงไม่งงกันนะครับ จอกว้าง 800 ปุ่มมีขนาด 200 จะให้มันอยู่กึ่งกลาง ต้องจัดที่ x เท่าไหร่ (x นับจากมุมซ้ายของปุ่มนะครับ อย่าลืม)?

สุดท้าย ทดสอบ รันโปรแกรมเลย อยากเห็นปุ่มเต็มแก่แล้ว

No Button Menu

*เอ้าเห้ย ทำไมปุ่มไม่โชว์ละเนี่ย ? *

ยังจำ Stage กันได้มั้ยครับ ตัว Stage มันก็เปรียบเสมือนฉากเกมๆหนึ่ง ที่คอยรับ input ต่างๆ รวมถึงแสดงผลออกทางจอภาพ ทุกๆอยากที่จะแสดงผล จำเป็นต้องเพิ่มเข้าไปใน Stage ด้วย เพิ่มอันนี้ต่อท้าย TextButton เมื่อกี้ครับ

stage.addActor(buttonStart);

ทดสอบอีกรอบ มหัศจรรย์ เพิ่มบรรทัดเดียว เห็นผลเลย ^^

Button Menu

ว้าว ปุ่มกดโชว์แล้วครับ แต่ว่ายังไม่สามารถทำอะไรกับมันได้ ก็เพิ่ม InputListener ให้กับปุ่มกดครับ ปกติใน Android พวก Button เราสามารถ setOnClickListener ให้มันได้ใช้มั้ยครับ ตัว scene2d.ui#Button มันก็มีเหมือนกันครับ นั่นก็คือ addListener

buttonStart.addListener(new ClickListener() {
    @Override
    public void clicked(InputEvent event, float x, float y) {
        super.clicked(event, x, y);
        game.setScreen(new GameScreen(game));
    }
});

ด้านบน ผมทำการ addListener จากนั้นก็ override เมธอด clicked เพื่อรอดักจับว่าเมื่อมีการกดปุ่มนี้เมื่อไหร่ ก็ให้ทำการเปิดหน้า GameScreen เลย ง่ายมั้ย?

ก่อนจบ ที่ เมธอด resize() เพิ่มอันนี้เข้าไป เพื่อให้ Stage มันปรับขนาดเวลาหน้าจอของเรามีการเปลี่ยนแปลง

@Override
public void resize(int width, int height) {
    stage.getViewport().update(width, height, true);
}

สุดท้าย อย่าลืม dispose สิ่งที่เราสร้างมาครับ คือ Stage และ Skin ส่วนสิ่งต่างๆที่อยู่ในออปเจ็ค Drop อย่าไป dispose มันนะครับ มันยังต้องใช้งานอยู่ ^^

@Override
public void dispose() {
    stage.dispose();
    skin.dispose();
}

โค๊ดทั้งหมด

MainMenuScreen.java

package com.badlogic.drop;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.viewport.StretchViewport;

public class MainMenuScreen implements Screen {

    final Drop game;

    OrthographicCamera camera;

    private Stage stage;
    private Skin skin;

    public MainMenuScreen(final Drop gam) {
        game = gam;

        camera = new OrthographicCamera();
        camera.setToOrtho(false, 800, 480);

        stage = new Stage(new StretchViewport(800, 480));
        Gdx.input.setInputProcessor(stage);

        skin = new Skin(Gdx.files.internal("uiskin.json"));

        TextButton buttonStart = new TextButton("START", skin);
        buttonStart.setWidth(200);
        buttonStart.setHeight(50);
        buttonStart.setPosition(800 / 2 - 200 / 2, 300);

        stage.addActor(buttonStart);

        buttonStart.addListener(new ClickListener() {
            @Override
            public void clicked(InputEvent event, float x, float y) {
                super.clicked(event, x, y);
                game.setScreen(new GameScreen(game));
            }
        });

    }

    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(0, 0, 0.2f, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        stage.act(Gdx.graphics.getDeltaTime());
        stage.draw();
    }

    @Override
    public void resize(int width, int height) {
        stage.getViewport().update(width, height, true);
    }

    @Override
    public void show() {
    }

    @Override
    public void hide() {
    }

    @Override
    public void pause() {
    }

    @Override
    public void resume() {
    }

    @Override
    public void dispose() {
        stage.dispose();
        skin.dispose();
    }
}

หากใครมีปัญหาตรงไหน สอบถามได้ครับ หรือใครมีคำแนะนำ ข้อเสนอแนะ ติชม ก็พร้อมยินดีครับ อยากเห็นคนเก่งๆมาช่วยชี้แนะครับ มาแชร์ประสบการณ์กัน หากใครเห็นว่าบทความนี้มีประโยชน์ อย่าลืมบอกต่อเพื่อนๆกันด้วยนะครับ :D

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

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