2017-03-09 64 views
0

我想使用Bullet在Libgdx中製作碰撞檢測器。在這裏,我想將一個碰撞對象的power變量作爲參數傳遞給另一個對象的onCollision()函數。這裏BallBrick延伸AbstractObject。另外poweronCollision()AbstractObject中聲明,但在BrickBall中初始化。我在每個班級都設置了btCollisionObject.userData=this。什麼是最有效的方法? 這是我目前的contactListener:如何將存儲爲java對象的對象變量傳遞給存儲爲java對象的另一個對象的Function?

package com.anutrix.brickbreaker3d.Helpers; 

import com.anutrix.brickbreaker3d.gameObjects.AbstractObject; 
import com.anutrix.brickbreaker3d.gameObjects.Ball; 
import com.anutrix.brickbreaker3d.gameObjects.Brick; 
import com.badlogic.gdx.Gdx; 
import com.badlogic.gdx.graphics.g3d.ModelInstance; 
import com.badlogic.gdx.physics.bullet.collision.ContactListener; 
import com.badlogic.gdx.physics.bullet.collision.btCollisionObject; 
import com.badlogic.gdx.utils.Array; 

public class CollisionListener extends ContactListener { 

    @Override 
    public boolean onContactAdded(btCollisionObject ob0, int partId0, int index0, btCollisionObject ob1, int partId1, int index1) { 

     Gdx.app.log("sdkjg", "fsfgsdg"); 
     Ball bl = null; 
     Brick br = null; 
     AbstractObject aO0 = (AbstractObject) ob0.userData; 
     AbstractObject aO1 = (AbstractObject) ob1.userData; 
     if (aO0 instanceof Ball) { 
      bl = (Ball) aO0; 
     } else if (aO1 instanceof Ball) { 
      bl = (Ball) aO1; 
     } 

     if (aO0 instanceof Brick) { 
      br = (Brick) aO0; 
     } else if (aO1 instanceof Brick) { 
      br = (Brick) aO1; 
     } 
     bl.onCollision(br.power); 
     br.onCollision(bl.power); 
     return true; 
    } 
} 

這裏是Ball類:

public class Ball extends AbstractObject { 

    public Integer power; 

    public Ball(Integer id, Integer type, Vector3 position) { 
     super(id, type, position); 
     modelInstance = new ModelInstance(Assets.instance.ball.get(type)); 
     shape = new btSphereShape(0.2f); 
     body = new btCollisionObject(); 
     body.setCollisionShape(shape); 
     super.setPosition(position); 
     this.power = type + 1; 
     this.body.setCollisionFlags(this.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK); 
     active=true; 
     body.userData=this; 
    } 

    public Integer getPower() { 
     return power; 
    } 

    public void resetPower() { 
     this.power = this.getType()+1; 
    } 

    public void onCollision(Integer power) { 
     this.collided=true; 
    } 

    @Override 
    public void getDetails(){ 
     Gdx.app.log("Life", power.toString()); 
     Gdx.app.log("Active", Boolean.toString(this.active)); 
     super.getDetails(); 
    } 
} 

這裏的磚類:

package com.anutrix.brickbreaker3d.gameObjects; 

import com.anutrix.brickbreaker3d.Helpers.Assets; 
import com.badlogic.gdx.Gdx; 
import com.badlogic.gdx.graphics.g3d.ModelInstance; 
import com.badlogic.gdx.math.Vector3; 
import com.badlogic.gdx.physics.bullet.collision.btBoxShape; 
import com.badlogic.gdx.physics.bullet.collision.btCollisionObject; 

/** 
* 
* @author Anutrix 
*/ 
public class Brick extends AbstractObject { 

    public Integer power; 

    public Brick(Integer id, Integer type, Vector3 position) { 
     super(id, type, position); 
     modelInstance = new ModelInstance(Assets.instance.brick.get(type)); 
     shape = new btBoxShape(new Vector3(1f, 0.5f, 1f)); 
     body = new btCollisionObject(); 
     body.setCollisionShape(shape); 
     super.setPosition(position); 
     this.power = type + 1; 
     this.body.setCollisionFlags(this.body.getCollisionFlags() | btCollisionObject.CollisionFlags.CF_CUSTOM_MATERIAL_CALLBACK); 
     active=true; 
     body.userData=this; 
    } 

