Update template to work with LittleKt 0.10.0 WebGPU update (#6)

* update template to work with new WebGPU LittleKt 0.10.0 update

* remove SNAPSHOT version of LittleKt

* gradle: update to 8.5
This commit is contained in:
Colt Daily 2024-07-16 10:27:33 -04:00 committed by GitHub
parent a544e4e349
commit fc0503d099
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 194 additions and 314 deletions

3
.gitignore vendored
View file

@ -61,4 +61,5 @@ libgdx-*.zip.MD5
.storage
.httpCache
kotlin-js-store
kotlin-js-store
.kotlin

View file

@ -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.

View file

@ -1,10 +1,10 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.multiplatform) apply false
}
allprojects {
repositories {
mavenLocal()
google()
mavenCentral()
}

View file

@ -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<JavaExec> { 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<JavaExec>("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<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension> {
versions.webpackCli.version = "4.10.0"
}

View file

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.VIBRATE"/>
<application
android:name=".AndroidApp"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
<activity
android:name=".AppActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -1,6 +0,0 @@
package com.game.template
import android.app.Application
class AndroidApp : Application()

View file

@ -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)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">LittleKt Game Template</string>
</resources>

View file

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

View file

@ -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)

View file

@ -3,10 +3,58 @@
<head>
<meta charset="UTF-8">
<title>LittleKt Game Template</title>
<style>
body {
background: #6c3ee2;
color: white;
}
a {
color: #f4325f;
background: #2e0e80;
padding: 15px;
border-radius: 5px;
}
#error-message {
font-size: 24px;
display: none;
}
#canvas {
border: 1px solid #000000;
}
</style>
</head>
<body>
<div id="canvas-container" style="text-align:center;">
<canvas id="canvas" width="960" height="540" style="border:1px solid #000000;"></canvas>
<h1>
LittleKt WebGPU Examples
</h1>
<p id="error-message">
Your browser does not currently support WebGPU. You must use Chrome Canary, Edge Canary, or Firefox Nightly and
enable WebGPU (See <a href="https://github.com/gpuweb/gpuweb/wiki/Implementation-Status">here</a>
for more details)
</p>
<div id="content">
<canvas id="canvas" width="960" height="540"></canvas>
</div>
</div>
</body><script type="text/javascript" src="game.js"></script>
</body>
<script type="application/javascript">
if (!navigator.gpu) {
document.body.className = 'error';
document.getElementById('content').style.display = "none";
document.getElementById('error-message').style.display = "initial";
}
</script>
<!-- Uncomment the below script tag if you want simple FPS tracking on screen -->
<!--
<script type="text/javascript" src="https://mrdoob.github.io/stats.js/build/stats.min.js" onload="if (navigator.gpu) {
var stats = new Stats();
document.getElementById('content').appendChild(stats.dom);
requestAnimationFrame(function loop() {
stats.update();
requestAnimationFrame(loop)
});
}"></script>
-->
<script type="text/javascript" src="game.js"></script>
</html>

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -1,72 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- http://localhost:80 -->
<meta http-equiv="origin-trial"
content="AjytAe1FWUIWH/RzqsOWlXYw7kGwt11ejbvR/ArmAU4QbXIPg69wzpniDhPByiOZ9pdNqsdMsXsoxggVtfGhwwMAAABOeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwIiwiZmVhdHVyZSI6IldlYkFzc2VtYmx5R0MiLCJleHBpcnkiOjE2OTg5Njk1OTl9">
<!-- http://localhost:8080 -->
<meta http-equiv="origin-trial"
content="AtSRFRWKTCcQHDFUUQL1VaVz9mjmsK1xm5k0S8etkmvMLa5TsiNeQsle/eDgdZVkm1PM1WmrSMx52kI8yAP2UgEAAABQeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlHQyIsImV4cGlyeSI6MTY5ODk2OTU5OX0=">
<!-- http://localhost:8081 -->
<meta http-equiv="origin-trial"
content="AspyK9xe4tO2aIvJWnr5HTRT9GqpGJID2qZjj+0OvhKHp9JrUP+92PF+Rt11Tyi7cKSEB7OFFGdeVs7Ns5TMhAUAAABQeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODEiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlHQyIsImV4cGlyeSI6MTY5ODk2OTU5OX0=">
<!-- http://localhost:8082 -->
<meta http-equiv="origin-trial"
content="AoPGqwBGEQW3twSDYvXt1JHz5zg7Z4o+Orwr8NxdVoXJ68zjwOi2C8/0w4xFYW6UUH8w97eIjykGOa/+jIBfTQMAAABQeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODIiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlHQyIsImV4cGlyeSI6MTY5ODk2OTU5OX0=">
<!-- http://localhost:8083 -->
<meta http-equiv="origin-trial"
content="AvV2HYiHMw7lpIVcmGMSVmm6xC0Gzu4W5xBnZExmfHtE5zyNKFszcyNGm5IfCFQ6swnMotbn16lS1ozkJ0QzEAEAAABQeyJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjgwODMiLCJmZWF0dXJlIjoiV2ViQXNzZW1ibHlHQyIsImV4cGlyeSI6MTY5ODk2OTU5OX0=">
<title>LittleKt - WASM</title>
<style>
#warning {
display: none;
}
#warning li {
padding-bottom: 15px;
}
#warning span.code {
font-family: monospace;
}
</style>
</head>
<body>
<div id="warning">
⚠️ Please make sure that your runtime environment supports the latest version of Wasm GC and Exception-Handling
proposals.
For more information, see <a href="https://kotl.in/wasm-help">https://kotl.in/wasm-help</a>.
<br/>
<br/>
<ul>
<li>For <b>Chrome</b> and <b>Chromium-based</b> browsers (Edge, Brave etc.), it <b>should just work</b> since
version 119.
</li>
<li>For <b>Firefox</b> 120 it <b>should just work</b>.</li>
<li>For <b>Firefox</b> 119:
<ol>
<li>Open <span class="code">about:config</span> in the browser.</li>
<li>Enable <strong>javascript.options.wasm_gc</strong>.</li>
<li>Refresh this page.</li>
</ol>
</li>
</ul>
</div>
<div id="canvas-container" style="text-align:center;">
<canvas id="canvas" width="960" height="540" style="border:1px solid #000000;"></canvas>
</div>
</body>
<script type="text/javascript" src="game.js"></script>
<script type="application/javascript">
const unhandledError = (event, error) => {
if (error instanceof WebAssembly.CompileError) {
document.getElementById("warning").style.display="initial";
}
}
addEventListener("error", (event) => unhandledError(event, event.error));
addEventListener("unhandledrejection", (event) => unhandledError(event, event.reason));
</script>
</html>

View file

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

View file

@ -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" }

View file

@ -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

View file

@ -1,5 +1,6 @@
pluginManagement {
repositories {
mavenLocal()
google()
gradlePluginPortal()
mavenCentral()