天天看点

(libgdx小结)碰撞检测

碰撞检测通过一个Mario的例子来说明 1.二维数组 Tiles

碰撞检测首先要提到的就是这个二维数组,为什么他特别重要呢?这个数组在前面的博文中,也是给大家介绍了,它是一个 Tilelayer类中我为我们封装的,来方便我们查找每一个单元格的二维数组。

(1)Tiles 数组分析

Tiles数组实际上是将指定像素的地图,分割成行列数不同的矩形方块,同时将每一个方块都进行编号,例如:起点的单元格编号就是tile[0][0]。这样 确定一个单元格的位置,如果他是第M行、第N列的单元格,那么它在libgdx中的去数组编号就是-----tile[m-1][n-1].

如图:

(libgdx小结)碰撞检测

(2)图块单元格

在 TileMap中我们使用图层的方式来实现地图的编辑,图块是地图的基本组成单位。在libgdx的 tiles数组中,如果一个tile内部有图块的话,那么它的返回值就是1,如果没有,那么他的返回值就是0。例如:起点tile[0][0]内部没有图块,那么我们得到 tile[0][0] = 0,法相反如果有图块的话那么tile[0][0] = 1。

如图:

(libgdx小结)碰撞检测

2.碰撞检测原理

根绝上面的知识我们知道了libgdx已经给我们封装好了一个数组,而且这个数组也有编号,只是这个编号的的x、y、都需要我们进行减一换算才能确定目标。同样的,一般我的游戏主角在移动过程中都有自己的坐标,那么我们可不可以利用这个坐标来确定人物在那个图层呢?这是当然可以的了。

(1)检测点

什么是检测点呢?所谓的检测点,其实就是用于确定游戏主角所在单元格位置的坐标。大家都知道,我们的游戏主角一般的都是可以放在一个矩形的内部,可以实现包裹。主角像右移动就检测矩形右侧边的2个点(建议不要设置端点),向上移动就检测 就检测矩形上方边的2个点(建议不要设置端点),以此类推。

PS:不推荐设置端点的原因,是因为可能出现端点和单元格端点重合这种情况,这样很难判断是相邻的2个方向向哪侧检测。大家可以将坐标点减去0.03 这样就不是端点了。不过原理还是取端点,只是坐标移动了一下,这样比较方便。

影响检测点个数的因素主要有2个:

(1)包裹游戏主角的矩形大小。 (2)地图中单元格面积的大小。

如果我们包裹游戏 主角的矩形面积小于单元格(tile)的面积,那么我们只需要检测4个点。但是并不是移动的时候4个点全部检测,例如向右移动仅仅检测右侧2点、向下移动仅仅检测下面2点,以此类推。相对来说主角矩形面积小于单元格还是比较简单的。

如图:

(libgdx小结)碰撞检测

如果我们包裹主角的矩形面积大于单元格的时候,那么我们就要检测更多的点了,比如我们主角的矩形是1.5倍的单元格,那么我们要检测9个点,原理同上。包裹矩形越大,检测点就越多,这样是为了精确,防止主角头部脚部过不去,但是身子却可以通过地图的情况,就是为了防止这种情况,才需要检测中间的点。推荐大家使用1.5倍的包裹矩形。

如图:

(libgdx小结)碰撞检测

(2)检测原理

通过上面的分析,我们了解了检测点的情况。利用检测点,我们可以了解到,游戏主角在哪一个单元个的内部。同样也分为二种情况:

(1)主角的包裹矩形大小小于单元格的面积,移动中判断移动方向对应的边上2点所在单元格处,是否有图块,通过布尔类型的返回值来实现,如果有图块就不移动,如果没有就移动。

(1)主角的包裹矩形大小大于单元格的面积,移动中判断移动方向对应的边上3点所在单元格处,是否有图块,通过布尔类型的返回值来实现,如果有图块就不移动,如果没有就移动。

详细实现,见下文实例。

2.实例演示

为了方便大家理解,这次我们同样也使用之前博客的超级玛丽的例子。之前我们的超级玛丽移动,并没有实现和地图的检测,只是简单的的设置了坐标,通过坐标来解决人物移动障碍的问题,但是在实际游戏处理中,这样设置坐标是不实际的,你不可能对每一个地图的不同图款做坐标处理,这样做也好似非常愚蠢的。通过上面的分析,相信我们已经知道该如何实现超级玛丽的碰撞检测了,下面土豆就带大家实现依稀超级玛丽的碰撞检测的具体实现。

(1)游戏素材

