如何实现随机敌人生成libgdx 随机化敌人的产卵清洁代码

问题描述

我是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提供),并且通常在生态系统中使用。它一定会激发您以较少分离的方式设计代码,并且可以在框架内巧妙地完成诸如“波浪”建模,不同类型的船只,随机生成点之类的事情。