1

就上下文而言,我正在尝试制作一款类似于 Pokemon 的游戏。你获得,训练和战斗怪物。

每种怪物都是从抽象基类继承的类(因此它们可以具有独特的行为),并且希望在整个游戏中会有大量不同的物种。前任:

abstract class Monster {
  int hp;
  void attack();
  //Etc.
}

public class FireBreathingDragon extends Monster {
  static String species_name = "Fire Breathing Dragon";
  //Blah
}

所以玩家在探索的时候,会随机遇到一个区域内的怪物。然后游戏需要从生活在该区域的物种列表中随机创建一个怪物。现在,为了使这段代码在区域之间可重用(并使得在代码中的其他位置动态创建怪物变得容易),我不想将可能性硬编码到该区域中。相反,我想我想要一些类似于工厂的东西,可以根据需要创造给定物种的怪物,比如:

public class MonsterFactory {
  Monster createMonster(
    String species_name,
    //Possibly other paramters
  );
}

createMonster当您(可能)拥有数十或数百个不同的 Monster 类时,问题就会以“不错”或“优雅”的方式实现。当然,您可以使用非常长的if-else if-elseorswitch语句,但是编写和扩展起来很糟糕。有没有很好的方法来做到这一点?如果在增加更多怪物时相对容易扩展也很好。

还是我应该使用一些完全不同的设计?

免责声明:我的 java 有点生疏,语法可能不完美,对此感到抱歉。

4

6 回答 6

4

您可以MonsterList.

List<Class<? extends Monster>> monsterTypes = new LinkedList<>();
monsterTypes.add(FireBreathingDragon.class);
// more

这不必是硬编码的。您可以将其外部化为某种 XML、Json 或其他文件格式。

然后,工厂实例或类可以从列表中随机索引处选择怪物类型。然后,您可以使用反射来实例化该类型。

于 2013-08-13T18:16:09.220 回答
2

最简单的解决方案是拥有一个数据驱动的怪物类。这意味着你只有一个职业(或少数),而这个职业可以用于各种各样具有不同属性和能力的怪物。

您可以拥有一个 CSV 文件,其中包含每个物种以及该物种的所有属性和能力。这样,您可以通过在电子表格中添加一行来添加物种。

于 2013-08-13T18:16:16.553 回答
1

此解决方案使用没有任何形式反射的类工厂。为什么这在问题的上下文中很重要(“最优雅的方式”)?来自与另一位贡献者的一次非常有趣的交流:我引用 Sun/Oracle 的反射 API 教程“反射很强大,但不应该乱用。如果可以在不使用反射的情况下执行操作,那么最好避免使用它。” 为了证明这一点,Sun/Oracle 的作者诉诸 Java 内部的极端技术原因。我同意他们的观点,但我的主要原因是长期的代码维护和工具。反射的主要替代方法是什么?基于注释的自动代码生成。我不能在这么短的空间里做这样的事情,但我可以产生或多或少应该是结果代码:

public interface Factory<T> {
    T make();
}
public static class BasicMonster {
}
public static class Monster1 extends BasicMonster {
    public static final Factory<Monster1> getFactory() {
        return new Factory<Monster1>() {
            public Monster1 make() { return new Monster1() ;  }
        };
    }
}
public static class Monster2 extends BasicMonster {
    public static final Factory<Monster2> getFactory() {
        return new Factory<Monster2>() {
            public Monster2 make() { return new Monster2() ;  }
        };
    }
}
List<Factory<? extends BasicMonster>> monsterFactories= new ArrayList<Factory<? extends BasicMonster>>();
{
    monsterFactories.add(Monster1.getFactory());
    monsterFactories.add(Monster2.getFactory());
}
...
BasicMonster newMonster= monsterFactories.get(aRandomOne).make() ;

static class用于指示非内部类的表单。

即使 list monsterFactories 是通过反射初始化的,代码中工厂对象的存在也允许比反射构造函数调用更高级别的静态分析。

于 2013-08-19T04:07:02.000 回答
0

你应该看看笛卡尔积算法。它将生成每种产品组合,然后您可以随机选择一种。

本质上,该算法将采用属性数组并创建不同属性的唯一组合并将它们添加到数组中。然后,您可以在创建敌人时从数组中随机选择一个键。这样每个敌人都有随机机会拥有任意数量的属性。

于 2013-08-13T18:36:35.307 回答
0

有一个提供怪物的接口或基类。

我想我会包含这个 wiki-bit,“工厂方法模式是一种面向对象的创建设计模式,用于实现工厂的概念并处理创建对象(产品)的问题,而无需指定确切的对象类被创造出来。”

这使您可以专门使用超类方法或接口,而无需知道接口的特定子类型。这很重要,因为您不能调用new base_monster();

abstract class base_monster  {
  abstract base_monster factory();
}

/// make sure every monster has a name...
//
abstract class Monster extends base_monster { 
   String name; 
   static int object_counter = 0;

    Monster factory() { 
       name = Integer(object_counter).toString(); 
       object_counter();
       return this; 
    }

    /// this class has a useful setter
    void object_counter( int c ) { object_counter++; out.println( object_counter );    }
}

class Griffon extends Monster {
  Monster factory() { return new Griffon(); }
}


class Harpy extends Monster {
  Harpy() { name = "Grizelda WhuttleThut III"; }
  Harpy factory() { return new Harpy(); }
}


class BlackHarpy  extends Harpy {
  BlackHarpy  factory() { super.factory(); return new BlackHarpy(); }
}


// we assume that each class has a default constructor. But, 
// if the array is filled with monsters of different subclasses we
// would have to use reflection or nasty instanceof switches to be
// able to call a (specific) defined constructor.

ArrayList<Monster> monsters = new ArrayList<Monster>();    

monsters.add( new BlackHarpy() );
for( int I = 0; I < ave_monsters_appearing; I++ )
    monsters.add( new Harpy() );
//
// an array of ten harpies and a boss Harpy.

///
// how can this array of monsters be copied into the other array?
// (we want object copies, not reference copies)
///

ArrayList<Monster> local_monsters = new ArrayList<Monster>();    

/// solution: use the factory method
for( Monster m : monsters ) 
   local_monsters.add( m.factory() ); 

.
. 希望这可以解决没有静态方法的问题。

于 2013-08-19T03:15:43.613 回答
0

您可以将所有类放在一个特定的包中,然后扫描该目录中的类文件,加载它们,并跟踪扩展Monster. 您甚至可以定义一些自定义注释来帮助管理它,例如@IgnoreMonster暂时禁用某些注释而无需更改文件的位置。这类似于 Hibernate 扫描源以查找实体映射的方式。

这是一个例子。所有 Monster 类都放在 package 中dload.monsters。首先,这是我用于此示例的基类:

package dload.monsters;

public abstract class Monster {

    public abstract String getName ();

}

然后,MonsterFactory它扫描包中的所有类dload.monsters(抱歉有点草率,我略过了异常处理):

package dload.monsters;
import java.io.*;
import java.net.*;
import java.util.*;

public class MonsterFactory {

    private static final List<Class<? extends Monster>> monsterClasses = new ArrayList<Class<? extends Monster>>();
    private static final Random random = new Random();

    @SuppressWarnings("unchecked") // <- for monsterClasses.add((Class<? extends Monster>)cls);
    public static void loadMonsters () throws Exception {

        // in this example, Monster is in the same package as the monsters. if
        // that is not the case, replace "." with path relative to Monster.
        File folder = new File(Monster.class.getResource(".").toURI());

        for (File f : folder.listFiles()) {
            if (f.getName().endsWith(".class")) {
                String name = f.getName().split("\\.")[0];
                // replace "dload.monsters." below with package monsters are in
                Class<?> cls = ClassLoader.getSystemClassLoader().loadClass("dload.monsters." + name);
                // if Monster is not in same package as monsters, you can remove
                // cls.equals(Monster.class) check. this check makes sure the loaded
                // class extends Monster, but is not the Monster class itself (since
                // its also in that package).
                if (Monster.class.isAssignableFrom(cls) && !cls.equals(Monster.class)) {
                    System.out.println("Found " + cls.getSimpleName());
                    monsterClasses.add((Class<? extends Monster>)cls);
                }
            }
        }

        // at this point all Class's for monsters are in monsterClasses list.

    }

    public static Monster randomMonster () throws Exception {

        // choose a class at random
        int n = random.nextInt(monsterClasses.size());
        Class<? extends Monster> cls = monsterClasses.get(n);

        // instantiate it
        return cls.newInstance();

    }

}

然后,当你想使用它时:

public static void main (String[] args) throws Exception {

    // load monsters; only need to do this once at startup
    MonsterFactory.loadMonsters();

    // create 10 random monsters
    for (int n = 0; n < 10; ++ n) {
        Monster m = MonsterFactory.randomMonster();
        System.out.println("name is " + m.getName());
    }

}

请注意,您可以随时查看怪物的Class相关注释。

另一种选择是,如果类已经加载(如果它们从未被使用或显式加载,则不会加载) Instrumentation.getAllLoadedClasses()用于获取当前加载的所有类的列表,然后扫描所有类以查找那些可分配给Monster.

注意:我确实觉得有一种更简洁的方法来进行实际扫描,而且我还没有在 JAR 中测试过。欢迎提出建议。

话虽如此,如果怪物的行为可以完全由数据定义,我也支持并推荐上述数据驱动的方法。

于 2013-08-13T18:18:53.500 回答