如何将存储为java的Object的对象变量传递给另一个对象的Function,该函数也存储为java的Object?

时间:2017-03-09 13:30:26

标签: java object libgdx bulletphysics

我正在尝试使用Bullet在Libgdx中制作碰撞检测器。在这里,我希望将一个碰撞对象的power变量作为参数传递给另一个对象的onCollision()函数。此处BallBrick扩展AbstractObjectpoweronCollision()也在AbstractObject中声明,但在BrickBall中初始化。我在每个班级都设置了btCollisionObject.userData=this。最有效的方法是什么? 这是我目前的contactListener:

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();
    }
}

这是Brick类:

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");
    }
}

是否有替代所有的铸造?施法会降低表现吗?

1 个答案:

答案 0 :(得分: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<>();
    }
}

所以现在一个用法示例假设你想用3种砖块创建一些类似Arkanoid的游戏:

  • 总是蓝色的一击砖
  • 在第一次击中后将颜色从红色变为粉红色的双击砖
  • 黑色的超级砖,根本无法销毁
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");
    }
}

并且你可以这样做:

public void test() {
    AbstractObject simpleBrick = new SimpleBrick();
    AbstractObject doubleBrick = new DoubleBrick();
    AbstractObject superBrick = new SuperBrick();
    AbstractObject ball = new Ball();

    CollisionsDispatcher dispatcher = new MyCollisionsDispatcher();

    dispatcher.handleCollision(ball, simpleBrick);
    dispatcher.handleCollision(simpleBrick, ball);

    dispatcher.handleCollision(ball, doubleBrick);
    dispatcher.handleCollision(doubleBrick, ball);
    dispatcher.handleCollision(doubleBrick, ball);

    dispatcher.handleCollision(ball, superBrick);
    dispatcher.handleCollision(superBrick, ball);
    dispatcher.handleCollision(superBrick, ball);
}

并且输出完全符合预期:

  

球撞了一块砖

     

通常的砖被一个球击中。 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;
}

这里的主要缺点是主要优点的另一面:

  • 您可以将所有与碰撞相关的代码放在一个地方MyCollisionsDispatcher,但“单个地方”可能会变得非常大。
  • 另一个好处是,使用这种方法你可能有一个“插件”系统,即有人可以通过在调度程序中注册适当的处理程序来添加新的AbstractObject子类而不触及现有代码中的任何内容。这样做的缺点是,在任何主流语言中,我都知道你放弃了编译时间检查,确实实现了所有必需的处理程序,因为它是双重调度。

摘要(以及一些比较)

就长期管理和代码清晰度而言,我认为这是一种品味的问题,除非您有其他限制使其中某些不适用,否则需要采用哪种解决方案。每种合适的技术都是相对先进的,可能会让开发人员发现如何不知道它。

就性能而言,第一条规则是:测量它!。仍然我会打破它并做我的预测,双重调度比显式Map快,如果有很多子类(仍然,YMMV)比一堆instanceof更快至于内存消耗我没有看到任何显着的差异。

正如有人说Software Engineering Is Art Of Compromise所以最后由你做出正确的权衡取舍。