DEADSOFTWARE

Refactor world generator
authorfredboy <fredboy@protonmail.com>
Tue, 16 Apr 2024 18:21:42 +0000 (01:21 +0700)
committerfredboy <fredboy@protonmail.com>
Tue, 16 Apr 2024 18:21:42 +0000 (01:21 +0700)
Fix freeze when drop appears

android/build.gradle
build.gradle
core/build.gradle
core/src/ru/deadsoftware/cavedroid/game/GamePhysics.java
core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt [new file with mode: 0644]
core/src/ru/deadsoftware/cavedroid/game/objects/Drop.java
core/src/ru/deadsoftware/cavedroid/game/world/GameWorld.java
core/src/ru/deadsoftware/cavedroid/game/world/GameWorldGenerator.kt
desktop/build.gradle

index 06d300527e875c27594d34e5014ec17bec627c90..5ad0a3172a649c8fabfd2b735f2add2abcc36f0a 100644 (file)
@@ -20,8 +20,8 @@ android {
         }
     }
     compileOptions {
-        sourceCompatibility 11
-        targetCompatibility 11
+        sourceCompatibility 17
+        targetCompatibility 17
     }
     packagingOptions {
         exclude 'META-INF/robovm/ios/robovm.xml'
index 8250a6a7459fe08f1fdef7b77cb08594c41f4d3e..a67d0716bc75a1a02a152b08876c5da26a84816d 100644 (file)
@@ -10,7 +10,7 @@ buildscript {
 
     dependencies {
         classpath 'com.android.tools.build:gradle:8.2.2'
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23"
     }
 }
 
index 2c25846e203fb3bf023292d8c2856f4c279c5ce6..d968841dbff78228ef471c0889b4b0a190d0530d 100644 (file)
@@ -4,8 +4,8 @@ plugins {
     id "idea"
 }
 
-sourceCompatibility = 11
+sourceCompatibility = 17
 
 sourceSets.main.java.srcDirs = [ "src/" ]
 
-java.targetCompatibility = JavaVersion.VERSION_11
\ No newline at end of file
+java.targetCompatibility = JavaVersion.VERSION_17
\ No newline at end of file
index 6b69faada2470f556265259c5df8f5aa6cda74b7..e3ceb531cb3a37c4ac9aec5adf5b6c83152dc838 100644 (file)
@@ -96,18 +96,19 @@ public class GamePhysics {
 
     private void dropPhy(Drop drop, float delta) {
         int dropToPlayer = drop.closeToPlayer(mGameWorld, mMobsController.getPlayer());
+
         if (dropToPlayer > 0) {
-            drop.moveToPlayer(mGameWorld, mMobsController.getPlayer(), dropToPlayer);
+            drop.moveToPlayer(mGameWorld, mMobsController.getPlayer(), dropToPlayer, delta);
         } else {
-            if (drop.getVelocity().x >= .5f) {
-                drop.getVelocity().x -= .5f;
-            } else if (drop.getVelocity().x <= -.5f) {
-                drop.getVelocity().x += .5f;
+            if (drop.getVelocity().x >= 300f) {
+                drop.getVelocity().x = 300f;
+            } else if (drop.getVelocity().x <= -300f) {
+                drop.getVelocity().x = -300f;
             } else {
                 drop.getVelocity().x = 0;
             }
-            if (drop.getVelocity().y < 9) {
-                drop.getVelocity().y += gravity.y / 4;
+            if (drop.getVelocity().y < PL_TERMINAL_VELOCITY) {
+                drop.getVelocity().y += gravity.y * delta;
             }
         }
         drop.move(delta);
@@ -116,7 +117,7 @@ public class GamePhysics {
         if (checkColl(drop)) {
             drop.getVelocity().set(0, -1);
             do {
-                drop.move(delta);
+                drop.move(1);
             } while (checkColl(drop));
             drop.getVelocity().setZero();
         }
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/Biome.kt
new file mode 100644 (file)
index 0000000..e2d3d7d
--- /dev/null
@@ -0,0 +1,6 @@
+package ru.deadsoftware.cavedroid.game.model.world
+
+enum class Biome {
+    PLAINS,
+    DESERT
+}
\ No newline at end of file
diff --git a/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt b/core/src/ru/deadsoftware/cavedroid/game/model/world/generator/WorldGeneratorConfig.kt
new file mode 100644 (file)
index 0000000..251b901
--- /dev/null
@@ -0,0 +1,37 @@
+package ru.deadsoftware.cavedroid.game.model.world.generator
+
+import com.badlogic.gdx.utils.TimeUtils
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+
+data class WorldGeneratorConfig(
+    val width: Int,
+    val height: Int,
+    val seed: Long,
+    val minSurfaceHeight: Int,
+    val maxSurfaceHeight: Int,
+    val biomes: List<Biome>,
+    val minBiomeSize: Int,
+    val seaLevel: Int,
+) {
+
+    companion object {
+        private const val DEFAULT_WIDTH = 1024
+        private const val DEFAULT_HEIGHT = 256
+        private const val DEFAULT_MIN_BIOME_SIZE = 64
+
+        fun getDefaultWithSeed(seed: Long): WorldGeneratorConfig {
+            return WorldGeneratorConfig(
+                width = DEFAULT_WIDTH,
+                height = DEFAULT_HEIGHT,
+                seed = TimeUtils.millis(),
+                minSurfaceHeight = DEFAULT_HEIGHT / 4,
+                maxSurfaceHeight = DEFAULT_HEIGHT * 3 / 4,
+                biomes = Biome.entries.toList(),
+                minBiomeSize = DEFAULT_MIN_BIOME_SIZE,
+                seaLevel = DEFAULT_HEIGHT / 2,
+            )
+        }
+
+    }
+
+}
index 995c79e9bc88a544804c740f8ac5582e9ed68610..9e4e570396b903139e8a3d590966c7ae7dbb6ab7 100644 (file)
@@ -44,7 +44,7 @@ public class Drop extends Rectangle implements Serializable {
         return 0;
     }
 
-    public void moveToPlayer(GameWorld gameWorld, Player player, int ctp) {
+    public void moveToPlayer(GameWorld gameWorld, Player player, int ctp, float delta) {
         if (ctp > 0) {
             float px = player.getX();
             float py = player.getY();
@@ -61,29 +61,29 @@ public class Drop extends Rectangle implements Serializable {
             float dx = 0, dy = 0;
 
             if (px + player.getWidth() < x + 4) {
-                dx = -.5f;
+                dx = -300f;
             } else if (px > x + 4) {
-                dx = .5f;
+                dx = 300f;
             }
 
             if (py + player.getHeight() < y + 4) {
-                dy = -.5f;
+                dy = -300f;
             } else if (py > y + 4) {
-                dy = .5f;
+                dy = .300f;
             }
 
-            velocity.add(dx, dy);
+            velocity.add(dx * delta, dy * delta);
 
-            if (velocity.x > 2) {
-                velocity.x = 1;
-            } else if (velocity.x < -2) {
-                velocity.x = -1;
+            if (velocity.x > 300f) {
+                velocity.x = 300f;
+            } else if (velocity.x < -300f) {
+                velocity.x = -300f;
             }
 
-            if (velocity.y > 2) {
-                velocity.y = 1;
-            } else if (velocity.y < -2) {
-                velocity.y = -1;
+            if (velocity.y > 300f) {
+                velocity.y = 300f;
+            } else if (velocity.y < -300f) {
+                velocity.y = -300f;
             }
         }
     }
index 60a34e14353704bc0c76d52b505efa80cb9f7b58..b5ac7eef895e2c280578da80c1d133cd8e036705 100644 (file)
@@ -8,6 +8,7 @@ import ru.deadsoftware.cavedroid.game.GameScope;
 import ru.deadsoftware.cavedroid.game.mobs.FallingGravel;
 import ru.deadsoftware.cavedroid.game.mobs.FallingSand;
 import ru.deadsoftware.cavedroid.game.mobs.MobsController;
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig;
 import ru.deadsoftware.cavedroid.game.objects.Block;
 import ru.deadsoftware.cavedroid.game.objects.DropController;
 
@@ -51,7 +52,7 @@ public class GameWorld implements Disposable {
         if (isNewGame) {
             mWidth = DEFAULT_WIDTH;
             mHeight = DEFAULT_HEIGHT;
-            Pair<int[][], int[][]> maps = GameWorldGenerator.INSTANCE.generate(mWidth, mHeight, TimeUtils.millis());
+            Pair<int[][], int[][]> maps = new GameWorldGenerator(WorldGeneratorConfig.Companion.getDefaultWithSeed(TimeUtils.millis())).generate();
             mForeMap = maps.getFirst();
             mBackMap = maps.getSecond();
             mMobsController.getPlayer().respawn(this);
@@ -217,7 +218,7 @@ public class GameWorld implements Disposable {
                 setForeMap(x, y, 0);
                 mMobsController.addMob(FallingSand.class, x * 16, y * 16);
                 updateBlock(x, y - 1);
-            }   
+            }
         }
 
         if (getForeMap(x, y) == 11) {
index bf37f454c3ad36851a40062e5d74b2ffad7cdfde..8776254f7dca8d6a1d3b3212aff95335bdb22733 100644 (file)
 package ru.deadsoftware.cavedroid.game.world
 
-import com.badlogic.gdx.utils.TimeUtils
 import ru.deadsoftware.cavedroid.game.GameItems
+import ru.deadsoftware.cavedroid.game.model.world.Biome
+import ru.deadsoftware.cavedroid.game.model.world.generator.WorldGeneratorConfig
 import kotlin.math.abs
+import kotlin.math.max
 import kotlin.random.Random
 
-object GameWorldGenerator {
+class GameWorldGenerator(
+    private val config: WorldGeneratorConfig,
+) {
 
-    private const val BIOME_MIN_SIZE = 64
+    private val random = Random(config.seed)
 
-    private enum class Biome {
-        PLAINS,
-        DESERT
-    }
+    private val foreMap by lazy { Array(config.width) { IntArray(config.height) } }
+    private val backMap by lazy { Array(config.width) { IntArray(config.height) } }
+
+    private val heights by lazy { generateHeights() }
+    private val biomesMap by lazy { generateBiomes() }
+
+    private fun generateHeights(): IntArray {
+        val surfaceHeightRange = config.minSurfaceHeight .. config.maxSurfaceHeight
+        val result = IntArray(config.width)
 
-    private fun generateHeights(width: Int, min: Int, max: Int, random: Random) = IntArray(width).apply {
-        set(0, (min + max) / 2)
-        for (x in 1 until width) {
-            val previous = get(x - 1)
+        result[0] = (config.minSurfaceHeight + config.maxSurfaceHeight) / 2
+
+        for (x in 1 ..< config.width) {
+            val previous = result[x - 1]
             var d = random.nextInt(-5, 6).let { if (it !in -4..4) it / abs(it) else 0 }
 
-            if (previous + d !in min..max) { d = -d }
-            if (lastIndex - x < abs(get(0) - previous) * 3) {
-                d = get(0).compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
+            if (previous + d !in surfaceHeightRange) { d = -d }
+
+            if (result.lastIndex - x < abs(result[0] - previous) * 3) {
+                d = result[0].compareTo(previous).let { if (it != 0) it / abs(it) else 0 }
             }
 
-            set(x, get(x - 1) + d)
+            result[x] = result[x - 1] + d
         }
+
+        return result
     }
 
-    private fun generateBiomes(width: Int, random: Random) = buildMap<Int, Biome> {
+    private fun generateBiomes(): Map<Int, Biome> {
         val xSequence = sequence {
             var lastX = 0
             var count = 0
 
-            while (lastX < width - BIOME_MIN_SIZE - 1) {
+            while (lastX < config.width - config.minBiomeSize - 1) {
                 yield(lastX)
 
-                lastX = random.nextInt(lastX + BIOME_MIN_SIZE, width)
+                lastX = random.nextInt(lastX + config.minBiomeSize, config.width)
                 count++
             }
         }
 
-        return xSequence.associateWith { Biome.values()[random.nextInt(Biome.values().size)] }
+        return xSequence.associateWith { config.biomes.random(random) }
     }
 
-    private fun plainsBiome(
-        foreMap: Array<IntArray>,
-        backMap: Array<IntArray>,
-        width: Int,
-        height: Int,
-        x: Int,
-        xHeight: Int,
-        random: Random,
-    ) {
-        foreMap[x][xHeight] = GameItems.getBlockId("grass")
-        foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
-        backMap[x][xHeight] = GameItems.getBlockId("grass")
-        backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
-        for (y in xHeight + 1 until height - 1) {
+    private fun plainsBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val grassId = GameItems.getBlockId("grass")
+        val bedrockId = GameItems.getBlockId("bedrock")
+        val dirtId = GameItems.getBlockId("dirt")
+        val stoneId = GameItems.getBlockId("stone")
+
+        foreMap[x][surfaceHeight] = grassId
+        foreMap[x][config.height - 1] = bedrockId
+        backMap[x][surfaceHeight] = grassId
+        backMap[x][config.height - 1] = bedrockId
+
+        for (y in surfaceHeight + 1 ..< config.height - 1) {
             foreMap[x][y] = when {
-                y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("dirt")
-                else -> GameItems.getBlockId("stone")
+                y < surfaceHeight + random.nextInt(5, 8) -> dirtId
+                else -> stoneId
             }
             backMap[x][y] = foreMap[x][y]
         }
     }
 
-    private fun desertBiome(
-        foreMap: Array<IntArray>,
-        backMap: Array<IntArray>,
-        width: Int,
-        height: Int,
-        x: Int,
-        xHeight: Int,
-        random: Random,
-    ) {
-        foreMap[x][xHeight] = GameItems.getBlockId("sand")
-        foreMap[x][height - 1] = GameItems.getBlockId("bedrock")
-        backMap[x][xHeight] = GameItems.getBlockId("sand")
-        backMap[x][height - 1] = GameItems.getBlockId("bedrock")
-
-        for (y in xHeight + 1 until height - 1) {
+    private fun desertBiome(x: Int) {
+        assert(x in 0 ..< config.width) { "x not in range of world width" }
+
+        val surfaceHeight = heights[x]
+
+        val sandId = GameItems.getBlockId("sand")
+        val bedrockId = GameItems.getBlockId("bedrock")
+        val sandstoneId = GameItems.getBlockId("sandstone")
+        val stoneId = GameItems.getBlockId("stone")
+
+
+        foreMap[x][surfaceHeight] = sandId
+        foreMap[x][config.height - 1] = bedrockId
+        backMap[x][surfaceHeight] = sandId
+        backMap[x][config.height - 1] = bedrockId
+
+        for (y in surfaceHeight + 1 ..< config.height - 1) {
             foreMap[x][y] = when {
-                y < xHeight + random.nextInt(5, 8) -> GameItems.getBlockId("sand")
-                else -> GameItems.getBlockId("stone")
+                y < surfaceHeight + random.nextInt(5, 8) -> sandId
+                y < surfaceHeight + random.nextInt(0, 2) -> sandstoneId
+                else -> stoneId
             }
             backMap[x][y] = foreMap[x][y]
         }
+
+        if (surfaceHeight < config.seaLevel && random.nextInt(100) < 5) {
+            generateCactus(x)
+        }
     }
 
-    private fun fillWater(foreMap: Array<IntArray>, width: Int, height: Int, waterLevel: Int) {
-        for (x in 0 until width) {
-            for (y in waterLevel until height) {
+    private fun fillWater() {
+        val waterId = GameItems.getBlockId("water")
+
+        for (x in 0 ..< config.width) {
+            for (y in config.seaLevel ..< config.height) {
                 if (foreMap[x][y] != 0) {
                     break
                 }
 
-                foreMap[x][y] = GameItems.getBlockId("water")
+                foreMap[x][y] = waterId
             }
         }
     }
 
+    private fun generateCactus(x: Int) {
+        val cactusId = GameItems.getBlockId("cactus")
+        val cactusHeight = random.nextInt(5)
+        val h = heights[x] - 1
+
+        for (y in h downTo max(0, h - cactusHeight)) {
+            foreMap[x][y] = cactusId
+        }
+    }
+
     /**
-     * Generates world of given width and height with given seed
-     * @param width world width
-     * @param height world height
-     * @param seed seed for random number generator
-     * @return pair of foreground and background layers
+     * Generate world
      */
-    fun generate(width: Int, height: Int, seed: Long = TimeUtils.millis()): Pair<Array<IntArray>, Array<IntArray>> {
-        val random = Random(seed)
-        val foreMap = Array(width) { IntArray(height) }
-        val backMap = Array(width) { IntArray(width) }
-        val heightsMap = generateHeights(width, height / 4, height * 3 / 4, random)
-        val biomesMap = generateBiomes(width, random)
-
+    fun generate(): Pair<Array<IntArray>, Array<IntArray>> {
         var biome = Biome.PLAINS
 
-        for (x in 0 until width) {
-            val xHeight = heightsMap[x]
+        for (x in 0 until config.width) {
+            val xHeight = heights[x]
             biome = biomesMap[x] ?: biome
 
             when (biome) {
-                Biome.PLAINS -> plainsBiome(foreMap, backMap, width, height, x, xHeight, random)
-                Biome.DESERT -> desertBiome(foreMap, backMap, width, height, x, xHeight, random)
+                Biome.PLAINS -> plainsBiome(x)
+                Biome.DESERT -> desertBiome(x)
             }
         }
 
-        fillWater(foreMap, width, height, height / 2)
+        fillWater()
 
         return Pair(foreMap, backMap)
     }
index 88ef144f460626ead46e01f094e94219b2fd352a..57fae418b7fbd7e2f47c036681b5f841f9991a05 100644 (file)
@@ -1,6 +1,6 @@
 apply plugin: "kotlin"
 
-sourceCompatibility = 11
+sourceCompatibility = 17
 sourceSets.main.java.srcDirs = [ "src/" ]
 sourceSets.main.resources.srcDirs = ["../android/assets"]