    public Integer getPower() { 
     return power; 
    } 

    public void onCollision(Integer power) { 
     this.power = this.power-power; 
     if(this.power<=0){ 
      this.active=false; 
     } 
     this.collided=false;//reset 
    } 

    @Override 
    public void getDetails(){ 
     Gdx.app.log("Life", power.toString()); 
     Gdx.app.log("Active", Boolean.toString(this.active)); 
     super.getDetails(); 
    } 
} 

這裏是AbstractObject類:

package com.anutrix.brickbreaker3d.gameObjects; 

import com.anutrix.brickbreaker3d.Helpers.Assets; 
import com.badlogic.gdx.Gdx; 
import com.badlogic.gdx.graphics.g3d.ModelInstance; 
import com.badlogic.gdx.math.Vector3; 
import com.badlogic.gdx.physics.bullet.collision.btCollisionObject; 
import com.badlogic.gdx.physics.bullet.collision.btCollisionShape; 

public class AbstractObject { 

    private Integer id; 
    private Integer type; 
    private Vector3 position; 

    public ModelInstance modelInstance; 

    public btCollisionShape shape; 
    public btCollisionObject body; 
    public Integer power; 
    public boolean collided; 
    public boolean active; 

    public AbstractObject(Integer id, Integer type, Vector3 position) { 
     this.id = id; 
     this.type = type; 
     this.position = position; 
     this.collided = false; 
    } 

    public void setPosition(float x, float y, float z) { 
     this.setPosition(new Vector3(x, y, z)); 
    } 

    public Integer getId() { 
     return id; 
    } 

    public void setId(Integer id) { 
     this.id = id; 
    } 

    public Integer getType() { 
     return type; 
    } 

    public void setType(Integer type) { 
     this.type = type; 
     this.setModelInstance(new ModelInstance(Assets.instance.brick.get(type))); 
    } 

    public Vector3 getPosition() { 
     return position; 
    } 

    public void setPosition(Vector3 position) { 
     this.position = position; 
     this.modelInstance.transform.translate(position); 
     this.body.setWorldTransform(modelInstance.transform); 
    } 

    public ModelInstance getModelInstance() { 
     return modelInstance; 
    } 

    public void setModelInstance(ModelInstance modelInstance) { 
     this.modelInstance = modelInstance; 
    } 

    public btCollisionObject getObject() { 
     return body; 
    } 

    public void onCollision(Integer power){ 

    } 

    public void getDetails() { 
     Gdx.app.log("ID", id.toString()); 
     Gdx.app.log("Type", type.toString()); 
     Gdx.app.log("Position", position.toString()); 
     Gdx.app.log("Collision", Boolean.toString(collided)); 
     Gdx.app.log("---------------", "---------------"); 
    } 

    public void dispose() { 
     shape.dispose(); 
     body.dispose(); 
     Gdx.app.log(this.toString(), "dispose"); 
    } 
} 

是否有替代所有的鑄造?鑄造會降低性能嗎?

+0

請張貼'Ball'和'Brick'代碼,並解釋什麼是錯了你當前的代碼 – 2017-03-09 13:33:30

+0

,我不相信你需要第二個if塊(假設兩個對象都不同)的否則,如果能換對於一個普通別人也 – efekctive

+1

我會用一個抽象的'onCollision(INT)'和'getPower()''在你@RC取出一個連鑄圓的AbstractObject' – 2017-03-09 15:30:46

回答

4

我認爲你所碰到的是一種在主流現代語言中OOP設計中的經典問題,即缺少multiple dispatchmultimethods。有幾種典型的方法可以打擊它,而最傳統的方法是使用double dispatch和可選的visitor pattern

總體思路看起來是這樣的

public abstract class AbstractObject { 

    ... 

    public final void dispatchCollision(AbstractObject other) { 
     other.dispatchCollisionImpl(this); 
    } 

    protected abstract void dispatchCollisionImpl(AbstractObject other); 

    protected abstract void onCollisionWithBall(Ball ball); 

    protected abstract void onCollisionWithBrick(Brick ball); 
} 


public class Ball extends AbstractObject { 

    ... 

    @Override 
    protected void dispatchCollisionImpl(AbstractObject other) { 
     other.onCollisionWithBall(this); // this is where main "magic" happens 
    } 

    @Override 
    protected void onCollisionWithBall(Ball ball) { 
     throw new UnsupportedOperationException("Ball-ball collision should never happen"); 
    } 

    @Override 
    protected void onCollisionWithBrick(Brick ball) { 
     // your actual brick-ball collision logic 
    } 

} 

代碼在Brick類是相當對稱的Ball代碼。

,然後在CollisionListener你可以簡單地這樣做:

public class CollisionListener extends ContactListener { 

    @Override 
    public boolean onContactAdded(btCollisionObject ob0, int partId0, int index0, btCollisionObject ob1, int partId1, int index1) { 
     AbstractObject aO0 = (AbstractObject) ob0.userData; 
     AbstractObject aO1 = (AbstractObject) ob1.userData; 

     aO0.dispatchCollision(aO1); 
     //aO1.dispatchCollision(aO0); // if you want to do both 

     return true; 
    } 
} 

這種方法的主要缺點是,如果你有你的AbstractObject很多子類,需要在增加對他們每個人的方法每個子類。另一方面,您可以在某些基類中爲這些方法添加一些默認的通用邏輯。

如果你有很多的子類或者需要一些類似插件的支持,你可能應該使用更先進的技術來進行多方法模擬,例如有明確的全局Map<Tuple<Class,Class>, Handler>用於分派。


顯式多方法

下面是關於如何創建類似於多的方法更明確的東西一個想法:所以現在

public class ClassesPair { 
    public final Class<? extends AbstractObject> targetClass; 
    public final Class<? extends AbstractObject> objectClass; 

    public ClassesPair(Class<? extends AbstractObject> targetClass, Class<? extends AbstractObject> objectClass) { 
     this.targetClass = targetClass; 
     this.objectClass = objectClass; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) return true; 
     if (o == null || getClass() != o.getClass()) return false; 

     ClassesPair that = (ClassesPair) o; 

     if (!targetClass.equals(that.targetClass)) return false; 
     return objectClass.equals(that.objectClass); 
    } 

    @Override 
    public int hashCode() { 
     int result = targetClass.hashCode(); 
     result = 31 * result + objectClass.hashCode(); 
     return result; 
    } 
} 

public interface CollisionHandler<T extends AbstractObject, O extends AbstractObject> { 
    void handleCollision(T target, O object); 
} 

public class CollisionsDispatcher { 
    private final Map<ClassesPair, CollisionHandler> originalDispatchMap = new HashMap<>(); 
    private Map<ClassesPair, CollisionHandler> extendedDispatchMap = new HashMap<>(); 

    private CollisionHandler getHandlerOrParent(Class<? extends AbstractObject> targetClass, Class<? extends AbstractObject> objectClass) { 
     //Need to decide on the rules, for now target is more important 
     Class stopClass = AbstractObject.class.getSuperclass(); 
     for (Class tmpTarget = targetClass; tmpTarget != stopClass; tmpTarget = tmpTarget.getSuperclass()) { 
      for (Class tmpObject = objectClass; tmpObject != stopClass; tmpObject = tmpObject.getSuperclass()) { 
       CollisionHandler collisionHandler = originalDispatchMap.get(new ClassesPair(tmpTarget, tmpObject)); 
       if (collisionHandler != null) 
        return collisionHandler; 
      } 
     } 
     return null; 
    } 

