管理多个屏幕

我们的菜单屏有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;
}
}

现在,构思下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 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);
}

不要小看这里的debug代码,在开启debug的情况下它会在你设定的间隔时间就rebuild我们的stage,也就是说你可以在运行的时候[desktop]做更新。(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 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

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

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

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

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

    声明:<使用Libgdx进行游戏开发>是一个系列,文章的原文是<Learning Libgdx Game Development>,大家请周知.后续的文章连接在这里 使用Lib ...

  3. 使用Html5+C#+微信 开发移动端游戏详细教程: (四)游戏中层的概念与设计

    众所周知,网站的前端页面结构一般是由div组成,父div包涵子div,子div包涵各种标签和项, 同理,游戏中我们也将若干游戏模块拆分成层,在后续的代码维护和游戏程序逻辑中将更加清晰和便于控制. We ...

  4. 微信小程序开发教程 #043 - 在小程序开发中使用 npm

    本文介绍了如何在微信小程序开发中使用 npm 中包的功能,大大提高微信小程序的开发效率,同时也是微信小程序系列教程的视频版更新. 微信小程序在发布之初没有对 npm 的支持功能,这也是目前很多前端开发 ...

  5. PythonWeb开发教程(一),开发之前需要准备什么

    什么是web开发呢,其实就是开发一个网站了.那开发网站需要用到哪些知识呢 1.python基础,因为用python开发的,所以python指定要会,最起码你也得会条件判断,循环,函数,类这些知识: 2 ...

  6. [libgdx游戏开发教程]使用Libgdx进行游戏开发(11)-高级编程技巧 Box2d和Shader

    高级编程技巧只是相对的,其实主要是讲物理模拟和着色器程序的使用. 本章主要讲解利用Box2D并用它来实现萝卜雨,然后是使用单色着色器shader让画面呈现单色状态:http://files.cnblo ...

  7. [libgdx游戏开发教程]使用Libgdx进行游戏开发(10)-音乐和音效

    本章音效文件都来自于公共许可: http://files.cnblogs.com/mignet/sounds.zip 在游戏中,播放背景音乐和音效是基本的功能. Libgdx提供了跨平台的声音播放功能 ...

  8. [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游戏框架搭建

    让我们抛开理论开始code吧. 入口类CanyonBunnyMain的代码: package com.packtpub.libgdx.canyonbunny; import com.badlogic. ...

  9. [libgdx游戏开发教程]使用Libgdx进行游戏开发(9)-场景过渡

    本章主要讲解场景过渡效果的使用.这里将用到Render to Texture(RTT)技术. Libgdx提供了一个类,实现了各种常见的插值算法,不仅适合过渡效果,也适合任意特定行为. 在本游戏里面, ...

随机推荐

  1. hdu 1133 Buy the Ticket (大数+递推)

    Buy the Ticket Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)To ...

  2. 51nod 1779逆序对统计(状压DP)

    按照插入数的大小排序, 然后依次进行dp. 用一个状态表示n个数是否被选了 10110 就是表示第1.3.4个位置都选了 那么如果此时这个数该填到5这个位置,那么必定会造成一个逆序(因为下一个数会填到 ...

  3. [APIO2018] New Home

    题面在这里 description 在一个数轴上: 给定\(n\)个商店,每个商店有一个开业时间,关门时间,坐标和销售物品的种类 同时有\(m\)个询问,每个询问给你一个时间\(t[i]\)和地点\( ...

  4. MySQL in查询优化

    https://blog.csdn.net/gua___gua/article/details/47401621 MySQL in查询优化<一> 原创 2015年08月10日 17:57: ...

  5. linux管理(二)---网络使用情况的监控

    我们经常在监控服务器或者排查程序性能瓶颈时需要知道  网络带宽的使用情况,看看带宽是不是瓶颈. linux系统中监控网络的工具和命令很多. 但其实主要分2种,一种是实时监控带宽情况(速度如何),一种是 ...

  6. 【BZOJ 2753 滑雪与时间胶囊】

    Time Limit: 50 Sec  Memory Limit: 128 MBSubmit: 2843  Solved: 993[Submit][Status][Discuss] Descripti ...

  7. 关于final局部变量引用的研究

    嵌套类(内部类)方法安全引用外部方法局部变量的原理 嵌套类方法引用外部局部变量,必需将声明为final,否则将出现 Cannot refer to a non-final variable * ins ...

  8. Why is the ibdata1 file continuously growing in MySQL?

    We receive this question about the ibdata1 file in MySQL very often in Percona Support. The panic st ...

  9. tomcat work目录

    1    用tomcat作web服务器的时候,部署的程序在webApps下,这些程序都是编译后的程序(发布到tomcat的项目里含的类,会被编译成.class后才发布过来,源文件没有发布过来,但这里的 ...

  10. ListView使用--文章集锦

    详解ListView加载网络图片的优化,让你轻松掌握! ListView具有多种item布局--实现微信对话列 关注公众号,分享干货,讨论技术