问题描述
我是Libgdx的初学者,我有一个简单的galaga类型的游戏设置,玩家可以通过与具有不同属性的不同等级敌人的波动来赚取积分。然后,玩家可以使用这些积分来升级某些战舰状态。大多数情况下,我现在专注于完成所有工作,使游戏进度在玩家飞船状态和不同敌人状态之间达到平衡。我希望游戏是无限的,因为玩家可以一直持续下去,但是我似乎无法弄清楚如何设置敌人的生成,以便随着玩家的前进,敌人会有不同/更难的数据还有更多的敌人。
这是我的spawnEnemies
类中的GameScreen
方法,该方法将EnemyShip
对象添加到迭代通过的数组中,然后在render方法中渲染每条船。
public void spawnEnemies(float deltaTime) {
waveTimer += deltaTime; // sets to currentTime
if (waveTimer > timeBetweenWaves) { // if after time between waves
enemySpawnTimer += deltaTime;
if (enemySpawnTimer > timeBetweenEnemySpawns && enemiesspawned < maxEnemies) { // after enemy spawn timer and only if its less than max enemies
enemyShipList.add(enemyType()); // adds to enemy ship list which is then iterated through and rendered
enemiesspawned++;
enemySpawnTimer -= timeBetweenEnemySpawns;
}
}
if (enemiesDestroyed == maxEnemies) {
nextWave();
}
}
private EnemyShip enemyType() {
if (waveCounter >= 1 && waveCounter <= 2) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,WORLD_HEIGHT - 5,25,2,34,45,0.8f,Assets.instance.enemyShips.ENEMY_BLACK_01,Assets.instance.lasers.LASER_BLUE_05,6f,.4f);
} else if (waveCounter >= 3 && waveCounter <= 6) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,50,4,1,38,Assets.instance.enemyShips.ENEMY_BLUE_03,Assets.instance.lasers.LASER_RED_05,.4f);
}else if (waveCounter >= 7 && waveCounter <= 10) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,75,5,42,Assets.instance.enemyShips.ENEMY_BLACK_02,Assets.instance.lasers.LASER_BLUE_04,.4f);
}else if (waveCounter >= 11 && waveCounter <= 14) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,100,6,54,0.7f,Assets.instance.enemyShips.ENEMY_GREEN_03,.4f);
}else if (waveCounter >= 15 && waveCounter <= 18) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,125,48,58,Assets.instance.enemyShips.ENEMY_RED_04,Assets.instance.lasers.LASER_GREEN_03,.4f);
}else if (waveCounter >= 19 && waveCounter <= 24) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,8,7,3,60,Assets.instance.enemyShips.ENEMY_GREEN_04,Assets.instance.lasers.LASER_RED_03,.4f);
}
else if (waveCounter >= 25 && waveCounter <= 28){
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,.4f);
}else if (waveCounter >= 29 && waveCounter <= 32) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,150,9,Assets.instance.enemyShips.ENEMY_BLACK_04,Assets.instance.lasers.LASER_GREEN_13,.4f);
}else if (waveCounter >= 33 && waveCounter <= 35) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,10,64,0.6f,Assets.instance.enemyShips.ENEMY_RED_02,Assets.instance.lasers.LASER_BLUE_12,.4f);
}else if (waveCounter >= 36 && waveCounter <= 39) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,175,12,65,Assets.instance.enemyShips.ENEMY_BLACK_05,Assets.instance.lasers.LASER_BLUE_10,.4f);
}else if (waveCounter >= 40 && waveCounter <= 45) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,68,Assets.instance.enemyShips.ENEMY_GREEN_05,.4f);
}else if (waveCounter >= 6 && waveCounter <= 49) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,200,14,70,0.5f,Assets.instance.enemyShips.ENEMY_RED_05,Assets.instance.lasers.LASER_GREEN_12,.4f);
}
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5,220,80,0.1f,Assets.instance.enemyShips.ENEMY_BLUE_05,.4f);
}
我以前有单独的EnemyShip
子类(例如,level01Enemy,level02Enemy),但后来将其更改为父类EnemyShip
,因为我认为没有必要设置单独的类,因为我只是在更改统计信息以及船/激光纹理区域。然后,我将每个统计信息硬编码。这是一个临时解决方案,但我想编写简洁的代码,而不必对所有统计信息进行硬编码。如果我必须改变整体方法或代码太糟糕,请告诉我,因为正如我所说,我是初学者。
解决方法
随机化敌人的产卵
要随机化生成的敌人数量,您可以通过某些功能简单地降低timeBetweenEnemySpawns
的值并提高maxEnemies
的值。例如,您可以在nextWave
方法中执行此操作:
private void nextWave() {
//...
timeBetweenEnemySpawns *= 0.95;//decrease the time for enemies to spawn by 5% per wave
maxEnemeies = (int) (1.05f * maxEnmeies);//increase the max number of enemies by 5% per wave
}
以类似的方式,您可以增加敌舰的伤害,生命值或其他数据。
清洁代码
编写干净的代码总是一个好主意,因为否则,您的代码将在每次迭代中变得越来越难看,直到您无法处理它并不得不放弃该项目为止。这是每个程序员都必须学习的一课:)
不幸的是,编写干净的代码并不像对每个wave的所有EnemyShip统计信息进行硬编码一样容易,因此,如果您不直接理解以下所有代码,请不要失望。
要创建具有不同参数的对象,最好使用Factory Pattern。因此,您只需调用工厂方法(使用非常多的视图参数)来创建对象。
配置敌方船只的真正干净的解决方案是使用data driven approach。这意味着您无需在代码中配置敌舰,而要使用(结构更强的)配置文件。我会推荐使用JSON。
另一个解决方案(不是 clean 而是更简单)是使用枚举为敌舰配置参数(例如在EnemyShipFactory.ShipLevel枚举中)。
将它们放在一起可能会导致这样的解决方案:
EnemyShipFactory
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.ObjectMap;
public class EnemyShipFactory {
private ObjectMap<String,EnemyShipStats> enemyShipStats;
public EnemyShipFactory() {
// load the enemy ship stats from the json file
loadStats();
}
@SuppressWarnings("unchecked")
private void loadStats() {
Json json = new Json();// create a json object to load the json configuration file
FileHandle configFileHandle = Gdx.files.internal("galaga/enemy_ship_stats.json");//references the json config file in the assets folder
enemyShipStats = json.fromJson(ObjectMap.class,EnemyShipStats.class,configFileHandle);//load the config into objects
}
public EnemyShip createEnemyShip(int waveCount) {
//here you still need to convert the waveCount to the level of enemy ships
//I'll use an enum here,but you could also do this by using another configuration json file
String level = ShipLevel.of(waveCount).name();
EnemyShipStats stats = enemyShipStats.get(level);
return new EnemyShip(stats);
}
private enum ShipLevel {
LEVEL_1(1,2),//
LEVEL_2(3,6),//
LEVEL_3(7,10);//
//more levels...
public final int minWaveCount;
public final int maxWaveCount;
private ShipLevel(int minWaveCount,int maxWaveCount) {
this.minWaveCount = minWaveCount;
this.maxWaveCount = maxWaveCount;
}
public static ShipLevel of(int waveCount) {
for (ShipLevel level : values()) {
if (level.minWaveCount >= waveCount && level.maxWaveCount <= waveCount) {
return level;
}
}
return LEVEL_3;//return max level by default
}
}
}
EnemyShipStats
public class EnemyShipStats {
//replace this with the names and types of the stats that your need
public float width;
public float height;
public float damage;
public String texture;
//...
}
EnemyShip
public class EnemyShip {
public EnemyShip(EnemyShipStats stats) {
//create the enemy ship based on the stats
//probably just call the constructor you currently use like this:
this(stats.width,stats.height,stats.damage,stats.texture);
}
public EnemyShip(float width,float height,float damage,String texture) {
//...
}
//...
}
enemy_ship_stats.json
//put this file in the assets folder,inside a directory 'galaga'
{
// the keys are the names of the enum in EnemyShipFactory.ShipLevel
LEVEL_1: {
//the values are the EnemyShipStats objects
width: 42,height: 42,damage: 42,texture: some_texture_name
},LEVEL_2: {
//the values are the EnemyShipStats objects
width: 42,LEVEL_3: {
//the values are the EnemyShipStats objects
width: 42,texture: some_texture_name
}
}
现在,您可以像这样更改enemyType
方法:
//declare this as a global field
private EnemyShipFactory factory = new EnmeyShipFactory();
private EnemyShip enemyType() {
return factory.createEnemyShip(waveCount);
}
,
从某种意义上说,这是一个“好问题”,但对于Stack Overflow来说却是一个可怕的问题-实际上,它是100%基于观点的(我投票决定将其关闭)。
为游戏的数据结构建模是一个巨大的主题-不仅是一门科学,而且是一门艺术,而不是一门科学-您的决定将强烈影响未来游戏发展的可能性(您是否希望生成水平?编辑器,实时调试,为特定片段添加游戏脚本的可能性,主题化,更复杂的gfx,更复杂的规则?-其中一些目标是排他性的!)。
您可能会尝试做的一件事,我认为这将对您有所帮助-通过Ashley教程并重组代码以使用它。 Ashley是一个实体/组件/系统库,它与LibGDX很好地集成在一起(甚至由setup gui提供),并且通常在生态系统中使用。它一定会激发您以较少分离的方式设计代码,并且可以在框架内巧妙地完成诸如“波浪”建模,不同类型的船只,随机生成点之类的事情。