[libgdx游戏开发教程]使用Libgdx进行游戏开发(7)-Screen2D屏幕布局的最佳实践

来源:互联网 发布:ubuntu修改系统语言 编辑:程序博客网 时间:2024/06/05 01:00

本章素材下载:http://files.cnblogs.com/mignet/images.zip

管理多个屏幕

我们的菜单屏有2个按钮,一个play一个option。option里就是一些开关的设置,比如音乐音效等。这些设置将会保存到Preferences中。

多屏幕切换是游戏的基本机制,Libgdx提供了一个叫Game的类已经具有了这样的功能。

为了适应多屏幕的功能,我们的类图需要做一些修改:

改动在:CanyonBunnyMain不再实现ApplicationListener接口,而是继承自Game类。这个类提供了setScreen()方法来进行切换。

我们定义抽象的AbstractGameScreen来统一共同的行为。同时,它实现了Libgdx的Screen接口(show,hide)。

GameScreen将取代CanyonBunnyMain的位置。

开始编写类AbstractGameScreen:

package com.packtpub.libgdx.canyonbunny.screens;import com.badlogic.gdx.Game;import com.badlogic.gdx.Screen;import com.badlogic.gdx.assets.AssetManager;import com.packtpub.libgdx.canyonbunny.game.Assets;public abstract class AbstractGameScreen implements Screen {    protected Game game;    public AbstractGameScreen(Game game) {        this.game = game;    }    public abstract void render(float deltaTime);    public abstract void resize(int width, int height);    public abstract void show();    public abstract void hide();    public abstract void pause();    public void resume() {        Assets.instance.init(new AssetManager());    }    public void dispose() {        Assets.instance.dispose();    }}

GameScreen把职责拿过来:

package com.packtpub.libgdx.canyonbunny.screens;import com.badlogic.gdx.Game;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.graphics.GL10;import com.packtpub.libgdx.canyonbunny.game.WorldController;import com.packtpub.libgdx.canyonbunny.game.WorldRenderer;public class GameScreen extends AbstractGameScreen {    private static final String TAG = GameScreen.class.getName();    private WorldController worldController;    private WorldRenderer worldRenderer;    private boolean paused;    public GameScreen(Game game) {        super(game);    }    @Override    public void render(float deltaTime) {        // Do not update game world when paused.        if (!paused) {            // Update game world by the time that has passed            // since last rendered frame.            worldController.update(deltaTime);        }        // Sets the clear screen color to: Cornflower Blue        Gdx.gl.glClearColor(0x64 / 255.0f, 0x95 / 255.0f, 0xed / 255.0f,                0xff / 255.0f);        // Clears the screen        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);        // Render game world to screen        worldRenderer.render();    }    @Override    public void resize(int width, int height) {        worldRenderer.resize(width, height);    }    @Override    public void show() {        worldController = new WorldController(game);        worldRenderer = new WorldRenderer(worldController);        Gdx.input.setCatchBackKey(true);    }    @Override    public void hide() {        worldRenderer.dispose();        Gdx.input.setCatchBackKey(false);    }    @Override    public void pause() {        paused = true;    }    @Override    public void resume() {        super.resume();        // Only called on Android!        paused = false;    }}

那么CanyonBunnyMain就瘦身了:

package com.packtpub.libgdx.canyonbunny;import com.badlogic.gdx.Application;import com.badlogic.gdx.Game;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.assets.AssetManager;import com.packtpub.libgdx.canyonbunny.game.Assets;import com.packtpub.libgdx.canyonbunny.screens.MenuScreen;public class CanyonBunnyMain extends Game {    @Override    public void create() {        // Set Libgdx log level        Gdx.app.setLogLevel(Application.LOG_DEBUG);        // Load assets        Assets.instance.init(new AssetManager());        // Start game at menu screen        setScreen(new MenuScreen(this));    }}

WorldController开始持有game的引用,以便于跳转;

package com.packtpub.libgdx.canyonbunny.game;import com.badlogic.gdx.Application.ApplicationType;import com.badlogic.gdx.Game;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.Input.Keys;import com.badlogic.gdx.InputAdapter;import com.badlogic.gdx.graphics.Pixmap;import com.badlogic.gdx.graphics.Pixmap.Format;import com.badlogic.gdx.math.Rectangle;import com.packtpub.libgdx.canyonbunny.game.objects.BunnyHead;import com.packtpub.libgdx.canyonbunny.game.objects.BunnyHead.JUMP_STATE;import com.packtpub.libgdx.canyonbunny.game.objects.Feather;import com.packtpub.libgdx.canyonbunny.game.objects.GoldCoin;import com.packtpub.libgdx.canyonbunny.game.objects.Rock;import com.packtpub.libgdx.canyonbunny.screens.MenuScreen;import com.packtpub.libgdx.canyonbunny.util.CameraHelper;import com.packtpub.libgdx.canyonbunny.util.Constants;public class WorldController extends InputAdapter {    private static final String TAG = WorldController.class.getName();    public CameraHelper cameraHelper;    public Level level;    public int lives;    public int score;    private float timeLeftGameOverDelay;    private Game game;    private void backToMenu() {        // switch to menu screen        game.setScreen(new MenuScreen(game));    }    public boolean isGameOver() {        return lives < 0;    }    public boolean isPlayerInWater() {        return level.bunnyHead.position.y < -5;    }    private void initLevel() {        score = 0;        level = new Level(Constants.LEVEL_01);        cameraHelper.setTarget(level.bunnyHead);    }    // Rectangles for collision detection    private Rectangle r1 = new Rectangle();    private Rectangle r2 = new Rectangle();    private void onCollisionBunnyHeadWithRock(Rock rock) {        BunnyHead bunnyHead = level.bunnyHead;        float heightDifference = Math.abs(bunnyHead.position.y                - (rock.position.y + rock.bounds.height));        if (heightDifference > 0.25f) {            boolean hitLeftEdge = bunnyHead.position.x > (rock.position.x + rock.bounds.width / 2.0f);            if (hitLeftEdge) {                bunnyHead.position.x = rock.position.x + rock.bounds.width;            } else {                bunnyHead.position.x = rock.position.x - bunnyHead.bounds.width;            }            return;        }        switch (bunnyHead.jumpState) {        case GROUNDED:            break;        case FALLING:        case JUMP_FALLING:            bunnyHead.position.y = rock.position.y + bunnyHead.bounds.height                    + bunnyHead.origin.y;            bunnyHead.jumpState = JUMP_STATE.GROUNDED;            break;        case JUMP_RISING:            bunnyHead.position.y = rock.position.y + bunnyHead.bounds.height                    + bunnyHead.origin.y;            break;        }    }    private void onCollisionBunnyWithGoldCoin(GoldCoin goldcoin) {        goldcoin.collected = true;        score += goldcoin.getScore();        Gdx.app.log(TAG, "Gold coin collected");    }    private void onCollisionBunnyWithFeather(Feather feather) {        feather.collected = true;        score += feather.getScore();        level.bunnyHead.setFeatherPowerup(true);        Gdx.app.log(TAG, "Feather collected");    }    private void testCollisions() {        r1.set(level.bunnyHead.position.x, level.bunnyHead.position.y,                level.bunnyHead.bounds.width, level.bunnyHead.bounds.height);        // Test collision: Bunny Head <-> Rocks        for (Rock rock : level.rocks) {            r2.set(rock.position.x, rock.position.y, rock.bounds.width,                    rock.bounds.height);            if (!r1.overlaps(r2))                continue;            onCollisionBunnyHeadWithRock(rock);            // IMPORTANT: must do all collisions for valid            // edge testing on rocks.        }        // Test collision: Bunny Head <-> Gold Coins        for (GoldCoin goldcoin : level.goldcoins) {            if (goldcoin.collected)                continue;            r2.set(goldcoin.position.x, goldcoin.position.y,                    goldcoin.bounds.width, goldcoin.bounds.height);            if (!r1.overlaps(r2))                continue;            onCollisionBunnyWithGoldCoin(goldcoin);            break;        }        // Test collision: Bunny Head <-> Feathers        for (Feather feather : level.feathers) {            if (feather.collected)                continue;            r2.set(feather.position.x, feather.position.y,                    feather.bounds.width, feather.bounds.height);            if (!r1.overlaps(r2))                continue;            onCollisionBunnyWithFeather(feather);            break;        }    }    public WorldController(Game game) {        this.game = game;        Gdx.input.setInputProcessor(this);        init();    }    private void handleDebugInput(float deltaTime) {        if (Gdx.app.getType() != ApplicationType.Desktop)            return;        if (!cameraHelper.hasTarget(level.bunnyHead)) {            // Camera Controls (move)            float camMoveSpeed = 5 * deltaTime;            float camMoveSpeedAccelerationFactor = 5;            if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))                camMoveSpeed *= camMoveSpeedAccelerationFactor;            if (Gdx.input.isKeyPressed(Keys.LEFT))                moveCamera(-camMoveSpeed, 0);            if (Gdx.input.isKeyPressed(Keys.RIGHT))                moveCamera(camMoveSpeed, 0);            if (Gdx.input.isKeyPressed(Keys.UP))                moveCamera(0, camMoveSpeed);            if (Gdx.input.isKeyPressed(Keys.DOWN))                moveCamera(0, -camMoveSpeed);            if (Gdx.input.isKeyPressed(Keys.BACKSPACE))                cameraHelper.setPosition(0, 0);        }        // Camera Controls (zoom)        float camZoomSpeed = 1 * deltaTime;        float camZoomSpeedAccelerationFactor = 5;        if (Gdx.input.isKeyPressed(Keys.SHIFT_LEFT))            camZoomSpeed *= camZoomSpeedAccelerationFactor;        if (Gdx.input.isKeyPressed(Keys.COMMA))            cameraHelper.addZoom(camZoomSpeed);        if (Gdx.input.isKeyPressed(Keys.PERIOD))            cameraHelper.addZoom(-camZoomSpeed);        if (Gdx.input.isKeyPressed(Keys.SLASH))            cameraHelper.setZoom(1);    }    private void moveCamera(float x, float y) {        x += cameraHelper.getPosition().x;        y += cameraHelper.getPosition().y;        cameraHelper.setPosition(x, y);    }    @Override    public boolean keyUp(int keycode) {        if (keycode == Keys.R) {            init();            Gdx.app.debug(TAG, "Game World Resetted!");        }// Toggle camera follow        else if (keycode == Keys.ENTER) {            cameraHelper.setTarget(cameraHelper.hasTarget() ? null                    : level.bunnyHead);            Gdx.app.debug(TAG,                    "Camera follow enabled: " + cameraHelper.hasTarget());        }        // Back to Menu        else if (keycode == Keys.ESCAPE || keycode == Keys.BACK) {            backToMenu();        }        return false;    }    private void handleInputGame(float deltaTime) {        if (cameraHelper.hasTarget(level.bunnyHead)) {            // Player Movement            if (Gdx.input.isKeyPressed(Keys.LEFT)) {                level.bunnyHead.velocity.x = -level.bunnyHead.terminalVelocity.x;            } else if (Gdx.input.isKeyPressed(Keys.RIGHT)) {                level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x;            } else {                // Execute auto-forward movement on non-desktop platform                if (Gdx.app.getType() != ApplicationType.Desktop) {                    level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x;                }            }            // Bunny Jump            if (Gdx.input.isTouched() || Gdx.input.isKeyPressed(Keys.SPACE))                level.bunnyHead.setJumping(true);        } else {            level.bunnyHead.setJumping(false);        }    }    private void init() {        Gdx.input.setInputProcessor(this);        cameraHelper = new CameraHelper();        lives = Constants.LIVES_START;        timeLeftGameOverDelay = 0;        initLevel();    }    public void update(float deltaTime) {        handleDebugInput(deltaTime);        if (isGameOver()) {            timeLeftGameOverDelay -= deltaTime;            if (timeLeftGameOverDelay < 0)                backToMenu();        } else {            handleInputGame(deltaTime);        }        level.update(deltaTime);        testCollisions();        cameraHelper.update(deltaTime);        if (!isGameOver() && isPlayerInWater()) {            lives--;            if (isGameOver())                timeLeftGameOverDelay = Constants.TIME_DELAY_GAME_OVER;            else                initLevel();        }    }    private Pixmap createProceduralPixmap(int width, int height) {        Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);        // Fill square with red color at 50% opacity        pixmap.setColor(1, 0, 0, 0.5f);        pixmap.fill();        // Draw a yellow-colored X shape on square        pixmap.setColor(1, 1, 0, 1);        pixmap.drawLine(0, 0, width, height);        pixmap.drawLine(width, 0, 0, height);        // Draw a cyan-colored border around square        pixmap.setColor(0, 1, 1, 1);        pixmap.drawRectangle(0, 0, width, height);        return pixmap;    }}

现在,构思下开始菜单的样子,准备创建了。

 

接下来就是这个富有特色的MenuScreen的创建了。首先要准备图片和加载,和前文一样打包。然后使用一个JSON文件来定义Menu的皮肤。

比如我们起名叫:canyonbunnyui.json

{com.badlogic.gdx.scenes.scene2d.ui.Button$ButtonStyle: {play: { down: play-dn, up: play-up },options: { down: options-dn, up: options-up }},com.badlogic.gdx.scenes.scene2d.ui.Image: {background: { drawable: background },logo: { drawable: logo },info: { drawable: info },coins: { drawable: coins },bunny: { drawable: bunny },},}

增加常量到Constants:

    public static final String TEXTURE_ATLAS_UI = "images/canyonbunny-ui.pack";    public static final String TEXTURE_ATLAS_LIBGDX_UI = "images/uiskin.atlas";    // Location of description file for skins    public static final String SKIN_LIBGDX_UI = "images/uiskin.json";    public static final String SKIN_CANYONBUNNY_UI = "images/canyonbunny-ui.json";

Libgdx构建Scene2D (UI),使用的特性就是TableLayout和skins。

Libgdx附带了一个很牛叉的工具组来让开发者很容易创建场景. 场景的层次组织结构很像硬盘上文件夹文件的结构.在Libgdx里,这些对象被称为演员Actor. 演员可以相互嵌套来组成演员组. 演员组是一个非常有用的特性, 因为任何对父Actor的改动,都会应用到他的子Actor. 此外, 每个演员都有自己的坐标系, 这就使得定义演员组里的成员的相对偏移量变得很容易(无论是位置,旋转角度还是缩放).Scene2D支持已经旋转或者缩放的Actor的碰撞检测. Libgdx灵活的事件系统允许按需处理和分发输入事件以便父Actor可以在输入事件到达子Actor之前拦截它. 最后, 内置的action系统可以很容易用来操纵actors,也可以通过执行动作序列来完成复杂的效果,平移, 或者是两者组合. 所有这些描述的功能都封装在Stage类, 它包含层次结构和分发用户的事件. 在任何时候,Actor都能够加入它或者从它移除. Stage类和Actor类都包含act()方法,这个方法得到一个时间作为参数然后执行基于时间的动作。调用Stage的act()将会引起整个场景的act()调用。Stage和Actor的act()方法其实基本上和我们所知道的update()方法一样,只是用了一个不同的名字. 更多关于Scene2D, 参考官方文档https://code.google.com/p/libgdx/wiki/scene2d/.到目前为止, 在我们的游戏中我们没有使用任何的Scene2D的这些特性, 虽然我们都已经用Scene2D的对象实现了游戏的场景。记住,使用场景有一定的开销. Libgdx试图全力保持开销在最低的程度,比如: 如果对象不需要旋转和缩放就跳过复杂的转换矩阵的计算. 所以, 这取决于你的需求.我们要创建的菜单很复杂,我们直接用libgdx已经支持的 Scene2D UI来做. 如果有特殊需要,我们还可以继承这些UI,实现它们的接口,以增强它们的功能. 在Libgdx中, 这些UI元素都叫做组件widgets.下面是所有在当前Scene2D UI有效的widget简表:Button, CheckBox, Dialog, Image, ImageButton, Label, List, ScrollPane,SelectBox, Slider, SplitPane, Stack, Window, TextButton, TextField,Touchpad 和 Tree.Scene2D UI 也支持简单的创建新的自定义的widgets种类. 我们将只涉及我们的菜单中将要用到的一些widget.完整描述每一个widget的列表,请参考官方文档https://code.google.com/p/libgdx/wiki/scene2dui/.除了Scene2D UI, Libgdx还集成了一个单独的项目--TableLayout.TableLayout使用Tables很容易创建和维护动态的(或者叫与分辨率无关的)布局,也提供了很直观的API. Table提供了访问TableLayout的功能, 同时Table也实现了作为widget的功能, 因此Table可以完全无缝集成到Scene2D的UI中.强烈推荐去看官方文档https://code.google.com/p/table-layout/.Scene2D UI另一个重要的特征就是支持皮肤skins. 皮肤是资源的集合,包括样式和UI组件. 资源可以是texture regions(纹理区域), fonts(字体)和 colors(颜色). 通常来讲, 皮肤使用的纹理区域,来自一个纹理集. 每个部件的样式定义使用JSON文件存储在一个单独的文件中.

我们现在来实际的实现Menu屏,首先来看一下层级关系:

场景图从一个空的Stage开始. 然后,第一个添加到stage的子actor是一个Stack. Stack允许你添加可以相互覆盖的actor. 我们将利用这一特性创建多个层. 每一层都使用一个Table作为父actor.

使用堆叠起来的table可以使我们能够很容易和很逻辑性的布局actor.

我们一步步来,先实现这个多层堆叠起来的结构(MenuScreen):

    private Stage stage;    private Skin skinCanyonBunny;    // menu    private Image imgBackground;    private Image imgLogo;    private Image imgInfo;    private Image imgCoins;    private Image imgBunny;    private Button btnMenuPlay;    private Button btnMenuOptions;    // options    private Window winOptions;    private TextButton btnWinOptSave;    private TextButton btnWinOptCancel;    private CheckBox chkSound;    private Slider sldSound;    private CheckBox chkMusic;    private Slider sldMusic;    private SelectBox selCharSkin;    private Image imgCharSkin;    private CheckBox chkShowFpsCounter;    // debug    private final float DEBUG_REBUILD_INTERVAL = 5.0f;    private boolean debugEnabled = false;    private float debugRebuildStage;    private void rebuildStage() {        skinCanyonBunny = new Skin(                Gdx.files.internal(Constants.SKIN_CANYONBUNNY_UI),                new TextureAtlas(Constants.TEXTURE_ATLAS_UI));        // build all layers        Table layerBackground = buildBackgroundLayer();        Table layerObjects = buildObjectsLayer();        Table layerLogos = buildLogosLayer();        Table layerControls = buildControlsLayer();        Table layerOptionsWindow = buildOptionsWindowLayer();        // assemble stage for menu screen        stage.clear();        Stack stack = new Stack();        stage.addActor(stack);        stack.setSize(Constants.VIEWPORT_GUI_WIDTH,                Constants.VIEWPORT_GUI_HEIGHT);        stack.add(layerBackground);        stack.add(layerObjects);        stack.add(layerLogos);        stack.add(layerControls);        stage.addActor(layerOptionsWindow);    }    private Table buildBackgroundLayer() {        Table layer = new Table();        return layer;    }    private Table buildObjectsLayer() {        Table layer = new Table();        return layer;    }    private Table buildLogosLayer() {        Table layer = new Table();        return layer;    }    private Table buildControlsLayer() {        Table layer = new Table();        return layer;    }    private Table buildOptionsWindowLayer() {        Table layer = new Table();        return layer;    }

那么,核心的问题是,怎么让这一套理论来实现的东东能够适应各种屏幕size呢?修改下面代码

    @Override    public void resize(int width, int height) {        stage.setViewport(Constants.VIEWPORT_GUI_WIDTH,                Constants.VIEWPORT_GUI_HEIGHT, false);    }    @Override    public void hide() {        stage.dispose();        skinCanyonBunny.dispose();    }    @Override    public void show() {        stage = new Stage();        Gdx.input.setInputProcessor(stage);        rebuildStage();    }

给menu加上debug的代码:

    @Override    public void render(float deltaTime) {        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);        if (debugEnabled) {            debugRebuildStage -= deltaTime;            if (debugRebuildStage <= 0) {                debugRebuildStage = DEBUG_REBUILD_INTERVAL;                rebuildStage();            }        }        stage.act(deltaTime);        stage.draw();        //Table.drawDebug(stage);新版中是stage.setDebug    }

不要小看这里的debug代码,在开启debug的情况下它会在你设定的间隔时间就rebuild我们的stage,也就是说你可以在运行的时候做更新。(JVM的代码热交换特性)

比如你正在调整某个menu的位置,直接改配置文件,不用重启就可以看效果,这将节省大量的时间。

接下来,一一实现每一层具体的功能。

首先是背景层,加上背景图片:

private Table buildBackgroundLayer() {        Table layer = new Table();        // + Background        imgBackground = new Image(skinCanyonBunny, "background");        layer.add(imgBackground);        return layer;    }

然后是Object层:

private Table buildObjectsLayer() {        Table layer = new Table();        // + Coins        imgCoins = new Image(skinCanyonBunny, "coins");        layer.addActor(imgCoins);        imgCoins.setPosition(135, 80);        // + Bunny        imgBunny = new Image(skinCanyonBunny, "bunny");        layer.addActor(imgBunny);        imgBunny.setPosition(355, 40);        return layer;    }

接着是logo层:

private Table buildLogosLayer() {        Table layer = new Table();        layer.left().top();        // + Game Logo        imgLogo = new Image(skinCanyonBunny, "logo");        layer.add(imgLogo);        layer.row().expandY();        // + Info Logos        imgInfo = new Image(skinCanyonBunny, "info");        layer.add(imgInfo).bottom();        if (debugEnabled)            layer.debug();        return layer;    }

接着是控制层:按钮或者菜单层

private Table buildControlsLayer() {        Table layer = new Table();        layer.right().bottom();        // + Play Button        btnMenuPlay = new Button(skinCanyonBunny, "play");        layer.add(btnMenuPlay);        btnMenuPlay.addListener(new ChangeListener() {            @Override            public void changed(ChangeEvent event, Actor actor) {                onPlayClicked();            }        });        layer.row();        // + Options Button        btnMenuOptions = new Button(skinCanyonBunny, "options");        layer.add(btnMenuOptions);        btnMenuOptions.addListener(new ChangeListener() {            @Override            public void changed(ChangeEvent event, Actor actor) {                onOptionsClicked();            }        });        if (debugEnabled)            layer.debug();        return layer;    }    private void onPlayClicked() {        game.setScreen(new GameScreen(game));    }    private void onOptionsClicked() {    }

添加选项层:

这个option使用的素材是Libgdx默认的素材:

• uiskin.png
• uiskin.atlas
• uiskin.json
• default.fnt

为了保存玩家选择的结果,我们新建一个GamePreferences的类来保存用户数据:

package com.packtpub.libgdx.canyonbunny.util;import com.badlogic.gdx.Gdx;import com.badlogic.gdx.Preferences;import com.badlogic.gdx.math.MathUtils;public class GamePreferences {    public static final String TAG = GamePreferences.class.getName();    public static final GamePreferences instance = new GamePreferences();    public boolean sound;    public boolean music;    public float volSound;    public float volMusic;    public int charSkin;    public boolean showFpsCounter;    private Preferences prefs;    // singleton: prevent instantiation from other classes    private GamePreferences() {        prefs = Gdx.app.getPreferences(Constants.PREFERENCES);    }    public void load() {        sound = prefs.getBoolean("sound", true);        music = prefs.getBoolean("music", true);        volSound = MathUtils                .clamp(prefs.getFloat("volSound", 0.5f), 0.0f, 1.0f);        volMusic = MathUtils                .clamp(prefs.getFloat("volMusic", 0.5f), 0.0f, 1.0f);        charSkin = MathUtils.clamp(prefs.getInteger("charSkin", 0), 0, 2);        showFpsCounter = prefs.getBoolean("showFpsCounter", false);    }    public void save() {        prefs.putBoolean("sound", sound);        prefs.putBoolean("music", music);        prefs.putFloat("volSound", volSound);        prefs.putFloat("volMusic", volMusic);        prefs.putInteger("charSkin", charSkin);        prefs.putBoolean("showFpsCounter", showFpsCounter);        prefs.flush();    }}

很眼熟吧,不错,跟cocos里的userdata一样,都是用xml文件在存储。

创建一个可选择项的皮肤类CharacterSkin,让兔子头可以换肤:

package com.packtpub.libgdx.canyonbunny.util;import com.badlogic.gdx.graphics.Color;public enum CharacterSkin {    WHITE("White", 1.0f, 1.0f, 1.0f), GRAY("Gray", 0.7f, 0.7f, 0.7f), BROWN(            "Brown", 0.7f, 0.5f, 0.3f);    private String name;    private Color color = new Color();    private CharacterSkin(String name, float r, float g, float b) {        this.name = name;        color.set(r, g, b, 1.0f);    }    @Override    public String toString() {        return name;    }    public Color getColor() {        return color;    }}

给menu屏加上option层的代码:

     private Skin skinLibgdx;
    private void loadSettings() {        GamePreferences prefs = GamePreferences.instance;        prefs.load();        chkSound.setChecked(prefs.sound);        sldSound.setValue(prefs.volSound);        chkMusic.setChecked(prefs.music);        sldMusic.setValue(prefs.volMusic);        selCharSkin.setSelection(prefs.charSkin);        onCharSkinSelected(prefs.charSkin);        chkShowFpsCounter.setChecked(prefs.showFpsCounter);    }    private void saveSettings() {        GamePreferences prefs = GamePreferences.instance;        prefs.sound = chkSound.isChecked();        prefs.volSound = sldSound.getValue();        prefs.music = chkMusic.isChecked();        prefs.volMusic = sldMusic.getValue();        prefs.charSkin = selCharSkin.getSelectionIndex();        prefs.showFpsCounter = chkShowFpsCounter.isChecked();        prefs.save();    }    private void onCharSkinSelected(int index) {        CharacterSkin skin = CharacterSkin.values()[index];        imgCharSkin.setColor(skin.getColor());    }    private void onSaveClicked() {        saveSettings();        onCancelClicked();    }    private void onCancelClicked() {        btnMenuPlay.setVisible(true);        btnMenuOptions.setVisible(true);        winOptions.setVisible(false);    }

在rebuildStage中加上:

skinLibgdx = new Skin(Gdx.files.internal(Constants.SKIN_LIBGDX_UI),new TextureAtlas(Constants.TEXTURE_ATLAS_LIBGDX_UI));

hide中加上:

skinLibgdx.dispose();

最后,来完成buildOptionWindowLayer():

private Table buildOptionsWindowLayer() {        winOptions = new Window("Options", skinLibgdx);        // + Audio Settings: Sound/Music CheckBox and Volume Slider        winOptions.add(buildOptWinAudioSettings()).row();        // + Character Skin: Selection Box (White, Gray, Brown)        winOptions.add(buildOptWinSkinSelection()).row();        // + Debug: Show FPS Counter        winOptions.add(buildOptWinDebug()).row();        // + Separator and Buttons (Save, Cancel)        winOptions.add(buildOptWinButtons()).pad(10, 0, 10, 0);        // Make options window slightly transparent        winOptions.setColor(1, 1, 1, 0.8f);        // Hide options window by default        winOptions.setVisible(false);        if (debugEnabled)            winOptions.debug();        // Let TableLayout recalculate widget sizes and positions        winOptions.pack();        // Move options window to bottom right corner        winOptions.setPosition(                Constants.VIEWPORT_GUI_WIDTH - winOptions.getWidth() - 50, 50);        return winOptions;    }        private Table buildOptWinAudioSettings() {        Table tbl = new Table();        // + Title: "Audio"        tbl.pad(10, 10, 0, 10);        tbl.add(new Label("Audio", skinLibgdx, "default-font", Color.ORANGE))                .colspan(3);        tbl.row();        tbl.columnDefaults(0).padRight(10);        tbl.columnDefaults(1).padRight(10);        // + Checkbox, "Sound" label, sound volume slider        chkSound = new CheckBox("", skinLibgdx);        tbl.add(chkSound);        tbl.add(new Label("Sound", skinLibgdx));        sldSound = new Slider(0.0f, 1.0f, 0.1f, false, skinLibgdx);        tbl.add(sldSound);        tbl.row();        // + Checkbox, "Music" label, music volume slider        chkMusic = new CheckBox("", skinLibgdx);        tbl.add(chkMusic);        tbl.add(new Label("Music", skinLibgdx));        sldMusic = new Slider(0.0f, 1.0f, 0.1f, false, skinLibgdx);        tbl.add(sldMusic);        tbl.row();        return tbl;    }    private Table buildOptWinSkinSelection() {        Table tbl = new Table();        // + Title: "Character Skin"        tbl.pad(10, 10, 0, 10);        tbl.add(new Label("Character Skin", skinLibgdx, "default-font",                Color.ORANGE)).colspan(2);        tbl.row();        // + Drop down box filled with skin items        selCharSkin = new SelectBox(CharacterSkin.values(), skinLibgdx);        selCharSkin.addListener(new ChangeListener() {            @Override            public void changed(ChangeEvent event, Actor actor) {                onCharSkinSelected(((SelectBox) actor).getSelectionIndex());            }        });        tbl.add(selCharSkin).width(120).padRight(20);        // + Skin preview image        imgCharSkin = new Image(Assets.instance.bunny.head);        tbl.add(imgCharSkin).width(50).height(50);        return tbl;    }    private Table buildOptWinDebug() {        Table tbl = new Table();        // + Title: "Debug"        tbl.pad(10, 10, 0, 10);        tbl.add(new Label("Debug", skinLibgdx, "default-font", Color.RED))                .colspan(3);        tbl.row();        tbl.columnDefaults(0).padRight(10);        tbl.columnDefaults(1).padRight(10);        // + Checkbox, "Show FPS Counter" label        chkShowFpsCounter = new CheckBox("", skinLibgdx);        tbl.add(new Label("Show FPS Counter", skinLibgdx));        tbl.add(chkShowFpsCounter);        tbl.row();        return tbl;    }    private Table buildOptWinButtons() {        Table tbl = new Table();        // + Separator        Label lbl = null;        lbl = new Label("", skinLibgdx);        lbl.setColor(0.75f, 0.75f, 0.75f, 1);        lbl.setStyle(new LabelStyle(lbl.getStyle()));        lbl.getStyle().background = skinLibgdx.newDrawable("white");        tbl.add(lbl).colspan(2).height(1).width(220).pad(0, 0, 0, 1);        tbl.row();        lbl = new Label("", skinLibgdx);        lbl.setColor(0.5f, 0.5f, 0.5f, 1);        lbl.setStyle(new LabelStyle(lbl.getStyle()));        lbl.getStyle().background = skinLibgdx.newDrawable("white");        tbl.add(lbl).colspan(2).height(1).width(220).pad(0, 1, 5, 0);        tbl.row();        // + Save Button with event handler        btnWinOptSave = new TextButton("Save", skinLibgdx);        tbl.add(btnWinOptSave).padRight(30);        btnWinOptSave.addListener(new ChangeListener() {            @Override            public void changed(ChangeEvent event, Actor actor) {                onSaveClicked();            }        });        // + Cancel Button with event handler        btnWinOptCancel = new TextButton("Cancel", skinLibgdx);        tbl.add(btnWinOptCancel);        btnWinOptCancel.addListener(new ChangeListener() {            @Override            public void changed(ChangeEvent event, Actor actor) {                onCancelClicked();            }        });        return tbl;    }

补上onOptionClicked:

private void onOptionsClicked() {        loadSettings();        btnMenuPlay.setVisible(false);        btnMenuOptions.setVisible(false);        winOptions.setVisible(true);    }

要使用这些用户设置,需要在show里添加:GamePreferences.instance.load();

在兔子头的类的render中添加:

// Apply Skin Colorbatch.setColor(CharacterSkin.values()[GamePreferences.instance.charSkin].getColor());

然后在worldrender里的renderGui加上控制fps的设置:

if (GamePreferences.instance.showFpsCounter)renderGuiFpsCounter(batch);

游戏的基本功能到此完成。

在下一章我们将涉及更多的东西以便让游戏更有趣


PS:欢迎各路游戏爱好者入群426950359


 

0 0
原创粉丝点击