手把手教你写蛇蛇大作战(六)

这篇我们实现的功能主要是菜单界面的编写,还有设置控制。

效果图

首先还是老样子,先看图。

效果图

先准备好素材图

素材

这个是我打包后的图片,具体小图片可以去我的GITHUB 下载

代码优化

这次我把代码整理了一下,分了下包。看起来结构明显。

代码结构如下图

结构

由于需要多个界面,我们抽象出DirectedGame.javaAbstractGameScreen.java

  • DirectedGame.java 主要管理界面的切换,页面间的切换动画。
  • AbstractGameScreen.java 抽象的界面,所有的界面的夫类

DirectedGame.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
public class DirectedGame implements ApplicationListener {
private boolean init;
private AbstractGameScreen currScreen;
private AbstractGameScreen nextScreen;
private FrameBuffer currFbo;
private FrameBuffer nextFbo;
private SpriteBatch batch;
private float t;

private ScreenTransition screenTransition;

public void setScreen(AbstractGameScreen screen) {
setScreen(screen, null);
}

public void setScreen(AbstractGameScreen screen, ScreenTransition screenTransition) {
int w = Gdx.graphics.getWidth();
int h = Gdx.graphics.getHeight();
if (!init) {
currFbo = new FrameBuffer(Pixmap.Format.RGB888, w, h, false);
nextFbo = new FrameBuffer(Pixmap.Format.RGB888, w, h, false);
batch = new SpriteBatch();
init = true;
}
// start new transition;
nextScreen = screen;
nextScreen.show(); //active next screen
nextScreen.resize(w, h);
nextScreen.render(0);// let screen update() once
if (currScreen != null) currScreen.pause();
nextScreen.pause();
Gdx.input.setInputProcessor(null);//disable input
this.screenTransition = screenTransition;
t = 0;
}

@Override
public void create() {

}

@Override
public void resize(int width, int height) {
if (currScreen != null) currScreen.resize(width, height);
if (nextScreen != null) nextScreen.resize(width, height);
}

@Override
public void render() {
// get delta time and ensure an upper limit of one 60th second
float deltaTime = Math.min(Gdx.graphics.getDeltaTime(), 1.0f / 60.0f);
if (nextScreen == null) {
// no ongoing transition
if (currScreen != null) currScreen.render(deltaTime);
} else {
// ongoing transition
float duration = 0;
if (screenTransition != null) {
duration = screenTransition.getDuration();
}
//update progress of ongoing transition
t = Math.min(t + deltaTime, duration);
if (screenTransition == null || t >= duration) {
// no transition effect set or transition has just finished
if (currScreen != null) currScreen.hide();
nextScreen.resume();
// enable input for next screen
Gdx.input.setInputProcessor(nextScreen.getInputProcessor());
// switch screens
currScreen = nextScreen;
nextScreen = null;
screenTransition = null;
} else {
// render screens to FBOs
currFbo.begin();
if (currScreen != null) currScreen.render(deltaTime);
currFbo.end();
nextFbo.begin();
nextScreen.render(deltaTime);
nextFbo.end();
//render transition effect to screen
float alpha = t / duration;
screenTransition.render(batch, currFbo.getColorBufferTexture(), nextFbo.getColorBufferTexture(), alpha);
}
}
}

@Override
public void pause() {
if (currScreen != null) currScreen.pause();
}

@Override
public void resume() {
if (currScreen != null) currScreen.resume();
}

@Override
public void dispose() {
if (currScreen != null) currScreen.hide();
if (nextScreen != null) nextScreen.hide();
if (init) {
currFbo.dispose();
currScreen = null;
nextFbo.dispose();
nextScreen = null;
batch.dispose();
init = false;
}
}
}

