游戏设计:组织对象arraylists

时间:2017-03-18 08:42:32

标签: java arraylist

为了学习Java,我正在创造一个经典的“空间飞行”#34;第二场比赛。除了玩家对象之外,还存在许多敌人/障碍物(清道夫,猎人,彗星,小行星),每个都有自己的类,扩展了GameObject类。由于可能有几种清道夫,彗星等,它们存储在arraylists中。然而,由于每个对象可以相互交互,因此存在许多循环和重复代码,例如每个外星人根据彗星arraylist,小行星数组列表中的对象等进行交互。

在我的游戏更新功能中,我有:

public void update() {

    ArrayList<Rock> rockList = rock.getRockList();
    ArrayList<Scavenger> scavengerList = scavenger.getScavengerList();
    ArrayList<Hunter> hunterList = hunter.getHunterList();
    ....
    npc.update(player, scavengerList, hunterList, rockList);
    ...

}

并在我的NPC类中(扩展了GameObject类)

public void update(Player player, ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) {

    for(int i = 0; i < scavengerList.size(); i++) {         
        scavengerList.get(i).update(player,scavengerList, ,hunterList rockList);
    }
    for(int i = 0; i < hunterList.size(); i++) {            
        hunterList.get(i).update(player,scavengerList, hunterList, rockList);
    }
...
}

最后我在我的清道夫类,我的猎人班等中有一个更新功能,如

public class Hunter extends NPC{
...
public void update(Player player,ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) {
"update all hunter objects according to the player, scavengers, rocks etc"
}

这种方法看起来相当麻烦,而且随着更多类的创建,需要解析和循环的数字或者arraylists失控。 任何人都可以推荐更好的方法吗? 我想显而易见的方法是让一个列表包含所有NPC对象,然后跟踪它们的类类型并相应地更新。 这是一种更好的方式,还是有人能指出我正确的方向?

1 个答案:

答案 0 :(得分:2)

是的,有更好的方法。

对于游戏中的每种类型的对象,计算出它需要展示的一组行为/特征。应将这些行为定义为接口。然后处理行为/特征的代码可以使用接口,而不必知道任何关于实际类的内容。

例如,如果某些物体根据当前速度每次转动并且可能与其他物体发生碰撞,则可能存在界面:

public interface Moving {
    void move();
    boolean hasCollided(Shape shape);
    void handleCollision(Collision collision);
}

任何移动的类都将实现此接口。然后World对象可以有List<Moving> movingObjects,然后使用:

movingObjects.forEach(Moving::move);

使用update方法。

要在移动后处理碰撞,您可能会遇到以下情况:

List<Collision> collisions = getAllCollisions(movingObjects);
for (Collision collision: collisions) {
    for (Moving element: collision.getCollidingObjects) {
        element.handleCollision(collision);
    }
}

如果实现接口的几个类使用类似的机制来移动自己,那么你应该将该逻辑移动到一个单独的类中:

class Inertia implements Moving {
    private Velocity velocity;

    @Override
    public void move(Shape shape) {
        velocity.applyTo(shape);
    }

    @Override
    public void applyForceFrom(Position position) {
        velocity.accelerateAwayFrom(position);
    }
}

然后,您的世界对象可以将其移动行为委托给此类:

class Asteroid implements Moving {
    private final Inertia inertia;
    private Shape shape = new Circle(radius);

    @Override
    public void move() {
        inertia.move(shape);
    }

    @Override
    public boolean hasCollided(Shape other) {
        return this.shape.intersects(other);
    }

    @Override
    public void handleCollision(Collision collision) {
        intertia.applyForceFrom(collision.getCentreOfMass());
    }
}

这似乎是一种不必要的间接,但经验表明,从长远来看这是值得的。有关详细信息,请参阅Delegation Pattern

如果每个对象的移动不同(例如,一些受重力影响,一些受AI控制等),或者一个类可以在其move中应用多个代表(例如重力和惯性),则可以有多个代表或者如果一个类的行为是唯一的,那么它可以实现自己的move。所有这一切都可以在World不需要知道任何关于对象的类正在调用move的情况下发生。

作为一般规则,尽量避免使用extends来继承超类的行为。你的Hunter扩展NPC扩展GameObject的结构将很方便,直到你意识到你也希望Hunter扩展敌人或AIControlled或其他东西。硬性体验已经向OO编程人员表明,这些类型的层次结构最初看起来既合理又优雅,但随着您添加更复杂的功能而变得无法管理。

更进一步,隐藏哪些对象实现World哪些行为接口的所有细节,您可能希望查看Visitor Pattern。这将允许世界对象作为移动者访问所有游戏对象,然后作为AIAgent访问,然后作为用户控制的对象等等,而无需知道他们在访问期间做了什么(或者如果他们做任何事情)。如果应用得很好,它会非常强大,但需要一点时间习惯。

最后,游戏编写者使用了一种非常常见的架构模式Entity Component System。如果您只是学习Java,我暂时忽略了这一点,但如果您成为一名认真的游戏开发人员,您可能会发现它比我上面描述的架构有所改进。

我显然在示例中遗漏了很多细节(例如ShapeCirclePositionVelocity,{{1}的定义等等)但这是一般的想法。还有更多内容,值得寻找面向对象设计的书籍或教程,以便更深入地了解。