如何将存储为 java 的对象的对象变量传递给也存储为 java 的对象的另一个对象的函数?
How to pass an object's variable stored as java's Object to another object's Function which is also stored as java's Object?
我正在尝试使用 Bullet 在 Libgdx 中制作碰撞检测器。在这里,我想将一个碰撞对象的 power
变量作为参数传递给另一个对象的 onCollision()
函数。这里 Ball
和 Brick
扩展 AbstractObject
。 power
和 onCollision()
在 AbstractObject
中声明,但在 Brick
和 Ball
中初始化。我在每个 class.What 中设置了 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
Class:
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();
}
}
这是砖class:
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 Class:
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");
}
}
是否有替代所有这些转换的方法?转换会降低性能吗?
我认为您遇到的是主流现代语言中 OOP 设计中的一个 class 问题,即缺少 multiple dispatch or multimethods. There are a few typical ways to fight it and the most traditional one uses double dispatch and optionally a 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
class 中的代码与 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
有很多子class,则需要在每个子class 中为每个子添加方法。另一方面,您可以在某些基础 classes.
中为此类方法添加一些默认的通用逻辑
如果你有很多子classes或需要一些类似插件的支持,你可能应该使用更高级的多方法模拟技术,例如具有显式全局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 种积木创建一些类似打砖块的游戏:
- 一击砖,永远是蓝色的
- 两次点击砖块,第一次点击后颜色从红色变为粉红色
- 黑色的超级砖块,根本无法摧毁
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);
}
并且输出与预期的完全一样:
Ball hit some brick
Usual brick was hit by a ball. newCount = 0
Ball hit some brick
Usual brick was hit by a ball. newCount = 1
Usual brick was hit by a ball. newCount = 0
Ball hit some brick
Super brick was hit by a ball but nothing happened
Super brick was hit by a ball but nothing happened
所以在你的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
subclass 而无需触及现有代码中的任何内容,只需注册适当的调度程序中的处理程序。这样做的缺点是,在我知道的任何主流语言中,您都会丢失编译时检查,以确保每个必要的处理程序都已实际实现,因为它是双分派的。
总结(以及一些比较)
就长期管理和代码清晰度而言,我认为选择哪种解决方案只是个人喜好问题,除非您有其他限制使其中一些不适用。每种适用的技术都相对先进,可能会使不了解它的开发人员陷入困境。
就性能而言,第一个规则是:衡量它!。我仍然会打破它并做我的预测,如果有很多 subclasses(仍然,YMMV),双重调度比显式 Map 更快,显式 Map 比一堆 instanceof
更快。我没有看到任何显着差异。
正如有人所说 Software Engineering Is Art Of Compromise 所以最终由你来做出正确的权衡。
我正在尝试使用 Bullet 在 Libgdx 中制作碰撞检测器。在这里,我想将一个碰撞对象的 power
变量作为参数传递给另一个对象的 onCollision()
函数。这里 Ball
和 Brick
扩展 AbstractObject
。 power
和 onCollision()
在 AbstractObject
中声明,但在 Brick
和 Ball
中初始化。我在每个 class.What 中设置了 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
Class:
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();
}
}
这是砖class:
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 Class:
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");
}
}
是否有替代所有这些转换的方法?转换会降低性能吗?
我认为您遇到的是主流现代语言中 OOP 设计中的一个 class 问题,即缺少 multiple dispatch or multimethods. There are a few typical ways to fight it and the most traditional one uses double dispatch and optionally a 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
class 中的代码与 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
有很多子class,则需要在每个子class 中为每个子添加方法。另一方面,您可以在某些基础 classes.
如果你有很多子classes或需要一些类似插件的支持,你可能应该使用更高级的多方法模拟技术,例如具有显式全局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 种积木创建一些类似打砖块的游戏:
- 一击砖,永远是蓝色的
- 两次点击砖块,第一次点击后颜色从红色变为粉红色
- 黑色的超级砖块,根本无法摧毁
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);
}
并且输出与预期的完全一样:
Ball hit some brick
Usual brick was hit by a ball. newCount = 0
Ball hit some brick
Usual brick was hit by a ball. newCount = 1
Usual brick was hit by a ball. newCount = 0
Ball hit some brick
Super brick was hit by a ball but nothing happened
Super brick was hit by a ball but nothing happened
所以在你的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
subclass 而无需触及现有代码中的任何内容,只需注册适当的调度程序中的处理程序。这样做的缺点是,在我知道的任何主流语言中,您都会丢失编译时检查,以确保每个必要的处理程序都已实际实现,因为它是双分派的。
总结(以及一些比较)
就长期管理和代码清晰度而言,我认为选择哪种解决方案只是个人喜好问题,除非您有其他限制使其中一些不适用。每种适用的技术都相对先进,可能会使不了解它的开发人员陷入困境。
就性能而言,第一个规则是:衡量它!。我仍然会打破它并做我的预测,如果有很多 subclasses(仍然,YMMV),双重调度比显式 Map 更快,显式 Map 比一堆 instanceof
更快。我没有看到任何显着差异。
正如有人所说 Software Engineering Is Art Of Compromise 所以最终由你来做出正确的权衡。