天天看點

(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小結)碰撞檢測