管理多个屏幕

我们的菜单屏有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);}@Overridepublic 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 BlueGdx.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();}@Overridepublic void resize(int width, int height) {worldRenderer.resize(width, height);}@Overridepublic void show() {worldController = new WorldController(game);worldRenderer = new WorldRenderer(worldController);Gdx.input.setCatchBackKey(true);}@Overridepublic void hide() {worldRenderer.dispose();Gdx.input.setCatchBackKey(false);}@Overridepublic void pause() {paused = true;}@Overridepublic 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 {@Overridepublic void create() {// Set Libgdx log level
        Gdx.app.setLogLevel(Application.LOG_DEBUG);// Load assetsAssets.instance.init(new AssetManager());// Start game at menu screensetScreen(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 screengame.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 detectionprivate 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 <-> Rocksfor (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 Coinsfor (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 <-> Feathersfor (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);}@Overridepublic boolean keyUp(int keycode) {if (keycode == Keys.R) {init();Gdx.app.debug(TAG, "Game World Resetted!");}// Toggle camera followelse if (keycode == Keys.ENTER) {cameraHelper.setTarget(cameraHelper.hasTarget() ? null: level.bunnyHead);Gdx.app.debug(TAG,"Camera follow enabled: " + cameraHelper.hasTarget());}// Back to Menuelse if (keycode == Keys.ESCAPE || keycode == Keys.BACK) {backToMenu();}return false;}private void handleInputGame(float deltaTime) {if (cameraHelper.hasTarget(level.bunnyHead)) {// Player Movementif (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 platformif (Gdx.app.getType() != ApplicationType.Desktop) {level.bunnyHead.velocity.x = level.bunnyHead.terminalVelocity.x;}}// Bunny Jumpif (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;elseinitLevel();}}private Pixmap createProceduralPixmap(int width, int height) {Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);// Fill square with red color at 50% opacitypixmap.setColor(1, 0, 0, 0.5f);pixmap.fill();// Draw a yellow-colored X shape on squarepixmap.setColor(1, 1, 0, 1);pixmap.drawLine(0, 0, width, height);pixmap.drawLine(width, 0, 0, height);// Draw a cyan-colored border around squarepixmap.setColor(0, 1, 1, 1);pixmap.drawRectangle(0, 0, width, height);return pixmap;}
}

现在,构思下menu screen的样子,准备创建了。

接下来就是这个富有特色的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 skinspublic 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;// menuprivate Image imgBackground;private Image imgLogo;private Image imgInfo;private Image imgCoins;private Image imgBunny;private Button btnMenuPlay;private Button btnMenuOptions;// optionsprivate 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;// debugprivate 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 layersTable 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呢?修改下面代码

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

给menu加上debug的代码:

@Overridepublic 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);}

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

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

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

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

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

然后是Object层:

private Table buildObjectsLayer() {Table layer = new Table();// + CoinsimgCoins = new Image(skinCanyonBunny, "coins");layer.addActor(imgCoins);imgCoins.setPosition(135, 80);// + BunnyimgBunny = 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 LogoimgLogo = new Image(skinCanyonBunny, "logo");layer.add(imgLogo);layer.row().expandY();// + Info LogosimgInfo = 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 ButtonbtnMenuPlay = new Button(skinCanyonBunny, "play");layer.add(btnMenuPlay);btnMenuPlay.addListener(new ChangeListener() {@Overridepublic void changed(ChangeEvent event, Actor actor) {onPlayClicked();}});layer.row();// + Options ButtonbtnMenuOptions = new Button(skinCanyonBunny, "options");layer.add(btnMenuOptions);btnMenuOptions.addListener(new ChangeListener() {@Overridepublic 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 classesprivate 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);}@Overridepublic 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 transparentwinOptions.setColor(1, 1, 1, 0.8f);// Hide options window by defaultwinOptions.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 sliderchkSound = 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 sliderchkMusic = 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 itemsselCharSkin = new SelectBox(CharacterSkin.values(), skinLibgdx);selCharSkin.addListener(new ChangeListener() {@Overridepublic void changed(ChangeEvent event, Actor actor) {onCharSkinSelected(((SelectBox) actor).getSelectionIndex());}});tbl.add(selCharSkin).width(120).padRight(20);// + Skin preview imageimgCharSkin = 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" labelchkShowFpsCounter = 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();// + SeparatorLabel 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 handlerbtnWinOptSave = new TextButton("Save", skinLibgdx);tbl.add(btnWinOptSave).padRight(30);btnWinOptSave.addListener(new ChangeListener() {@Overridepublic void changed(ChangeEvent event, Actor actor) {onSaveClicked();}});// + Cancel Button with event handlerbtnWinOptCancel = new TextButton("Cancel", skinLibgdx);tbl.add(btnWinOptCancel);btnWinOptCancel.addListener(new ChangeListener() {@Overridepublic 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 Color
batch.setColor(
CharacterSkin.values()[GamePreferences.instance.charSkin]
.getColor());

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

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

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

当然,基本功能的完成一般就意味着游戏才完成了一半,更多工作需要继续...

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


PS:欢迎各路游戏爱好者入群426950359,暗号:Mignet


转载于:https://www.cnblogs.com/mignet/p/libgdx_game_development_07.html

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

  1. 开发H5时背景照片兼容不同手机屏幕处理的最佳实践

    最近在重新做过去常做的翻页H5,关于背景图有些新的经验心得分享. 翻页H5的背景图通常是要求全屏的.这里就存在一个问题,目前市面上的手机屏幕比例非常的不统一,这导致在背景图上的处理相当要注意. 通常我 ...

  2. [libGDX游戏开发教程]使用LibGDX进行游戏开发(1)-游戏设计

    声明:本章是一个系列的开始,英文原文是<Learning libGDX Game Development>,大家请周知. [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游 ...

  3. [libGDX游戏开发教程]使用libGDX进行游戏开发(12)-Action动画

    前文章节列表: 使用libGDX进行游戏开发(11)-高级编程技巧  使用libGDX进行游戏开发(10)-音乐音效不求人,程序员也可以DIY  使用libGDX进行游戏开发(9)-场景过渡 使用li ...

  4. 视频教程-微信小程序开发教程(第1篇)-微信开发

    微信小程序开发教程(第1篇) 微信企业号星级会员.10多年软件从业经历,国家级软件项目负责人,主要从事软件研发.软件企业员工技能培训.已经取得计算机技术与软件资格考试(软考)--"信息系统项 ...

  5. STM32F103C8T6基础开发教程(HAL库)—开发环境配置

    STM32F103C8T6基础开发教程目录 STM32F103C8T6基础开发教程(HAL库)-开发环境配置 STM32F103C8T6基础开发教程(HAL库)-Keil添加注释的快捷键 STM32F ...

  6. C#开发笔记之21-C#解析Json(序列化/反序列化)的最佳实践。

    本文由 比特飞 原创发布,欢迎大家踊跃转载. 转载请注明本文地址:C#开发笔记之21-C#解析Json(序列化/反序列化)的最佳实践. | .Net中文网. C#开发笔记概述 另外可参考文章:C#开发 ...

  7. python网络游戏开发教程_python 网游开发教程 | python做的游戏有哪些

    python游戏开发是做什么的 现在很多的线下培训,质量参差不而且线下的培都十分高昂,加上你的线下生活费的话,学一个技术没有三万是学不下来的,你可以考虑线上的教育,只培训费就会给你省一大笔,学习时间还 ...

  8. python简单小游戏代码教程,Python简单小游戏代码

    球球各位大神怎么用python写一个猜词小游戏的代码? key = input('请输入一个单词:')description = input('输入单词描述:')chance = 5mark = 5p ...

  9. Libusb开发教程三 USB设备程序开发

    首先,需要指出本篇博客的基础仍然是基于 libusb V1.0 以下.主要考虑到保持与之前博客的连贯性,在上一篇博客中介绍了老版本中可以被调用的 API 以及功能介绍,对于库中 API 接口函数的使用 ...

最新文章

  1. 【译】JavaScript中的Callbacks
  2. 《西游记》原著的一点读后感
  3. JavaScript字符串、数组、对象方法总结
  4. JS中函数和变量声明的提升
  5. zend studio html乱码,解决Eclipse/Zend Studio编辑xml/html乱码问题
  6. 2014中国十大管理实践-世界经理人网站
  7. dx逆向建模步骤_产品温度的逆向建模的系统和方法与流程
  8. 解决WPS word论文英文摘要不对齐的问题
  9. rac多scan-ip配置
  10. mysql netbeans_使用Netbeans操作MySQL数据库
  11. 生死看淡,不服就GAN(八)----WGAN的改进版本WGAN-GP
  12. 【计导非课系列】绪言——什么是“计导非课”系列?
  13. 基于OpenWRT的软件开发流程
  14. Python学习 Day31 JS类数组对象
  15. 视频直播只是指的实时互动直播吗?
  16. 一个平庸的人的自我反思
  17. 优先队列——最短路径
  18. 贵州/全国专升本C语言知识技巧复习资料
  19. 操盘手的选股技巧,使用海龟交易法则应对证券牛市市场
  20. 爬取豆瓣TOP250-避免反爬虫(2021-10-09)

热门文章

  1. signature=d522a0024e7d20dbfee94b566a5dfed5,Severely Fading MIMO Channels
  2. windows下载安装Zeal
  3. 南瑞研发电力业务基础软件平台通过国家电网公司验收
  4. ]巧妙洗头,白发变黑发
  5. Flash像册制作(SWF,屏保,EXE,HTML).
  6. [buuoj]极客大挑战 2019]PHP 1
  7. 用Podofo绘制带透明度的png图片
  8. 【uniapp原生插件】招商银行一网通支付androidiOS
  9. 《创新公司:皮克斯的启示》读书笔记
  10. proto文件描述语法