Libgdx游戏开发(7)——开始游戏界面实现

上篇文章介绍了如何实现暂停功能,但实际上,上篇的做法可能不够优雅。

因为暂停和游戏界面可以分成两个Screen对象,这样只需要监听键盘输入,更改显示不同的Screen对象即可。

本文的实现目标是使用Screen来实现,在进入游戏前,先显示一个游戏主界面,按下enter键再开始游戏。

前置知识

还记得之前的例子,都是继承ApplicationAdapter类,并在其的render方法中实现我们的游戏逻辑。

由于我们要使用Screen对象,所以我们得使用Game对象类替换我们之前继承的ApplicationAdapter对象。

实际上,Game对象和ApplicationAdapter最终父类都是ApplicationListener,只不过Game对象帮我们封装好了管理Screen的方法。

Game对象提供了一个setScreen()方法来设置当前显示的Screen对象。

基础使用

1.使用Game类作为入口

在我们启动方法中,使用Game作为启动的入口,下面的MyGame即为Game对象。

MyGame代码

class MyGame : Game() {

val batch: SpriteBatch by lazy { SpriteBatch() }

val font: BitmapFont by lazy { BitmapFont() }

val shape: ShapeRenderer by lazy { ShapeRenderer() }

override fun create() {

//这里调用下变量,实际相当于初始化了

batch

font

shape

//注意这里,已经设置了首屏幕!!

this.setScreen(MainScreen(this))

}

override fun render() {

super.render()

}

override fun dispose() {

super.dispose()

//释放资源

shape.dispose()

font.dispose()

batch.dispose()

}

}

之后,这个MyGame将作为全局单例对象来进行使用。

由于是单例对象,所以,我们可以在其创建的时候,进行相关资源的创建,比如绘制图片和文字等对象创建(这里不再赘述,若是类有些陌生可详见之前文章讲解),以及嘴硬的资源释放,避免出现内存溢出问题。

而官方给出的代码示例中,是将此MyGame对象作为之后Screen的构造函数传入(因为需要调用Game对象对应方法来设置当前显示屏幕).

但我觉得可能在整个全局静态类直接调用可能会好点?但不确定是否是最优做法。

所以下面还是先按照官方例子走一遍。

2.创建对应的Screen

假设我们先简单些,有2个Screen,一个是主界面MainScreen,另一个则是游戏运行界面GameScreen。

和ApplicationAdapter类似,Screen接口也有一个ScreenAdapter空实现类。

我们可以直接继承ScreenAdapter类,从而只重写我们需要的方法即可,代码更加清晰。

MainScreen就简单绘制下游戏主界面的文字提示,代码如下:

class MainScreen(val game: MyGame) : ScreenAdapter() {

override fun render(delta: Float) {

game.apply {

batch.begin();

font.draw(batch, “Welcome to Drop!!! “, 100f, 150f);

font.draw(batch, “Tap anywhere to begin!”, 100f, 100f)

batch.end();

}

//当鼠标点击则触发开始游戏,这里相信各位自己也能做些扩展,比如按下enter键来实现(前面文章也已经讲解过了)

if (Gdx.input.isTouched()) {

game.setScreen(GameScreen(game))

dispose();

}

}

而我们的GameScreen,则是之前我们的相关代码,只是绘制的时候使用的是全局对象Game里的相关对象进行绘制。

class GameScreen() : ScreenAdapter() {

val game by lazy { GloGame.game }

val ball by lazy { Ball() }

val line by lazy { MyBan() }

val pauseInput by lazy { PauseInput() }

override fun show() {

Gdx.input.inputProcessor = pauseInput

}

override fun render(delta: Float) {

if (pauseInput.handlePause {

drawLogic()

//绘制暂停的页面提示

GloGame.game.apply {

Gdx.gl.glClearColor(0f, 0f, 0f, 0.8f); // 设置清屏颜色为透明度80%的黑色

batch.begin()

font.draw(batch, “Pause”, 100f, 150f)

batch.end()

}

}) {

return

}

drawLogic()

updateXy()

}

private fun drawLogic() {

Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)

val shape = game.shape

line.draw(shape)

ball.draw(shape)

}

fun updateXy() {

//运动的逻辑

ball.gundon()

line.control()

ball.checkFz()

//检测碰撞到数横条

ball.checkLineP(line)

}

}

