【问题标题】:Java Inheritance/OOP - call child type specific method with only a handle on parentJava Inheritance/OOP - 调用子类型特定的方法,只有父句柄
【发布时间】:2011-11-20 07:26:53
【问题描述】:

我正在用 Java 为 Android 游戏编写游戏引擎,我的引擎处理不同形状的碰撞检测。每个形状都是它自己的类(Square、Circle 等),并且派生自一个通用的抽象父 Collidable。我有一个物理管理器类,它基本上检查游戏中的任何现有对象是否与另一个对象发生碰撞,然后在检测到碰撞时执行适当的操作。碰撞检查在每个物理形状子类中实现,如下面的代码所示。

public abstract class Collidable
{
}

public class Square extends Collidable
{
    public boolean Collides(Square) {...}
    public boolean Collides(Circle) {...}
    public boolean Collides(Triangle) {...}
}

public class Circle extends Collidable
{
    public boolean Collides(Square) {...}
    public boolean Collides(Circle) {...}
    public boolean Collides(Triangle) {...}
}

public class Triangle extends Collidable
{
    public boolean Collides(Square) {...}
    public boolean Collides(Circle) {...}
    public boolean Collides(Triangle) {...}
}

public class PhysicsMgr
{
    public boolean Collides(Collidable p1, Collidable p2)
    {
        return p1.Collides(p2);
        // This obviously won't work because there is no Collides
        // method in Collidable. I want it to somehow call the child's
        // method and pass in p2 as its child type rather than as
        // a parent. Or somehow do this:
        return (p1.child()).Collides(p2.child());
            // I know that obviously nothing like this exists.
    }
}

我知道“instanceof”并且真的不想检查 p1 和 p2 的子类型与我拥有的每个碰撞形状。一定会有更好的办法。我正在寻找解决我当前问题的方法,或者最好重新设计我当前的碰撞检测系统以完全避免这个问题。

谢谢!

【问题讨论】:

标签: java oop inheritance collision-detection game-engine


【解决方案1】:

你应该阅读visitor pattern

【讨论】:

  • 访问者模式绝对是要走的路。在 Collidable 中声明抽象方法Collides(Square) 等。还要声明一个抽象方法Collides(Collidable),在每个具体子类中实现为collidable.collides(this)。由于这段代码出现在每个具体的子类中,编译器可以确定要调用哪个版本的collides
  • @Ted:正是我想要的。完美的!所以(对不起,这是我懒惰并且没有阅读访问者模式来回答这个问题)如果我在父类中声明抽象方法然后以这种方式在具体的子类中实现它们,那是使用访问者模式吗?跨度>
  • 抱歉把你丢在外面了,yurib,我知道你基本上说了同样的话。谢谢你们!
【解决方案2】:

首先,我不会让 Collidable 成为一个抽象类。尽管可能有很好的论据;在我看来,这是一种“是”一种情况,很多物体都可以碰撞。

话虽如此,我的建议如下:

// Assuming you're working in 2 dimensions
public class Coordinates {

  public Coordinates(float x, float y) {
     // etc etc etc
  }
}

public interface ICollidable {

  // Using unusually long name to illustrate point,
  // but feel free to rename.
  public int getMaxDistanceFromCenterOfMass(Coordinates unitVector);
  public Coordinates getCenterOfMass();
}

然后,对于 Square、Triangle 和 Circle,我将实现接口。

public class Square implements ICollidable {

  @Override
  public int getMaxDistanceFromCenterOfMass(Coordinates unitVector) {
    // Must declare and initialize
    return this.lengthOfSide;
  }

  @Override
  public Coordinates getCenterOfMass() {
    return this.centerOfMass;
  }
}

public class Circle implements ICollidable {

  @Override
  public int getMaxDistanceFromCenterOfMass(Coordinates unitVector) {
    // Must declare and initialize
    return this.radius;
  }

  @Override
  public Coordinates getCenterOfMass() {
    return this.centerOfMass;
  }
}

public class Triangle implements ICollidable {

  @Override
  public int getMaxDistanceFromCenterOfMass(Coordinates unitVector) {
    // Must declare and initialize
    return this.lengthOfSide;
  }

  @Override
  public Coordinates getCenterOfMass() {
    return this.centerOfMass;
  }
}

然后,在你的 PhysicsMgr...

public class PhysicsMgr {

  public boolean Collides(ICollidable p1, ICollidable p2) {
    Coordinates cm1 = p1.getCenterOfMass();
    Coordinates cm2 = p2.getCenterOfMass();

    int length = Math.sqrt(Math.pow(cm1.x - cm2.x, 2) + Math.pow(cm1.y - cm2.y, 2))

    // It is a misnomer to use coordinates as a unit vector, but if I defined a 
    // UnitVector class, it would be exactly the same with the exception of
    // the class name for this situation.
    Coordinates unitVector = new Coordinates((cm1.x - cm2.x)/length, (cm1.y - cm2.y)/length);

    int collisionDistance1 = p1.getMaxDistanceFromCenterOfMass(unitVector);
    int collisionDistance2 = p2.getMaxDistanceFromCenterOfMass(unitVector);

    return (length - collisionDistance1 - collisionDistance2) <= 0;
  }
}

这里的一个主要警告是,从字面上使用距质心的 maxDistance 只会为您提供正方形和三角形的近似值。确切地说,您必须声明一些方向,theta,并计算从物体的质心到沿单位向量的边缘的距离(这很棘手,但很准确)。

这方面的另一个好处是,随着引擎变得更加复杂,它允许您轻松添加其他可碰撞对象。这也使得所有对象都不必相互了解。

我做了 3 年的物理助教,这实际上是我第一次接触编程的方式。如果您对额外的工作感兴趣,这里是我们使用的书的参考:http://matterandinteractions.org/ 这对程序员来说非常棒,因为它通过使用 Python 中的编码示例(特别是 vpython http://vpython.org/)来教授物理。所以这对于物理编程来说是一个很好的参考。

【讨论】:

  • 将所有内容近似为圆圈当然“修复”了计算中对单独案例的需求。而且我认为,即使在混合中添加方向以得出“棘手”的公式也不会对所有形状都准确,除非您再次引入单独的显式案例。
  • 引入显式案例不是这里的方法。否则,支持任何新对象将需要将新代码添加到物理引擎已知宇宙中的每个其他对象。我只是把实现称为棘手,因为大多数人不习惯用单位向量和 theta 方向做物理;不是因为它的神奇或定义松散。给定物体的单位矢量和方向(以及与半圆等复杂物体的代表性点的距离 cm),您可以确定两个物体碰撞的确切点。
  • 您好,首先感谢您的详细帖子!不过,我的问题应该更清楚一点。我的对象不是圆形、正方形和三角形,这只是它们的形状。所以我有一个实体类,它是实际对象“玩家、坏人、障碍物等”,它有一个成员“受保护的 Collidable mPhysicsShape”,它简单地描述了该形状的物理特性。因为我在做 2D 的东西,而且我的对象是一个基于物理的游戏,所以几乎所有东西都在物理上表示为某种原始形状。不过,您的解决方案会导致一切都像一个圆圈一样。
【解决方案3】:
public boolean Collides(Square) {...}
public boolean Collides(Circle) {...}
public boolean Collides(Triangle) {...}

您将需要对各种形状组合进行单独的实现(我认为,因为没有通用算法)。因此,在某一时刻,需要致电instanceof。恐怕有一个抽象方法或接口方法public boolean Collides(Collidable) 在这里没有帮助,而且你现在拥有的东西也无法得到显着改进。这是 OOP 局限性的教科书案例,因为这些碰撞检测方法不能巧妙地附加到任何形状类,它们介于两者之间,就像你的物理管理器一样。

【讨论】:

  • 您实际上可以通过使用@Ted Hopp 描述的“双重调度”方法来避免显式的instanceof 调用,但它涉及大量重复或样板代码。 OTOH 将为您提供编译器的一种完整性检查。无论哪种方式,它都不漂亮。
  • 您可以将需要非平凡实现的交叉形状方法的数量减少一半:一旦您实现了Square.Collides(Circle),那么您就可以定义Circle.Collides(Square square) { return square.Collides(this); }
  • 是的,这就是我所说的样板代码(另一个例子是 Collides(Collidable) 的重复实现,它只是反弹回来以促进双重调度)。还必须注意避免递归循环。但我同意你提出的方法是完全有效的。我并不是说在物理管理器中聚合所有内容会更好。这两种方法都有很多不足之处。但我不知道有更令人满意的第三种方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-14
相关资源
最近更新 更多