    public CollisionHandler getHandler(Class<? extends AbstractObject> targetClass, Class<? extends AbstractObject> objectClass) { 
     ClassesPair key = new ClassesPair(targetClass, objectClass); 
     CollisionHandler collisionHandler = extendedDispatchMap.get(key); 
     if (collisionHandler == null) { 

      // choice #1 
      // Just fail every time nothing was found 
      //throw new UnsupportedOperationException("Collision of " + targetClass.getName() + " with " + objectClass.getName() + "' is not supported"); 

      // choice #2 go through handlers for parents. 
      // It provides ability to put some generic logic only once 
      // Need to decide on the rules, for now target is more important 
      collisionHandler = getHandlerOrParent(targetClass, objectClass); 
      if (collisionHandler != null) { 
       extendedDispatchMap.put(key, collisionHandler); // put it back for faster future usages 
      } else { 
       throw new UnsupportedOperationException("Collision of " + targetClass.getName() + " with " + objectClass.getName() + "' is not supported"); 
      } 

      // choice #3 
      // Just do nothing. Everything that has no explicit handler is not affected by collision 
      // return null; 
     } 
     return collisionHandler; // God save Java with its type erasure for generics! 
    } 

    public void handleCollision(AbstractObject target, AbstractObject object) { 
     CollisionHandler handler = getHandler(target.getClass(), object.getClass()); 
     if (handler != null) { // this check only for choice #3 
      handler.handleCollision(target, object); // God save Java with its type erasure for generics! 
     } 
    } 

    public <T extends AbstractObject, O extends AbstractObject> void registerHandler(Class<T> targetClass, Class<O> objectClass, CollisionHandler<? super T, ? super O> handler) { 
     ClassesPair key = new ClassesPair(targetClass, objectClass); 
     originalDispatchMap.put(key, handler); 
     // just clear extended cache. It is much easier than to track all possible propagated values 
     // and handle them properly. On the other hand registerHandler should be called only a few 
     // time during set up so it shouldn't be real penalty in performance 
     extendedDispatchMap = new HashMap<>(); 
    } 
} 

一個使用示例假設您想用三種磚創建一些打磚塊遊戲:

  • 一擊磚這始終是藍色
  • 兩安打磚從紅色改變顏色,粉色後先打
  • 超磚是黑色的,並且不能在所有
public abstract class AbstractBrick extends AbstractObject { 

    protected int hitCount; 

    public AbstractBrick(int hitCount) { 
     this.hitCount = hitCount; 
    } 

    public int getHitCount() { 
     return hitCount; 
    } 

    public void setHitCount(int hitCount) { 
     this.hitCount = hitCount; 
    } 

    public abstract Color getColor(); 

    @Override 
    protected void dispatchCollisionImpl(AbstractObject other) { 
     other.onCollisionWithBrick(this); 
    } 

    @Override 
    protected void onCollisionWithBall(Ball ball) { 

    } 

    @Override 
    protected void onCollisionWithBrick(AbstractBrick ball) { 

    } 

} 

// takes one hit to break 
public class SimpleBrick extends AbstractBrick { 
    public SimpleBrick() { 
     super(1); 
    } 

    @Override 
    public Color getColor() { 
     return Color.BLUE; 
    } 
} 

// takes two hits to break 
public class DoubleBrick extends AbstractBrick { 
    public DoubleBrick() { 
     super(2); 
    } 

    @Override 
    public Color getColor() { 
     if (hitCount == 2) 
      return Color.RED; 
     else 
      return Color.PINK; 
    } 
} 

// never breaks 
public class SuperBrick extends AbstractBrick { 
    public SuperBrick() { 
     super(-1); 
    } 

    @Override 
    public Color getColor() { 
     return Color.BLACK; 
    } 
} 
被銷燬

所以,現在你創建的CollisionsDispatcher特定實例與它

public class MyCollisionsDispatcher extends CollisionsDispatcher { 

    public MyCollisionsDispatcher() { 
     // Pre-register all required handlers 
     // using Java-8 syntax for "::" instead of anonymous classes 
     registerHandler(Ball.class, AbstractBrick.class, this::handleBallBrick); 
     registerHandler(AbstractBrick.class, Ball.class, this::handleUsualBrickBall); 
     registerHandler(SuperBrick.class, Ball.class, this::handleSuperBrickBall); 
    } 

