2017-03-18 51 views
0

为了学习Java,我制作了一个经典的“在太空中飞行”2D游戏。除了玩家对象之外,还存在众多的敌人/障碍(拾荒者,猎人,彗星和小行星),每个敌人都拥有自己的类来扩展GameObject类。由于可以有几个清道夫,彗星等这些存储在arraylists。但是,由于每个对象都可以相互交互,所以存在很多循环和重复代码,例如每个外来人员根据彗星阵列列表中的对象,小行星阵列列表等进行交互。游戏设计:组织对象阵列列表

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

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类(扩展了游戏对象类)

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

这种方法似乎相当麻烦,并且随着更多类的创建,需要解析和循环的数字或阵列列表已经失控。 任何人都可以推荐一个更好的方式来做到这一点? 我想明显的方​​法是有一个列表包含所有NPC对象,然后跟踪他们的类类型并相应地更新。 这是一个更好的方式,或者任何人都可以指向正确的方向吗?

+0

传递'World'实例不是更容易吗?你可以基本上做'for(updatableThing:updatableThings){updatableThing.update(world); }' – plalx

+0

也许我不完全理解(仍在学习),但是World实例仍然会包含一个包含并更新所有对象的列表,或者我误解了一些东西? – user2913053

回答

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来继承超类的行为。你的猎人扩展NPC扩展GameObject的结构将会很方便,直到你意识到你还希望猎人扩展敌人或AIControlled或其他东西。经验表明,OO编码人员认为这些类型的层次结构最初看起来明智而优雅,但随着您添加更复杂的功能变得难以管理。

若要更进一步并隐藏所有对象实现哪些行为接口从World的所有详细信息,您可能需要查看Visitor Pattern。这将允许世界对象作为移动者访问所有游戏对象,然后作为AIAgent,然后作为用户控制对象等,而不必知道他们在访问期间做了什么(或者他们什么都可以做)。如果使用得当,它功能非常强大,但需要一些习惯。

最后,有一个非常常见的架构模式,被游戏编写者称为Entity Component System。如果你刚刚学习Java,我暂时忽略这一点,但如果你成为一个认真的游戏开发人员,你可能会发现这是对上述架构的改进。

我已经明显离开了的例子很多细节(如ShapeCirclePositionVelocityCollision等的定义),但这是一般的想法。这还有很多,值得一看关于面向对象设计的书籍或教程,以便更深入地研究。

+0

非常详尽的答案,谢谢。我可以看到如何避免在类似的类中重复代码。但是,如果物体运动依赖于彼此,会发生什么。岩石可以相互碰撞(从而改变方向),猎人可以躲避岩石或追求玩家。这将要求移动功能知道所有对象并相应地计算移动。移动函数是否知道哪个对象调用了它,然后循环移动列表,正如我在原始文章中提到的那样,基本上我所有的当前列表都加入了一个列表中? – user2913053

+0

我会在我的答案中提供碰撞的通用模型,让您知道如何处理这个问题。类似的模型应该适用于其他示例。 – sprinter

+0

好吧,我的答案现在变得很可笑了。如果它有帮助,那么请接受它,然后问另一个问题,如果有什么具体的你需要帮助。 – sprinter