AbstractGameScreen.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public abstract class AbstractGameScreen implements Screen {
protected DirectedGame game;

public AbstractGameScreen(DirectedGame game) {
this.game = game;
}

@Override
public abstract void show();

@Override
public abstract void render(float delta);

@Override
public abstract void resize(int width, int height);

@Override
public abstract void hide();

@Override
public abstract void pause();

public abstract InputProcessor getInputProcessor();

@Override
public void resume() {
Assets.instance.init(new AssetManager());
}

@Override
public void dispose() {
Assets.instance.dispose();
}
}

添加完几个类,我们把SnakeIo也改为继承DirectedGame 然后新建GameScreen.java

这个类的主要职责就是原来SnakeIo一样的。大致搬过来。

看看主要实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class GameScreen extends AbstractGameScreen {
private WorldController worldController;
private WorldRenderer worldRenderer;

private boolean paused;//游戏暂停

public GameScreen(DirectedGame game) {
super(game);
}

@Override
public void show() {
worldController = new WorldController(game);
worldRenderer = new WorldRenderer(worldController);
Gdx.input.setCatchBackKey(true);//获取Android返回键
}

@Override
public void render(float delta) {
if (!paused) {//如果游戏进入后台 不更新游戏
worldController.update(delta);
}
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
worldRenderer.render();
}

@Override
public void resize(int width, int height) {
worldRenderer.resize(width, height);
}

@Override
public void hide() {
worldController.dispose();
worldRenderer.dispose();
Gdx.input.setCatchBackKey(false);
}

@Override
public void pause() {
paused = true;
AudioManager.instance.stopMusic();//暂停
}

@Override
public void resume() {
super.resume();
paused = false;
AudioManager.instance.play(Assets.instance.sounds.bm);//播放游戏背景音乐
}

@Override
public InputProcessor getInputProcessor() {
return worldController;
}
}

菜单类

首先介绍下绘制菜单界面的主要逻辑。

绘制菜单我们需要一个舞台Stage

然后把游戏背景先添加add(),然后添加设置按钮,游戏logo,无尽模式,团战模
式,挑战模式,赏金模式 Button。

我们看下具体实现

