为了学习Java,我正在制作一个经典的“太空飞行”2d游戏。除了玩家的目标之外,还有许多敌人/障碍物(食腐动物、猎人、彗星、小行星),每个人都有自己的类,扩展了一个GameObject类。由于可能有几个拾荒者,彗星等,这些都存储在数组。然而,由于每个物体都可以相互作用,所以有很多循环和重复的代码,例如每个外星根据彗星排列列表中的物体、小行星阵列列表等进行交互。
在我的游戏更新功能中我有:
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"
}这种方法似乎相当麻烦,而且随着更多类的创建,需要解析和遍历的数量或数组列表正在失控。有人能推荐一种更好的方法吗?我想最明显的方法是有一个包含所有NPC对象的列表,然后跟踪它们的类类型并进行相应的更新。这是一个更好的方法,还是谁能指出我的正确方向?
发布于 2017-03-18 09:20:13
是的有更好的方法。
对于游戏中的每一种类型的对象,计算出它需要展示的一组行为/特征。这些行为应该被定义为接口。然后,处理行为/特征的代码可以使用接口,而不必了解任何有关实际类的信息。
例如,如果某些物体按其当前速度移动,并可能与其他物体发生碰撞,则可能存在一个接口:
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);
}
}然后,您的world对象可以将它们的移动行为委托给该类:
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());
}
}这似乎是一个不必要的间接,但经验表明,这是值得的长期。有关更多详细信息,请参阅委托模式。
如果每个对象的运动不同,您可以有几个代表(例如,一些受重力影响,一些由人工智能控制),或者一个类可以在其move中应用多个委托(例如重力和惯性),或者一个类可以在其行为唯一的情况下实现自己的move。所有这一切都可能发生,而不需要World就知道对象的类正在调用move。
作为一般规则,尽量避免使用extends来继承超类的行为。您的结构猎人扩展全国人民代表大会扩展GameObject将是方便的,直到你意识到你也希望猎人扩展敌人或AIControlled或其他东西。艰难的经验表明,OO编码器最初看起来是明智和优雅的层次结构,但随着添加更复杂的功能,它们变得难以管理。
为了更进一步,隐藏那些对象实现World中哪些行为接口的所有细节,您可能想看看访客模式。这将允许世界对象作为一个移动者访问所有游戏对象,然后作为一个AIAgent,然后作为一个用户控制的对象等等,而不必知道他们在访问期间做了什么(或者他们做了什么)。如果应用得很好的话,它是非常强大的,但它需要一些习惯。
最后,游戏编写人员使用了一种非常常见的架构模式,称为实体组件系统。如果您只是在学习Java,我暂时会忽略这一点,但是如果您成为了一个严肃的游戏开发人员,您可能会发现它比我前面描述的体系结构有所改进。
我显然忽略了这个例子中的很多细节(比如Shape、Circle、Position、Velocity、Collision等的定义)。但这是一般的想法。这里面有很多东西,值得找一本关于面向对象设计的书或教程来深入研究。
https://stackoverflow.com/questions/42872235
复制相似问题