管理多个屏幕

我们的菜单屏有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. 【Autofac】- 创建的类的生命周期

    1.InstancePerDependency 对每一个依赖或每一次调用创建一个新的唯一的实例.这也是默认的创建实例的方式. 官方文档解释:Configure the component so tha ...

  2. PHP蜘蛛爬虫开发文档

    <我用爬虫一天时间“偷了”知乎一百万用户,只为证明PHP是世界上最好的语言 >所使用的程序框架 编写PHP网络爬虫, 需要具备以下技能: 爬虫采用PHP编写 从网页中抽取数据需要用XPat ...

  3. powerdesigner 外键生成sql语句设置在创建表里面

    根据情况需要将创建外键表的sql语句生成在创建表的sql语句中,如下设置:

  4. BZOJ4476 JSOI2015送礼物(分数规划+单调队列)

    看到这个式子当然先二分答案.得max-min-(j-i+k)ans>=0. 显然max-min相同的情况下所选区间长度越短越好,所以max和min都应该取在边界.那么实际上我们根本不用管端点是否 ...

  5. Educational Codeforces Round 54 (Rated for Div. 2) ABCD

    A. Minimizing the String time limit per test 1 second memory limit per test 256 megabytes Descriptio ...

  6. Django随笔 01

    Django 视图 不处理用户输入,而仅仅决定要展现哪些数据给用户: Django 模板 仅仅决定如何展现Django视图指定的数据. dd http://blog.csdn.net/pipisorr ...

  7. 网络(bzoj 4538)

    Description 一个简单的网络系统可以被描述成一棵无根树.每个节点为一个服务器.连接服务器与服务器的数据线则看做一条树边.两个服务器进行数据的交互时,数据会经过连接这两个服务器的路径上的所有服 ...

  8. centos7下yum快速安装 mariadb(mysql)

    从最新版本的centos系统开始,默认的是 Mariadb而不是mysql! 使用系统自带的repos安装很简单: yum install mariadb mariadb-server systemc ...

  9. 【BZOJ2460】【BJOI2011】元素 [线性基]

    元素 Time Limit: 20 Sec  Memory Limit: 128 MB[Submit][Status][Discuss] Description 相传,在远古时期,位于西方大陆的 Ma ...

  10. ecma 2018, javascript spread syntax behaves like Object.assign

    as the subject. It is only supported in Chrome version 60+, so, first check the version, or just use ...