MenuScreen.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
public class MenuScreen extends AbstractGameScreen {

private Stage stage;//舞台
private Skin snakeSkin;//皮肤,就是上面那个图片
private Skin defaultSkin;//这个是LibGdx 的默认皮肤,由于没找到合适的素材,所以设置Window 和CheckBox 的样式都是用到默认的。

private Image imgBackground;//背景
private Image imgLogo;//logo
private Button endlessModel;//无尽模式
private Button teamModel;//团战模式
private Button challengeModel;//挑战模式
private Button robcoinModel;//赏金模式
private Button setting;//设置

private Window winSetting;//设置windown
private CheckBox chkSound;//声音CheckBox
private CheckBox chkMusic;//音乐CheckBox
private CheckBox leftOp;//TouchPad 在左CheckBox
private CheckBox rightOp;//TouchPad 在有CheckBox
private TextButton btnWinSave;//保存按钮
private TextButton btnWinCancel;//取消按钮

private boolean debugEnable = false;//调试控制

public MenuScreen(DirectedGame game) {
super(game);
}

@Override
public void show() {
GamePreferences.instance.load();
stage = new Stage(new StretchViewport(Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT));
buildStage();//构建舞台
}
/**
* 构建舞台
*/
private void buildStage() {
snakeSkin = new Skin(Gdx.files.internal(Constants.SKIN_TOUCHPAD_UI),
new TextureAtlas(Gdx.files.internal(Constants.TEXTURE_ATLAS_TOUCHPAD_UI)));
defaultSkin = new Skin(Gdx.files.internal(Constants.SKIN_LIBGDX_UI),
new TextureAtlas(Constants.TEXTURE_ATLAS_LIBGDX_UI));
Table layerBackground = buildBackgroundLayer();
Table layerLogo = buildLogoLayer();
Table layerGame = buildGameModelLayer();
Table layerSetting = buildSettingLayer();
Table layerSettingWin = buildSettingWindowLayer();

stage.clear();
Stack stack = new Stack();//这里的堆栈是Libgdx 实现的一个视图的容器,可以分层的摆放个个容器
stage.addActor(stack);//将堆栈添加进舞台
stack.setSize(Constants.VIEWPORT_GUI_WIDTH, Constants.VIEWPORT_GUI_HEIGHT);
stack.add(layerBackground);
stack.add(layerLogo);
stack.add(layerGame);
stack.add(layerSetting);
stage.addActor(layerSettingWin);
}

/**
* 构建背景
*/
private Table buildBackgroundLayer() {
Table layer = new Table();
imgBackground = new Image(snakeSkin, "home_bg");
layer.add(imgBackground);
return layer;
}
/**
* 构建Logo
*/
private Table buildLogoLayer() {
Table layer = new Table();
layer.center().top();
imgLogo = new Image(snakeSkin, "snake_name_icon");
imgLogo.setOrigin(imgLogo.getWidth() / 2, imgLogo.getHeight() / 2);
imgLogo.setScale(0.5f);//由于找到的素材太大了,我们把它缩放为原来的一半
layer.add(imgLogo);
if (debugEnable) layer.debug();
return layer;
}
/**
* 构建 无尽模式,团战模式,挑战模式,赏金模式
*/
private Table buildGameModelLayer() {
Table layer = new Table();//表格布局
layer.center();//布局位置
endlessModel = new Button(snakeSkin, "endless");
layer.add(endlessModel).width(100).height(140).padLeft(5);//原图片素材比较大所以就给它一个高度
teamModel = new Button(snakeSkin, "team");
layer.add(teamModel).width(100).height(140).padLeft(5);
Table childTable = new Table();//里面嵌套了个表格布局
challengeModel = new Button(snakeSkin, "challenge");
childTable.add(challengeModel).width(120).height(70);
childTable.row();//换行
robcoinModel = new Button(snakeSkin, "robcoin");
childTable.add(robcoinModel).width(120).height(70);
layer.add(childTable).padLeft(5);
if (debugEnable) layer.debug();

//监听时间
endlessModel.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
playGame();
}
});
teamModel.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
playGame();
}
});
challengeModel.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
playGame();
}
});
robcoinModel.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
playGame();
}
});
return layer;
}
/**
* 开始游戏,跳转进入游戏界面
*/
private void playGame() {
ScreenTransition transition = ScreenTransitionFade.init(0.75f);
game.setScreen(new GameScreen(game), transition);
}
/**
* 构建 设置按钮
*/
private Table buildSettingLayer() {
Table layer = new Table();
layer.top().right();
setting = new Button(snakeSkin, "setting");
layer.add(setting).padRight(20).padTop(20);
setting.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
showOptionsWindow(true, true);// 监听事件
}
});
return layer;
}
/**
* 构建 设置Window
*/
private Table buildSettingWindowLayer() {
winSetting = new Window("Setting", defaultSkin);
winSetting.center();
winSetting.add(buildSetWinAudioSettings()).row();
winSetting.add(buildSetWinOpreateSettings()).row();
winSetting.add(buildWinBtn());
showOptionsWindow(false, false);
winSetting.pack();
winSetting.setPosition((Constants.VIEWPORT_GUI_WIDTH - winSetting.getWidth()) / 2, (Constants.VIEWPORT_GUI_HEIGHT - winSetting.getHeight()) / 2);
return winSetting;
}
/**
* 构建 游戏音效控制
*/
private Table buildSetWinAudioSettings() {
Table table = new Table();
table.pad(10, 10, 0, 10);
table.add(new Label("Audio", snakeSkin, "default-font", Color.RED)).colspan(3);
table.row();
chkSound = new CheckBox("", defaultSkin);
table.add(chkSound).padLeft(10);
table.add(new Label("Sound", defaultSkin)).padLeft(5);
chkMusic = new CheckBox("", defaultSkin);
table.add(chkMusic).padLeft(10);
table.add(new Label("Music", defaultSkin)).padLeft(5);
chkSound.setChecked(GamePreferences.instance.sound);
chkMusic.setChecked(GamePreferences.instance.music);
return table;
}
/**
* 构建 控制TouchPad 在左或者在右
*/
private Table buildSetWinOpreateSettings() {
Table table = new Table();
table.pad(10, 10, 0, 10);
table.add(new Label("Operation", snakeSkin, "default-font", Color.RED)).colspan(3);
table.row();
leftOp = new CheckBox("", defaultSkin);
table.add(leftOp).padLeft(10);
table.add(new Label("Left", defaultSkin)).padLeft(5);
rightOp = new CheckBox("", defaultSkin);
table.add(rightOp).padLeft(10);
table.add(new Label("Right", defaultSkin)).padLeft(5);
ButtonGroup<CheckBox> buttonGroup = new ButtonGroup<CheckBox>();//由于CheckBox 是多选 需要加个ButtonGroup 变为单选
buttonGroup.add(leftOp, rightOp);
//读取配置
leftOp.setChecked(GamePreferences.instance.touchPadLeft);
rightOp.setChecked(!GamePreferences.instance.touchPadLeft);
return table;
}
/**
* 构建 保存和取消按钮
*/
private Table buildWinBtn() {
Table tbl = new Table();
Label lbl = null;
lbl = new Label("", defaultSkin);
lbl.setColor(0.75f, 0.75f, 0.75f, 1);
lbl.setStyle(new Label.LabelStyle(lbl.getStyle()));
lbl.getStyle().background = defaultSkin.newDrawable("white");
tbl.add(lbl).colspan(2).height(1).width(220).pad(0, 0, 0, 1);
tbl.row();
lbl = new Label("", defaultSkin);
lbl.setColor(0.5f, 0.5f, 0.5f, 1);
lbl.setStyle(new Label.LabelStyle(lbl.getStyle()));
lbl.getStyle().background = defaultSkin.newDrawable("white");
tbl.add(lbl).colspan(2).height(1).width(220).pad(0, 1, 5, 0);
tbl.row();
btnWinSave = new TextButton("Save", defaultSkin);
tbl.add(btnWinSave).padRight(30);
btnWinSave.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
onSaveClicked();
}
});
btnWinCancel = new TextButton("Cancel", defaultSkin);
tbl.add(btnWinCancel);
btnWinCancel.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
showOptionsWindow(false, false);
}
});
return tbl;
}

