• Libgdx游戏开发(5)——碰撞反弹的简单实践


    本文为作者原创,允许转载,不过请在文章开头明显处注明链接和出处!!! 谢谢配合~
    作者:stars-one
    链接:https://www.cnblogs.com/stars-one/p/18253036

    本篇大约有5809个字,阅读预计需要7.26分钟


    原文: Libgdx游戏开发(5)——碰撞反弹的简单实践-Stars-One的杂货小窝

    本篇简单以一个小球运动,一步步实现碰撞反弹的效果

    本文代码示例以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
            }
        }
    }
    

    效果如下:

    加个板子进行弹球

    在上面的基础上,我们添加一个板子用来接球

    1. 使用ShapeRenderer对象绘制实心矩形作为板子
    2. 考虑板子和球的碰撞
    3. 方向键左右可控制板子移动
    4. 碰到下边缘,球消失

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

    效果如下:

    参考

  • 相关阅读:
    这位00后经历人生重大变故后,选择了智能家居,选择了Aqara绿米
    MetaAI的融合怪:BlenderBot
    经典/最新计算机视觉论文及代码推荐
    10、Spring 源码学习 ~ Bean 的加载步骤详解(一)
    游戏思考25:MMORPG场景服务器作用及说明(后面会增加人物行走双端相关总结,未完待续10/20)
    浅谈word格式:.doc和.docx的优缺点及区别
    画图时使用的函数和一些错误处理
    数据结构-二叉排序树(建立、查找、修改)
    netty入门前置知识-NIO
    基于51单片机DHT11温湿度测量仪protues仿真设计(源码+仿真+讲解视频+设计报告)
  • 原文地址:https://www.cnblogs.com/stars-one/p/18253036