libGDX Scene2d tutorial – Part 1 – Loading Screen

libGDX Scene2d tutorial – Part 1 – Loading Screen

בעבר כתבתי על איך ליצור פרוייקט libGDX, ואיך לכתוב משחק פשוט. היום נתחיל בסדרת מדריכים לכתיבת משחק ב- libGDX עם שימוש ב- Scene2d – ספרייה שמקלה על ניהול ה- UI.

התחלה

ניצור פרוייקט חדש. אין צורך ב- Box2d או ספריות נוספות, scene2d מגיעה באופן מובנה בתוך libGDX.

המשחק שלנו יכלול 5 מסכים:

  • Loading – מסך טעינה, בו נטען לזיכרון את כל ה- assets הדרושים, כמו תמונות, קבצי קול וכו׳.
  • Splash – מסך ספלאש, בו נציג את הלוגו שלנו.
  • Menu – מסך תפריט ראשי, בו נציג את ממשק ניהול המשחק (כפתור להתחלת משחק, שיתוף וכו׳).
  • Play – מסך משחק, העיקר.
  • Game Over – מסך סיום משחק בו נציג את התוצאה.


Main Class

קראתי לפרוייקט Spacers, וזה שם המחלקה שנוצרה – היא ה- Entry Point שלנו למשחק. נשנה את ה- type ממנו אנחנו יורשים ל- Game.

נעבוד בצורה כזו שהמחלקה הזו תחזיק את ההגדרות (לדוגמא, רוחב ואורך מסך) ואת כל המסכים. וכל מסך יחזיק ב- reference בחזרה.

זה נראה כך:

public class Spacers extends Game {
 
	// screens
	public LoadingScreen loadingScreen;
	public SplashScreen splashScreen;
	public MenuScreen menuScreen;
	public PlayScreen playScreen;
 
	public int SCREEN_WIDTH = 480;
	public int SCREEN_HEIGHT = 800;
 
	public OrthographicCamera camera;
 
	@Override
	public void create () {
		Assets.init();
 
		camera = new OrthographicCamera();
		camera.setToOrtho(false, SCREEN_WIDTH, SCREEN_HEIGHT);
 
		loadingScreen = new LoadingScreen(this);
		splashScreen = new SplashScreen(this);
		menuScreen = new MenuScreen(this);
		playScreen = new PlayScreen(this);
 
 
		setScreen(loadingScreen);
	}
 
	@Override
	public void render () {
		super.render();
	}
 
	@Override
	public void dispose () {
		loadingScreen.dispose();
		splashScreen.dispose();
		menuScreen.dispose();
		playScreen.dispose();
 
		Assets.dispose();
	}
}

כבר בהתחלה אני יוצר מופע של כל אחד מהמסכים, מה שיאפשר לנו גמישות בהמשך, שנוכל לשנות מסך מכל מסך בו נהיה.

הדבר המעניין בקוד הנ״ל זה Assets – מחלקה סטטית שמנהלת את כל נושא ה״נכסים״ (תרגום לא משהו, אני יודע). מייד עם עליית המשחק נקרא למתודת init ובסוף ל- dispose.

AssetManager

בתוך מחלקת Assets נחזיק אובייקט מסוג AssetManager. תפקידו הוא… לנהל assets (מפתיע). כלומר, במקום שנצטרך ליצור משתנה עבור כל texture או קובץ קול שנרצה להחזיק בזיכרון, ישנו אובייקט שמתפקד כמעין container. הוא טוען קובץ ע״י פקודת load עם ה- path למיקום של הקובץ בתיקיית assets וסוג הקובץ. והדרך להשתמש בקובץ הוא ע״י אותו path – שבעצם מתפקד כ- key.

זה הקוד (יש כאן רק חלק מה- assets בהם נשתמש, בהמשך נוסיף עוד):

public class Assets {
    // static names for assets, for easier usage
    public static String LOGO = "logo.png";
    public static String PLAY_BUTTON = "images/play.png";
    public static String SPACESHIP = "images/spaceship.png";
    public static String ALIEN_1 = "images/alien1.png";
    public static String BULLET = "images/bullet.png";
    public static String SKIN = "skin/a/uiskin.atlas";
 
    private static AssetManager assetManager;
    public static BitmapFont mainFont;
 
    public static void init(){
        assetManager = new AssetManager();
        // load font before other assets, cause we need it first
        float fontSize = .50f;
        mainFont = new BitmapFont(Gdx.files.internal("fonts/goodTimes.fnt"));
        mainFont.getData().setScale(fontSize, fontSize);
    }
    public static void dispose(){
        assetManager.dispose();
        mainFont.dispose();
    }
 
    public static void load() {
        assetManager.load(LOGO, Texture.class);
        assetManager.load(PLAY_BUTTON, Texture.class);
        assetManager.load(SPACESHIP, Texture.class);
        assetManager.load(ALIEN_1, Texture.class);
        assetManager.load(BULLET, Texture.class);
        assetManager.load(SKIN, TextureAtlas.class);
    }
 
    // get loading progress
    public static float getProgress(){
        return assetManager.getProgress();
    }
    // is loading finished?
    public static boolean update(){
        return assetManager.update();
    }
 
    public static Texture getTexture(String tex){
        return assetManager.get(tex, Texture.class);
    }
 
    public static TextureAtlas getTextureAtlas(String tex){
        return assetManager.get(tex, TextureAtlas.class);
    }
}

כדי לאפשר קוד קריא יותר, נשמור את ה- path של כל asset במשתנה משלו, כך שכדי לקרוא ל- texture נצטרך רק פקודה כזו:

Assets.getTexture(Assets.SPACESHIP)

בלי לכתוב את ה- path מחדש.

 

דבר נוסף שמאפשר לנו AssetManager, זה לקבל עדכון תוך כדי טעינה על הסטטוס. זה שימושי כדי להראות למשתמש עוד כמה זמן הוא צריך לחכות.

הדבר היחיד שטוענים מייד בהתחלה זה את הפונט – אנחנו רוצים להציג הודעה של "Loading" בזמן הטעינה, לכן הפונט לא יכול להיות חלק מה- assets אותם אנחנו טוענים.

Screen

הקוד של המסך עצמו נראה כך:

public class LoadingScreen implements Screen {
    private final Spacers app;
    private SpriteBatch batcher;
    private ShapeRenderer shapeRenderer;
    private float progress;
    private float titleWidth;
    private String TITLE = "Loading...";
 
    public LoadingScreen(final Spacers spacers) {
        this.app = spacers;
        shapeRenderer = new ShapeRenderer();
 
        batcher = new SpriteBatch();
        batcher.setProjectionMatrix(app.camera.combined);
 
        GlyphLayout layout = new GlyphLayout();
        layout.setText(Assets.mainFont, TITLE);
        titleWidth = layout.width;
    }
    @Override
    public void show() {
        shapeRenderer.setProjectionMatrix(app.camera.combined);
        this.progress = 0f;
        Assets.load();
    }
 
    private void update(float delta) {
        progress = MathUtils.lerp(progress, Assets.getProgress(), .1f);
        if (Assets.update() && progress >= Assets.getProgress() - .001f) {
            app.setScreen(app.splashScreen);
        }
    }
 
    @Override
    public void render(float delta) {
        Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
 
        batcher.begin();
        batcher.enableBlending();
        // draw title
        Assets.mainFont.draw(batcher, TITLE, (app.SCREEN_WIDTH - titleWidth) / 2 - 1, app.SCREEN_HEIGHT / 2 + 50);
        batcher.end();
 
        update(delta);
 
        int xOffset = 30;
        int yOffset = 10;
 
        // draw progress bar - red on black
        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
        shapeRenderer.setColor(Color.BLACK);
        shapeRenderer.rect(xOffset, app.camera.viewportHeight / 2 - yOffset, app.camera.viewportWidth - xOffset * 2, yOffset * 2);
 
        shapeRenderer.setColor(Color.RED);
        shapeRenderer.rect(xOffset + 2, app.camera.viewportHeight / 2 - (yOffset - 2),
                progress * (app.camera.viewportWidth - (xOffset + 2) * 2), (yOffset - 2) * 2);
        shapeRenderer.end();
    }
 
    @Override
    public void dispose() {
        batcher.dispose();
        shapeRenderer.dispose();
    }
}

בזמן ההצגה של המסך (show) נקרא לפונקציית load כדי להתחיל בטעינה, וב- render נעדכן בכל פעם את ההתקדמות.

ברגע שהטעינה מסתיימת נעבור מיידית למסך הבא – Splash Screen.

 

עד כאן החלק הראשון. בינתיים לא השתמשנו ב- Scene2d, אבל זה יקרה ממש בחלק הבא.

 

 

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedIn

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *