简体   繁体   English

如何在减少代码重复的情况下为游戏添加Levels?

[英]How can I add Levels to my game with less duplication of code?

I am designing a game with multiple levels. 我正在设计一个多层次的游戏。 I have a setup class that sets up the board based on the argument it receives, which indicates which level it should set up. 我有一个安装类,它根据收到的参数设置电路板,它指示应该设置的电平。 Here is the class: 这是班级:

public class BoardState {
    public BoardState(InitialState state) {
        switch (state) {
            case EMPTY:
                setupEmptyState();
                break;
            case INTEGRATIONTEST:
                setupIntegrationTestState();
                break;
            case LEVEL_1:
                setupLevelOne();
                break;
            case LEVEL_2:
                setupLevelTwo();
                break;
            default:
                throw new Error("Invalid level selection");
        }
    }

    private void setupEmptyState() { }

    private void setupIntegrationTestState() { }

    private void setupLevelOne() { }

    private void setupLevelTwo() { }
}

This works fine, but every time I add a new level I have to add code in three places: The InitialState enum which defines the list of accepted states, the switch statement in the constructor, and the body of the class, where I have to add a method to set up the level in question. 这工作正常,但每次我添加一个新的级别时,我必须在三个地方添加代码: InitialState enum ,它定义了接受状态列表,构造函数中的switch语句,以及类的主体,我必须在添加一个方法来设置相关级别。

One nice thing that I want to keep is the fact that my GUI automatically populates with a new button for each level I add based on the enum defining the list of levels. 我想要保留的一件好事是,我的GUI会根据定义级别列表的enum自动填充我添加的每个级别的新按钮。

How can I refactor this code so that there is less overhead associated with adding a new level? 如何重构此代码,以便减少与添加新级别相关的开销?

Often when you need to reduce code duplication, an interface arise. 通常,当您需要减少代码重复时,就会出现一个界面。 This time (based on your comment in OP) it seems you need to add different objects to the board depending on which level you are: 这一次(根据您在OP中的评论),您似乎需要根据您所在的级别向棋盘添加不同的对象:

import java.util.List;

public interface LevelSettings {
    List<GameObject> startingObjects();
}

Now, BoardState looks like that (no more setupX() methods) 现在, BoardState看起来像那样(没有更多的setupX()方法)

import java.util.List;

public class BoardState {
    private final List<GameObject> gameObjects;

    public BoardState(LevelSettings settings) {
        this.gameObjects = settings.startingObjects();
    }
}

Since you also specified it is nice for you to have an enum to dynamically creates buttons on the GUI, one can combine the best of both world (interface and enum) by implementing the interface in an enum... 既然你也指定了一个很好的你有一个enum动态创建GUI的按钮,你可以通过在枚举中实现接口来结合两全其美(界面和枚举)...

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY {
        @Override
        public List<GameObject> startingObjects() {
            return Collections.emptyList();
        }
    },
    INTEGRATIONTEST {
        @Override
        public List<GameObject> startingObjects() {
            GameObject g1 = new GameObject("dummy 1");
            GameObject g2 = new GameObject("dummy 2");
            return Arrays.asList(g1, g2);
        }
    },
    LEVEL_1 {
        @Override
        public List<GameObject> startingObjects() {
            //read a config file to get the starting objects informations
            //or also hardcoded (not preferred)
        }
    },
    LEVEL_2 {
        @Override
        public List<GameObject> startingObjects() {
            //read a config file to get the starting objects
            //or also hardcoded (not preferred)
        }
    };
}

And that's it basically. 这基本上就是这样。 If you need to add LEVEL_3 do it in InitialState and everything will follow. 如果您需要添加LEVEL_3做它InitialState ,一切都会随之而来。

Going one step further 更进一步

From here it goes beyond what you requested, feel free to ignore this part if you are not convinced. 从这里开始,它超出了你的要求,如果你不相信,可以随意忽略这一部分。

As a good practice I would store these configurations only in config files to reduce even more the code duplication and gain in flexibility: 作为一种好的做法,我只将这些配置存储在配置文件中,以减少更多的代码重复并获得灵活性:

import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("empty.level");
        }
    },
    INTEGRATIONTEST {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("integration_test.level");
        }
    },
    LEVEL_1 {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("1.level");
        }
    },
    LEVEL_2 {
        @Override
        public List<GameObject> startingObjects() {
            return readFromFile("2.level");
        }
    };

    private static List<GameObject> readFromFile(String filename) {
        //Open file
        //Serialize its content in GameObjects
        //return them as a list
    }
}

So that when you decide to add a new level you actually only need to know the filename in which the level's configuration is stored. 因此,当您决定添加新级别时,实际上只需要知道存储级别配置的文件名。

Going another step further 再向前迈进一步

What you will see there is really tricky and I don't advice you to use it in production code (but it reduces code duplication) ! 您将看到的内容非常棘手,我建议您不要在生产代码中使用它 (但它会减少代码重复)!

import java.util.List;

public enum InitialState implements LevelSettings {
    EMPTY, INTEGRATIONTEST, LEVEL_1, LEVEL_2;

    @Override
    public List<GameObject> startingObjects() {
        return readFromFile(this.name() + ".level");
    }

    private static List<GameObject> readFromFile(String filename) {
        //Open file
        //Serialize its content in GameObjects
        //return them as a list
    }
}

Here we rely on enum names themselves to find the corresponding correct file. 在这里,我们依靠枚举名称来找到相应的正确文件。 This code works because it is based on the convention that the files are named accordingly to the enum names with the ".level" extension. 此代码有效,因为它基于约定 ,即文件的名称相应于具有“.level”扩展名的枚举名称。 When you need to add a new level, just add it to the enum and that's it... 当你需要添加一个新级别时,只需将它添加到枚举中就可以了......

You could use inheritance, polymorphism is the keyword here. 你可以使用继承, 多态是这里的关键字。

Set up your InitialState class as abstract base class (or interface if you have no common fields) and define a method public abstract void setup(); InitialState类设置为抽象基类(如果没有公共字段,则public abstract void setup();接口)并定义方法public abstract void setup(); .

abstract class InitialState {
    public abstract void setup();
}

Then, for each of your original switch cases, derive a class from your base class, for example LevelOne , and implement its specific by overriding setup() . 然后,对于每个原始切换案例,从基类派生一个类,例如LevelOne ,并通过重写setup()实现其特定。

class LevelOne extends InitialState {
    @Override
    public void setup() {
        // The code from "setupLevelOne()" goes here
    }
}

Your BoardState class reduces to this: 您的BoardState类减少到:

public class BoardState {
    public BoardState(InitialState state) {
        // At runtime, the right method of the actual
        // state type will be called dynamically
        state.setup();
    }
}

However, if you need to set interal state of your BoardState class, consider defining the setup method as public abstract void setup(BoardState boardState) , so you can access its getter and setter methods. 但是,如果需要设置BoardState类的内部状态,请考虑将setup方法定义为public abstract void setup(BoardState boardState) ,以便可以访问其getter和setter方法。

This appraoch could also foster reuse of code, as you could add several abstract layers for different types of levels. 这个appraoch也可以促进代码的重用,因为你可以为不同类型的级别添加几个抽象层。

well you can refactor all of the work into a one method. 那么你可以将所有的工作重构为一种方法。 say it takes an int as the ID of the level, while it loads a JSON file containing structured information of each level, and creates the given level. 假设它将一个int作为关卡的ID,同时它加载一个包含每个关卡的结构化信息的JSON文件,并创建给定的关卡。 for example : 例如 :

"levels" : [
  "level" : {
      "id" : "001",
      "size" : "200",
      "difficulty" : "2"
  },
  "level" : {
      "id" : "002",
      "size" : "300",
      "difficulty" : "3"
  }
]

then, in your code: 然后,在你的代码中:

public void setupLevel(int id) throws levelNotFoundException{
    //somehow like this
    Document doc = parse("levels.json");
    for(element elm: doc.get("levels")){
        if(Integer.parseInt(elm.get("id")).equals(id)){
            //setup your level
        }
    }
}

and then somewhere you call your method: 然后在某个地方调用你的方法:

int levelId = getNextLevel();
try{
setupLevel(levelId);
} catch (LevelNotFoundException e){e.printStackTrace();}

or you can use XML, or simply hard code it, and store all levels in an array 或者您可以使用XML,或者只是对其进行硬编码,并将所有级别存储在数组中

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM