เขียนเกมด้วย LibGDX 7 – Simple Game - scene2d.ui
วันนี้จะมาแนะนำการทำหน้า MenuScreen ด้วย scene2d.ui ครับ จากบทความที่แล้ว imple Game ภาคพิเศษ เราได้ตัวเกมที่มีหน้าเมนูก่อนเข้าเล่นเกม แต่ว่าไม่มีเมนูอะไรให้เลือกเลย เช่น กด Start กด HighScore อะไรพวกนี้ เป็นต้น วันนี้ก็เลยจะมานำเสนอวิธีการทำ Menu ดังกล่าวครับ
บทความตอนอื่นๆ ในซีรีย์ เขียนเกมด้วย 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 สำหรับแตะหน้าจอเท่านั้น ถึงจะเริ่มเกม
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 นับจากมุมซ้ายของปุ่มนะครับ อย่าลืม)?
สุดท้าย ทดสอบ รันโปรแกรมเลย อยากเห็นปุ่มเต็มแก่แล้ว
**เอ้าเห้ย ทำไมปุ่มไม่โชว์ละเนี่ย ? **
ยังจำ Stage กันได้มั้ยครับ ตัว Stage มันก็เปรียบเสมือนฉากเกมๆหนึ่ง ที่คอยรับ input ต่างๆ รวมถึงแสดงผลออกทางจอภาพ ทุกๆอยากที่จะแสดงผล จำเป็นต้องเพิ่มเข้าไปใน Stage ด้วย เพิ่มอันนี้ต่อท้าย TextButton เมื่อกี้ครับ
stage.addActor(buttonStart);
ทดสอบอีกรอบ มหัศจรรย์ เพิ่มบรรทัดเดียว เห็นผลเลย ^^
ว้าว ปุ่มกดโชว์แล้วครับ แต่ว่ายังไม่สามารถทำอะไรกับมันได้ ก็เพิ่ม 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
- Authors
- Name
- Chai Phonbopit
- Website
- @Phonbopit