前言
到目前为止,我们或许已经可以制作一个类似于 谷歌小恐龙 这样的小游戏了,但是,当实际操作起来的时候,我们会发现:如何检测小恐龙与障碍物之间的碰撞?不会吧?碰撞检测很复杂的,难道还要自己造轮子?不不不, libGDX 自身提供了碰撞检测的方法:使用com.badlogic.gdx.math.Rectangle
类。
Rectangle 碰撞检测
Rectangle 类创建一个矩形,它提供了碰撞检测,它的常用构造方法如下:
- Rectangle() : 创建一个空的矩形,坐标宽高都为0
- Rectangle(Rectangle rect) : 以 rect 创建一个新的矩形
- Rectangle(float x,float y,float width,float height) : 创建一个(x,y)且大小 width*height 的矩形
Rectangle de 使用
Rectangle 类包含了对矩形的各种操作:
//设置坐标
rect.setX(x);
rect.x=x;
rect.setY(y);
rect.y=y;
rect.setPosition(x,y);
// 设置大小
rect.setWidth(width);
rect.width=width;
rect.setHeight(height);
rect.height=height;
rect.setSize(width,height);
rect.setBounds(x,y,width,height);
//获取属性的略......
Rectangle 还有如上讲到的碰撞检测方法:
//检测(x,y)处的点是否在矩形内
boolean isContains=rect.contains(x,y);
//vec2 是 com.badloc.gdx.math.Vector2 的变量,也表示一个坐标
isContains=rect.contains(vec2);
//检测rect2(Rectangle)是否完全在矩形内
isContains=rect.contains(rect2);
//检测rect与rect2是否完全相同(大小,坐标)
isEquals=rect.equals(rect2);
//检测rect与rect2是否存在重叠(碰撞检测就用这个)
isOverlaps=rect.overlaps(rect2);
碰撞深度检测
游戏中,很多的物体并不是一撞到就死,更多的只是拦截而已,也就是 碰撞箱,但 libGDX 并不提供碰撞箱,因此,我们需要造轮子。
碰撞箱目的是让两个物体无法重叠,因此我们只要求出两个 Rectangle 碰撞的深度,再根据深度移出某一个 Rectangle 就可以实现 碰撞箱 ,只需要两个方法:
//检测碰撞y深度
public float testCollisionHeight(Rectangle rect,Rectangle otherRect) {
if(!(rect.overlaps(otherRect)))return 0; //无触碰情况
if(rect.y>otherRect.y)
//this在上
return ( ( otherRect.height + otherRect.y ) - rect.y );
else
//this在下
return - ( ( rect.height + rect.y ) - otherRect.y ) ;
}
//检测碰撞x深度
public float testCollisionWidth(Rectangle rect,Rectangle otherRect) {
if(!(rect.overlaps(otherRect)))return 0; //无触碰情况
if(rect.x>otherRect.x)
//this在右
return ( ( otherRect.width + otherRect.x ) - rect.x );
else
//this在左
return -( ( rect.width + rect.x ) - otherRect.x );
}
上面两个方法返回的是 rect 应该向 x和y 移动多少(加法),也就是说,rect 是被动的,例如游戏中可以推的箱子等等,而 otherRect 是主动的,例如推箱子的角色等等。
实例:互推
为测试它们是否真的有效,我们举一个实例:
下载这张图片,命名为 badlogic.png 放在 asset 文件夹
package com.libGDX.test;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Rectangle;
//应用主类
public class Test extends ApplicationAdapter {
private SpriteBatch batch;
//原始纹理
private Texture texture;
//左边和右边的精灵
private Sprite left;
private Sprite right;
//优先级,是否left优先
private boolean before;
@Override
public void create() {
batch=new SpriteBatch();
//加载纹理
texture=new Texture(Gdx.files.internal("badlogic.png"));
//根据纹理创建 Sprite
left=new Sprite(texture);
right=new Sprite(texture);
//设置大小和位置
left.setBounds(0,200,100,100);
right.setBounds(400,200,100,100);
//left 优先
before=true;
}
@Override
public void render() {
//白色清屏
Gdx.gl.glClearColor(1,1,1,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
//Gdx.graphics.getDeltaTime() 是增量时间
act(Gdx.graphics.getDeltaTime());
batch.begin();
//绘制 Sprite
left.draw(batch);
right.draw(batch);
batch.end();
}
//更新状态
private void act(float delta) {
//点击则翻转优先级
if(Gdx.input.justTouched())before=!before;
/*分别向左右移动,将 距离 乘以 增量时间,
* 保证无论性能好坏(帧率大小),每秒都能移动 距离
*/
left.setX(left.getX()+100*delta);
right.setX(right.getX()-100*delta);
if(before)
/* left 优先,right 移动使用加法,
* 第三节有讲过 Sprite 的 getBoundRectangle()方法,
* 根据自身的坐标大小返回一个 Rectangle
*/
right.setX(right.getX()+testCollisionWidth(right.getBoundingRectangle(), left.getBoundingRectangle()));
else
//right 优先
left.setX(left.getX()+testCollisionWidth(left.getBoundingRectangle(),right.getBoundingRectangle()));
}
//检测碰撞y深度
public float testCollisionHeight(Rectangle rect,Rectangle otherRect) {
if(!(rect.overlaps(otherRect)))return 0; //无触碰情况
if(rect.y>otherRect.y)
//this在上
return ( ( otherRect.height + otherRect.y ) - rect.y );
else
//this在下
return - ( ( rect.height + rect.y ) - otherRect.y ) ;
}
//检测碰撞x深度
public float testCollisionWidth(Rectangle rect,Rectangle otherRect) {
if(!(rect.overlaps(otherRect)))return 0; //无触碰情况
if(rect.x>otherRect.x)
//this在右
return ( ( otherRect.width + otherRect.x ) - rect.x );
else
//this在左
return -( ( rect.width + rect.x ) - otherRect.x );
}
@Override
public void dispose(){
batch.dispose();
texture.dispose();
}
运行结果:
实例 :小恐龙躲障碍
学会了如此6批的东西,怎能不大展身手一番?那么,我们做一个小恐龙躲障碍(类似谷歌小恐龙)的小游戏吧!
下载下面两张图片,命名为 dinasour.png 和 cacti.png ,放到 asset 文件夹
在项目下创建 data 文件夹,再添加 scoreMax.txt:
package com.libGDX.test;
import java.util.Random;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.Sprite;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.utils.Array;
//应用主类
public class Test extends ApplicationAdapter {
private SpriteBatch batch;
//纹理
private Texture cactiTexture;
private Texture dinasourTexture;
private Array<Sprite> cactis; //仙人掌
private Sprite dinasour; //小恐龙
private Random r; //随机数生成实例
private int time; //累加时间
private int jump; //跳跃高度
private int score; //本场分数
private FileHandle scoreFile; //存储分数的 .txt 文件
private BitmapFont font; //位图字体
private int maxScore; //历史最高分数
//世界宽高
public final int WORLD_WIDTH=500;
public final int WORLD_HEIGHT=200;
@Override
public void create() {
batch=new SpriteBatch();
//加载纹理
cactiTexture=new Texture(Gdx.files.internal("cacti.png"));
dinasourTexture=new Texture(Gdx.files.internal("dinasour.png"));
cactis=new Array<>();
dinasour=new Sprite(dinasourTexture);
r=new Random();
//获取历史最高分数,data/scoreMax.txt 分数文件
scoreFile=new FileHandle("data/scoreMax.txt");
String str=scoreFile.readString();
if(str.equals(""))maxScore=0; //如果scoreMax 没有内容,则为零
else maxScore=Integer.valueOf(str);
font=new BitmapFont();
font.setColor(Color.BLACK);
font.getData().setScale(3);
reset();
}
@Override
public void render() {
Gdx.gl.glClearColor(1,1,1,1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
act(Gdx.graphics.getDeltaTime());
batch.begin();
for(Sprite s : cactis)
s.draw(batch);
dinasour.draw(batch);
font.draw(batch,"UI "+String.valueOf(maxScore)+" "+String.valueOf(score),150,170);
batch.end();
}
//重置属性
private void reset() {
//设置小恐龙的属性
dinasour.setBounds(50,0,60,60);
//设置显示的最高分数
maxScore=Integer.valueOf(scoreFile.readString());
//重置分数为零
score=0;
//重置无跳跃
jump=0;
//时间清零
time=0;
//清空仙人掌
for(int i=cactis.size-1;i>=0;--i) {
cactis.removeIndex(i);
}
}
//更新游戏逻辑
private void act(float delta) {
//累加时间(*100)
time+=delta*100;
//点击即跳跃
if(dinasour.getY()==0&&Gdx.input.justTouched())jump=18;
//小恐龙y坐标变化,jump正数跳跃,负数下降
dinasour.setY(dinasour.getY()+jump);
//如果小恐龙未落地,则持续减少 jump
if(dinasour.getY()>0)jump-=delta*60;
else { //否则jump重置,小恐龙落地
dinasour.setY(0);
jump=0;
}
//每0.1s(10/100) 加一分
if(time%10==0)score++;
//每0.4s 有50% 的几率刷新仙人掌
if(time%40==0&&r.nextInt(2)==0) {
Sprite cacti=new Sprite(cactiTexture);
//设置仙人掌属性,高在40~60之间
cacti.setBounds(WORLD_WIDTH, 0, 50,r.nextInt(20)+40);
//添加到数组
cactis.add(cacti);
}
Sprite cacti;
//遍历仙人掌数组
for(int i=cactis.size-1;i>=0;--i) {
//获取仙人掌
cacti=cactis.get(i);
//向左移动
cacti.setX(cacti.getX()-500*delta);
//如果与小恐龙接触,重新开始游戏
if(dinasour.getBoundingRectangle().overlaps(cacti.getBoundingRectangle())) {
//如果当前是最高分,则写入新的最高分数
if(score>maxScore)scoreFile.writeString(String.valueOf(score),false);
reset();
}
//超出屏幕删除仙人掌
if(cacti.getX()<0-50)
cactis.removeIndex(i);
}
}
@Override
public void dispose() {
batch.dispose();
dinasourTexture.dispose();
cactiTexture.dispose();
}
}
运行结果: