如何以更少的代码重复将关卡添加到我的游戏中?
How can I add Levels to my game with less duplication of code?
我正在设计一个多层次的游戏。我有一个设置 class,它根据接收到的参数设置电路板,指示应该设置哪个级别。这是 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() { }
}
这很好用,但每次我添加一个新级别时,我都必须在三个地方添加代码:InitialState
enum
定义接受状态列表,switch
构造函数中的语句,以及 class 的主体,我必须在其中添加一个方法来设置相关级别。
我想保留的一件好事是,我的 GUI 会根据 enum
定义级别列表,为我添加的每个级别自动填充一个新按钮。
如何重构此代码以减少与添加新关卡相关的开销?
你可以使用继承,多态性是这里的关键词。
将您的 InitialState
class 设置为抽象基础 class(如果没有公共字段,则为接口)并定义一个方法 public abstract void setup();
.
abstract class InitialState {
public abstract void setup();
}
然后,对于每个原始开关案例,从基础 class 派生一个 class,例如 LevelOne
,并通过覆盖 setup()
来实现其特定.
class LevelOne extends InitialState {
@Override
public void setup() {
// The code from "setupLevelOne()" goes here
}
}
您的 BoardState
class 减少为:
public class BoardState {
public BoardState(InitialState state) {
// At runtime, the right method of the actual
// state type will be called dynamically
state.setup();
}
}
但是,如果您需要设置 BoardState
class 的内部状态,请考虑将设置方法定义为 public abstract void setup(BoardState boardState)
,这样您就可以访问它的 getter 和setter 方法。
这种方法还可以促进代码的重用,因为您可以为不同类型的关卡添加多个抽象层。
通常当您需要减少代码重复时,就会出现一个接口。这次(根据您在 OP 中的评论)看来您需要根据您所在的级别向面板添加不同的对象:
import java.util.List;
public interface LevelSettings {
List<GameObject> startingObjects();
}
现在,BoardState
看起来像那样(不再有 setupX()
方法)
import java.util.List;
public class BoardState {
private final List<GameObject> gameObjects;
public BoardState(LevelSettings settings) {
this.gameObjects = settings.startingObjects();
}
}
由于您还指定了 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)
}
};
}
基本上就是这样。如果您需要添加 LEVEL_3
,请在 InitialState
中添加,一切都会随之而来。
更进一步
从这里开始超出您的要求,如果您不相信,请忽略这部分。
作为一个好习惯,我会仅将这些配置存储在配置文件中,以进一步减少代码重复并获得灵活性:
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
}
}
这样当您决定添加一个新关卡时,您实际上只需要知道存储该关卡配置的文件名。
更进一步
你会看到的东西真的很棘手,我不建议你在生产代码中使用它(但它减少了代码重复)!
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
}
}
这里我们依靠枚举名称本身来找到对应的正确文件。此代码之所以有效,是因为它基于 约定 ,文件根据枚举名称命名,扩展名为“.level”。当您需要添加新级别时,只需将其添加到枚举中即可...
好吧,您可以将所有工作重构为一个方法。
说它需要一个 int 作为级别的 ID,同时它加载一个包含每个级别的结构化信息的 JSON 文件,并创建给定的级别。
例如:
"levels" : [
"level" : {
"id" : "001",
"size" : "200",
"difficulty" : "2"
},
"level" : {
"id" : "002",
"size" : "300",
"difficulty" : "3"
}
]
然后,在您的代码中:
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
}
}
}
然后在某个地方调用您的方法:
int levelId = getNextLevel();
try{
setupLevel(levelId);
} catch (LevelNotFoundException e){e.printStackTrace();}
或者你可以使用XML,或者简单地硬编码,并将所有级别存储在一个数组中
我正在设计一个多层次的游戏。我有一个设置 class,它根据接收到的参数设置电路板,指示应该设置哪个级别。这是 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() { }
}
这很好用,但每次我添加一个新级别时,我都必须在三个地方添加代码:InitialState
enum
定义接受状态列表,switch
构造函数中的语句,以及 class 的主体,我必须在其中添加一个方法来设置相关级别。
我想保留的一件好事是,我的 GUI 会根据 enum
定义级别列表,为我添加的每个级别自动填充一个新按钮。
如何重构此代码以减少与添加新关卡相关的开销?
你可以使用继承,多态性是这里的关键词。
将您的 InitialState
class 设置为抽象基础 class(如果没有公共字段,则为接口)并定义一个方法 public abstract void setup();
.
abstract class InitialState {
public abstract void setup();
}
然后,对于每个原始开关案例,从基础 class 派生一个 class,例如 LevelOne
,并通过覆盖 setup()
来实现其特定.
class LevelOne extends InitialState {
@Override
public void setup() {
// The code from "setupLevelOne()" goes here
}
}
您的 BoardState
class 减少为:
public class BoardState {
public BoardState(InitialState state) {
// At runtime, the right method of the actual
// state type will be called dynamically
state.setup();
}
}
但是,如果您需要设置 BoardState
class 的内部状态,请考虑将设置方法定义为 public abstract void setup(BoardState boardState)
,这样您就可以访问它的 getter 和setter 方法。
这种方法还可以促进代码的重用,因为您可以为不同类型的关卡添加多个抽象层。
通常当您需要减少代码重复时,就会出现一个接口。这次(根据您在 OP 中的评论)看来您需要根据您所在的级别向面板添加不同的对象:
import java.util.List;
public interface LevelSettings {
List<GameObject> startingObjects();
}
现在,BoardState
看起来像那样(不再有 setupX()
方法)
import java.util.List;
public class BoardState {
private final List<GameObject> gameObjects;
public BoardState(LevelSettings settings) {
this.gameObjects = settings.startingObjects();
}
}
由于您还指定了 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)
}
};
}
基本上就是这样。如果您需要添加 LEVEL_3
,请在 InitialState
中添加,一切都会随之而来。
更进一步
从这里开始超出您的要求,如果您不相信,请忽略这部分。
作为一个好习惯,我会仅将这些配置存储在配置文件中,以进一步减少代码重复并获得灵活性:
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
}
}
这样当您决定添加一个新关卡时,您实际上只需要知道存储该关卡配置的文件名。
更进一步
你会看到的东西真的很棘手,我不建议你在生产代码中使用它(但它减少了代码重复)!
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
}
}
这里我们依靠枚举名称本身来找到对应的正确文件。此代码之所以有效,是因为它基于 约定 ,文件根据枚举名称命名,扩展名为“.level”。当您需要添加新级别时,只需将其添加到枚举中即可...
好吧,您可以将所有工作重构为一个方法。 说它需要一个 int 作为级别的 ID,同时它加载一个包含每个级别的结构化信息的 JSON 文件,并创建给定的级别。 例如:
"levels" : [
"level" : {
"id" : "001",
"size" : "200",
"difficulty" : "2"
},
"level" : {
"id" : "002",
"size" : "300",
"difficulty" : "3"
}
]
然后,在您的代码中:
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
}
}
}
然后在某个地方调用您的方法:
int levelId = getNextLevel();
try{
setupLevel(levelId);
} catch (LevelNotFoundException e){e.printStackTrace();}
或者你可以使用XML,或者简单地硬编码,并将所有级别存储在一个数组中