Libgdx游戏开发(6)——游戏暂停
暂停也是一个游戏的必要功能了,本文研究了Libgdx实现游戏暂停
例子以桌面端游戏实现讲解为主,至于移动端,可能之后会进行补充...
本文最终实现的就是
按下esc暂停,之后会出现一个界面提示,表示当前已经暂停
重新按下esc,则返回继续游戏
本篇稍微学习了下libgdx里的输入事件监听
最初方案1
最初看的教程是,通过一个boolean变量来控制render渲染,这里我们以上文例子Libgdx游戏开发(5)——碰撞反弹的简单实践-Stars-One的杂货小窝代码为例,增加一个暂停功能
注:下面贴的是全部代码,发布各位自行运行,但后续代码例子只看
CircleBallTest这个类,为了方便阅读其他类就不再贴出了...
import com.badlogic.gdx.ApplicationAdapter
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Input
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.graphics.glutils.ShapeRenderer
class CircleBallTest : ApplicationAdapter() {
lateinit var shape: ShapeRenderer
val ball by lazy { Ball() }
val line by lazy { MyBan() }
override fun create() {
shape = ShapeRenderer()
}
//增加一个变量标识
var isPause = false
override fun render() {
//监听按下esc键,修改标识
if (Gdx.input.isKeyPressed(Input.Keys.ESCAPE)) {
isPause = !isPause
}
if (isPause) {
//如果是暂停,则不再进行绘制
return
}
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.control()
ball.gundon()
line.draw(shape)
ball.draw(shape)
ball.checkFz()
//检测碰撞到数横条
ball.checkLineP(line)
}
}
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()
}
fun control() {
if (Gdx.input.isKeyPressed(Input.Keys.LEFT)) {
x -= 200 * Gdx.graphics.deltaTime
}
if (Gdx.input.isKeyPressed(Input.Keys.RIGHT)) {
x += 200 * Gdx.graphics.deltaTime
}
//这里屏蔽y坐标改变,只给控制左右移动
return
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
y += 200 * Gdx.graphics.deltaTime
}
if (Gdx.input.isKeyPressed(Input.Keys.DOWN)) {
y -= 200 * Gdx.graphics.deltaTime
}
}
}
class Ball {
var size = 5f
var x = 50f
var y = 50f
var speedX = 5f
var speedY = 5f
fun checkLineP(myBan: MyBan) {
if (y - size <= myBan.y) {
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次esc,但是发现好像没暂停,什么原因导致的?
原因很简单,render()方法是每帧进行渲染的,所以导致我们的监听会执行多次,我们加个日志打印就能发现端倪,如下图:

按下了一次,但由于每帧都会渲染,所以触发了多次
优化方案2 - 事件拦截器监听按键
经过了一番百度和GPT询问,得知Libgdx里有一个输入事件的拦截器接口InputProcessor
为了方便我们不必重写每个此接口的每个方法,我们可以使用InputAdapter(这个是InputProcessor接口的空实现类),之后重写需要的方法即可
我们以上面需求,实现监听esc键的监听,代码如下:
class InputP:InputAdapter(){
//暂停标志
var isPause = false
override fun keyDown(keycode: Int): Boolean {
if (keycode == Input.Keys.ESCAPE) {
//按下esc按键则修改状态
isPause=!isPause
}
//ps:如果想监听android上的返回键,则可以使用Input.Keys.BACK,不过得先调用
return true
}
}
接着通过Gdx.input.inputProcessor(我这里是kotlin写法,java的话则是个setinputProcessor*()方法)进行设置拦截事件的拦截
class CircleBallTest : ApplicationAdapter() {
lateinit var shape: ShapeRenderer
val ball by lazy { Ball() }
val line by lazy { MyBan() }
//懒加载创建拦截器对象
val inputPro by lazy { InputP() }
override fun create() {
shape = ShapeRenderer()
//开始之前就设置拦截器
Gdx.input.inputProcessor = inputPro
}
override fun render() {
//通过拦截器里的标志判断当前是否暂停绘制
if (inputPro.isPause) {
} else {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.draw(shape)
ball.draw(shape)
line.control()
ball.gundon()
ball.checkFz()
//检测碰撞到数横条
ball.checkLineP(line)
}
}
}
效果:

从动图效果来看,暂停功能是实现了,但是出现了闪动的问题
这里虽然具体原理不清楚,但是根据之前做过Android动态壁纸的研究,知道这种底层还是使用OpenGl绘制,所以直接猜测OpenGl渲染缓存中有上一帧数据,导致了此问题
优化方案3 - 暂停状态重绘
根据上面的原因,所以有下解决思路:
暂停状态下,重新绘制当前的UI,但不改变物体的x,y坐标
这里就提到了上章节说的,为什么要将绘制和坐标逻辑计算分开不同方法来写的原因了
如下代码(只贴核心代码):
override fun render() {
if (inputPro.isPause) {
//这里重新绘制
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.draw(shape)
ball.draw(shape)
return
}
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.draw(shape)
ball.draw(shape)
line.control()
ball.gundon()
ball.checkFz()
//检测碰撞到数横条
ball.checkLineP(line)
}
效果如下图:

但个人感觉这种方案,在暂停了但仍然会不停的绘制,感觉有些性能浪费,于是有了下面的方案4
优化方案4
再回到问题上来,因为是上一帧和当前帧切换导致的问题,所以我们将上一帧和当前帧整成一样,绘制的时候就不会出现闪动的状态了吧,得到一个新的解决思路:
每次进入暂停状态后,绘制2遍帧数据,保证上一帧和当前帧相同,之后即可跳过绘制过程,由于前2帧一直是相同的,所以就不会出现抖动的效果,即则完成我们需要的效果和优化的效果
class InputP : InputAdapter() {
var isPause = false
override fun keyDown(keycode: Int): Boolean {
if (keycode == Input.Keys.ESCAPE) {
isPause = !isPause
}
//如果不是暂停状态,则重置
if (isPause.not()) {
count=0
}
return true
}
var count = 0
fun handlePase(drawAction: () -> Unit) {
//这里保证绘制完2帧
if (count > 1) {
return
}
drawAction.invoke()
count++
}
}
class CircleBallTest : ApplicationAdapter() {
lateinit var shape: ShapeRenderer
val ball by lazy { Ball() }
val line by lazy { MyBan() }
override fun create() {
shape = ShapeRenderer()
Gdx.input.inputProcessor = inputPro
}
val inputPro by lazy { InputP() }
override fun render() {
if (inputPro.isPause) {
inputPro.handlePase {
draw()
}
return
}
draw()
line.control()
ball.gundon()
ball.checkFz()
//检测碰撞到数横条
ball.checkLineP(line)
}
private fun draw() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.draw(shape)
ball.draw(shape)
}
}
由于效果与上面相同,这里就不上图了
优化方案5
上面已经完成实现暂停功能了,现在我们在上面基础上个加个暂停文字提示(之前章节已经讲过如何绘制文字了),这里简单起见,我们直接显示pause单词
class CircleBallTest : ApplicationAdapter() {
lateinit var shape: ShapeRenderer
val ball by lazy { Ball() }
val line by lazy { MyBan() }
val batch: SpriteBatch by lazy { SpriteBatch() }
val font: BitmapFont by lazy { BitmapFont() }
override fun create() {
shape = ShapeRenderer()
Gdx.input.inputProcessor = inputPro
}
val inputPro by lazy { InputP() }
override fun render() {
if (inputPro.isPause) {
inputPro.handlePase {
draw()
//绘制暂停提示
Gdx.gl.glClearColor(0f, 0f, 0f, 0.8f); // 设置清屏颜色为透明度80%的黑色
batch.begin()
font.draw(batch, "Pause", 100f, 150f)
batch.end()
}
return
}
draw()
line.control()
ball.gundon()
ball.checkFz()
//检测碰撞到数横条
ball.checkLineP(line)
}
private fun draw() {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
line.draw(shape)
ball.draw(shape)
}
}
效果如下:

补充 - 监听android手机的返回键
如果想要监听android手机的返回键,则需要先设置Gdx.input.setCatchKey(Input.Keys.BACK, true),之后和上述一样监听keycode==Keys.BACK即可实现,如下图代码示例


这里就不放演示动图了,实际测试效果按下返回键即可暂停,但好像分辨率没有兼容,导致小球特别小,后续优化的时候再研究了...
参考
- Pause/Resume a Simple Libgdx Game for android - Stack Overflow
- Back and menu key catching - libGDX
- Event handling - libGDX
- Input handling - libGDX
Libgdx游戏开发(6)——游戏暂停的更多相关文章
- [libGDX游戏开发教程]使用libGDX进行游戏开发(1)-游戏设计
声明:<使用Libgdx进行游戏开发>是一个系列,文章的原文是<Learning Libgdx Game Development>,大家请周知.后续的文章连接在这里 使用Lib ...
- JavaFX横幅类游戏开发 教训 游戏贴图
上一节课,我们即将完成战旗Demo有了一个大概的了解.教训这,我们将学习绘制游戏地图. 由于JavaFX 2.2中添加了Canvas相关的功能,我们就能够使用Canvas来实现游戏绘制了. 游戏地图绘 ...
- java游戏开发杂谈 - 游戏物体
现实生活中,有很多物体,每个物体的长相.行为都不同. 物体存在于不同的空间内,它只在这个空间内发生作用. 物体没用了,空间就把它剔除,不然既占地方,又需要花精力管理. 需要它的时候,就把它造出来,不需 ...
- java游戏开发杂谈 - 游戏编程浅析
每个游戏,你所看到的它的一切,都是计算机画出来的! 地图是画出来,人物是画出来的,树木建筑是画出来的,菜单按钮是画出来的,滚动的文字.闪烁的图标.云雾烟火,都是画出来的. 游戏编程,所要做的,就是控制 ...
- FXGL游戏开发-JavaFX游戏框架
FXGL 是一个JavaFX 游戏开发的框架,这个框架有两个版本,其中基于JDK1.8的版本已经不再维护,目前最新的是基于JDK11的版本,也就是Openjfx的版本. FXGL 提供了各种游戏范例: ...
- Unity3D游戏开发之游戏读/存档功能在Unity3D中的实现
喜欢我的博客请记住我的名字:秦元培,我的博客地址是:http://qinyuanpei.com 转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanp ...
- [libgdx游戏开发教程]使用Libgdx进行游戏开发(2)-游戏框架搭建
让我们抛开理论开始code吧. 入口类CanyonBunnyMain的代码: package com.packtpub.libgdx.canyonbunny; import com.badlogic. ...
- 【Unity3d游戏开发】游戏中的贝塞尔曲线以及其在Unity中的实现
RT,马三最近在参与一款足球游戏的开发,其中涉及到足球的各种运动轨迹和路径,比如射门的轨迹,高吊球,香蕉球的轨迹.最早的版本中马三是使用物理引擎加力的方式实现的足球各种运动,后来的版本中使用了根据物理 ...
- 三、微信小游戏开发 --- 小游戏API调用Platform
微信小游戏API Platform主要是Egret用于来调用平台的SDK的. 在Egret中使用接口定义Platform. Egret项目中默认的platform值是DebugPlatform. 发布 ...
- csdn vip文章:Unity游戏开发-小游戏-非常简单的游戏-unity-
原文https://blog.csdn.net/qq_20484877/article/details/81841190 1*创建物体 Create菜单下 3D Object菜单下Cube 1.1设置 ...
随机推荐
- 如何实现surging 多语言混合微服务异构
1. 背景 作为微服务体系, 应该是不限语言的, 不管是.net.java, 都可以是一个微服务. 可以使用JAVA或者.NET 去实现业务模块,通过统一的消息模型进行传输调用因客户技术栈以多语言,多 ...
- 03.go-admin代码生成器的使用
目录 简介 基于Gin + Vue + Element UI的前后端分离权限管理系统 一 编写go-admin应用,第1步 二.开始项目 三.建议开发目录 四.修改配置 五.代码生成 1. 导入表 2 ...
- Linux下vim的常用命令总结
vim按d表示剪切 按dd剪切一行 vim命令:命令模式 /关键字 n继续向下查找 vim的多行注释: 1.按ctrl + v进入 visual block模式 2.按上下选中要注释的行 3.按大写字 ...
- SRAM、DRAM、Flash、DDR有什么区别
SRAM SRAM的全称是Static Rnadom Access Memory,翻译过来即静态随机存储器.这里的静态是指这种存储器只需要保持通电,里面的数据就可以永远保持.但是当断点之后,里面的数据 ...
- 网络性能监测与诊断的专家-AnaTraf
网络性能问题是困扰许多企业和组织的常见问题.网络速度慢.延迟高.丢包率高.应用卡顿等问题都会严重影响用户体验和工作效率.为了解决这些问题,企业需要对网络流量进行分析和诊断,找出问题根源并采取措施进行优 ...
- kubernetes 之 Rolling Update 滚动升级
滚动升级 1.错误的yml文件 [machangwei@mcwk8s-master ~]$ cat mcwHttpd.yml apiVersion: apps/v1 kind: Deployment ...
- WPF 不透明蒙板概述
本文内容 先决条件 使用不透明蒙板创建视觉效果 创建不透明蒙板 将渐变用作不透明蒙板 显示另外 4 个 不透明蒙板能够使部分元素或视觉对象透明或部分透明. 要创建不透明蒙版,请将 Brush 应用于元 ...
- Android Media Framework(一)OpenMAX框架简介
学习开源代码最快的方式是先阅读它的文档,再查看它的头文件,最后研读代码实现并进行编译调试.Android早期引入OpenMAX IL作为使用音视频编解码器的标准接口,了解Android Media框架 ...
- SpringBoot使用@Value获取不到值的问题
背景 在一次SpringBoot项目改造为Cloud的过程中,使用Nacos作为配置中心获取属性,改造后程序启动报错,查看日志,定位到代码: 解决方案 如果了解Bean的生命周期的同学应该知道,Spr ...
- itest(爱测试) 4.5.7 发布,开源BUG 跟踪管理 & 敏捷测试管理&极简项目管理软件
itest 简介 itest 开源敏捷测试管理,testOps 践行者,极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试5合1,又有丰富的统计分析.可按测试包分配测试用例执行,也可建测试迭代 ...