如何仅将方法应用于舞台中特定子类的演员

3
我的游戏有一个名为敌人(Enemy)的类,它继承了演员(Actor)。此外,我还有一个敌人影响器(EnemyAffector)类,其中有一些影响敌人(Enemies)的方法,大致如下:
class EnemyAffector {
    public void affect(Enemy enemy) { ... }
}

在我的Stage中的act方法中,我希望遍历所有的Enemies并对它们应用EnemyAffector。显而易见的方法是通过强制类型转换:
@Override
public void act() {
    super.act();
    for (Actor actor: getActors()) {
        if (actor instanceof Enemy) {
            enemyAffector.affect((Enemy)actor);
        }
    }
...
}

然而,传说中使用instanceof应该避免。因此,我考虑保留一个额外的Array,将Enemies添加到其中,当它们被添加到Stage时,即通过重载我的Stage添加另一个addActor方法:

public void addActor(EnemyProjectile pEnemyProjectile) { // add to Enemy Array }

所以现在我的act方法看起来像这样:

@Override
public void act() {
    super.act();
    for (Enemy enemy: enemyArray) {
        enemyAffector.affect(enemy);
    }
...
}

太好了!没有 instanceof 了!

然而,这种解决方案的问题在于我总是需要在 Enemy Array 和标准的 Actor Array 之间进行同步,因为在 Enemy Array 中的 Enemies 可能已经从 Stage 中移除。

我的问题是,有没有不同、更简洁的方法可以在这里采取?


你什么时候移除演员?你不能在从舞台演员列表中移除演员的同时,将其从敌人列表中移除吗? - DoubleDouble
@vojta 有时候这可能是最好的。如果 OP 最终不想从列表中删除 instanceof 敌人(具有 2 条生命或电影式“死亡对话”原因?),他应该知道自己应该创建一个新的解决方案 - 或者如果他最终希望为其他对象执行不同的操作(*单独的 instanceof 用于“Player”、“AlliedNPC”等)- 在开始时这样做 可能 会导致“instanceof”的不受欢迎(混乱的代码),但如果经过深思熟虑,它可能是无害的。 - DoubleDouble
我认为在这种情况下,instanceof 是最简单且最好的解决方案。 - Tenfour04
我不同意的唯一原因是因为我认为他将来很可能需要再次区分不同的“Actors”,并且他会被诱惑再次创建一个列表,并使用另一个“instanceof” - 但如果/直到那个时候到来,他可以等待,所以我也不反对。只要不要走上“黑暗面”就好了。 - DoubleDouble
关于你的第一条评论:Actors会从Stage中移除自己:actor.remove()。因此,在Stage中,我需要手动检查哪些Actors仍然在Stage中。 - asherbret
显示剩余2条评论
3个回答

1
"

在我看来,最简单且代码量最少的方法是使用UserObject Actor字段,例如:

"
    Actor actor = new Actor(); //or its children ofc
    actor.setUserObject("enemy");

    //...

    if( actor.getUserObject().equals("enemy") )
    {
        //yup this is enemy
    }

当然,由于面向对象编程的原因,这并不是最好的解决方案,在某些情况下可能不太灵活。但说实话,在像这样的情况下,处理额外的数组等只会让事情变得更加复杂。
请注意,UserObject是对象类型,因此如果您想将某些Java无法自行转换的东西推送到其中,您将不得不自己进行转换。

然而,如果您想要达到超级好的效果,这里有另一个更优雅的解决方案:

  • You are overriding the Actor class with your own MyActor class with some customAction() method which do nothing. You will be inheriting from this class not from Actor now
  • In every child class the method do nothing but in your Enemy class you are implementing the funcionality
  • In a loop you are casting to (MyActor) and calling customAction method

    for (Actor actor: getActors()) {
        ((MyActor)actor).customAction();
    }
    

    The customAction method has a logic from EnemyEffector

如果您继承了继承自Actor的类(如Label类),这将会产生一些问题;如果EnemyEffector逻辑正在使用Actor实例不应知道的数据,这也将是一个问题,因为它们是全局的,不应该是冗余的(当然,您也可以将信息作为customAction方法的参数传递)。

扩展ActorBase类不是一个选项,因为你提到的原因:它会阻止我扩展Actor的内置子类。第一种选择基本上是具有不同语义的instanceof,正如你也提到的那样。 - asherbret

1
您可以使用添加到演员的UserObject
public enum ActorType
{
    PLAYER, ENEMY, ETC;
}

//...

Enemy enemy = new Enemy();

// Or set this in the constructor
enemy.setUserObject(ActorType.ENEMY);

for (Actor actor : actors)
{
    if (actor.getUserObject() == ActorType.ENEMY)
    {
        // Do stuff...
    }
}

或者将您的两个数组放入一个额外的类中,并添加用于添加/删除的方法:

public class ActorManager
{
    private Array<Actor>    actors  = new Array<>();
    private Array<Enemy>    enemies = new Array<>();

    public void add(Actor actor)
    {
        actors.add(actor);
    }

    public void add(Enemy enemy)
    {
        actors.add(enemy);
        enemies.add(enemy);
    }

    public void remove(Actor actor)
    {
        actors.removeValue(actor, true);
    }

    public void remove(Enemy enemy)
    {
        actors.removeValue(enemy, true);
        enemies.removeValue(enemy, true);
    }

    // To make sure our backing arrays are not modified:

    public Array<Actor> getAll()
    {
        return new Array<>(actors);
    }

    public Array<Actor> getEnemies()
    {
        return new Array<>(enemies);
    }
}

第一部分并没有真正解决问题,它只是将检查“instanceof”与检查“UserObject”值进行了切换。第二部分很有趣,因为它将演员管理的逻辑提取到了一个独立的类中,但我不清楚这个类在“Stage”和/或“Actors”上下文中如何使用。 - asherbret
如果你在第一部分检查了UserObject,那么你可以执行enemyAffector.affect((Enemy) actor);,因为你只将它分配给敌人,这不是你想要的吗? - Pinkie Swirl
对于第二部分,您可以添加一个舞台(其中包含所有演员),而不是字段“actors”,并为添加/删除包装舞台方法,甚至可以给它一个演员工厂,以便只有这个类可以创建新的演员,然后将其添加到您的舞台。您还可以添加更多内容,这只是一个小例子。例如,我有一个实体的2D数组,这样我就可以从单击位置快速检查/获取实体(无需循环等)。由于所有这些数组/集合都需要同步,因此涉及实体的所有操作都由此类处理和分发。 - Pinkie Swirl

1
我在使用LibGDXActorStage类时,遇到了类似的情况。我创建了自己的子类来扩展Actor,我将其用作所有演员的“基础演员”对象。
这样,您可以设置在某些情况下所有演员都会调用的方法。
class MyStage extends Stage
{
    public void addActor(MyActor pEnemyProjectile) { // call onAdd }

    //override other methods as necessary,
      //possibly getActors() to return Actors as MyActors, etc
}

class MyActor extends Actor
{

    public void update(double delta)
    {
        //I find it easiest to have "removal" code happen in the Actor's update method, then I can call onRemove()
        //This could happen by finding death conditions or just checking a "isDead" boolean.
    }

    public void onAdd()
    {
        //can set up a default behavior, or just override on other subclasses
    }

    public void onRemove()
    {
        //call for every Actor that gets removed - can extend and remove Enemies from an Enemy list if desired
    }

    //maybe it would be better to check if the Object can be Affected here?
      //rather than keeping and maintaining a separate list
    public boolean isAffected()
    {
        return false; //return true by Enemies
    }
}

class Enemy extends MyActor
{
    //implement onRemove, onAdd, etc..
}

如果更好的话,MyActor 也可以被创建为一个 interface - 然后你就可以轻松地创建继承自 ActorLabel 或其他 LibGdx 类的子类。

我个人选择将其作为子类,只是因为我可以创建更容易输入和调用的帮助方法 - 例如我的 "movement" 和 "rotation" 代码,它们不使用 LibGdx 提供的 Action 类。


不错的解决方案! :) 看起来和我的一模一样 :) - m.antkowicz
@m.antkowicz,我没有注意到你回答的第二部分!如果我有冒犯之处,请见谅。 - DoubleDouble
谢谢您的回答。请查看我对@m.antkowicz回答的评论。 - asherbret

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接