private void onSaveClicked() {
showOptionsWindow(false, false);
saveSettings();//
}

private void saveSettings() {
GamePreferences prefs = GamePreferences.instance;
prefs.sound = chkSound.isChecked();
prefs.music = chkMusic.isChecked();
prefs.touchPadLeft = leftOp.isChecked();
prefs.save();
}

@Override
public void render(float delta) {
Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(delta);
stage.draw();
}

@Override
public void resize(int width, int height) {
stage.getViewport().update(width, height, true);
}

@Override
public void hide() {
stage.dispose();
snakeSkin.dispose();
defaultSkin.dispose();
}

@Override
public void pause() {

}

@Override
public InputProcessor getInputProcessor() {
return stage;
}
/**
* 控制设置Window 是否显示,是否需要动画
*/
public void showOptionsWindow(boolean visible, boolean animated) {
float alphaTo = visible ? 0.8f : 0.0f;
float duration = animated ? 1.0f : 0.0f;
final Touchable touchEnable = visible ? Touchable.enabled : Touchable.disabled;
winSetting.addAction(sequence(touchable(touchEnable), alpha(alphaTo, duration)));
}
}

结语

菜单界面的功能基本实现了。可以去GITHUB 看完整代码。

文章作者: zhangman523
文章链接: http://blog.zhangman523.cn/2018/09/28/snake_menu/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zhangman523
支付宝打赏
微信打赏