这里我们封装了个简单的类实现游戏暂停(不过又觉得好像应该把暂停封装为一个Screen对象比较好)

class PauseInput() : InputAdapter() {

var isPaused = false

private var count = 0

override fun keyDown(keycode: Int): Boolean {

if (keycode == Input.Keys.ESCAPE) {

isPaused=isPaused.not()

return true // 表示已经处理了按键事件

}

return false; // 表示未处理按键事件

}

fun handlePause(action: () -> Unit): Boolean {

if (isPaused) {

//保证当前帧和上一帧相同后,就不再绘制了

if (count <= 1) {

action.invoke()

count++

}

} else {

count = 0

}

return isPaused

}

}

还有一些其他类,之前章节写的对应代码,为了方便实践,再贴一遍:

class MyBan {

var width = 200f

var height = 10f

var x = 0f

var y = height

fun draw(shape: ShapeRenderer) {

shape.begin(ShapeRenderer.ShapeType.Filled)

//这里注意: x,y是指矩形的左上角

shape.rect(x, height, width, height)

shape.end()

}

val spped = 400

fun control() {

if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {

x -= spped * Gdx.graphics.deltaTime

}

if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {

x += spped * Gdx.graphics.deltaTime

}

//这里屏蔽y坐标改变,只给控制左右移动

return

if (Gdx.input.isKeyPressed(Input.Keys.UP)) {

y += spped * Gdx.graphics.deltaTime

}

if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {

y -= spped * Gdx.graphics.deltaTime

}

}

}

class Ball {

var size = 5f

var x = 50f

var y = 50f

var speedX = 5f

var speedY = 5f

//与板子的碰撞检测

fun checkLineP(myB: MyBan) {

val flag = x – size >= myB.x && x + size <= myB.x + myB.width

if (y – size <= myB.y && flag) {

speedY = speedY * -1

}

}

fun gundon() {

x += speedX

y += speedY

}

fun draw(shape: ShapeRenderer) {

shape.begin(ShapeRenderer.ShapeType.Filled)

shape.circle(x, y, size)

shape.end()

}

fun checkFz() {

//到达右边缘,x变反

if (x + size >= Gdx.graphics.width) {

speedX = speedX * -1

}

//到达下边缘,y变反

//todo 这个是判输条件!

if (y – size <= 0) {

//消失

//speedY = speedY * -1

}

//到达上边缘,y变反

if (y + size >= Gdx.graphics.height) {

speedY = speedY * -1

}

//到达左边缘,x变反

if (x – size <= 0) {

speedX = speedX * -1

}

}

}

3.最终效果

使用上的注意事项

1. 切换到一个新的Screen的时候,如果之前的Screen不再使用,需要手动调用Screen.dispose方法,进行资源的释放。

2. 给Game对象设置Screen的时候,设置的新的那个Screen会调用onShow()方法,而之前的Screen会调用onHide()方法。

3. 如果有需要的话,一般在onShow()方法,给当前Screen设置一个输入监听器。

优化尝试 – 全局game对象

使用一个全局静态类来管理game对象,取消对应Screen构造方法传game对象,测试发现似乎没啥问题。

object GloGame{

lateinit var game: MyGame

}

class MyGame : Game() {

val batch: SpriteBatch by lazy { SpriteBatch() }

val font: BitmapFont by lazy { BitmapFont() }

val shape: ShapeRenderer by lazy { ShapeRenderer() }

override fun create() {

//这里调用下变量,实际相当于初始化了

batch

font

shape

GloGame.game = this

this.setScreen(MainScreen())

}

override fun render() {

super.render()

}

override fun dispose() {

super.dispose()

//释放资源

shape.dispose()

font.dispose()

batch.dispose()

}

}

参考

  • Extending the Simple Game – libGDX
  • Multiple Game Screens – Happy Coding
  • LibGDX_4.7: 场景(Screen)_libgdx screen-CSDN博客

未经允许不得转载:大白鲨游戏网 » Libgdx游戏开发(7)——开始游戏界面实现