为了方便我们实现碰撞检测,这次我们选取单元格为 48像素,新建一个地图。同时我们重新处理一下Mario的素材图片,将人物的位置调换一下,将超级玛丽的包裹矩形设置为 64(约为单元格的1.5倍).

素材如图:

(1)新地图                          (2)超级玛丽素材

(libgdx小结)碰撞检测
(libgdx小结)碰撞检测

(2)新建一个地图和对象层,同时设置对象属性为Mario。

如图:

(libgdx小结)碰撞检测

(3)新建一个Collision(碰撞)类,同时创建一个静态语句,实习map的实例化和tile数组的实例。

如图:

(libgdx小结)碰撞检测

(4)选取9个点作为碰撞检测的点,代码中我使用的是 M 和 N,为了大家方便理解,这个点实际传入的是主角的坐标,我在途中用 x 和 y 来表示,方便大家理解。

如图:

(libgdx小结)碰撞检测

(5)创建静态方法 leftEnable 和 downEnable 来实现检测单元格是否有图块,如果有那么就不能通过,返回false。如果没有图块,那么就可以通过,返回true。

即:  能通过----返回 true 。       不能通过 ---- 返回 false  。

如图:

(a)

(libgdx小结)碰撞检测

(b)

(libgdx小结)碰撞检测

(6)方法讲解

(1)首先,我们讲一下这个 “ int   m1   =   MathUtils . ceilPositive (( x   +   12 )  /   map . tileWidth );”这句话, MathUtils . ceilPositive(delta)是对delta这一参数进行加 1 取整运算,这样第一个点所在单元格的x坐标就计算出来了。同理计算y的,这样计算出来的 tile[m-1][y -1] 就是单元格的坐标。如果这个值d等于0 就是没有图块,等于1就是有图块。  

(2) “ int   m1   =   MathUtils . ceilPositive (( x   +   12 )  /   map . tileWidth );”这里的 12 就是微调,我并没有选取端点而是加了 12 将点移动到对应边内部,这样不会出现端点重合的情况,推荐大家这样设置。

(3)只有3个点有一个不是0的情况下,马里奥都是不可以运动过去的。反之,可以运动。

如图:

(libgdx小结)碰撞检测

(7)模拟重力,重写Mario类中的update()方法,实现一个y坐标一直自加的状态。当然,当检测到下面有图来块的情况下,就不自加,就停止,这样就形成了地面可以站立的效果。               加入向左移动的碰撞检测,当人物的状态是向左的时候同时检测左侧没有图块,这样才可以移动,都是通过调用Collision类中的静态方法来实现的。

     向右移动,这里为了区分碰撞检测效果,土豆这里没有做碰撞检测,只是当有向右移动的状态的时候,实现向右的坐标自加。

如图:

(libgdx小结)碰撞检测

(8)重写act()方法,调用update()方法,同时 实现 statetime 和系统时间delta 递增。delta是stage中的间隔时间。

如图:

(libgdx小结)碰撞检测

这样一个简单的碰撞检测就实现了,这里向右的碰撞检测,希望同学们学习了以后,可以自己完成,基本的理论就是这样,希望能帮助大家理解碰撞检测。

二、代码 MyGame

package com.example.groupactiontest;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.tiled.TileAtlas;
import com.badlogic.gdx.graphics.g2d.tiled.TileMapRenderer;
import com.badlogic.gdx.graphics.g2d.tiled.TiledLoader;
import com.badlogic.gdx.graphics.g2d.tiled.TiledMap;
import com.badlogic.gdx.graphics.g2d.tiled.TiledObject;
import com.badlogic.gdx.graphics.g2d.tiled.TiledObjectGroup;
import com.badlogic.gdx.scenes.scene2d.Stage;

/**
 * 这时候在游戏的主界面主要涉及到的东西就只有
 * 舞台、地图、mario
 * 
 * 这个类做的东西主要就是把界面渲染出来
 * @author pc
 *
 */
public class MyGame implements ApplicationListener {

	Stage stage;
	Mario mario;
	
	public static TiledMap map;
	TileMapRenderer render;
	TileAtlas atlas;
	
	OrthographicCamera camera;
	
	@Override
	public void create() {
		stage = new Stage(480, 320, false);
		
		
		map = TiledLoader.createMap(Gdx.files.internal("1.tmx"));
		atlas = new TileAtlas(map, Gdx.files.internal(""));
		render = new TileMapRenderer(map, atlas, 10, 10);
		
		camera = new OrthographicCamera();
		camera.setToOrtho(false, 480, 336);
		
		mario = new Mario(0, 0);
		setMario();//将mario的位置设置成地图中的位置
		
		stage.addActor(mario);
		stage.addActor(mario.buttonL);
		stage.addActor(mario.buttonR);
		
		Gdx.input.setInputProcessor(stage);
	}

	
	private void setMario() {
		for(TiledObjectGroup objectGroup : map.objectGroups){
			for(TiledObject object : objectGroup.objects){
				if("mario".equals(object.name)){
					mario.x = object.x;
					mario.y = render.getMapHeightUnits() - object.y;
				}
			}
		}
	}


	@Override
	public void dispose() {
		// TODO Auto-generated method stub

	}

	@Override
	public void pause() {
		// TODO Auto-generated method stub

	}

	@Override
	public void render() {
		Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
		
		render.render(camera);
		
		stage.act();
		stage.draw();
	}

	@Override
	public void resize(int arg0, int arg1) {
		// TODO Auto-generated method stub

	}

	@Override
	public void resume() {
		// TODO Auto-generated method stub

	}

}
           

Mario

package com.example.groupactiontest;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.Animation;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;

/**
 * mario具有以下属性:坐标、状态、动作
 * mario具有以下行为:人物移动、状态改变
 * @author pc
 *
 */
public class Mario extends Actor {

	//mario的坐标
	public static float x;
	public static float y;
	
	public float stateTime = 0;
	
	//mario的状态
	enum STATE{
		LEFT,RIGHT,IDLE
	}STATE state;
	
	//mario的动作
	Animation aniLeft;
	Animation aniRight;
	Animation aniIdle;
	
	//用于动画
	Texture texture;
	TextureRegion currentFrame;
	
	//按钮
	ImageButton buttonL;
	ImageButton buttonR;
	
	
	
	public Mario(float x, float y) {
		this.x = x;
		this.y = y;
		
		state = STATE.IDLE;//mario的默认状态为idle(空闲)
		
		this.show();
	}
	
	
	private void show() {
		texture = new Texture(Gdx.files.internal("Mario.png"));
		TextureRegion[][] split = TextureRegion.split(texture, 64, 64);
	    TextureRegion[][] mirror = TextureRegion.split(texture, 64, 64);
		
	    /**
	     * 获取对称的画面
	     */
		for(TextureRegion[] region1 : mirror){
			for(TextureRegion region2 : region1){
				region2.flip(true, false);
			}
		}
		
		/**
		 * 左中右动画的实现
		 */
		TextureRegion[] rightAniFrame = new TextureRegion[3];
		rightAniFrame[0] = split[0][1];
		rightAniFrame[1] = split[0][2];
		rightAniFrame[2] = split[0][0];
		aniRight = new Animation(0.1f,rightAniFrame);
		
		TextureRegion[] leftAniFrame = new TextureRegion[3];
		leftAniFrame[0] = mirror[0][1];
		leftAniFrame[1] = mirror[0][2];
		leftAniFrame[2] = mirror[0][0];
		aniLeft = new Animation(0.1f, leftAniFrame);
		
		TextureRegion[] idleAniFrame = new TextureRegion[1];
		idleAniFrame[0] = split[0][0];
		aniIdle = new Animation(0.1f, idleAniFrame);
		
		
		
		
		
		/**
		 * ImageButon的初始化
		 */
		buttonL = new ImageButton(new TextureRegionDrawable(split[1][0]),new TextureRegionDrawable(split[1][1]));
		buttonR = new ImageButton(new TextureRegionDrawable(mirror[1][0]),new TextureRegionDrawable(mirror[1][1]));
		buttonL.setPosition(20, 20);
		buttonR.setPosition(100, 20);
		
		
		
		
		
		
		/**
		 * ImageButton的监听事件
		 */
		buttonL.addListener(new InputListener(){
			@Override
			public boolean touchDown(InputEvent event, float x, float y,
					int pointer, int button) {
				
				state = STATE.LEFT;
				return true;
			}
			
			@Override
			public void touchUp(InputEvent event, float x, float y,
					int pointer, int button) {
				
				state = STATE.IDLE;
				
				super.touchUp(event, x, y, pointer, button);
			}
		});
		
		buttonR.addListener(new InputListener(){
			@Override
			public boolean touchDown(InputEvent event, float x, float y,
					int pointer, int button) {
				
				state = STATE.RIGHT;
				return true;
			}
			
			@Override
			public void touchUp(InputEvent event, float x, float y,
					int pointer, int button) {
				
				state = STATE.IDLE;
				
				super.touchUp(event, x, y, pointer, button);
			}
		});
		
	}

    /**
     * 人物移动
     */
	public void update(){
		if(Collision.downEnable(x, y)){
			y -= 6.5*stateTime;
			
			if(y < 100){
				y = 100;
			}
		}
		
		if(state == STATE.LEFT && Collision.leftEnable(x, y)){
			x -= 1.5f;
			
			if(x < 20){//空气墙。即mario最左不能超过这个坐标
				x = 20;
			}
		}
		
		if(state == STATE.RIGHT ){
			x += 1.5f;
			
//			if(x > 400){//空气墙。即mario最左不能超过这个坐标
//				x = 400;
//			}
		}
	}
	
	/**
	 * 状态判断,更换画面
	 */
	public void aniCheck(){
		if(state == STATE.LEFT){
			currentFrame = aniLeft.getKeyFrame(stateTime, true);
		}else if(state == STATE.RIGHT){
			currentFrame = aniRight.getKeyFrame(stateTime, true);
		}else if(state == STATE.IDLE){
			currentFrame = aniIdle.getKeyFrame(stateTime,true);
		}
	}
	
	
	@Override
	public void act(float arg0) {//在一定的时间段内,更新演员的状态
		// TODO Auto-generated method stub
		super.act(arg0);
	}

	@Override
	public void draw(SpriteBatch batch, float parentAlpha) {//演员本身可以在舞台中绘画
		
		stateTime += Gdx.graphics.getDeltaTime();//修改statetime
		update();//移动人物
		aniCheck();//更改人物状态
		
		
		batch.draw(currentFrame, x, y);//把当前帧滑到界面上
		
		super.draw(batch, parentAlpha);
	}

	
}
           

Collision

package com.example.groupactiontest;

import com.badlogic.gdx.graphics.g2d.tiled.TiledMap;
import com.badlogic.gdx.math.MathUtils;

/**
 * 碰撞检测类主要有以下2个属性:map(地图)、tiles(图块)
 * 有以下两种行为:leftEnable(检测左边是否可以通行),downEnable(检测右边是否可以通行)
 * 
 * @author pc
 *
 */
public class Collision {

	public static int[][] tiles;
	public static TiledMap map;
	
	static{
		map = MyGame.map;
		tiles = map.layers.get(0).tiles;
	}
	
	/**
	 * 对左侧边进行碰撞检测(x坐标相同,y坐标不同)
	 * @param x
	 * @param y
	 * @return
	 */
	public static boolean leftEnable(float x, float y){
		int m1 = MathUtils.ceilPositive((x)/map.tileWidth);
		int n1 = tiles.length - MathUtils.ceilPositive((y) / map.tileHeight);
		
		int m7 = MathUtils.ceilPositive((x)/map.tileWidth);
		int n7 = tiles.length - MathUtils.ceilPositive((y+32) / map.tileHeight);
		
		int m8 = MathUtils.ceilPositive((x)/map.tileWidth);
		int n8 = tiles.length - MathUtils.ceilPositive((y+64) / map.tileHeight);
		
		if(tiles[n1-1][m1-1] != 0 || tiles[n7-1][m7-1] != 0 ||tiles[n8-1][m8-1] != 0 ){//n1代表的是第几行,m2代表的是第几列.千万别写错了,否则会出现数组越界异常
			return false;
		}else{
			return true;
		}
	}
	
	/**
	 * 对底边进行碰撞检测(x坐标不同,y坐标相同)
	 * @param x
	 * @param y
	 * @return
	 */
	public static boolean downEnable(float x, float y){
		int m1 = MathUtils.ceilPositive((x + 12)/map.tileWidth);
		int n1 = tiles.length - MathUtils.ceilPositive((y-1) / map.tileHeight);
		
		int m2 = MathUtils.ceilPositive((x + 24)/map.tileWidth);
		int n2 = tiles.length - MathUtils.ceilPositive((y-1) / map.tileHeight);
		
		int m3 = MathUtils.ceilPositive((x + 40)/map.tileWidth);
		int n3 = tiles.length - MathUtils.ceilPositive((y-1) / map.tileHeight);
		
		if(tiles[n1-1][m1-1] != 0 || tiles[n2-1][m2-1] != 0 ||tiles[n3-1][m3-1] != 0 ){
			return false;
		}else{
			return true;
		}
	}
}
           

四、源码下载 http://download.csdn.net/detail/caihongshijie6/7008601

五、效果图

(libgdx小结)碰撞检测