【问题标题】:How to implement random enemy spawns libgdx [closed]如何实现随机敌人生成 libgdx [关闭]
【发布时间】:2020-12-30 19:10:31
【问题描述】:

我是 Libgdx 的初学者,我有一个简单的 Galaga 类型的游戏设置,玩家通过具有不同统计数据的不同级别敌人的波来获得积分。然后玩家可以使用这些点升级某些船舶统计数据。几乎所有事情都已完成,我现在所关注的就是随着玩家的进步,在玩家飞船统计数据和不同敌人统计数据之间平衡游戏玩法。我希望游戏是无限的,因为玩家可以持续下去,但我似乎无法弄清楚如何设置敌人产卵,以便随着玩家的进步,敌人有不同/更难的统计数据还有更多的敌人。

这是我的GameScreen 类中的spawnEnemies 方法,它将EnemyShip 对象添加到一个数组中,该数组被迭代,然后每艘船都在渲染方法中呈现。

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();
    }
}

添加到列表中的船是由这个enemyType方法中的当前波决定的:

    private EnemyShip enemyType() { 
    if (waveCounter >= 1 && waveCounter <= 2) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 25, 2, 2, 0, 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, WORLD_HEIGHT - 5, 50, 4, 4, 1, 38,45,0.8f, Assets.instance.enemyShips.ENEMY_BLUE_03, Assets.instance.lasers.LASER_RED_05, 6f, .4f);
    }else if  (waveCounter >= 7 && waveCounter <= 10) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 75, 4, 5, 1, 42,50,0.8f, Assets.instance.enemyShips.ENEMY_BLACK_02, Assets.instance.lasers.LASER_BLUE_04, 6f, .4f);
    }else if  (waveCounter >= 11 && waveCounter <= 14) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 100, 6, 6, 2, 45,54,0.7f, Assets.instance.enemyShips.ENEMY_GREEN_03, Assets.instance.lasers.LASER_BLUE_05, 6f, .4f);
    }else if  (waveCounter >= 15 && waveCounter <= 18) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 6, 6, 2, 48,58,0.7f, Assets.instance.enemyShips.ENEMY_RED_04, Assets.instance.lasers.LASER_GREEN_03, 6f, .4f);
    }else if  (waveCounter >= 19 && waveCounter <= 24) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 8, 7, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_GREEN_04, Assets.instance.lasers.LASER_RED_03, 6f, .4f);
    }

    else if  (waveCounter >= 25 && waveCounter <= 28){
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 8, 8, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_BLACK_02, Assets.instance.lasers.LASER_RED_05,6f,.4f);
    }else if  (waveCounter >= 29 && waveCounter <= 32) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 150, 8, 9, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_BLACK_04, Assets.instance.lasers.LASER_GREEN_13, 6f, .4f);
    }else if  (waveCounter >= 33 && waveCounter <= 35) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 150, 9, 10, 4, 54,64,0.6f, Assets.instance.enemyShips.ENEMY_RED_02, Assets.instance.lasers.LASER_BLUE_12, 6f, .4f);
    }else if  (waveCounter >= 36 && waveCounter <= 39) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 175, 9, 12, 4, 58,65,0.6f, Assets.instance.enemyShips.ENEMY_BLACK_05, Assets.instance.lasers.LASER_BLUE_10, 6f, .4f);
    }else if  (waveCounter >= 40 && waveCounter <= 45) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 175, 10, 12, 5, 60,68,0.6f, Assets.instance.enemyShips.ENEMY_GREEN_05, Assets.instance.lasers.LASER_RED_03, 6f, .4f);
    }else if  (waveCounter >= 6 && waveCounter <= 49) {
        return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 200, 12, 14, 5, 64,70,0.5f, Assets.instance.enemyShips.ENEMY_RED_05, Assets.instance.lasers.LASER_GREEN_12, 6f, .4f);
    }

    return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 220, 14, 1, 0, 68,80,0.1f, Assets.instance.enemyShips.ENEMY_BLUE_05, Assets.instance.lasers.LASER_BLUE_05, 6f, .4f);
}

我之前有单独的 EnemyShip 子类(即 level01Enemy、level02Enemy),但后来将其更改为父类 EnemyShip,因为我认为单独的类没有意义,因为我只是更改了统计信息和船/激光纹理区域。然后我对每个数据进行硬编码。这是一个临时解决方案,但我想编写干净的代码,而不必硬编码所有统计信息。如果我必须改变我的整个方法或者我的代码很糟糕,请告诉我,因为正如我所说,我是初学者。

【问题讨论】:

    标签: java random libgdx spawning


    【解决方案1】:

    随机生成敌人

    要随机化生成的敌人数量,您可以简单地减少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
    }
    

    以类似的方式,您可以增加敌舰的伤害、生命值或其他属性。

    清理代码

    编写干净的代码总是一个好主意,因为否则你的代码会随着每次迭代变得越来越难看,直到你无法处理它并不得不放弃项目。这是每个程序员都必须学习的一课:)

    不幸的是,编写干净的代码并不像硬编码每一波的所有 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;
        //...
    }
    

    敌舰

    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,
            height: 42,
            damage: 42,
            texture: some_texture_name
        },
        LEVEL_3: {
            //the values are the EnemyShipStats objects
            width: 42,
            height: 42,
            damage: 42,
            texture: some_texture_name
        }
    }
    

    现在您可以像这样更改您的 enemyType 方法:

    //declare this as a global field
    private EnemyShipFactory factory = new EnmeyShipFactory();
    
    private EnemyShip enemyType() { 
        return factory.createEnemyShip(waveCount);
    }
    

    【讨论】:

    • 看起来这是一个很好的解决方案,但是我对 JSON 不是很熟悉。如何将 JSON 文件中的纹理字符串转换为从 Assets 类中提取的纹理区域?
    • 这取决于你如何打包你的资产。如果您使用TextureAtlas,它将是textureAtlas.findRegion(enemyShipStats.texture)。如果你想以不同的方式加载它们(没有纹理图集),它可能会有点不同。如果是这样,你最好就这个话题提出一个新问题。
    【解决方案2】:

    从某种意义上说,这是一个“好问题”,但对于 Stack Overflow 来说却是一个糟糕的问题——它几乎 100% 是基于意见的(我投票决定关闭它)。

    为游戏建模数据结构是一门庞大的学科 - 与其说是一门科学,不如说是一门艺术 - 您的决定将强烈影响游戏未来发展的可能性(您想要生成级别吗?级别编辑器?实时调试?为特定插曲添加游戏内脚本的可能性?主题化?更复杂的 gfx?更复杂的规则?- 其中一些目标是独家的!)。

    您可能会尝试并且在我看来会对您有所帮助的一件事 - 是通过 Ashley 教程并重组您的代码以使用它。 Ashley 是一个实体/组件/系统库,它很好地与 LibGDX 集成(甚至由 setup gui 提供)并且在生态系统中常用。它一定会激发您以一种较少解耦的方式设计代码,并且可以在框架内巧妙地完成诸如“波浪”建模、不同类型的船只、随机生成点等事情。

    【讨论】:

      猜你喜欢
      • 2011-10-26
      • 1970-01-01
      • 2019-08-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多