本篇简单以一个小球运动,一步步实现碰撞反弹的效果
本文代码示例以kotlin为主,且需要有一定的Libgdx入门基础
注:下面动态图片看着有些卡顿,是录制的问题,实际上运行时很流畅的
水平滚动
简单起见,我们通过ShapeRenderer绘制一个圆形,作为我们的小球,并让其从开始位置向右水平移动
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 override fun create() { shape = ShapeRenderer() } var x = 50f var y = 50f override fun render() { //每次渲染绘制前,清除屏幕 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) x += 5 //设置填充模式,圆形默认即为白色 shape.begin(ShapeRenderer.ShapeType.Filled); //圆形半径为50,起点位置位于(50,50) shape.circle(x, y, 50f) shape.end() } //这里忽略了相关资源释放代码逻辑... }
启动游戏代码(方便阅读,下文中此代码不会再贴出!):
package com.arthurlumertz.taplixic; import com.badlogic.gdx.backends.lwjgl3.*; public class DesktopLauncher { public static void main (String[] arg) { Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration(); config.setWindowedMode(960, 540); config.setForegroundFPS(60); new Lwjgl3Application(new CircleBallTest(), config); } }
效果如下:

水平滚动并反弹
上述已经实现了一个小球滚动,但发现滚动到边缘就不见了,我们加个效果,碰到右边缘就反弹
package com.arthurlumertz.taplixic 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 override fun create() { shape = ShapeRenderer() } var x = 50f var y = 50f var isRight = true override fun render() { //每次渲染绘制前,清除屏幕 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) if (isRight) { x += 5 } else { x-=5 } //设置填充模式,圆形默认即为白色 shape.begin(ShapeRenderer.ShapeType.Filled); //圆形半径为50,起点位置位于(50,50) shape.circle(x, y, 50f) shape.end() //右边缘检测 圆心的x坐标加上半径大于或等于当前游戏屏幕宽度 if (x + 50 >= Gdx.graphics.width) { isRight=false } //左边缘检测 圆心的x坐标减去半径小于或等于0(起点) if (x - 50 <=0) { isRight=true } } //这里忽略了相关资源释放代码逻辑... }
效果如下(录制效果的时候没加左边缘检测):

这里实际可以直接将对应的
+5和-5统一转为一个速度加量,方向需要反转的时候乘以-1即可同时,我们上述小球相关代码封装为一个Ball类来进行使用,优化后的代码如下:
//定义一个ball类实现相关操作 class Ball{ var size = 50f var x = 50f var y = 50f var speedX = 5f fun gundon() { x += speedX } fun draw(shape: ShapeRenderer) { shape.begin(ShapeRenderer.ShapeType.Filled) shape.circle(x, y, size) shape.end() } //检测边缘反弹 fun checkFz() { //到达右边缘,加量变反 if (x + size >= Gdx.graphics.width) { speedX = speedX * -1 } //到达左边缘,加量变反 if (x - size <= 0) { speedX = speedX * -1 } } }
游戏代码:
class CircleBallTest : ApplicationAdapter() { lateinit var shape: ShapeRenderer val ball by lazy { Ball() } override fun create() { shape = ShapeRenderer() } override fun render() { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) ball.draw(shape) ball.gundon() ball.checkFz() } //这里忽略了相关资源释放代码逻辑... }
这里代码我是将绘制,坐标和边缘碰撞检测分别封装对应的方法
draw()绘制gundon()修改坐标的方法checkFz()则是进行碰撞检测的方法
这里为什么要将绘制和修改坐标抽成2个方法,是因为我研究游戏暂停的时候发现的,这里先卖个关子,之后会讲到(算是自己无意摸索出来的小技巧)
四面滚动反弹
上述我们只是在水平方向移动,现在想要小球斜方向发出,之后四周反弹,应该如何实现呢?
想要斜方向发出,我们还需要在上面实现的基础上加个y坐标加量,同时修改x,y坐标,就能让小球斜着运动了(数学中的线性方程,或者可以看做是给了小球上方向和右方向的力)
当然,如果你修改对应的增加量数值,可以实现不同斜率方向
这里我固定x和y的加量相同,即45度方向运动
四周反弹其实可以拆分为左右和上下方向,碰到左和右就反转x的增量,碰到上和下就反转y的增量
import com.badlogic.gdx.ApplicationAdapter import com.badlogic.gdx.Gdx 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() } override fun create() { shape = ShapeRenderer() } override fun render() { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) ball.draw(shape) ball.checkFz() } } class Ball{ var size = 50f var x = 50f var y = 50f var speedX = 5f //y坐标增量 var speedY = 5f 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变反 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 } } }
效果如下:

加个板子进行弹球
在上面的基础上,我们添加一个板子用来接球
- 使用ShapeRenderer对象绘制实心矩形作为板子
- 考虑板子和球的碰撞
- 方向键左右可控制板子移动
- 碰到下边缘,球消失
shape.rect()方法用来绘制一个矩形,在x,y坐标绘制一个定义的宽高矩形,(x,y)坐标即为此矩形的左上角
圆心的y坐标 - 半径 >= 矩形的y坐标,圆心x坐标-半径小于矩形的x坐标,圆心x坐标+半径大于或等于矩形的x坐标+矩形宽度,即视为两者碰撞
下面代码和Ball一样,封装了一个MyBan类,实现板子的绘制和控制移动
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() } override fun render() { Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) line.draw(shape) line.control() 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(myB: MyBan) { if (y - size <= myB.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 } } }
效果如下:
