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

简介: 使用上的注意事项1. 切换到一个新的Screen的时候,如果之前的Screen不再使用,需要手动调用Screen.dispose方法,进行资源的释放2. 给Game对象设置Screen的时候,设置的新的那个Screen会调用onShow()方法,而之前的Screen会调用onHide()方法3. 如果有需要的话,一般在onShow()方法,给当前Screen设置一个输入监听器优化尝试 - 全局game对象

原文: Libgdx游戏开发(7)——开始游戏界面实现-Stars-One的杂货小窝

上篇文章也是讲解了如何实现暂停,但实际上,上篇的做法可能不够优雅

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

本文的实现目标:

使用Screen来实现,进入游戏前,先显示一个游戏主界面,按下enter再开始游戏

前置知识

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

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

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

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

基础使用

1.使用Game类作为入口

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

object DesktopLauncher {
    @JvmStatic
    fun main(arg: Array<String>) {
        val config = Lwjgl3ApplicationConfiguration()
        config.setForegroundFPS(60)
        //设置游戏窗口大小为800*480
        config.setWindowedMode(800, 480)
        //设置开启垂直同步
        config.useVsync(true)
        //Lwjgl3Application(CircleBallTest(), config)
        Lwjgl3Application(MyGame(), config)
    }
}

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()
    }
}

参考

相关文章
|
存储 缓存 资源调度
Koodo Reader : 一个开源免费的电子书阅读器
【1月更文挑战第3天】 今天在浏览 GitHub 的时候,偶然发现了一个非常有趣的开源项目——Koodo Reader。这个项目是一款开源免费的电子书阅读器,支持多种格式。它具有一些非常独特的功能,深深地吸引了我的注意。在接下来的内容中,我将为大家详细介绍一下这个备受关注的阅读器项目。
1506 3
Koodo Reader : 一个开源免费的电子书阅读器
|
移动开发 vr&ar
数据库系统概论——关系代数详解
关系代数是一种抽象的查询语言,是关系数据操纵语言的一种传统表达方式,它是利用对关系的运算来表达查询的。任何运算都是将一定的运算符作用于一定的运算对象上,得到预期的运算结果。关系代数的运算对象是关系,运算结果亦为关系。集合运算符将关系看成元组的集合从关系的“水平”方向即行的角度来进行运算专门的关系运算符不仅涉及行而且涉及列算术比较符辅助专门的关系运算符进行操作逻辑运算符辅助专门的关系运算符进行操作。
1441 1
数据库系统概论——关系代数详解
|
12月前
|
SQL 存储 关系型数据库
添加数据到数据库的SQL语句详解与实践技巧
在数据库管理中,添加数据是一个基本操作,它涉及到向表中插入新的记录
1361 4
|
12月前
|
Java 关系型数据库 MySQL
数据库的连接用Java
本文介绍了如何使用Java连接MySQL数据库,包括注册JDBC驱动、创建数据库连接URL、设置数据库用户和密码、建立连接以及关闭连接的完整代码示例。
324 0
数据库的连接用Java
|
JSON 数据格式 Docker
Docker 网络命令大全,建议收藏!
【7月更文挑战第22天】
514 7
Docker 网络命令大全,建议收藏!
|
供应链 监控 数据安全/隐私保护
ERP系统中的供应商协同与供应链优化解析
【7月更文挑战第25天】 ERP系统中的供应商协同与供应链优化解析
859 0
|
存储 大数据 数据挖掘
大数据中的交易数据
【4月更文挑战第11天】大数据中的交易数据,包含购买记录、订单详情等,为企业决策提供关键信息。通过分析,企业能理解客户习惯、优化产品与定价,预测市场趋势,发现新机会。结合其他数据类型可做全面分析,但需应对数据量大、存储处理难及隐私安全问题。利用交易数据,企业能提升营销精准度,实现持续增长。
291 4
|
存储 缓存 算法
作者推荐 | 【深入浅出MySQL】「底层原理」探秘缓冲池的核心奥秘,揭示终极洞察
MySQL作为一个存储系统,有着一个关键的优化机制——缓冲池(buffer pool),它极大地提高了数据的访问效率,避免了频繁的磁盘IO操作。通过将常用的数据存储在内存中,MySQL可以快速响应查询请求,减少耗时的磁盘访问。这一优化机制在提升数据库性能方面起到了重要的作用。
666 7
作者推荐 | 【深入浅出MySQL】「底层原理」探秘缓冲池的核心奥秘,揭示终极洞察
|
SQL 安全 测试技术
5.2 使用sqlmap进行MSSQL注入及防御
5.2 使用sqlmap进行MSSQL注入及防御
877 0
|
机器学习/深度学习 监控 vr&ar
计算机组成原理——TEC-2机微程序设计实验
选定指令操作码,指令格式,设计一条指令,其功能是把用绝对地址表示的内存单元 A中的内容与内存单元B中的内容相加,结果存放在B单元中。 1、通过以下实例熟悉微指令的装入、新指令的运行及测试。 把用绝对地址表示的内存单元ADDR1的内容与内存单元ADDR2中的内容相加,结果存放到内存单元ADDR2中。
764 0
计算机组成原理——TEC-2机微程序设计实验