我正在构建一个简单的冒险游戏,有“升级”功能。
在此游戏中,用户控制一个玩家,该玩家可以升级以增加如下内容:
一个“升级”有一个最大的级别上限,对每个升级级别的影响,以及每次升级的成本。
这是我目前的方法。
这是我的Upgrades类,包含所有可能的升级:
public class Upgrades {
public static final int GOLD_LEVEL_CAP = 10;
public static final float GOLD_EFFECT_PER_LEVEL = 0.01f;
public static final int[] GOLD_UPGRADE_COSTS = {
100, 200, 300, 400, 500,
600, 700, 800, 900, 1000
};
public static final int HEALTH_LEVEL_CAP = 10;
public static final int HEALTH_EFFECTS_PER_LEVEL = 100;
public static final int[] HEALTH_UPGRADE_COSTS = {
100, 200, 300, 400, 500,
600, 700, 800, 900, 1000
};
public static final int POWER_LEVEL_CAP = 5;
public static final float POWER_EFFECT_PER_LEVEL = 0.05f;
public static final int[] POWER_UPGRADE_COSTS = {
1000, 2000, 3000, 4000, 5000
};
}这是存储当前升级的类:(属于Player类)
public class UpgradeProgress {
private int goldUpgradeLevel = 0;
private int healthUpgradeLevel = 0;
private int powerUpgradeLevel = 0;
public void upgradeGold() {
if (goldUpgradeLevel < Upgrades.GOLD_LEVEL_CAP) {
goldUpgradeLevel++;
}
}
// Getter
public int getGoldUpgradeLevel() {
return goldUpgradeLevel;
}
// and so on..
// same for Health and Power
}但是,如果我想添加一个新的升级,例如,我想添加一个“国防升级”。我应该同时修改Upgrades和UpgradeProgress类。
这是否有效和可接受的?
还是有更好的方法?
发布于 2014-07-30 23:13:01
由于巴佐拉和我在处理这一问题的最佳途径上意见不一,我在这里的回答是:
从他的答案中拿出bazolas升级课程,然后使用下面的实现来实现升级。
public class DamageUpgrade extends Upgrade {
public DamageUpgrade(int levelCap, float effectPerLevel, int[] upgradeCosts) {
super(levelCap, effectsPerLevel, upgradeCosts);
}
}
public class Player {
private float damage = 10; //base value
private DamageUpgrade dmgUp = new DamageUpgrade(2, 2, new int[]{10,20});
public float getDamage(){
//returns 10 with no Upgrade, 12 on first and 14 on second Upgrade
return damage + dmgUp.getTotalEffect();
}
}在我看来,为每一种升级类型创建类的优点是,它很容易实现,而且您可以相对容易地看到这些可获取的升级。这个解决方案可以通过使用一个映射来保存不同的Upgradetype来改进,这样就可以轻松地在Runtime动态添加一个新的Upgradetype,但是在这种类型的游戏中没有必要,因为所有的升级都已经在Compiletime中知道了(至少应该是这样)。
缺点当然是工作空间变得杂乱无章。你会有很多文件,几乎没有新的内容。这意味着你必须组织自己,例如,通过使用包(无论如何,我推荐)。
对于那些不喜欢多类解决方案的人:
使用Enum。忘记以前的一切,我现在要回收名字。levelCap被缩减了,就像Arraysize已经暗示的那样。
public enum Upgrade{
DAMAGE(2, new int[]{10,20}),
RANGE(1, new int[]{5,10,15,20});
private float effectsPerLevel;
private int[] upgradeCosts;
private Upgrade(float effectsPerLevel, int[] upgradeCosts){
this.effectsPerLevel = effectsPerLevel;
this.upgradeCosts = upgradeCosts;
}
//special getter for nonexistent field, because we still need the levelcap
public int getLevelCap(){
return upgradeCosts.length;
}
//add getters for all fields, in the following I assume you did this
}真正的魔法来了。我将使用EnumMap来跟踪每一次升级的当前级别。这需要一些准备。我必须用0初始化每个字段,最后的结果要复杂一些。由于它有点复杂,升级也包括在内。如果你对这个答案有疑问,请发表意见,我会澄清的。
public class Player{
private EnumMap<Upgrade, Integer> upg;
private float damage = 10;
private int balance; //included to show upgrading
//Note: You can extend this constructor as much as you want, this is the minimum
public Player() {
upg = new EnumMap<>(upg.class);
for (Upgrade u : Upgrades.values()){
upg.put(u, 0);
}
}
public float getDamage(){
return damage + upg.get(Upgrade.DAMAGE) * Upgrade.DAMAGE.getEffectPerLevel();
}
public void levelUp(Upgrade u){
if(upg.get(u) >= u.getLevelCap()) return; //early escape, we can't upgrade
if(balance < u.getUpgradeCost()[upg.get(u)]) return; //early escape, not enough money.
balance = balance - u.getUpgradeCost()[upg.get(u)];
upg.put(u, upg.get(u) + 1); //fetch the value, add one, store it back
}
}优点:一旦安装,添加一个新的升级就像添加另一个enum条目一样简单(如范围所示)。每当你添加一个新的升级类型,它将自动有0级,所以你不必担心升级没有初始化。一旦你进入它,使用枚举和地图是很棒的。您也可以在许多其他地方使用枚举。想为每次升级生成一个按钮,但不想每次都重写整个GUI?使用Upgrade.values()获取所有升级的数组,这将自动提供升级的数量。
缺点:一旦安装成功,它就能工作。要做到这一点很复杂,需要对Enum和Maps有一个扎实的理解(前者比后者更多)。
结论:第一个版本更容易,从长远来看,第二个版本更好(因为更好地重用)。这至少是我经过两个小时的思考后的看法。
发布于 2014-07-30 22:18:40
当前的方法不是一个直观的面向对象的设计。Upgrades类基本上就像C中的struct,它只是集合在一起的“东西”。复数名称“升级”本身已经表明,这是一个事物集合,使用" things“集合的自然方法是创建一个Thing类并将您的东西放入Collection<Thing>中。
也许有一个Feature类来表示特性是有意义的,如下所示:
class Feature {
private final String name;
private final float effectPerLevel;
private final int[] upgradeCosts;
Feature(String name, float effectPerLevel, int[] upgradeCosts) {
this.name = name;
this.effectPerLevel = effectPerLevel;
this.upgradeCosts = upgradeCosts;
}
public int getLevelCap() {
return upgradeCosts.length;
}
}基于Feature,您可以创建方便类Gold、Health、Power:
class Gold extends Feature {
public Gold() {
super("Gold", .01f, new int[]{
100, 200, 300, 400, 500,
600, 700, 800, 900, 1000
});
}
}
class Health extends Feature {
public Health() {
super("Health", 100, new int[]{
100, 200, 300, 400, 500,
600, 700, 800, 900, 1000
});
}
}这些是方便的类,因为您可以通过直接调用Feature的构造函数来创建这些特性。它将取决于您的整个程序,哪种方法将更符合人体工程学。
我删除了级别上限的常量,因为(至少现在),您似乎可以从upgradeCosts数组字段中派生出这个值。
我在这里并没有故意包括升级逻辑。其中一个原因是职责分离:理想情况下,类应该有一个单独的责任。Feature的职责是通过保持其特性来描述其特性。如果我想跟踪当前的功能和升级级别,这将是第二个责任。
将升级逻辑放在其他地方的另一个原因是,当您有多个player对象时,可以避免重复。每个玩家可能都有一个或多个特征。它们可能在不同的层次上具有这些特性,但是特性的定义特征是相同的,只是级别不同。换句话说,Feature对象可以由多个参与者共享,并且您可以只允许玩家在级别上有所变化。也许这是捕捉这一概念的一种方式:
class PlayerFeature {
private final Feature feature;
private int level;
PlayerFeature(Feature feature) {
this.feature = feature;
this.level = 1;
}
void upgrade() {
if (level < feature.getLevelCap()) {
++level;
}
}
}你可以定义Players,它可以有一个PlayerFeatureS的集合:
class Player {
Set<PlayerFeature> features = new HashSet<PlayerFeature>();
public void addFeature(PlayerFeature feature) {
features.add(feature);
}
}然后用这样的东西来设置一个游戏:
class Game {
public void initPlayers() {
Gold gold = new Gold();
Health health = new Health();
Player player1 = new Player();
player1.addFeature(new PlayerFeature(gold));
player1.addFeature(new PlayerFeature(health));
Player player2 = new Player();
player2.addFeature(new PlayerFeature(gold));
player2.addFeature(new PlayerFeature(health));
}
}通过这种方式拆分原始的Upgrades类,您可以获得灵活性。如果您想要对其中一个特性进行一些更改,例如Gold,您可以使用现代IDE中的键盘快捷方式很容易地跳到该类,而必须在不断增长的Upgrades类中进行文本搜索。
https://codereview.stackexchange.com/questions/58532
复制相似问题