英文:
How to implement random enemy spawns libgdx
问题
以下是您提供的代码的翻译部分:
public void spawnEnemies(float deltaTime) {
waveTimer += deltaTime; // 设置为当前时间
if (waveTimer > timeBetweenWaves) { // 如果经过了波次间隔时间
enemySpawnTimer += deltaTime;
if (enemySpawnTimer > timeBetweenEnemySpawns && enemiesSpawned < maxEnemies) { // 在敌人生成计时器之后,仅当敌人数量未达到最大值时执行
enemyShipList.add(enemyType()); // 添加到敌人船列表,然后在迭代中进行渲染
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 > 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();
}
}
The ship added to the list is determined by the current wave in this enemyType method:
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);
}
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
得分: 0
随机化敌人生成
要随机化生成的敌人数量,你可以简单地减少timeBetweenEnemySpawns
的值,并通过某个函数增加maxEnemies
的值。例如,在你的nextWave
方法中可以这样做:
private void nextWave() {
//...
timeBetweenEnemySpawns *= 0.95; //每波使敌人生成时间减少5%
maxEnemeies = (int) (1.05f * maxEnmeies); //每波使最大敌人数量增加5%
}
类似地,你也可以增加敌人飞船的伤害、生命值或其他属性。
清晰的代码
编写清晰的代码总是一个好主意,因为否则随着每次迭代,你的代码会变得越来越丑陋,直到你无法再处理它并不得不放弃这个项目。这是每个程序员都必须学会的一课
不幸的是,编写清晰的代码并不像为每个波次的敌人飞船硬编码所有属性那么容易,所以如果你不直接理解下面的所有代码,不要感到失望。
要创建具有不同参数的对象,使用工厂模式是一个不错的主意。因此,你只需调用一个工厂方法(只使用很少的参数)来创建对象。
一个真正清晰的配置敌人飞船的解决方案是使用数据驱动的方法。这意味着你不在代码中配置敌人飞船,而是使用(更结构化的)配置文件来完成。我建议使用JSON来实现这一点。
另一个解决方案(不太干净但更简单)是使用枚举来配置敌人飞船的参数(就像在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() {
// 从json文件加载敌人飞船属性
loadStats();
}
@SuppressWarnings("unchecked")
private void loadStats() {
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
}
}
现在你可以像这样更改你的enemyType
方法:
//将这行声明为全局字段
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;
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,
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
得分: 0
这在某种意义上是一个“好问题”,但对于Stack Overflow来说却是一个糟糕的问题 - 它基本上完全是基于个人意见的(我投票关闭了它)。
为游戏建模数据结构是一个庞大的主题 - 更像是一门艺术而不是科学 - 你的决策将会强烈影响到将来游戏的发展可能性(你想要生成关卡吗?关卡编辑器?实时调试?可能性添加特定插曲的游戏内脚本?主题?更复杂的图形效果?更复杂的规则? - 其中一些目标是互斥的!)。
你可以尝试的一件事,也是我个人认为会帮助你的 - 是参考Ashley的教程并重构你的代码来使用它。Ashley是一个Entity/Component/System库,与LibGDX(甚至由设置界面提供)完美集成,在生态系统中广泛使用。它肯定会激发你以一种更不解耦的方式设计代码,而诸如对“波”建模,不同类型的飞船,随机生成点等事情可以在框架内整洁地完成。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论