diff --git a/.gitignore b/.gitignore index 250f267..29806e0 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,5 @@ libgdx-*.zip.MD5 .storage .httpCache -kotlin-js-store \ No newline at end of file +kotlin-js-store +.kotlin \ No newline at end of file diff --git a/README.md b/README.md index 9bd8672..86e20ac 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ This template repository contains a base project for creating games with [Little the bare necessities to get a LittleKt project up and running. This includes the necessary plugins, dependencies and source set structure. -This project is set up to use all the available platforms that LittleKt currently supports: **JVM**, **Web**, and **Android**. +This project is set up to use all the available platforms that LittleKt currently supports: **JVM**, **Web**, and * +*Android**. If a certain platform isn't needed, simply deleting the source directory and the source sets in the `build.gradle.kts` file will get rid of it. @@ -15,11 +16,11 @@ respective platform. ### JVM -**Running:** +#### Running Run `LwjglApp` to execute on the desktop. -**Deploying:** +#### Deploying A custom deploy task is created specifically for JVM. Run the `package/packageFatJar` gradle task to create a fat executable JAR. This task can be tinkered with in the `build.gradlek.kts` file. @@ -27,36 +28,19 @@ executable JAR. This task can be tinkered with in the `build.gradlek.kts` file. If and when the packages are renamed from `com.game.template.LwjglApp` to whatever, ensure to update the `jvm.mainClass` property in the `gradle.properties` file to ensure that the `packageFatJar` task will work properly. +A jar will be outputted in the build directory. To run this JAR, use the following +command, replacing the name of the JAR, if needed: `java -jar --enable-preview game-1.0-all.jar` + +If you are running on Mac OS, then the following command can be +used: `java -jar -XstartOnFirstThread --enable-preview game-1.0-all.jar` + ### JS -**Running:** +#### Running Run the `kotlin browser/jsBrowserRun` gradle task like any other **Kotlin/JS** project to run in development mode. -**Deploying:** +#### Deploying Run the `kotlin browser/jsBrowserDistribution` gradle task to create a distribution build. This build will require a webserver in order to run. - -### WASM - -**Running:** - -Run the `kotlin browser/wasmJsBrowserRun` gradle task like any other **Kotlin/Wasm** project to run in development mode. - -**Deploying:** - -Run the `kotlin browser/wasmJsBrowserDistribution` gradle task to create a distribution build. This build will require a -webserver in order to run. - -### Android - -**Running:** - -Run `AndroidApp` class under `src/androidMain/`. - -**Deploying:** - -To create a release build of the Android application, use the `Build/Generate Signed Bundle / APK...` menu option. This -will require knowledge on creating keys for signing the Android app. Going over how to do that is out of scope for this -repository. \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 423641c..354af84 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.multiplatform) apply false } allprojects { repositories { + mavenLocal() google() mavenCentral() } diff --git a/game/build.gradle.kts b/game/build.gradle.kts index 2771712..4df6502 100644 --- a/game/build.gradle.kts +++ b/game/build.gradle.kts @@ -1,20 +1,18 @@ import org.apache.tools.ant.taskdefs.condition.Os -import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl repositories { maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlinx-html/maven") } plugins { - alias(libs.plugins.android.application) alias(libs.plugins.kotlin.multiplatform) } kotlin { - androidTarget() + tasks.withType { jvmArgs("--enable-preview", "--enable-native-access=ALL-UNNAMED") } jvm { + compilations.all { kotlinOptions.jvmTarget = "21" } compilations { val main by getting @@ -39,20 +37,19 @@ kotlin { duplicatesStrategy = DuplicatesStrategy.EXCLUDE dependsOn(named("jvmJar")) dependsOn(named("copyResources")) - manifest { - attributes["Main-Class"] = mainClassName - } + manifest { attributes["Main-Class"] = mainClassName } destinationDirectory.set(File("${layout.buildDirectory.asFile.get()}/publish/")) from( main.runtimeDependencyFiles.map { if (it.isDirectory) it else zipTree(it) }, main.output.classesDirs ) doLast { - project.logger.lifecycle("[LittleKt] The packaged jar is available at: ${outputs.files.first().parent}") + logger.lifecycle( + "[LittleKt] The packaged jar is available at: ${outputs.files.first().parent}" + ) } } - // workaround for https://youtrack.jetbrains.com/issue/KT-62214 - if (Os.isFamily(Os.FAMILY_MAC) && mainClassName != null) { + if (Os.isFamily(Os.FAMILY_MAC)) { register("jvmRun") { jvmArgs("-XstartOnFirstThread") mainClass.set(mainClassName) @@ -68,54 +65,32 @@ kotlin { } } } - compilations.all { - kotlinOptions.jvmTarget = "17" - } - testRuns["test"].executionTask.configure { - useJUnit() - } } - js(KotlinJsCompilerType.IR) { + js { binaries.executable() browser { - testTask { - useKarma { - useChromeHeadless() - } + testTask { useKarma { useChromeHeadless() } } + commonWebpackConfig { + devServer = + (devServer + ?: org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + .DevServer()) + .copy( + open = mapOf("app" to mapOf("name" to "chrome")), + ) } } - this.attributes.attribute( - KotlinPlatformType.attribute, - KotlinPlatformType.js - ) + this.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.js) - compilations.all { - kotlinOptions.sourceMap = true - } - } - - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - binaries.executable() - browser { - commonWebpackConfig(Action { - devServer = - (devServer ?: org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.DevServer()).copy( - open = mapOf( - "app" to mapOf( - "name" to "firefox" - ) - ), - ) - }) - } + compilations.all { kotlinOptions.sourceMap = true } } sourceSets { val commonMain by getting { dependencies { implementation(libs.littlekt.core) + implementation(libs.littlekt.scenegraph) implementation(libs.kotlinx.coroutines.core) } } @@ -132,30 +107,5 @@ kotlin { } } val jsTest by getting - val wasmJsMain by getting - val wasmJsTest by getting - val androidMain by getting } } - -android { - namespace = "com.game.template" - sourceSets["main"].apply { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - assets.srcDirs("src/commonMain/resources") - } - compileSdk = (findProperty("android.compileSdk") as String).toInt() - - defaultConfig { - minSdk = (findProperty("android.minSdk") as String).toInt() - targetSdk = (findProperty("android.targetSdk") as String).toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } -} - -rootProject.extensions.configure { - versions.webpackCli.version = "4.10.0" -} diff --git a/game/src/androidMain/AndroidManifest.xml b/game/src/androidMain/AndroidManifest.xml deleted file mode 100644 index a739e00..0000000 --- a/game/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt b/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt deleted file mode 100644 index ef46392..0000000 --- a/game/src/androidMain/kotlin/com/game/template/AndroidApp.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.game.template - -import android.app.Application - - -class AndroidApp : Application() \ No newline at end of file diff --git a/game/src/androidMain/kotlin/com/game/template/AppActivity.kt b/game/src/androidMain/kotlin/com/game/template/AppActivity.kt deleted file mode 100644 index 756258d..0000000 --- a/game/src/androidMain/kotlin/com/game/template/AppActivity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.game.template - -import com.lehaine.littlekt.Context -import com.lehaine.littlekt.ContextListener -import com.lehaine.littlekt.LittleKtActivity -import com.lehaine.littlekt.LittleKtProps -import com.lehaine.littlekt.graphics.Color - -class AppActivity : LittleKtActivity() { - - override fun LittleKtProps.configureLittleKt() { - activity = this@AppActivity - backgroundColor = Color.DARK_GRAY - } - - override fun createContextListener(context: Context): ContextListener { - return Game(context) - } -} \ No newline at end of file diff --git a/game/src/androidMain/res/drawable/ic_launcher.png b/game/src/androidMain/res/drawable/ic_launcher.png deleted file mode 100644 index 51f69fb..0000000 Binary files a/game/src/androidMain/res/drawable/ic_launcher.png and /dev/null differ diff --git a/game/src/androidMain/res/values/strings.xml b/game/src/androidMain/res/values/strings.xml deleted file mode 100644 index 341c6ad..0000000 --- a/game/src/androidMain/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - LittleKt Game Template - diff --git a/game/src/commonMain/kotlin/com/game/template/Game.kt b/game/src/commonMain/kotlin/com/game/template/Game.kt index 1a0291d..65ffd2f 100644 --- a/game/src/commonMain/kotlin/com/game/template/Game.kt +++ b/game/src/commonMain/kotlin/com/game/template/Game.kt @@ -1,25 +1,34 @@ package com.game.template -import com.lehaine.littlekt.Context -import com.lehaine.littlekt.ContextListener -import com.lehaine.littlekt.graph.node.resource.HAlign -import com.lehaine.littlekt.graphics.Color -import com.lehaine.littlekt.graphics.Fonts -import com.lehaine.littlekt.graphics.g2d.SpriteBatch -import com.lehaine.littlekt.graphics.g2d.shape.ShapeRenderer -import com.lehaine.littlekt.graphics.g2d.use -import com.lehaine.littlekt.graphics.gl.ClearBufferMask -import com.lehaine.littlekt.graphics.toFloatBits -import com.lehaine.littlekt.math.geom.degrees -import com.lehaine.littlekt.math.geom.radians -import com.lehaine.littlekt.util.viewport.ExtendViewport +import com.littlekt.Context +import com.littlekt.ContextListener +import com.littlekt.graphics.Color +import com.littlekt.graphics.HAlign +import com.littlekt.graphics.g2d.SpriteBatch +import com.littlekt.graphics.g2d.shape.ShapeRenderer +import com.littlekt.graphics.g2d.use +import com.littlekt.graphics.webgpu.* +import com.littlekt.math.geom.degrees +import com.littlekt.math.geom.radians +import com.littlekt.resources.Fonts +import com.littlekt.util.viewport.ExtendViewport +import com.littlekt.util.viewport.setViewport import kotlin.time.Duration.Companion.milliseconds - class Game(context: Context) : ContextListener(context) { override suspend fun Context.start() { - val batch = SpriteBatch(this) + val device = graphics.device + val surfaceCapabilities = graphics.surfaceCapabilities + val preferredFormat = graphics.preferredFormat + + graphics.configureSurface( + TextureUsage.RENDER_ATTACHMENT, + preferredFormat, + PresentMode.FIFO, + surfaceCapabilities.alphaModes[0] + ) + val batch = SpriteBatch(device, graphics, preferredFormat) val shapeRenderer = ShapeRenderer(batch) val viewport = ExtendViewport(960, 540) val camera = viewport.camera @@ -27,22 +36,86 @@ class Game(context: Context) : ContextListener(context) { var rotationTimer = 0.milliseconds onResize { width, height -> - viewport.update(width, height, context) + viewport.update(width, height) + graphics.configureSurface( + TextureUsage.RENDER_ATTACHMENT, + preferredFormat, + PresentMode.FIFO, + surfaceCapabilities.alphaModes[0] + ) } + onUpdate { dt -> + val surfaceTexture = graphics.surface.getCurrentTexture() + when (val status = surfaceTexture.status) { + TextureStatus.SUCCESS -> { + // all good, could check for `surfaceTexture.suboptimal` here. + } - onRender { dt -> - gl.clearColor(Color.DARK_GRAY) - gl.clear(ClearBufferMask.COLOR_BUFFER_BIT) + TextureStatus.TIMEOUT, + TextureStatus.OUTDATED, + TextureStatus.LOST -> { + surfaceTexture.texture?.release() + logger.info { "getCurrentTexture status=$status" } + return@onUpdate + } - batch.use(camera.viewProjection) { - Fonts.default.draw(it, "Hello LittleKt!", 0f, 0f, align = HAlign.CENTER) - shapeRenderer.filledRectangle(-50f, 50f, 100f, 50f, rotation, color = Color.RED.toFloatBits()) + else -> { + // fatal + logger.fatal { "getCurrentTexture status=$status" } + close() + return@onUpdate + } } + val swapChainTexture = checkNotNull(surfaceTexture.texture) + val frame = swapChainTexture.createView() + + val commandEncoder = device.createCommandEncoder() + val renderPassEncoder = + commandEncoder.beginRenderPass( + desc = + RenderPassDescriptor( + listOf( + RenderPassColorAttachmentDescriptor( + view = frame, + loadOp = LoadOp.CLEAR, + storeOp = StoreOp.STORE, + clearColor = + if (preferredFormat.srgb) Color.DARK_GRAY.toLinear() + else Color.DARK_GRAY + ) + ) + ) + ) + renderPassEncoder.setViewport(viewport) + camera.update() + + batch.use(renderPassEncoder, camera.viewProjection) { + Fonts.default.draw(it, "Hello LittleKt!", 0f, 0f, align = HAlign.CENTER) + shapeRenderer.filledRectangle(-50f, 50f, 100f, 50f, rotation, color = Color.RED) + } + renderPassEncoder.end() + rotationTimer += dt if (rotationTimer > 10.milliseconds) { rotationTimer = 0.milliseconds rotation += 1.degrees } + + val commandBuffer = commandEncoder.finish() + + device.queue.submit(commandBuffer) + graphics.surface.present() + + commandBuffer.release() + renderPassEncoder.release() + commandEncoder.release() + frame.release() + swapChainTexture.release() + } + + onRelease { + batch.release() + device.release() } } } \ No newline at end of file diff --git a/game/src/jsMain/kotlin/com/game/template/JsApp.kt b/game/src/jsMain/kotlin/com/game/template/JsApp.kt index 3277255..0f8e355 100644 --- a/game/src/jsMain/kotlin/com/game/template/JsApp.kt +++ b/game/src/jsMain/kotlin/com/game/template/JsApp.kt @@ -1,12 +1,12 @@ package com.game.template -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color +import com.littlekt.createLittleKtApp fun main() { createLittleKtApp { + width = 960 + height = 540 title = "LittleKt Game Template" - backgroundColor = Color.DARK_GRAY canvasId = "canvas" }.start { Game(it) diff --git a/game/src/jsMain/resources/index.html b/game/src/jsMain/resources/index.html index 35867d2..7cfdebd 100644 --- a/game/src/jsMain/resources/index.html +++ b/game/src/jsMain/resources/index.html @@ -3,10 +3,58 @@ LittleKt Game Template +
- +

+ LittleKt WebGPU Examples +

+

+ Your browser does not currently support WebGPU. You must use Chrome Canary, Edge Canary, or Firefox Nightly and + enable WebGPU (See here + for more details) +

+
+ +
- + + + + + \ No newline at end of file diff --git a/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt b/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt index 2cd94e4..a56c6d2 100644 --- a/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt +++ b/game/src/jvmMain/kotlin/com/game/template/LwjglApp.kt @@ -1,14 +1,14 @@ package com.game.template -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color +import com.littlekt.createLittleKtApp fun main() { createLittleKtApp { width = 960 height = 540 - backgroundColor = Color.DARK_GRAY title = "LittleKt Game Template" + traceWgpu = false + enableWGPULogging = false }.start { Game(it) } diff --git a/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt b/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt deleted file mode 100644 index 6f5828d..0000000 --- a/game/src/wasmJsMain/kotlin/com/template/game/WasmJsApp.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.template.game - -import com.game.template.Game -import com.lehaine.littlekt.createLittleKtApp -import com.lehaine.littlekt.graphics.Color - -fun main() { - createLittleKtApp { - title = "LittleKt Game Template" - backgroundColor = Color.DARK_GRAY - canvasId = "canvas" - }.start { - Game(it) - } -} \ No newline at end of file diff --git a/game/src/wasmJsMain/resources/index.html b/game/src/wasmJsMain/resources/index.html deleted file mode 100644 index 144ee4f..0000000 --- a/game/src/wasmJsMain/resources/index.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - LittleKt - WASM - - - -
- ⚠️ Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling - proposals. - For more information, see https://kotl.in/wasm-help. -
-
-
    -
  • For Chrome and Chromium-based browsers (Edge, Brave etc.), it should just work since - version 119. -
  • -
  • For Firefox 120 it should just work.
  • -
  • For Firefox 119: -
      -
    1. Open about:config in the browser.
    2. -
    3. Enable javascript.options.wasm_gc.
    4. -
    5. Refresh this page.
    6. -
    -
  • -
-
-
- -
- - - - - \ No newline at end of file diff --git a/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js b/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js deleted file mode 100644 index b45d42b..0000000 --- a/game/src/wasmJsMain/webpack.config.d/cleanupSourcemap.js +++ /dev/null @@ -1,37 +0,0 @@ -// Replace paths unavailable during compilation with `null`, so they will not be shown in devtools -; -(() => { - const fs = require("fs"); - const path = require("path"); - - const outDir = __dirname + "/kotlin/" - const projecName = path.basename(__dirname); - const mapFileLegacy = outDir + projecName + ".map" - const mapFile = outDir + projecName + ".wasm.map" - - let sourcemap - try { - sourcemap = JSON.parse(fs.readFileSync(mapFileLegacy)) - } catch (e) { - sourcemap = JSON.parse(fs.readFileSync(mapFile)) - } - const sources = sourcemap["sources"] - srcLoop: for (let i in sources) { - const srcFilePath = sources[i]; - if (srcFilePath == null) continue; - - const srcFileCandidates = [ - outDir + srcFilePath, - outDir + srcFilePath.substring("../".length), - outDir + "../" + srcFilePath, - ]; - - for (let srcFile of srcFileCandidates) { - if (fs.existsSync(srcFile)) continue srcLoop; - } - - sources[i] = null; - } - - fs.writeFileSync(mapFile, JSON.stringify(sourcemap)); -})(); \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aa4a18f..24b7e8a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,16 +1,15 @@ [versions] -agp = "8.2.0" -kotlin = "1.9.23" -kotlinx-html = "0.9.1" -kotlinx-coroutines = "1.8.0" -littlekt = "0.9.0" +kotlin = "2.0.0" +kotlinx-html = "0.11.0" +kotlinx-coroutines = "1.9.0-RC" +littlekt = "0.10.0" [libraries] kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } kotlinx-html-js = { module = "org.jetbrains.kotlinx:kotlinx-html-js", version.ref = "kotlinx-html" } -littlekt-core = { module = "com.lehaine.littlekt:core", version.ref = "littlekt" } +littlekt-core = { module = "com.littlekt:core", version.ref = "littlekt" } +littlekt-scenegraph = { module = "com.littlekt:scene-graph", version.ref = "littlekt" } [plugins] -android-application = { id = "com.android.application", version.ref = "agp" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index db9a6b8..a595206 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle.kts b/settings.gradle.kts index 15cb704..9b5a900 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,6 @@ pluginManagement { repositories { + mavenLocal() google() gradlePluginPortal() mavenCentral()