# 如何在libgdx中实现随机敌人生成

go评论47阅读模式

How to implement random enemy spawns libgdx

# 问题

``````public void spawnEnemies(float deltaTime) {
waveTimer += deltaTime; // 设置为当前时间

if (waveTimer > timeBetweenWaves) { // 如果经过了波次间隔时间
enemySpawnTimer += deltaTime;
if (enemySpawnTimer > timeBetweenEnemySpawns && enemiesSpawned < maxEnemies) { // 在敌人生成计时器之后，仅当敌人数量未达到最大值时执行
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, 2, 0, 34, 45, 0.8f, Assets.instance.enemyShips.ENEMY_BLACK_01, Assets.instance.lasers.LASER_BLUE_05, 6f, 0.4f);
} else if (waveCounter >= 3 && waveCounter <= 6) {
// ... （后续波次的代码类似）
}

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, 0.4f);
}
``````

I am a beginner in Libgdx and I have a simple Galaga type game setup where the player earns points through waves with different level enemies with various stats. The player can then upgrade certain ship stats with these points. Mostly everything is done all I am focused on now is making the gameplay balanced between player ship stats and the different enemy stats as the player progresses. I want the game to be infinite as in the player can go on for as long as they can last but I can't seem to figure out how to set up the enemy spawning so that as the player progresses the enemies have different/harder stats and there are more enemies.

Here is my `spawnEnemies` method in my `GameScreen` class which adds the `EnemyShip` object to an array that is iterated through and then each ship is rendered in the render method.

``````public void spawnEnemies(float deltaTime) {
waveTimer += deltaTime; // sets to currentTime

if (waveTimer &gt; timeBetweenWaves) { // if after time between waves
enemySpawnTimer += deltaTime;
if (enemySpawnTimer &gt; timeBetweenEnemySpawns &amp;&amp; enemiesSpawned &lt; 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();
}
}
``````

The ship added to the list is determined by the current wave in this enemyType method:

``````    private EnemyShip enemyType() {
if (waveCounter &gt;= 1 &amp;&amp; waveCounter &lt;= 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 &gt;= 3 &amp;&amp; waveCounter &lt;= 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 &gt;= 7 &amp;&amp; waveCounter &lt;= 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 &gt;= 11 &amp;&amp; waveCounter &lt;= 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 &gt;= 15 &amp;&amp; waveCounter &lt;= 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 &gt;= 19 &amp;&amp; waveCounter &lt;= 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 &gt;= 25 &amp;&amp; waveCounter &lt;= 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 &gt;= 29 &amp;&amp; waveCounter &lt;= 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 &gt;= 33 &amp;&amp; waveCounter &lt;= 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 &gt;= 36 &amp;&amp; waveCounter &lt;= 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 &gt;= 40 &amp;&amp; waveCounter &lt;= 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 &gt;= 6 &amp;&amp; waveCounter &lt;= 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);
}
``````

I previously had separate `EnemyShip` subclasses (i.e level01Enemy, level02Enemy) but then changed it to just the parent `EnemyShip` since I thought there was no point to having separate classes as I was only changing the stats and ship/laser texture regions. I then hardcoded the stats in each. This is a temporary solution, but I want to write clean code and not have to hardcode all the stats. If I have to change my whole approach or I have terrible code let me know because, as I said, I am a beginner.

# 答案1

### 随机化敌人生成

``````private void nextWave() {
//...
timeBetweenEnemySpawns *= 0.95; //每波使敌人生成时间减少5%
maxEnemeies = (int) (1.05f * maxEnmeies); //每波使最大敌人数量增加5%
}
``````

### 清晰的代码

EnemyShipFactory

``````import com.badlogic.gdx.Gdx;

public class EnemyShipFactory {

private ObjectMap<String, EnemyShipStats> enemyShipStats;

public EnemyShipFactory() {
// 从json文件加载敌人飞船属性
}

@SuppressWarnings("unchecked")
Json json = new Json();// 创建一个json对象来加载json配置文件
FileHandle configFileHandle = Gdx.files.internal("galaga/enemy_ship_stats.json");// 引用位于assets文件夹中的json配置文件
enemyShipStats = json.fromJson(ObjectMap.class, EnemyShipStats.class, configFileHandle);// 将配置加载到对象中
}

public EnemyShip createEnemyShip(int waveCount) {
// 在这里仍然需要将波次转换为敌人飞船的等级
// 我将在这里使用一个枚举，但你也可以通过使用另一个配置json文件来完成
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);//更多等级...

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;//默认情况下返回最高等级
}
}
}
``````

EnemyShipStats

``````public class EnemyShipStats {

//用你需要的属性的名称和类型替换这些
public float width;
public float height;
public float damage;
public String texture;
//...
}
``````

EnemyShip

``````public class EnemyShip {

public EnemyShip(EnemyShipStats stats) {
//基于属性创建敌人飞船
//可能只需调用你当前使用的构造函数，就像这样：
this(stats.width, stats.height, stats.damage, stats.texture);
}

public EnemyShip(float width, float height, float damage, String texture) {
//...
}

//...
}
``````

enemy_ship_stats.json

``````//将这个文件放在assets文件夹中，一个名为'galaga'的文件夹里
{
// 键是EnemyShipFactory.ShipLevel中的枚举名称
LEVEL_1: {
// 值是EnemyShipStats对象
width: 42,
height: 42,
damage: 42,
texture: some_texture_name
},
LEVEL_2: {
// 值是EnemyShipStats对象
width: 42,
height: 42,
damage: 42,
texture: some_texture_name
},
LEVEL_3: {
// 值是EnemyShipStats对象
width: 42,
height: 42,
damage: 42,
texture: some_texture_name
}
}
``````

``````//将这行声明为全局字段
private EnemyShipFactory factory = new EnmeyShipFactory();

private EnemyShip enemyType() {
return factory.createEnemyShip(waveCount);
}
``````

### Randomize enemy spawnings

To randomize the number of enemies that spawn you could simply decrease the value of `timeBetweenEnemySpawns` and increase the value of `maxEnemies` by some function. For example you could do this in your `nextWave` method:

``````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
}
``````

In a similar way you could increase the damage, health or other stats of your enemy ships.

### Clean Code

Writing clean code is always a good idea, because otherwhise your code will get more and more ugly with every iteration, till you can no longer handle it and have to abandon the project. That's a lesson every coder has to learn

Unfortunately writing clean code is not as easy as hard coding all EnemyShip stats for every wave, so don't be disappointed if you don't understand all of the following code directly.

To create objects, with different parameters it's a good idea to use a Factory Pattern. So you just call a factory method (using very view parameters) to create the objects.

A realy clean solution to configure the enemy ships would be to use a data driven approach. That means you don't configure the enemy ships in the code, but use (more structured) configuration files for this. I would recoment to use JSON for this.
Another solution (which is not that clean but easier) would be to use an enum to configure the parameters for the enemy ships (like in the EnemyShipFactory.ShipLevel enum).

Putting it all together it could result in a solution like this:

EnemyShipFactory

``````import com.badlogic.gdx.Gdx;
public class EnemyShipFactory {
private ObjectMap&lt;String, EnemyShipStats&gt; enemyShipStats;
public EnemyShipFactory() {
// load the enemy ship stats from the json file
}
@SuppressWarnings(&quot;unchecked&quot;)
Json json = new Json();// create a json object to load the json configuration file
FileHandle configFileHandle = Gdx.files.internal(&quot;galaga/enemy_ship_stats.json&quot;);//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&#39;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 &gt;= waveCount &amp;&amp; level.maxWaveCount &lt;= 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 &#39;galaga&#39;
{
// 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
}
}
``````

Now you can change your `enemyType` method like this:

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

# 答案2

This is a "good question" in one sense, but a terrible one for Stack Overflow - it's practically 100% opinion based (I voted to close it).

Modeling data structures for games is a huge subject - and more of an art than a science - your decision will strongly affect possibilities of evolving your game in the future (do you want generative levels? level editor? live-debug? possibility of adding in-game scripting for specific interludes? theming? more complex gfx? more complex rules? - some of these goals are exclusive!).

One thing you might try and in my opinion will help you - is to go through Ashley tutorials and restructure your code to use it. Ashley is an Entity/Component/System library, which is nicely integrated with LibGDX (even offered by the setup gui) and commonly used in the ecosystem. It is bound to inspire you to design your code in a less decoupled way, and things like modelling a "wave", different types of ships, random spawn points etc. can be done neatly within the framework.

• 本文由 发表于 2020年9月12日 21:48:23
• 转载请务必保留本文链接：https://go.coder-hub.com/63860985.html
• java
• libgdx
• random
• spawning

go 43

go 48