    void handleBallBrick(Ball ball, AbstractBrick brick) { 
     // bounce of the ball 
     // in this case it is not important which brick we hit 
     System.out.println("Ball hit some brick"); 
    } 

    void handleUsualBrickBall(AbstractBrick brick, Ball ball) { 
     int newCount = brick.getHitCount() - 1; 
     if (newCount != 0) { 
      brick.setHitCount(newCount); 
     } else { 
      // remove brick 
     } 

     System.out.println("Usual brick was hit by a ball. newCount = " + newCount); 
    } 

    void handleSuperBrickBall(SuperBrick brick, Ball ball) { 
     // do nothing. Super brick is so super! 
     System.out.println("Super brick was hit by a ball but nothing happened"); 
    } 
} 

註冊的所有必要的處理,並可以做這樣的事情:

和輸出酷似人們所期望的:

球擊出了一些磚

常見的磚用球擊中。 newCount = 0

球擊中一些磚

通常的磚用球擊中。 newCount = 1

通常的磚用球擊中。 newCount = 0

球擊出了一些磚

超級磚被球擊中,但什麼都沒有發生

超級磚被球擊中,但沒有在你的CollisionListener你碰巧

所以只能打電話

@Override 
public boolean onContactAdded(btCollisionObject ob0, int partId0, int index0, btCollisionObject ob1, int partId1, int index1) { 
    AbstractObject aO0 = (AbstractObject) ob0.userData; 
    AbstractObject aO1 = (AbstractObject) ob1.userData; 


    dispatcher.handleCollision(aO0, aO1); 
    // dispatcher.handleCollision(aO1, aO0); // if you want to do both 

    return true; 
} 

這裏的主要缺點是其他sid es的主要優點:

  • 你可以把所有的碰撞相關的代碼放在一個地方MyCollisionsDispatcher但是「單個地方」可能會相當大。
  • 另一個好處是,用這樣的方法,你可能有一個「插件」系統,即一個人可以添加新的AbstractObject子類,而僅通過在調度員註冊正確處理觸及現有的代碼什麼。這樣做的缺點是,在任何主流語言中,我都知道你的編譯時間過長會檢查每個必要的處理程序是否實際執行,因爲它是雙重調度。

摘要(以及一些比較)

在長期管理和代碼清楚起見,在我看來是一個品味的問題更喜歡哪種解決方案,除非你有其他的限制,這使得一些條款其中不適用。每種合適的技術都比較先進,可能會讓開發人員感到不知所措。

在性能第一的規則條款是:測量它!。不過我會打破它,做我的預測,認爲雙調度快則明確的地圖,如果有很多子類(當然,因人而異)至於內存消耗我看不出有任何顯著的差異比一堆instanceof更快。

正如有人說Software Engineering Is Art Of Compromise所以最後卻是由你來做出正確的取捨。

+0

我打算儘快創建更多的子類。所以就像你說的那樣,每個子類中的每個子類的更多方法將使程序更大。除了你的「魔法」代碼一眼就看不清楚了。你還可以給出更先進的方法的一般想法嗎?它可能有幫助。 – Anutrix

+0

@Anutrix,我沒有得到關於不同命名的最後評論。雙調度的基本思路是以下幾點:1.呼叫進入非虛'AbstractObject.dispatchCollision' 2.'dispatchCollision'代表它的虛擬'dispatchCollisionImpl',並通過這個時刻,我們(運行時)知道類的原來'other'所以它調用特定於該類的方法。 3.'dispatchCollisionImpl'再次切換參數並調用另一個已知參數類型的虛擬方法。在這一點上,這兩種類型都是已知的,因此調用適當子類的正確方法。 – SergGr

+0

@Anutrix,至於更一般的想法,關鍵問題是:1.根據對象類型的不同,有多少種不同的碰撞行爲? 2.您希望每種不同行爲的代碼抵制哪些內容?假設有10種不同類型的對象。這意味着你可能有100 = 10 * 10種類型的衝突,每種衝突都有自己的邏輯。你希望代碼中的那100個邏輯是什麼? – SergGr

相關問題