diff --git a/templates/Fun-and-Games-LibGDX/.gitignore b/templates/Fun-and-Games-LibGDX/.gitignore new file mode 100644 index 00000000..73042af8 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/.gitignore @@ -0,0 +1,4 @@ +# The bundle and its provenance record are generated by the publish workflow. +# Neither is ever committed — see templates/README.md, "Build provenance". +*.cgt +cgt-build.properties diff --git a/templates/Fun-and-Games-LibGDX/README.md b/templates/Fun-and-Games-LibGDX/README.md new file mode 100644 index 00000000..802abc4c --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/README.md @@ -0,0 +1,94 @@ +# Fun and Games LibGDX + +A [Code on the Go](https://github.com/appdevforall/CodeOnTheGo) template bundle that adds +**three small Android games** built with [libGDX](https://libgdx.com/) 1.13.1 to the New Project +screen. Each one creates a complete, playable game project that builds and runs on the device. +They are ports of the jMonkeyEngine games in [`../Fun-and-Games-JME`](../Fun-and-Games-JME). + +| Template | Directory | Language | Game | +|---|---|---|---| +| Demo (libGDX) | `demo/` | Java | A 3D Earth you drag to spin, an orbiting rocket with an exhaust trail and bloom glow, a starfield; tap for points | +| Block Game (libGDX) | `blockgame/` | Kotlin | Falling-block puzzle: drag to move, drag down to drop faster, tap to rotate, long-press to hard-drop | +| Bubble Wand (libGDX) | `bubblewand/` | Kotlin | First-person shooter: catch floating balloon animals in bubbles with an on-screen D-pad and **FIRE** button | + +## How it works + +There is no code. The bundle is a `.cgt` file: a zip of the three directories plus +`templates.json`, which Code on the Go reads directly. Install it under **Preferences → +Extensions Manager → Templates** with the **+** button, and the three templates appear in +**New Project**. Nothing to enable, no permissions. + +Files ending in `.peb` are rendered by Pebble at project-creation time and lose the suffix; +everything else is copied byte for byte. Besides `${{APP_NAME}}` and `${{PACKAGE_NAME}}`, these +templates take the toolchain from the IDE: `${{AGP_VERSION}}`, `${{KOTLIN_VERSION}}`, +`${{GRADLE_VERSION}}`, `${{COMPILE_SDK}}`, `${{TARGET_SDK}}`, `${{MIN_SDK}}` and the Java +compatibility tokens. + +## How the build works + +- **libGDX ships in the bundle.** `templates.json` lists the libGDX files under `libraries`, and + the bundle build downloads them from Maven Central and places them in each game's `app/libs/`: + `gdx-1.13.1.jar`, `gdx-jnigen-loader-2.5.2.jar` (which `gdx` needs), + `gdx-backend-android-1.13.1.aar`, and the `gdx-platform` natives jars for `armeabi-v7a` and + `arm64-v8a`. Each `app/build.gradle` uses those files, so a generated project does not fetch + libGDX. +- **Native libraries:** `libgdx.so` sits inside the per-ABI natives jars. A `copyAndroidNatives` + task in `app/build.gradle` unpacks them into `build/gdx-natives//` before AGP merges + native libraries. Builds include `armeabi-v7a` and `arm64-v8a` only. The unpacked files must not + go into `app/libs/`: a Copy task owns its whole destination, so Gradle would count the jars + there as its output and fail the compile for using them without depending on it. +- **AndroidX:** libGDX needs `androidx.core` at runtime. A local `.aar` does not carry its + dependencies, so `app/build.gradle` declares `androidx.core:core` directly. Code on the Go + resolves `androidx.*` only from its offline Maven repository, which lacks some of what newer + `androidx.core` releases pull in, so the templates force versions the repository has: + + | Module | Forced version | + |---|---| + | `androidx.core:core` | 1.9.0 | + | `androidx.collection:collection` | 1.4.2 | + | `androidx.annotation:annotation` | 1.8.1 | + | `androidx.lifecycle:lifecycle-runtime`, `lifecycle-common` | 2.5.1 | + + These match the versions Code on the Go's own AndroidX templates use. + +## Controls + +| Game | Controls | +|---|---| +| Demo | Drag to rotate the Earth · flick to spin with inertia · tap for +10 points | +| Block Game | Drag left/right to move · drag down to soft-drop · tap to rotate · long-press to hard-drop | +| Bubble Wand | Left half: drag to walk · right half: drag to look, tap to shoot · or use the D-pad and **FIRE** · **HIDE UI** toggles the buttons | + +A hardware keyboard also works (arrow keys, space, WASD). + +## Notes for template authors + +- **AGP 9 built-in Kotlin:** the Kotlin templates do not apply `org.jetbrains.kotlin.android` in + the app module (AGP 9 rejects it); `jvmTarget` is set through `kotlin { compilerOptions { } }`. +- **Pebble drops the newline right after `}}`.** A token at the end of a line needs a trailing + space (`compileSdk = ${{COMPILE_SDK}} `), or the next line is joined onto it. Do not strip the + trailing whitespace in these files. +- **Don't drop to libGDX 1.12.x.** It avoids AndroidX, but its `libgdx.so` isn't 16 KB-page + aligned, and Google Play Protect blocked the builds as harmful. libGDX 1.13.0 also uses + `androidx.core` at runtime without declaring it, so it crashes on launch without the pins above. +- **Changing the libGDX version** means changing three places together: the file names in each + `app/build.gradle.peb`, the `src` URLs and `sha256` values in `templates.json`, and + `gradle/libs.versions.toml.peb`. + +## Building + +The `.cgt` is **never committed**. The publish workflow generates it. To build one locally: + +```sh +./scripts/build-templates.sh Fun-and-Games-LibGDX +unzip -l dist/fun-and-games-libgdx.cgt +``` + +See [`../README.md`](../README.md) for authoring guidance and the format reference. + +## Credit + +Written by **James Turner**. Converted from the standalone +[FunandGames-libGDX](https://github.com/appdevforall/FunandGames-libGDX) repository to a template +bundle in this repository. The game source is unchanged; the build files now take libGDX from +`app/libs`. diff --git a/templates/Fun-and-Games-LibGDX/addon.json b/templates/Fun-and-Games-LibGDX/addon.json new file mode 100644 index 00000000..ef989a3b --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/addon.json @@ -0,0 +1,22 @@ +{ + "summary": "Adds three playable libGDX games to the New Project screen: a 3D demo, Block Game and Bubble Wand.", + "description": "Installs three small Android game projects built with libGDX, so you can start from a complete, playable game instead of an empty activity. Demo is a 3D Earth you spin by touch with an orbiting rocket, written in Java. Block Game is a falling-block puzzle and Bubble Wand is a first-person bubble shooter, both written in Kotlin. Each project builds and runs on the device. These are libGDX ports of the Fun and Games JME templates.", + "tags": [ + "games", + "libgdx", + "templates", + "kotlin", + "java" + ], + "origin": "appdevforall", + "license": "AGPL-3.0-or-later", + "author": { + "name": "James Turner", + "url": "" + }, + "template": { + "id": "org.appdevforall.funandgameslibgdx", + "version": "1.0.0", + "minAppVersion": "26.40" + } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/.gitignore b/templates/Fun-and-Games-LibGDX/blockgame/.gitignore new file mode 100644 index 00000000..bc3a162d --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/.gitignore @@ -0,0 +1,7 @@ +.gradle/ +build/ +local.properties +*.iml +.idea/ +.DS_Store +captures/ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/build.gradle.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/build.gradle.peb new file mode 100644 index 00000000..c80bb911 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/build.gradle.peb @@ -0,0 +1,101 @@ +plugins { + alias(libs.plugins.android.application) +} + +configurations { + natives +} + +// libGDX 1.13.x needs androidx.core at runtime. Pin its tree to versions that +// Code On The Go's offline Maven repository carries (the same ones its built-in +// AndroidX templates use); the newer androidx.core libGDX asks for pulls in +// modules (profileinstaller, collection 1.0.0, ...) that the repository lacks. +configurations.configureEach { + resolutionStrategy { + force "androidx.core:core:1.9.0" + force "androidx.collection:collection:1.4.2" + force "androidx.annotation:annotation:1.8.1" + force "androidx.lifecycle:lifecycle-runtime:2.5.1" + force "androidx.lifecycle:lifecycle-common:2.5.1" + } +} + +android { + namespace = "${{PACKAGE_NAME}}" + compileSdk = ${{COMPILE_SDK}} + + defaultConfig { + applicationId = "${{PACKAGE_NAME}}" + minSdk = ${{MIN_SDK}} + targetSdk = ${{TARGET_SDK}} + versionCode = 1 + versionName = "1.0" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + + sourceSets { + main { + // copyAndroidNatives unpacks libgdx.so here; app/libs holds only the shipped jars + jniLibs.srcDirs = [layout.buildDirectory.dir("gdx-natives").get().asFile] + } + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility = ${{JAVA_SOURCE_COMPAT}} + targetCompatibility = ${{JAVA_TARGET_COMPAT}} + } + + packaging { + jniLibs { + useLegacyPackaging = true + } + } +} + +// AGP 9+ compiles Kotlin itself (built-in Kotlin), so there is no kotlin-android plugin here +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget("${{JAVA_TARGET}}") + } +} + +dependencies { + implementation(files("libs/gdx-1.13.1.jar")) + implementation(files("libs/gdx-jnigen-loader-2.5.2.jar")) + implementation(files("libs/gdx-backend-android-1.13.1.aar")) + // gdx-backend-android needs androidx.core at runtime; a local .aar does not + // carry that dependency, so declare it here (pinned to 1.9.0 above). + implementation("androidx.core:core:1.9.0") + natives(files("libs/gdx-platform-1.13.1-natives-armeabi-v7a.jar")) + natives(files("libs/gdx-platform-1.13.1-natives-arm64-v8a.jar")) +} + +// libGDX ships its Android .so files inside per-ABI classifier jars; unpack them +// into build/gdx-natives// so AGP packages them as jniLibs. Written as a plain Copy +// task (no project access at execution time) so it works with the configuration cache. +tasks.register("copyAndroidNatives", Copy) { + def natives = configurations.natives + // Not app/libs: a Copy task owns its whole destination, so the jars there would + // count as its output and every compile task would need to depend on it. + into layout.buildDirectory.dir("gdx-natives") + ["armeabi-v7a", "arm64-v8a"].each { abi -> + def jar = natives.filter { it.name.endsWith("natives-" + abi + ".jar") } + from(zipTree { jar.singleFile }) { + include "*.so" + into abi + } + } +} + +tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { + it.dependsOn "copyAndroidNatives" +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/AndroidManifest.xml b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..734695f6 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/ic_launcher-playstore.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..b750a48d Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/ic_launcher-playstore.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/BlockGame.kt.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/BlockGame.kt.peb new file mode 100644 index 00000000..8a6a979c --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/BlockGame.kt.peb @@ -0,0 +1,318 @@ +package ${{PACKAGE_NAME}} + +import com.badlogic.gdx.ApplicationAdapter +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input +import com.badlogic.gdx.InputAdapter +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.GL20 +import com.badlogic.gdx.graphics.OrthographicCamera +import com.badlogic.gdx.graphics.g2d.BitmapFont +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.glutils.ShapeRenderer +import kotlin.math.abs +import kotlin.math.max + +private const val CELL_SIZE = 0.9f +private val LINE_SCORE = intArrayOf(0, 40, 100, 300, 1200) +private const val TAP_MAX_NANOS = 250_000_000L +private const val LONG_PRESS_MIN_NANOS = 400_000_000L +private const val DRAG_THRESHOLD_PIXELS = 6f + +private const val SCORE_TEXT_SCALE = 1.4f +private const val INFO_TEXT_SCALE = 1.1f +private const val MESSAGE_TEXT_SCALE = 1.6f + +enum class GameState { PLAYING, GAME_OVER } + +/** libGDX application implementing a self-contained falling-block puzzle game, rendered as filled rects under an orthographic camera. */ +class BlockGame : ApplicationAdapter() { + + private lateinit var camera: OrthographicCamera + private lateinit var shapes: ShapeRenderer + private lateinit var hudBatch: SpriteBatch + private lateinit var font: BitmapFont + + private var messageText = "" + + private val board = Board() + private val bag = ArrayDeque() + private lateinit var current: Piece + private lateinit var nextType: PieceType + + private var state = GameState.PLAYING + private var score = 0 + private var level = 1 + private var linesCleared = 0 + private var dropInterval = 1.0f + private var dropTimer = 0f + + private var viewWidth = 1f + + private var trackedPointer = -1 + private var downX = 0f + private var downY = 0f + private var lastX = 0f + private var lastY = 0f + private var downTimeNanos = 0L + private var dragged = false + + override fun create() { + camera = OrthographicCamera() + shapes = ShapeRenderer() + hudBatch = SpriteBatch() + font = BitmapFont() + Gdx.input.inputProcessor = input + + setUpCamera(Gdx.graphics.width, Gdx.graphics.height) + startNewGame() + } + + override fun resize(width: Int, height: Int) { + setUpCamera(width, height) + hudBatch.projectionMatrix.setToOrtho2D(0f, 0f, width.toFloat(), height.toFloat()) + } + + private fun setUpCamera(width: Int, height: Int) { + val aspect = width.toFloat() / height.toFloat() + val viewHeight = BOARD_ROWS + 2f + viewWidth = viewHeight * aspect + // Centred on the origin with y up, like the jME parallel-projection frustum + camera.setToOrtho(false, viewWidth, viewHeight) + camera.position.set(0f, 0f, 0f) + camera.update() + } + + private fun nextFromBag(): PieceType { + if (bag.isEmpty()) bag.addAll(PieceType.entries.shuffled()) + return bag.removeFirst() + } + + private fun startNewGame() { + board.reset() + score = 0 + level = 1 + linesCleared = 0 + dropInterval = 1.0f + dropTimer = 0f + bag.clear() + nextType = nextFromBag() + state = GameState.PLAYING + messageText = "" + spawnPiece() + } + + private fun spawnPiece() { + val type = nextType + nextType = nextFromBag() + current = Piece(type, 0, (BOARD_COLS - 4) / 2, 0) + if (!board.isValidPosition(current)) { + state = GameState.GAME_OVER + messageText = "GAME OVER\nTap to restart" + } + } + + private fun tryMove(dCol: Int, dRow: Int): Boolean { + val nc = current.col + dCol + val nr = current.row + dRow + if (board.isValidPosition(current, current.rotation, nc, nr)) { + current.col = nc + current.row = nr + return true + } + return false + } + + private fun tryRotate() { + val nr = (current.rotation + 1) % 4 + if (board.isValidPosition(current, nr)) { + current.rotation = nr + return + } + for (dx in intArrayOf(-1, 1, -2, 2)) { + if (board.isValidPosition(current, nr, current.col + dx, current.row)) { + current.rotation = nr + current.col += dx + return + } + } + } + + private fun softDrop(rows: Int) { + for (i in 0 until rows) { + if (state != GameState.PLAYING) return + if (tryMove(0, 1)) { + score += 1 + } else { + lockAndProceed() + return + } + } + } + + private fun hardDrop() { + var dropped = 0 + while (tryMove(0, 1)) dropped++ + score += dropped * 2 + lockAndProceed() + } + + private fun lockAndProceed() { + board.lockPiece(current) + val cleared = board.clearLines() + if (cleared > 0) { + linesCleared += cleared + score += LINE_SCORE[cleared] * level + level = linesCleared / 10 + 1 + dropInterval = max(0.1f, 1.0f - (level - 1) * 0.075f) + } + spawnPiece() + } + + override fun render() { + val tpf = Gdx.graphics.deltaTime + if (state == GameState.PLAYING) { + dropTimer += tpf + if (dropTimer >= dropInterval) { + dropTimer = 0f + if (!tryMove(0, 1)) lockAndProceed() + } + } + + Gdx.gl.glClearColor(0f, 0f, 0f, 1f) + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT) + renderBoard() + renderHud() + } + + private fun renderHud() { + val height = Gdx.graphics.height.toFloat() + hudBatch.begin() + + font.data.setScale(SCORE_TEXT_SCALE) + font.color = Color.WHITE + font.draw(hudBatch, "Score: $score", 10f, height - 10f) + val scoreLineHeight = font.lineHeight + + font.data.setScale(INFO_TEXT_SCALE) + font.draw( + hudBatch, "Level: $level Lines: $linesCleared Next: ${nextType.name}", + 10f, height - 10f - scoreLineHeight * 1.4f + ) + + if (messageText.isNotEmpty()) { + font.data.setScale(MESSAGE_TEXT_SCALE) + font.color = Color.YELLOW + font.draw(hudBatch, messageText, 20f, height / 2f + 40f) + } + + hudBatch.end() + } + + private fun drawCell(col: Int, row: Int, color: Color) { + val cx = col - BOARD_COLS / 2f + 0.5f + val cy = BOARD_ROWS / 2f - row - 0.5f + shapes.color = color + shapes.rect(cx - CELL_SIZE / 2f, cy - CELL_SIZE / 2f, CELL_SIZE, CELL_SIZE) + } + + private fun renderBoard() { + shapes.projectionMatrix = camera.combined + shapes.begin(ShapeRenderer.ShapeType.Filled) + for (r in 0 until BOARD_ROWS) { + for (c in 0 until BOARD_COLS) { + val color = board.grid[r][c] ?: continue + drawCell(c, r, color) + } + } + + if (state == GameState.PLAYING) { + for ((c, r) in current.boardCells()) { + if (r >= 0) drawCell(c, r, current.type.color) + } + } + shapes.end() + } + + override fun dispose() { + shapes.dispose() + hudBatch.dispose() + font.dispose() + } + + // --- Input: keyboard is a convenience for desktop/emulator testing, touch is primary. + // Screen y is flipped to bottom-up so the drag math matches the original jME TouchEvent coordinates. + private val input = object : InputAdapter() { + + override fun keyDown(keycode: Int): Boolean { + if (state == GameState.GAME_OVER) { + if (keycode == Input.Keys.ENTER || keycode == Input.Keys.SPACE) startNewGame() + return true + } + when (keycode) { + Input.Keys.LEFT -> tryMove(-1, 0) + Input.Keys.RIGHT -> tryMove(1, 0) + Input.Keys.DOWN -> softDrop(1) + Input.Keys.UP -> tryRotate() + Input.Keys.SPACE -> hardDrop() + else -> return false + } + return true + } + + // Drag left/right to move, drag down to soft-drop, tap to rotate, long-press to hard-drop. + override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (trackedPointer == -1) { + val x = screenX.toFloat() + val y = Gdx.graphics.height - screenY.toFloat() + trackedPointer = pointer + downX = x + downY = y + lastX = x + lastY = y + downTimeNanos = System.nanoTime() + dragged = false + } + return true + } + + override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean { + if (pointer != trackedPointer) return false + val x = screenX.toFloat() + val y = Gdx.graphics.height - screenY.toFloat() + val dx = x - lastX + val dy = y - lastY + if (abs(dx) > DRAG_THRESHOLD_PIXELS || abs(dy) > DRAG_THRESHOLD_PIXELS) dragged = true + if (state == GameState.PLAYING) { + val pixelsPerCell = Gdx.graphics.width / viewWidth + val colsDelta = (dx / pixelsPerCell).toInt() + if (colsDelta != 0) { + repeat(abs(colsDelta)) { tryMove(if (colsDelta > 0) 1 else -1, 0) } + lastX += colsDelta * pixelsPerCell + } + val rowsDelta = (-dy / pixelsPerCell).toInt() + if (rowsDelta > 0) { + softDrop(rowsDelta) + lastY -= rowsDelta * pixelsPerCell + } + } + return true + } + + override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (pointer != trackedPointer) return false + val elapsed = System.nanoTime() - downTimeNanos + if (!dragged) { + if (state == GameState.GAME_OVER) { + startNewGame() + } else if (elapsed >= LONG_PRESS_MIN_NANOS) { + hardDrop() + } else if (elapsed <= TAP_MAX_NANOS) { + tryRotate() + } + } + trackedPointer = -1 + return true + } + } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Board.kt.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Board.kt.peb new file mode 100644 index 00000000..47fdcec7 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Board.kt.peb @@ -0,0 +1,55 @@ +package ${{PACKAGE_NAME}} + +import com.badlogic.gdx.graphics.Color + +const val BOARD_COLS = 10 +const val BOARD_ROWS = 20 + +class Board { + val grid: Array> = Array(BOARD_ROWS) { arrayOfNulls(BOARD_COLS) } + + fun isValidPosition( + piece: Piece, + rot: Int = piece.rotation, + col: Int = piece.col, + row: Int = piece.row + ): Boolean { + for ((c, r) in piece.boardCells(rot, col, row)) { + if (c < 0 || c >= BOARD_COLS || r >= BOARD_ROWS) return false + if (r >= 0 && grid[r][c] != null) return false + } + return true + } + + fun lockPiece(piece: Piece) { + for ((c, r) in piece.boardCells()) { + if (r in 0 until BOARD_ROWS && c in 0 until BOARD_COLS) { + grid[r][c] = piece.type.color + } + } + } + + /** Clears completed rows, shifting everything above down. Returns the number of rows cleared. */ + fun clearLines(): Int { + var cleared = 0 + var r = BOARD_ROWS - 1 + while (r >= 0) { + if (grid[r].all { it != null }) { + for (rr in r downTo 1) { + grid[rr] = grid[rr - 1].copyOf() + } + grid[0] = arrayOfNulls(BOARD_COLS) + cleared++ + } else { + r-- + } + } + return cleared + } + + fun reset() { + for (r in 0 until BOARD_ROWS) { + grid[r] = arrayOfNulls(BOARD_COLS) + } + } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb new file mode 100644 index 00000000..b129194d --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb @@ -0,0 +1,13 @@ +package ${{PACKAGE_NAME}} + +import android.os.Bundle +import com.badlogic.gdx.backends.android.AndroidApplication +import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration + +class MainActivity : AndroidApplication() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val config = AndroidApplicationConfiguration().apply { useImmersiveMode = true } + initialize(BlockGame(), config) + } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Pieces.kt.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Pieces.kt.peb new file mode 100644 index 00000000..b4b110d7 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/java/PACKAGE_NAME/Pieces.kt.peb @@ -0,0 +1,67 @@ +package ${{PACKAGE_NAME}} + +import com.badlogic.gdx.graphics.Color + +enum class PieceType(val color: Color) { + I(Color(0f, 0.9f, 0.9f, 1f)), + O(Color(0.9f, 0.9f, 0f, 1f)), + T(Color(0.6f, 0f, 0.9f, 1f)), + S(Color(0f, 0.8f, 0f, 1f)), + Z(Color(0.9f, 0f, 0f, 1f)), + J(Color(0.1f, 0.2f, 0.9f, 1f)), + L(Color(0.9f, 0.5f, 0f, 1f)) +} + +// Each rotation state lists the 4 occupied (col, row) cells within a 4x4 bounding box. +val SHAPES: Map>> = mapOf( + PieceType.I to listOf( + listOf(intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(3, 1)), + listOf(intArrayOf(2, 0), intArrayOf(2, 1), intArrayOf(2, 2), intArrayOf(2, 3)), + listOf(intArrayOf(0, 2), intArrayOf(1, 2), intArrayOf(2, 2), intArrayOf(3, 2)), + listOf(intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(1, 2), intArrayOf(1, 3)) + ), + PieceType.O to listOf( + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(2, 1)) + ), + PieceType.T to listOf( + listOf(intArrayOf(1, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(1, 2)), + listOf(intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(1, 2)), + listOf(intArrayOf(1, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(1, 2)) + ), + PieceType.S to listOf( + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(0, 1), intArrayOf(1, 1)), + listOf(intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(2, 2)), + listOf(intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(0, 2), intArrayOf(1, 2)), + listOf(intArrayOf(0, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(1, 2)) + ), + PieceType.Z to listOf( + listOf(intArrayOf(0, 0), intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(1, 2)), + listOf(intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(1, 2), intArrayOf(2, 2)), + listOf(intArrayOf(1, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(0, 2)) + ), + PieceType.J to listOf( + listOf(intArrayOf(0, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(2, 0), intArrayOf(1, 1), intArrayOf(1, 2)), + listOf(intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(2, 2)), + listOf(intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(0, 2), intArrayOf(1, 2)) + ), + PieceType.L to listOf( + listOf(intArrayOf(2, 0), intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1)), + listOf(intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(1, 2), intArrayOf(2, 2)), + listOf(intArrayOf(0, 1), intArrayOf(1, 1), intArrayOf(2, 1), intArrayOf(0, 2)), + listOf(intArrayOf(0, 0), intArrayOf(1, 0), intArrayOf(1, 1), intArrayOf(1, 2)) + ) +) + +class Piece(val type: PieceType, var rotation: Int = 0, var col: Int = 3, var row: Int = 0) { + + fun cells(rot: Int = rotation): List = SHAPES.getValue(type)[rot.mod(4)] + + fun boardCells(rot: Int = rotation, atCol: Int = col, atRow: Int = row): List> = + cells(rot).map { cell -> Pair(atCol + cell[0], atRow + cell[1]) } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-hdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..2e787e24 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-mdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..8902e3ef Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..fcb4051a Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..5cf46d89 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..8f971df4 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/values/strings.xml.peb b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/values/strings.xml.peb new file mode 100644 index 00000000..a1147ffc --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/app/src/main/res/values/strings.xml.peb @@ -0,0 +1,3 @@ + + ${{APP_NAME}} + diff --git a/templates/Fun-and-Games-LibGDX/blockgame/build.gradle b/templates/Fun-and-Games-LibGDX/blockgame/build.gradle new file mode 100644 index 00000000..82fbdc4c --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/build.gradle @@ -0,0 +1,5 @@ +// Top-level build file +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradle.properties b/templates/Fun-and-Games-LibGDX/blockgame/gradle.properties new file mode 100644 index 00000000..729b51ac --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true +kotlin.code.style=official diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradle/libs.versions.toml.peb b/templates/Fun-and-Games-LibGDX/blockgame/gradle/libs.versions.toml.peb new file mode 100644 index 00000000..db153df7 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/gradle/libs.versions.toml.peb @@ -0,0 +1,13 @@ +[versions] +agp = "${{AGP_VERSION}}" +kotlin = "${{KOTLIN_VERSION}}" +gdx = "1.13.1" + +[libraries] +gdx-core = { group = "com.badlogicgames.gdx", name = "gdx", version.ref = "gdx" } +gdx-backend-android = { group = "com.badlogicgames.gdx", name = "gdx-backend-android", version.ref = "gdx" } +gdx-platform = { group = "com.badlogicgames.gdx", name = "gdx-platform", version.ref = "gdx" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.jar b/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.jar differ diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.properties.peb b/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.properties.peb new file mode 100644 index 00000000..f62f9d39 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/gradle/wrapper/gradle-wrapper.properties.peb @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-${{GRADLE_VERSION}}-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradlew b/templates/Fun-and-Games-LibGDX/blockgame/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/templates/Fun-and-Games-LibGDX/blockgame/gradlew.bat b/templates/Fun-and-Games-LibGDX/blockgame/gradlew.bat new file mode 100644 index 00000000..25da30db --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templates/Fun-and-Games-LibGDX/blockgame/settings.gradle.peb b/templates/Fun-and-Games-LibGDX/blockgame/settings.gradle.peb new file mode 100644 index 00000000..0a6e40d2 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/settings.gradle.peb @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "${{APP_NAME}}" +include(":app") diff --git a/templates/Fun-and-Games-LibGDX/blockgame/template/template.json b/templates/Fun-and-Games-LibGDX/blockgame/template/template.json new file mode 100644 index 00000000..c709d5a1 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/blockgame/template/template.json @@ -0,0 +1,49 @@ +{ + "name": "Block Game (libGDX)", + "description": "Creates a falling-block puzzle game (libGDX)", + "version": "0.1", + "parameters": { + "required": { + "appName": { + "identifier": "APP_NAME" + }, + "packageName": { + "identifier": "PACKAGE_NAME" + }, + "saveLocation": { + "identifier": "SAVE_LOCATION" + } + }, + "optional": { + "minsdk": { + "identifier": "MIN_SDK" + } + } + }, + "system": { + "agpVersion": { + "identifier": "AGP_VERSION" + }, + "kotlinVersion": { + "identifier": "KOTLIN_VERSION" + }, + "gradleVersion": { + "identifier": "GRADLE_VERSION" + }, + "compileSdk": { + "identifier": "COMPILE_SDK" + }, + "targetSdk": { + "identifier": "TARGET_SDK" + }, + "javaSourceCompat": { + "identifier": "JAVA_SOURCE_COMPAT" + }, + "javaTargetCompat": { + "identifier": "JAVA_TARGET_COMPAT" + }, + "javaTarget": { + "identifier": "JAVA_TARGET" + } + } +} diff --git a/templates/Fun-and-Games-LibGDX/blockgame/template/thumb.png b/templates/Fun-and-Games-LibGDX/blockgame/template/thumb.png new file mode 100644 index 00000000..b750a48d Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/blockgame/template/thumb.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/.gitignore b/templates/Fun-and-Games-LibGDX/bubblewand/.gitignore new file mode 100644 index 00000000..bc3a162d --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/.gitignore @@ -0,0 +1,7 @@ +.gradle/ +build/ +local.properties +*.iml +.idea/ +.DS_Store +captures/ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/build.gradle.peb b/templates/Fun-and-Games-LibGDX/bubblewand/app/build.gradle.peb new file mode 100644 index 00000000..c80bb911 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/build.gradle.peb @@ -0,0 +1,101 @@ +plugins { + alias(libs.plugins.android.application) +} + +configurations { + natives +} + +// libGDX 1.13.x needs androidx.core at runtime. Pin its tree to versions that +// Code On The Go's offline Maven repository carries (the same ones its built-in +// AndroidX templates use); the newer androidx.core libGDX asks for pulls in +// modules (profileinstaller, collection 1.0.0, ...) that the repository lacks. +configurations.configureEach { + resolutionStrategy { + force "androidx.core:core:1.9.0" + force "androidx.collection:collection:1.4.2" + force "androidx.annotation:annotation:1.8.1" + force "androidx.lifecycle:lifecycle-runtime:2.5.1" + force "androidx.lifecycle:lifecycle-common:2.5.1" + } +} + +android { + namespace = "${{PACKAGE_NAME}}" + compileSdk = ${{COMPILE_SDK}} + + defaultConfig { + applicationId = "${{PACKAGE_NAME}}" + minSdk = ${{MIN_SDK}} + targetSdk = ${{TARGET_SDK}} + versionCode = 1 + versionName = "1.0" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + + sourceSets { + main { + // copyAndroidNatives unpacks libgdx.so here; app/libs holds only the shipped jars + jniLibs.srcDirs = [layout.buildDirectory.dir("gdx-natives").get().asFile] + } + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility = ${{JAVA_SOURCE_COMPAT}} + targetCompatibility = ${{JAVA_TARGET_COMPAT}} + } + + packaging { + jniLibs { + useLegacyPackaging = true + } + } +} + +// AGP 9+ compiles Kotlin itself (built-in Kotlin), so there is no kotlin-android plugin here +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.fromTarget("${{JAVA_TARGET}}") + } +} + +dependencies { + implementation(files("libs/gdx-1.13.1.jar")) + implementation(files("libs/gdx-jnigen-loader-2.5.2.jar")) + implementation(files("libs/gdx-backend-android-1.13.1.aar")) + // gdx-backend-android needs androidx.core at runtime; a local .aar does not + // carry that dependency, so declare it here (pinned to 1.9.0 above). + implementation("androidx.core:core:1.9.0") + natives(files("libs/gdx-platform-1.13.1-natives-armeabi-v7a.jar")) + natives(files("libs/gdx-platform-1.13.1-natives-arm64-v8a.jar")) +} + +// libGDX ships its Android .so files inside per-ABI classifier jars; unpack them +// into build/gdx-natives// so AGP packages them as jniLibs. Written as a plain Copy +// task (no project access at execution time) so it works with the configuration cache. +tasks.register("copyAndroidNatives", Copy) { + def natives = configurations.natives + // Not app/libs: a Copy task owns its whole destination, so the jars there would + // count as its output and every compile task would need to depend on it. + into layout.buildDirectory.dir("gdx-natives") + ["armeabi-v7a", "arm64-v8a"].each { abi -> + def jar = natives.filter { it.name.endsWith("natives-" + abi + ".jar") } + from(zipTree { jar.singleFile }) { + include "*.so" + into abi + } + } +} + +tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { + it.dependsOn "copyAndroidNatives" +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/AndroidManifest.xml b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0fa308f2 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/ic_launcher-playstore.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..332d9aee Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/ic_launcher-playstore.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/BubbleWandGame.kt.peb b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/BubbleWandGame.kt.peb new file mode 100644 index 00000000..1e634073 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/BubbleWandGame.kt.peb @@ -0,0 +1,711 @@ +package ${{PACKAGE_NAME}} + +import com.badlogic.gdx.ApplicationAdapter +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.Input +import com.badlogic.gdx.InputAdapter +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.GL20 +import com.badlogic.gdx.graphics.PerspectiveCamera +import com.badlogic.gdx.graphics.VertexAttributes.Usage +import com.badlogic.gdx.graphics.g2d.BitmapFont +import com.badlogic.gdx.graphics.g2d.GlyphLayout +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import com.badlogic.gdx.graphics.g3d.Environment +import com.badlogic.gdx.graphics.g3d.Material +import com.badlogic.gdx.graphics.g3d.Model +import com.badlogic.gdx.graphics.g3d.ModelBatch +import com.badlogic.gdx.graphics.g3d.ModelInstance +import com.badlogic.gdx.graphics.g3d.attributes.BlendingAttribute +import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute +import com.badlogic.gdx.graphics.g3d.attributes.DepthTestAttribute +import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute +import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight +import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.SphereShapeBuilder +import com.badlogic.gdx.graphics.glutils.ShapeRenderer +import com.badlogic.gdx.math.MathUtils +import com.badlogic.gdx.math.Quaternion +import com.badlogic.gdx.math.Vector3 +import com.badlogic.gdx.utils.Align +import kotlin.math.abs + +private const val ARENA_HALF_SIZE = 25f +private const val PLAYER_EYE_HEIGHT = 1.7f +private const val MOVE_SPEED = 6f +private const val TURN_RATE = 1.6f +private const val LOOK_RATE = 1.2f +private const val LOOK_SENSITIVITY = 0.0011f +private const val PITCH_LIMIT = 80f * MathUtils.degreesToRadians +private const val JOYSTICK_MAX_RADIUS = 120f +private const val DRAG_THRESHOLD_PIXELS = 6f +private const val TAP_MAX_NANOS = 250_000_000L +private const val BUBBLE_SPEED = 18f +private const val BUBBLE_RADIUS = 0.25f +private const val BUBBLE_MAX_DISTANCE = 60f +private const val TARGET_RADIUS = 0.75f +private const val ENCASE_BUBBLE_RADIUS = 1.4f +private const val FLOAT_SPEED = 3f +private const val FLOAT_REMOVE_HEIGHT = 25f +private const val TARGET_COUNT = 8 +private const val TREE_COUNT = 14 +private const val ROCK_COUNT = 10 +private const val MOUNTAIN_COUNT = 10 + +private val MESH_ATTRIBUTES = (Usage.Position or Usage.Normal).toLong() + +private const val TARGETS_TEXT_SCALE = 1.3f +private const val MESSAGE_TEXT_SCALE = 1.6f +private const val BUTTON_TEXT_SCALE = 1.6f +private const val TOGGLE_TEXT_SCALE = 1f + +private val BALLOON_COLORS = arrayOf( + Color(1f, 0.25f, 0.4f, 1f), + Color(1f, 0.55f, 0.1f, 1f), + Color(1f, 0.85f, 0.1f, 1f), + Color(0.25f, 0.8f, 0.35f, 1f), + Color(0.2f, 0.55f, 1f, 1f), + Color(0.65f, 0.3f, 0.9f, 1f), + Color(1f, 0.5f, 0.75f, 1f) +) + +private class Bubble(val instance: ModelInstance, val position: Vector3, val velocity: Vector3, val spawnPos: Vector3) + +private class ButtonRect(val x: Float, val y: Float, val w: Float, val h: Float) { + fun contains(px: Float, py: Float) = px in x..(x + w) && py in y..(y + h) +} + +private class Button(val label: String, val rect: ButtonRect, val color: Color) + +private val TOGGLE_COLOR = Color(0.2f, 0.2f, 0.2f, 0.5f) + +/** First-person "bubble wand" shooter: hit floating targets with bubbles to encase and remove them. */ +class BubbleWandGame : ApplicationAdapter() { + + private lateinit var cam: PerspectiveCamera + private lateinit var modelBatch: ModelBatch + private lateinit var environment: Environment + private lateinit var shapes: ShapeRenderer + private lateinit var hudBatch: SpriteBatch + private lateinit var font: BitmapFont + private val glyphs = GlyphLayout() + + private val models = mutableListOf() + private lateinit var unitSphere: Model + private lateinit var balloonDogModels: List + private lateinit var wandHandle: ModelInstance + private lateinit var wandTip: ModelInstance + + // Lit scenery that never moves, plus the per-frame lists + private val scenery = mutableListOf() + private val targets = mutableListOf() + private val bubbles = mutableListOf() + private var gameWon = false + private var messageText = "" + + private var yaw = 0f + private var pitch = 0f + private val camRotation = Quaternion() + private val camLeft = Vector3() + + private var moveTouchId = -1 + private var moveStartX = 0f + private var moveStartY = 0f + private var moveDX = 0f + private var moveDY = 0f + + private var lookTouchId = -1 + private var lookDownX = 0f + private var lookDownY = 0f + private var lookLastX = 0f + private var lookLastY = 0f + private var lookDownTimeNanos = 0L + private var lookDragged = false + + private var keyForward = false + private var keyBack = false + private var keyLeft = false + private var keyRight = false + private var keyLookLeft = false + private var keyLookRight = false + private var keyLookUp = false + private var keyLookDown = false + + private var toggleRect = ButtonRect(0f, 0f, 0f, 0f) + private var buttonsVisible = true + + private val buttons = linkedMapOf() + private var btnUpPointer = -1 + private var btnDownPointer = -1 + private var btnLeftPointer = -1 + private var btnRightPointer = -1 + private var btnShootPointer = -1 + + override fun create() { + cam = PerspectiveCamera(40f, Gdx.graphics.width.toFloat(), Gdx.graphics.height.toFloat()) + cam.near = 0.05f + cam.far = 1000f + + modelBatch = ModelBatch() + shapes = ShapeRenderer() + hudBatch = SpriteBatch() + font = BitmapFont() + Gdx.input.inputProcessor = input + + val builder = ModelBuilder() + unitSphere = builder.createSphere(2f, 2f, 2f, 10, 10, Material(), MESH_ATTRIBUTES).also(models::add) + + setUpLights() + setUpGround(builder) + setUpLandscape() + setUpWand(builder) + balloonDogModels = BALLOON_COLORS.map { createBalloonDog(builder, it).also(models::add) } + layoutHud(Gdx.graphics.width, Gdx.graphics.height) + startNewGame() + } + + private fun litMaterial(color: Color, specular: Color = Color.WHITE, shininess: Float = 32f) = Material( + ColorAttribute.createDiffuse(color), + ColorAttribute.createSpecular(specular), + FloatAttribute.createShininess(shininess) + ) + + private fun unshadedMaterial(color: Color) = Material(ColorAttribute.createDiffuse(color)) + + /** Alpha-blended, no depth writes — the equivalent of jME's Transparent bucket + BlendMode.Alpha. */ + private fun transparentMaterial(color: Color) = Material( + ColorAttribute.createDiffuse(color.r, color.g, color.b, 1f), + BlendingAttribute(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA, color.a), + DepthTestAttribute(GL20.GL_LEQUAL, false) + ) + + /** A new instance of the shared unit sphere; instances copy materials, so each can be recoloured independently. */ + private fun sphereInstance(material: Material): ModelInstance { + val instance = ModelInstance(unitSphere) + instance.materials.first().set(material) + return instance + } + + private fun setUpLights() { + environment = Environment() + environment.set(ColorAttribute(ColorAttribute.AmbientLight, 0.5f, 0.5f, 0.55f, 1f)) + environment.add(DirectionalLight().set(Color.WHITE, Vector3(-0.4f, -1f, -0.3f).nor())) + } + + private fun setUpGround(builder: ModelBuilder) { + val groundModel = builder.createBox( + ARENA_HALF_SIZE * 2f, 0.2f, ARENA_HALF_SIZE * 2f, + litMaterial(Color(0.25f, 0.55f, 0.25f, 1f), Color.BLACK, 1f), + MESH_ATTRIBUTES + ).also(models::add) + val ground = ModelInstance(groundModel) + ground.transform.setToTranslation(0f, -0.1f, 0f) + scenery.add(ground) + } + + private fun setUpWand(builder: ModelBuilder) { + val handleModel = builder.createBox( + 0.04f, 0.04f, 0.44f, + litMaterial(Color(0.35f, 0.22f, 0.1f, 1f)), + MESH_ATTRIBUTES + ).also(models::add) + wandHandle = ModelInstance(handleModel) + wandTip = sphereInstance(unshadedMaterial(Color(0.6f, 0.9f, 1f, 1f))) + } + + /** Scatters trees, rocks and distant mountains around the arena for visual interest. */ + private fun setUpLandscape() { + val trunkMat = litMaterial(Color(0.4f, 0.26f, 0.12f, 1f), Color.BLACK, 4f) + val foliageMat = litMaterial(Color(0.15f, 0.5f, 0.2f, 1f), Color.BLACK, 4f) + val rockMat = litMaterial(Color(0.5f, 0.5f, 0.52f, 1f), Color.WHITE, 8f) + val mountainMat = litMaterial(Color(0.45f, 0.42f, 0.55f, 1f), Color.BLACK, 2f) + + repeat(TREE_COUNT) { + val angle = MathUtils.random() * MathUtils.PI2 + val dist = ARENA_HALF_SIZE - 1f - MathUtils.random() * 6f + val x = MathUtils.cos(angle) * dist + val z = MathUtils.sin(angle) * dist + + val trunk = sphereInstance(trunkMat) + trunk.transform.setToTranslation(x, 1.1f, z).scale(0.25f, 1.1f, 0.25f) + scenery.add(trunk) + + val foliageScale = 1f + MathUtils.random() * 0.4f + val foliage = sphereInstance(foliageMat) + foliage.transform.setToTranslation(x, 2.4f, z) + .scale(0.9f * foliageScale, 1.1f * foliageScale, 0.9f * foliageScale) + scenery.add(foliage) + } + + repeat(ROCK_COUNT) { + val angle = MathUtils.random() * MathUtils.PI2 + val dist = 4f + MathUtils.random() * (ARENA_HALF_SIZE - 5f) + val x = MathUtils.cos(angle) * dist + val z = MathUtils.sin(angle) * dist + val scale = 0.3f + MathUtils.random() * 0.5f + + val rock = sphereInstance(rockMat) + rock.transform.setToTranslation(x, scale * 0.3f, z).scale(scale, scale * 0.7f, scale) + scenery.add(rock) + } + + repeat(MOUNTAIN_COUNT) { i -> + val angle = (MathUtils.PI2 / MOUNTAIN_COUNT) * i + MathUtils.random() * 0.3f + val dist = ARENA_HALF_SIZE + 6f + MathUtils.random() * 10f + val x = MathUtils.cos(angle) * dist + val z = MathUtils.sin(angle) * dist + val height = 10f + MathUtils.random() * 10f + val width = 6f + MathUtils.random() * 6f + + val mountain = sphereInstance(mountainMat) + mountain.transform.setToTranslation(x, -height * 0.3f, z).scale(width, height, width) + scenery.add(mountain) + } + } + + /** Builds a cute single-color balloon-animal (dog) out of stretched spheres, all sharing one material. */ + private fun createBalloonDog(builder: ModelBuilder, color: Color): Model { + val mat = litMaterial(color, Color.WHITE, 96f) + builder.begin() + var partIndex = 0 + + fun part(sx: Float, sy: Float, sz: Float, px: Float, py: Float, pz: Float) { + val node = builder.node() + node.id = "part${partIndex++}" + node.scale.set(sx, sy, sz) + node.translation.set(px, py, pz) + val mesh = builder.part(node.id, GL20.GL_TRIANGLES, MESH_ATTRIBUTES, mat) + SphereShapeBuilder.build(mesh, 2f, 2f, 2f, 10, 10) + } + + part(0.55f, 0.28f, 0.28f, 0f, 0f, 0f) // body + part(0.3f, 0.3f, 0.3f, 0.68f, 0.14f, 0f) // head + part(0.18f, 0.16f, 0.16f, 0.95f, 0.04f, 0f) // snout + part(0.09f, 0.18f, 0.09f, 0.75f, 0.38f, 0.14f) // ear + part(0.09f, 0.18f, 0.09f, 0.75f, 0.38f, -0.14f) // ear + part(0.22f, 0.09f, 0.09f, -0.68f, 0.22f, 0f) // tail + part(0.08f, 0.26f, 0.08f, 0.35f, -0.32f, 0.16f) // leg + part(0.08f, 0.26f, 0.08f, 0.35f, -0.32f, -0.16f) // leg + part(0.08f, 0.26f, 0.08f, -0.35f, -0.32f, 0.16f) // leg + part(0.08f, 0.26f, 0.08f, -0.35f, -0.32f, -0.16f) // leg + + return builder.end() + } + + override fun resize(width: Int, height: Int) { + cam.viewportWidth = width.toFloat() + cam.viewportHeight = height.toFloat() + cam.update() + shapes.projectionMatrix.setToOrtho2D(0f, 0f, width.toFloat(), height.toFloat()) + hudBatch.projectionMatrix.setToOrtho2D(0f, 0f, width.toFloat(), height.toFloat()) + layoutHud(width, height) + } + + /** Lays out the on-screen D-pad (move), fire button and show/hide toggle, recording their hit-test bounds. */ + private fun layoutHud(width: Int, height: Int) { + buttons.clear() + + val size = 100f + val gap = 12f + val padding = 24f + val moveColor = Color(1f, 1f, 1f, 0.28f) + + buttons["up"] = Button("^", ButtonRect(padding + size + gap, padding + (size + gap) * 2, size, size), moveColor) + buttons["left"] = Button("<", ButtonRect(padding, padding + size + gap, size, size), moveColor) + buttons["right"] = Button(">", ButtonRect(padding + (size + gap) * 2, padding + size + gap, size, size), moveColor) + buttons["down"] = Button("v", ButtonRect(padding + size + gap, padding, size, size), moveColor) + + val shootSize = 140f + buttons["shoot"] = Button( + "FIRE", + ButtonRect(width - padding - shootSize, padding, shootSize, shootSize), + Color(1f, 0.3f, 0.3f, 0.4f) + ) + + // A small always-visible control, independent of the buttons above, that shows/hides them + val toggleSize = 70f + toggleRect = ButtonRect(width - padding - toggleSize, height - padding - toggleSize, toggleSize, toggleSize) + } + + private fun setButtonsVisible(visible: Boolean) { + buttonsVisible = visible + if (!visible) { + btnUpPointer = -1 + btnDownPointer = -1 + btnLeftPointer = -1 + btnRightPointer = -1 + btnShootPointer = -1 + keyForward = false + keyBack = false + keyLeft = false + keyRight = false + } + } + + private fun hitButton(x: Float, y: Float): String? { + for ((name, button) in buttons) { + if (button.rect.contains(x, y)) return name + } + return null + } + + private fun startNewGame() { + targets.clear() + bubbles.clear() + + repeat(TARGET_COUNT) { targets.add(spawnTarget()) } + + gameWon = false + messageText = "" + + yaw = 0f + pitch = 0f + cam.position.set(0f, PLAYER_EYE_HEIGHT, 0f) + updateCameraRotation() + } + + private fun spawnTarget(): Target { + val angle = MathUtils.random() * MathUtils.PI2 + val dist = 6f + MathUtils.random() * (ARENA_HALF_SIZE - 8f) + val x = MathUtils.cos(angle) * dist + val z = MathUtils.sin(angle) * dist + val y = 1f + MathUtils.random() * 3f + + val instance = ModelInstance(balloonDogModels.random()) + return Target(instance, Vector3(x, y, z), MathUtils.random() * MathUtils.PI2, TARGET_RADIUS) + } + + private fun shootBubble() { + val dir = Vector3(cam.direction).nor() + val right = Vector3(camLeft).scl(-1f) + val up = cam.up + val spawnPos = Vector3(cam.position) + .mulAdd(dir, 1.1f) + .mulAdd(right, 0.22f) + .mulAdd(up, -0.18f) + + val instance = sphereInstance(transparentMaterial(Color(0.75f, 0.9f, 1f, 0.55f))) + bubbles.add(Bubble(instance, Vector3(spawnPos), Vector3(dir).scl(BUBBLE_SPEED), spawnPos)) + } + + private fun captureTarget(target: Target) { + target.state = TargetState.CAPTURED + target.captureBubble = sphereInstance(transparentMaterial(Color(0.6f, 0.85f, 1f, 0.45f))) + } + + /** Same Tait-Bryan composition as jME's Quaternion.fromAngles(pitch, yaw, 0) followed by Camera.setAxes. */ + private fun updateCameraRotation() { + camRotation.setEulerAnglesRad(yaw, pitch, 0f) + cam.direction.set(0f, 0f, 1f).mul(camRotation) + cam.up.set(0f, 1f, 0f).mul(camRotation) + camLeft.set(1f, 0f, 0f).mul(camRotation) + cam.update() + } + + private fun applyMovement(tpf: Float) { + val forwardInput: Float + val strafeInput: Float + if (moveTouchId != -1) { + forwardInput = (moveDY / JOYSTICK_MAX_RADIUS).coerceIn(-1f, 1f) + strafeInput = (moveDX / JOYSTICK_MAX_RADIUS).coerceIn(-1f, 1f) + } else { + forwardInput = (if (keyForward) 1f else 0f) + (if (keyBack) -1f else 0f) + strafeInput = (if (keyRight) 1f else 0f) + (if (keyLeft) -1f else 0f) + } + if (forwardInput == 0f && strafeInput == 0f) return + + val dir = Vector3(cam.direction) + dir.y = 0f + if (dir.len2() > 0.0001f) dir.nor() + val left = Vector3(camLeft) + left.y = 0f + if (left.len2() > 0.0001f) left.nor() + + val moveVec = dir.scl(forwardInput).mulAdd(left, -strafeInput).scl(MOVE_SPEED * tpf) + val newLoc = Vector3(cam.position).add(moveVec) + newLoc.x = newLoc.x.coerceIn(-ARENA_HALF_SIZE + 1f, ARENA_HALF_SIZE - 1f) + newLoc.z = newLoc.z.coerceIn(-ARENA_HALF_SIZE + 1f, ARENA_HALF_SIZE - 1f) + newLoc.y = PLAYER_EYE_HEIGHT + cam.position.set(newLoc) + cam.update() + } + + private fun updateWand() { + val right = Vector3(camLeft).scl(-1f) + val pos = Vector3(cam.position) + .mulAdd(cam.direction, 0.9f) + .mulAdd(right, 0.22f) + .mulAdd(cam.up, -0.18f) + wandHandle.transform.setToTranslation(pos).rotate(camRotation).translate(0f, 0f, -0.15f) + wandTip.transform.setToTranslation(pos).rotate(camRotation).translate(0f, 0f, -0.4f).scale(0.055f, 0.055f, 0.055f) + } + + private fun updateBubbles(tpf: Float) { + val it = bubbles.iterator() + while (it.hasNext()) { + val b = it.next() + b.position.mulAdd(b.velocity, tpf) + + var hit = false + for (t in targets) { + if (t.state == TargetState.ACTIVE && b.position.dst(t.position) < BUBBLE_RADIUS + t.radius) { + captureTarget(t) + hit = true + break + } + } + + if (hit || b.position.dst(b.spawnPos) > BUBBLE_MAX_DISTANCE) { + it.remove() + } else { + b.instance.transform.setToTranslation(b.position).scale(BUBBLE_RADIUS, BUBBLE_RADIUS, BUBBLE_RADIUS) + } + } + } + + private fun updateTargets(tpf: Float) { + val it = targets.iterator() + while (it.hasNext()) { + val t = it.next() + if (t.state == TargetState.CAPTURED) { + t.position.y += FLOAT_SPEED * tpf + t.yaw += tpf * 1.5f + if (t.position.y > FLOAT_REMOVE_HEIGHT) { + it.remove() + continue + } + } + t.instance.transform.setToTranslation(t.position).rotateRad(Vector3.Y, t.yaw) + t.captureBubble?.transform?.set(t.instance.transform) + ?.scale(ENCASE_BUBBLE_RADIUS, ENCASE_BUBBLE_RADIUS, ENCASE_BUBBLE_RADIUS) + } + } + + override fun render() { + val tpf = Gdx.graphics.deltaTime + + if (keyLookLeft) yaw += TURN_RATE * tpf + if (keyLookRight) yaw -= TURN_RATE * tpf + if (keyLookUp) pitch = (pitch + LOOK_RATE * tpf).coerceIn(-PITCH_LIMIT, PITCH_LIMIT) + if (keyLookDown) pitch = (pitch - LOOK_RATE * tpf).coerceIn(-PITCH_LIMIT, PITCH_LIMIT) + updateCameraRotation() + + applyMovement(tpf) + updateBubbles(tpf) + updateTargets(tpf) + updateWand() + + val remaining = targets.count { it.state == TargetState.ACTIVE } + if (remaining == 0 && !gameWon) { + gameWon = true + messageText = "ALL TARGETS CAPTURED!\nTap to play again" + } + + Gdx.gl.glClearColor(0.55f, 0.75f, 0.95f, 1f) + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT) + + // One begin/end so ModelBatch sorts the transparent bubbles after all opaque geometry + modelBatch.begin(cam) + modelBatch.render(scenery, environment) + modelBatch.render(wandHandle, environment) + for (t in targets) modelBatch.render(t.instance, environment) + // No environment = unshaded + modelBatch.render(wandTip) + for (t in targets) t.captureBubble?.let { modelBatch.render(it) } + for (b in bubbles) modelBatch.render(b.instance) + modelBatch.end() + + renderHud(remaining) + } + + private fun renderHud(remaining: Int) { + val width = Gdx.graphics.width.toFloat() + val height = Gdx.graphics.height.toFloat() + + Gdx.gl.glEnable(GL20.GL_BLEND) + Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA) + shapes.begin(ShapeRenderer.ShapeType.Filled) + drawReticle(width / 2f, height / 2f) + if (buttonsVisible) { + for (button in buttons.values) { + shapes.color = button.color + shapes.rect(button.rect.x, button.rect.y, button.rect.w, button.rect.h) + } + } + shapes.color = TOGGLE_COLOR + shapes.rect(toggleRect.x, toggleRect.y, toggleRect.w, toggleRect.h) + shapes.end() + + hudBatch.begin() + font.color = Color.WHITE + font.data.setScale(TARGETS_TEXT_SCALE) + font.draw(hudBatch, "Targets remaining: $remaining", 10f, height - 10f) + + if (buttonsVisible) { + font.data.setScale(BUTTON_TEXT_SCALE) + for (button in buttons.values) drawCentered(button.label, button.rect) + } + font.data.setScale(TOGGLE_TEXT_SCALE) + drawCentered(if (buttonsVisible) "HIDE\nUI" else "SHOW\nUI", toggleRect) + + if (messageText.isNotEmpty()) { + font.color = Color.YELLOW + font.data.setScale(MESSAGE_TEXT_SCALE) + font.draw(hudBatch, messageText, width / 2f - 160f, height / 2f + 60f) + } + hudBatch.end() + } + + private fun drawCentered(text: String, rect: ButtonRect) { + glyphs.setText(font, text, Color.WHITE, rect.w, Align.center, false) + font.draw(hudBatch, glyphs, rect.x, rect.y + rect.h / 2f + glyphs.height / 2f) + } + + /** Crosshair-bullseye reticle: a dashed ring, four tick lines, and a center dot. */ + private fun drawReticle(cx: Float, cy: Float) { + shapes.color = Color.WHITE + + val ringRadius = 16f + val tickCount = 16 + repeat(tickCount) { i -> + val angle = (MathUtils.PI2 / tickCount) * i + val x = MathUtils.cos(angle) * ringRadius + val y = MathUtils.sin(angle) * ringRadius + shapes.rect(cx + x - 1.5f, cy + y - 1.5f, 3f, 3f) + } + + val lineLen = 10f + val gap = 6f + val thickness = 2f + shapes.rect(cx - gap - lineLen, cy - thickness / 2f, lineLen, thickness) // left + shapes.rect(cx + gap, cy - thickness / 2f, lineLen, thickness) // right + shapes.rect(cx - thickness / 2f, cy + gap, thickness, lineLen) // top + shapes.rect(cx - thickness / 2f, cy - gap - lineLen, thickness, lineLen) // bottom + shapes.rect(cx - 2f, cy - 2f, 4f, 4f) // center dot + } + + override fun dispose() { + models.forEach { it.dispose() } + modelBatch.dispose() + shapes.dispose() + hudBatch.dispose() + font.dispose() + } + + // --- Input: touch is primary (left half = move joystick, right half = look/shoot); WASD+arrows are a testing convenience. + // Screen y is flipped to bottom-up so hit tests and look math match the original jME TouchEvent coordinates. + private val input = object : InputAdapter() { + + private fun setKey(keycode: Int, pressed: Boolean): Boolean { + when (keycode) { + Input.Keys.W -> keyForward = pressed + Input.Keys.S -> keyBack = pressed + Input.Keys.A -> keyLeft = pressed + Input.Keys.D -> keyRight = pressed + Input.Keys.LEFT -> keyLookLeft = pressed + Input.Keys.RIGHT -> keyLookRight = pressed + Input.Keys.UP -> keyLookUp = pressed + Input.Keys.DOWN -> keyLookDown = pressed + Input.Keys.SPACE -> if (pressed) { + if (gameWon) startNewGame() else shootBubble() + } + else -> return false + } + return true + } + + override fun keyDown(keycode: Int) = setKey(keycode, true) + + override fun keyUp(keycode: Int) = setKey(keycode, false) + + override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + val x = screenX.toFloat() + val y = Gdx.graphics.height - screenY.toFloat() + if (toggleRect.contains(x, y)) { + setButtonsVisible(!buttonsVisible) + return true + } + val buttonHit = if (buttonsVisible) hitButton(x, y) else null + if (buttonHit != null) { + when (buttonHit) { + "up" -> { btnUpPointer = pointer; keyForward = true } + "down" -> { btnDownPointer = pointer; keyBack = true } + "left" -> { btnLeftPointer = pointer; keyLeft = true } + "right" -> { btnRightPointer = pointer; keyRight = true } + "shoot" -> { + btnShootPointer = pointer + if (gameWon) startNewGame() else shootBubble() + } + } + return true + } + if (x < Gdx.graphics.width / 2f) { + if (moveTouchId == -1) { + moveTouchId = pointer + moveStartX = x + moveStartY = y + moveDX = 0f + moveDY = 0f + } + } else if (lookTouchId == -1) { + lookTouchId = pointer + lookDownX = x + lookDownY = y + lookLastX = x + lookLastY = y + lookDownTimeNanos = System.nanoTime() + lookDragged = false + } + return true + } + + override fun touchDragged(screenX: Int, screenY: Int, pointer: Int): Boolean { + val x = screenX.toFloat() + val y = Gdx.graphics.height - screenY.toFloat() + if (pointer == moveTouchId) { + moveDX = (x - moveStartX).coerceIn(-JOYSTICK_MAX_RADIUS, JOYSTICK_MAX_RADIUS) + moveDY = (y - moveStartY).coerceIn(-JOYSTICK_MAX_RADIUS, JOYSTICK_MAX_RADIUS) + } else if (pointer == lookTouchId) { + val dx = x - lookLastX + val dy = y - lookLastY + lookLastX = x + lookLastY = y + if (abs(x - lookDownX) > DRAG_THRESHOLD_PIXELS || abs(y - lookDownY) > DRAG_THRESHOLD_PIXELS) { + lookDragged = true + } + yaw -= dx * LOOK_SENSITIVITY + pitch = (pitch + dy * LOOK_SENSITIVITY).coerceIn(-PITCH_LIMIT, PITCH_LIMIT) + } + return true + } + + override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int): Boolean { + if (pointer == btnUpPointer) { + btnUpPointer = -1 + keyForward = false + } else if (pointer == btnDownPointer) { + btnDownPointer = -1 + keyBack = false + } else if (pointer == btnLeftPointer) { + btnLeftPointer = -1 + keyLeft = false + } else if (pointer == btnRightPointer) { + btnRightPointer = -1 + keyRight = false + } else if (pointer == btnShootPointer) { + btnShootPointer = -1 + } else if (pointer == moveTouchId) { + moveTouchId = -1 + moveDX = 0f + moveDY = 0f + } else if (pointer == lookTouchId) { + val elapsed = System.nanoTime() - lookDownTimeNanos + if (!lookDragged && elapsed <= TAP_MAX_NANOS) { + if (gameWon) startNewGame() else shootBubble() + } + lookTouchId = -1 + } + return true + } + } +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb new file mode 100644 index 00000000..cc3f97f3 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/MainActivity.kt.peb @@ -0,0 +1,13 @@ +package ${{PACKAGE_NAME}} + +import android.os.Bundle +import com.badlogic.gdx.backends.android.AndroidApplication +import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration + +class MainActivity : AndroidApplication() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val config = AndroidApplicationConfiguration().apply { useImmersiveMode = true } + initialize(BubbleWandGame(), config) + } +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/Target.kt.peb b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/Target.kt.peb new file mode 100644 index 00000000..d74b5a25 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/java/PACKAGE_NAME/Target.kt.peb @@ -0,0 +1,15 @@ +package ${{PACKAGE_NAME}} + +import com.badlogic.gdx.graphics.g3d.ModelInstance +import com.badlogic.gdx.math.Vector3 + +enum class TargetState { ACTIVE, CAPTURED } + +/** + * A target in the arena: [instance] is its mesh, placed each frame from [position] and [yaw]; + * once hit it is encased in [captureBubble] and floats away. + */ +class Target(val instance: ModelInstance, val position: Vector3, var yaw: Float, val radius: Float) { + var state: TargetState = TargetState.ACTIVE + var captureBubble: ModelInstance? = null +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-hdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a3a48da3 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-mdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..31e3bfeb Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..cbb79173 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..1ea93651 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..ee3024c6 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/values/strings.xml.peb b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/values/strings.xml.peb new file mode 100644 index 00000000..a1147ffc --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/app/src/main/res/values/strings.xml.peb @@ -0,0 +1,3 @@ + + ${{APP_NAME}} + diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/build.gradle b/templates/Fun-and-Games-LibGDX/bubblewand/build.gradle new file mode 100644 index 00000000..82fbdc4c --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/build.gradle @@ -0,0 +1,5 @@ +// Top-level build file +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradle.properties b/templates/Fun-and-Games-LibGDX/bubblewand/gradle.properties new file mode 100644 index 00000000..729b51ac --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true +kotlin.code.style=official diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradle/libs.versions.toml.peb b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/libs.versions.toml.peb new file mode 100644 index 00000000..db153df7 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/libs.versions.toml.peb @@ -0,0 +1,13 @@ +[versions] +agp = "${{AGP_VERSION}}" +kotlin = "${{KOTLIN_VERSION}}" +gdx = "1.13.1" + +[libraries] +gdx-core = { group = "com.badlogicgames.gdx", name = "gdx", version.ref = "gdx" } +gdx-backend-android = { group = "com.badlogicgames.gdx", name = "gdx-backend-android", version.ref = "gdx" } +gdx-platform = { group = "com.badlogicgames.gdx", name = "gdx-platform", version.ref = "gdx" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.jar b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.jar differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.properties.peb b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.properties.peb new file mode 100644 index 00000000..f62f9d39 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/gradle/wrapper/gradle-wrapper.properties.peb @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-${{GRADLE_VERSION}}-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradlew b/templates/Fun-and-Games-LibGDX/bubblewand/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/gradlew.bat b/templates/Fun-and-Games-LibGDX/bubblewand/gradlew.bat new file mode 100644 index 00000000..25da30db --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/settings.gradle.peb b/templates/Fun-and-Games-LibGDX/bubblewand/settings.gradle.peb new file mode 100644 index 00000000..49039c22 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/settings.gradle.peb @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "${{APP_NAME}}" +include(":app") diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/template/icon.png b/templates/Fun-and-Games-LibGDX/bubblewand/template/icon.png new file mode 100644 index 00000000..26a8c68e Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/template/icon.png differ diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/template/template.json b/templates/Fun-and-Games-LibGDX/bubblewand/template/template.json new file mode 100644 index 00000000..e8ab0706 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/bubblewand/template/template.json @@ -0,0 +1,49 @@ +{ + "name": "Bubble Wand (libGDX)", + "description": "Creates a Bubble Wand Shooter Game (libGDX)", + "version": "0.1", + "parameters": { + "required": { + "appName": { + "identifier": "APP_NAME" + }, + "packageName": { + "identifier": "PACKAGE_NAME" + }, + "saveLocation": { + "identifier": "SAVE_LOCATION" + } + }, + "optional": { + "minsdk": { + "identifier": "MIN_SDK" + } + } + }, + "system": { + "agpVersion": { + "identifier": "AGP_VERSION" + }, + "kotlinVersion": { + "identifier": "KOTLIN_VERSION" + }, + "gradleVersion": { + "identifier": "GRADLE_VERSION" + }, + "compileSdk": { + "identifier": "COMPILE_SDK" + }, + "targetSdk": { + "identifier": "TARGET_SDK" + }, + "javaSourceCompat": { + "identifier": "JAVA_SOURCE_COMPAT" + }, + "javaTargetCompat": { + "identifier": "JAVA_TARGET_COMPAT" + }, + "javaTarget": { + "identifier": "JAVA_TARGET" + } + } +} diff --git a/templates/Fun-and-Games-LibGDX/bubblewand/template/thumb.png b/templates/Fun-and-Games-LibGDX/bubblewand/template/thumb.png new file mode 100644 index 00000000..332d9aee Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/bubblewand/template/thumb.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/.gitignore b/templates/Fun-and-Games-LibGDX/demo/.gitignore new file mode 100644 index 00000000..bc3a162d --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/.gitignore @@ -0,0 +1,7 @@ +.gradle/ +build/ +local.properties +*.iml +.idea/ +.DS_Store +captures/ diff --git a/templates/Fun-and-Games-LibGDX/demo/README.md b/templates/Fun-and-Games-LibGDX/demo/README.md new file mode 100644 index 00000000..1f7ed7ea --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/README.md @@ -0,0 +1,67 @@ +# libGDX Android Game + +A simple 3D space game for Android built with [libGDX 1.13.1](https://libgdx.com/) (ported from jMonkeyEngine 3.6). + +## Features + +- **Touch drag** to rotate the Earth on any axis +- **Inertia** — flick and it coasts to a stop +- **Tap** to earn points +- **Orbiting rocket** — a procedural rocket ship (red nose, white body, 4 fins) circles the Earth on a tilted plane +- **Orange exhaust trail** behind the rocket (pooled billboard decals) +- **Bloom glow** on the engine nozzle (`BloomEffect` — glow pass + separable gaussian blur, additively composited) +- **Starfield** — 250 stars scattered on a sphere shell, batched to one draw call +- **Ambient + directional lighting** for shaded, non-harsh geometry + +## Requirements + +| Item | Version | +|------|---------| +| Native libs | unpacked from the gdx-platform natives jars by `copyAndroidNatives` | +| SDK levels | set by Code on the Go when the project is created | +| OpenGL ES | 2.0+ | + +## Getting Started + +1. Tap **Run** in Code on the Go. + +libGDX 1.13.1 is already in `app/libs`, so the build does not fetch it from Maven Central. + +## Project Structure + +``` +app/ +├── libs/ # libGDX 1.13.1 jars and .aar +└── src/main/ + ├── java// + │ ├── MainActivity.java # AndroidApplication entry point + │ ├── CubeGame.java # ApplicationAdapter — all game logic + │ └── BloomEffect.java # glow/bloom post-process + ├── assets/Textures/ # earth.png, Particle.png + ├── AndroidManifest.xml + └── res/ + ├── mipmap-*/ic_launcher.png + └── values/strings.xml +``` + +## Key Dependencies + +``` +app/libs/gdx-1.13.1.jar # core: math, 3D models, 2D batching +app/libs/gdx-jnigen-loader-2.5.2.jar # loads the native library +app/libs/gdx-backend-android-1.13.1.aar # Android backend + AndroidApplication +app/libs/gdx-platform-1.13.1-natives-armeabi-v7a.jar # libgdx.so for armeabi-v7a +app/libs/gdx-platform-1.13.1-natives-arm64-v8a.jar # libgdx.so for arm64-v8a +``` + +`androidx.core` comes from Code on the Go's offline repository. + +libGDX is distributed under the Apache License 2.0. + +## Controls + +| Gesture | Action | +|---------|--------| +| Drag | Rotate the Earth | +| Flick | Spin with inertia | +| Tap | +10 points | diff --git a/templates/Fun-and-Games-LibGDX/demo/app/build.gradle.peb b/templates/Fun-and-Games-LibGDX/demo/app/build.gradle.peb new file mode 100644 index 00000000..d984069f --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/build.gradle.peb @@ -0,0 +1,100 @@ +plugins { + alias(libs.plugins.android.application) +} + +configurations { + natives +} + +// libGDX 1.13.x needs androidx.core at runtime. Pin its tree to versions that +// Code On The Go's offline Maven repository carries (the same ones its built-in +// AndroidX templates use); the newer androidx.core libGDX asks for pulls in +// modules (profileinstaller, collection 1.0.0, ...) that the repository lacks. +configurations.configureEach { + resolutionStrategy { + force "androidx.core:core:1.9.0" + force "androidx.collection:collection:1.4.2" + force "androidx.annotation:annotation:1.8.1" + force "androidx.lifecycle:lifecycle-runtime:2.5.1" + force "androidx.lifecycle:lifecycle-common:2.5.1" + } +} + +android { + namespace = "${{PACKAGE_NAME}}" + compileSdk = ${{COMPILE_SDK}} + + defaultConfig { + applicationId = "${{PACKAGE_NAME}}" + minSdk = ${{MIN_SDK}} + targetSdk = ${{TARGET_SDK}} + versionCode = 1 + versionName = "1.0" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + } + } + + sourceSets { + main { + // copyAndroidNatives unpacks libgdx.so here; app/libs holds only the shipped jars + jniLibs.srcDirs = [layout.buildDirectory.dir("gdx-natives").get().asFile] + } + } + + buildTypes { + release { + minifyEnabled false + } + } + + compileOptions { + sourceCompatibility = ${{JAVA_SOURCE_COMPAT}} + targetCompatibility = ${{JAVA_TARGET_COMPAT}} + } + + packaging { + jniLibs { + useLegacyPackaging = true + } + } +} + +dependencies { + implementation(files("libs/gdx-1.13.1.jar")) + implementation(files("libs/gdx-jnigen-loader-2.5.2.jar")) + implementation(files("libs/gdx-backend-android-1.13.1.aar")) + // gdx-backend-android needs androidx.core at runtime; a local .aar does not + // carry that dependency, so declare it here (pinned to 1.9.0 above). + implementation("androidx.core:core:1.9.0") + natives(files("libs/gdx-platform-1.13.1-natives-armeabi-v7a.jar")) + natives(files("libs/gdx-platform-1.13.1-natives-arm64-v8a.jar")) + +${% if LANGUAGE == 'kotlin' %} +tasks.withType().configureEach { + kotlinOptions.jvmTarget = "${{JAVA_TARGET}}" +} +${% endif %} +} + +// libGDX ships its Android .so files inside per-ABI classifier jars; unpack them +// into build/gdx-natives// so AGP packages them as jniLibs. Written as a plain Copy +// task (no project access at execution time) so it works with the configuration cache. +tasks.register("copyAndroidNatives", Copy) { + def natives = configurations.natives + // Not app/libs: a Copy task owns its whole destination, so the jars there would + // count as its output and every compile task would need to depend on it. + into layout.buildDirectory.dir("gdx-natives") + ["armeabi-v7a", "arm64-v8a"].each { abi -> + def jar = natives.filter { it.name.endsWith("natives-" + abi + ".jar") } + from(zipTree { jar.singleFile }) { + include "*.so" + into abi + } + } +} + +tasks.matching { it.name.contains("merge") && it.name.contains("JniLibFolders") }.configureEach { + it.dependsOn "copyAndroidNatives" +} diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/AndroidManifest.xml b/templates/Fun-and-Games-LibGDX/demo/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0fa308f2 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/Particle.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/Particle.png new file mode 100644 index 00000000..45639237 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/Particle.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/earth.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/earth.png new file mode 100644 index 00000000..c2ae25c4 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/assets/Textures/earth.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/ic_launcher-playstore.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..51e656b5 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/ic_launcher-playstore.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/BloomEffect.java.peb b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/BloomEffect.java.peb new file mode 100644 index 00000000..a467513a --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/BloomEffect.java.peb @@ -0,0 +1,166 @@ +package ${{PACKAGE_NAME}}; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.Mesh; +import com.badlogic.gdx.graphics.Pixmap; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.VertexAttribute; +import com.badlogic.gdx.graphics.VertexAttributes; +import com.badlogic.gdx.graphics.glutils.FrameBuffer; +import com.badlogic.gdx.graphics.glutils.ShaderProgram; +import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.GdxRuntimeException; + +/** + * Minimal stand-in for jME's BloomFilter(GlowMode.Objects): the caller renders only glow + * colours into {@link #beginGlow()}/{@link #endGlow()}, the result is blurred at reduced + * resolution, then added on top of the already-drawn scene. + */ +public class BloomEffect implements Disposable { + + private static final String VERTEX = + "attribute vec2 a_position;\n" + + "attribute vec2 a_texCoord0;\n" + + "varying vec2 v_uv;\n" + + "void main() {\n" + + " v_uv = a_texCoord0;\n" + + " gl_Position = vec4(a_position, 0.0, 1.0);\n" + + "}\n"; + + // 9-tap separable gaussian; u_step is one texel along the blur direction + private static final String BLUR_FRAGMENT = + "#ifdef GL_ES\nprecision mediump float;\n#endif\n" + + "uniform sampler2D u_texture;\n" + + "uniform vec2 u_step;\n" + + "varying vec2 v_uv;\n" + + "void main() {\n" + + " vec4 sum = texture2D(u_texture, v_uv) * 0.227027;\n" + + " sum += texture2D(u_texture, v_uv + u_step * 1.0) * 0.194595;\n" + + " sum += texture2D(u_texture, v_uv - u_step * 1.0) * 0.194595;\n" + + " sum += texture2D(u_texture, v_uv + u_step * 2.0) * 0.121622;\n" + + " sum += texture2D(u_texture, v_uv - u_step * 2.0) * 0.121622;\n" + + " sum += texture2D(u_texture, v_uv + u_step * 3.0) * 0.054054;\n" + + " sum += texture2D(u_texture, v_uv - u_step * 3.0) * 0.054054;\n" + + " sum += texture2D(u_texture, v_uv + u_step * 4.0) * 0.016216;\n" + + " sum += texture2D(u_texture, v_uv - u_step * 4.0) * 0.016216;\n" + + " gl_FragColor = sum;\n" + + "}\n"; + + private static final String COMPOSITE_FRAGMENT = + "#ifdef GL_ES\nprecision mediump float;\n#endif\n" + + "uniform sampler2D u_texture;\n" + + "uniform float u_intensity;\n" + + "varying vec2 v_uv;\n" + + "void main() {\n" + + " gl_FragColor = vec4(texture2D(u_texture, v_uv).rgb * u_intensity, 1.0);\n" + + "}\n"; + + private final ShaderProgram blurShader; + private final ShaderProgram compositeShader; + private final Mesh quad; + private FrameBuffer glowBuffer; + private FrameBuffer pingBuffer; + + private final float intensity; + private final float blurScale; + private final float downSamplingFactor; + + public BloomEffect(float intensity, float blurScale, float downSamplingFactor) { + this.intensity = intensity; + this.blurScale = blurScale; + this.downSamplingFactor = downSamplingFactor; + + blurShader = compile(BLUR_FRAGMENT); + compositeShader = compile(COMPOSITE_FRAGMENT); + + // Full-screen quad in clip space; FBO textures have v=0 at the bottom, so UVs map straight through + quad = new Mesh(true, 4, 6, + new VertexAttribute(VertexAttributes.Usage.Position, 2, "a_position"), + new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, "a_texCoord0")); + quad.setVertices(new float[] { + -1f, -1f, 0f, 0f, + 1f, -1f, 1f, 0f, + 1f, 1f, 1f, 1f, + -1f, 1f, 0f, 1f }); + quad.setIndices(new short[] { 0, 1, 2, 2, 3, 0 }); + } + + private static ShaderProgram compile(String fragment) { + ShaderProgram program = new ShaderProgram(VERTEX, fragment); + if (!program.isCompiled()) { + throw new GdxRuntimeException("Bloom shader failed to compile: " + program.getLog()); + } + return program; + } + + public void resize(int width, int height) { + disposeBuffers(); + int w = Math.max(1, (int) (width / downSamplingFactor)); + int h = Math.max(1, (int) (height / downSamplingFactor)); + glowBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, w, h, true); + pingBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, w, h, false); + glowBuffer.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); + pingBuffer.getColorBufferTexture().setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear); + } + + /** Starts the glow pass; everything drawn until {@link #endGlow()} becomes bloom source. */ + public void beginGlow() { + glowBuffer.begin(); + Gdx.gl.glClearColor(0f, 0f, 0f, 1f); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + } + + /** Blurs the glow pass and additively composites it onto the current framebuffer. */ + public void endGlow() { + glowBuffer.end(); + + Gdx.gl.glDisable(GL20.GL_DEPTH_TEST); + Gdx.gl.glDisable(GL20.GL_BLEND); + + float stepX = blurScale / glowBuffer.getWidth(); + float stepY = blurScale / glowBuffer.getHeight(); + + // Horizontal: glow -> ping + pingBuffer.begin(); + drawQuad(blurShader, glowBuffer.getColorBufferTexture(), stepX, 0f); + pingBuffer.end(); + + // Vertical: ping -> glow + glowBuffer.begin(); + drawQuad(blurShader, pingBuffer.getColorBufferTexture(), 0f, stepY); + glowBuffer.end(); + + Gdx.gl.glEnable(GL20.GL_BLEND); + Gdx.gl.glBlendFunc(GL20.GL_ONE, GL20.GL_ONE); + glowBuffer.getColorBufferTexture().bind(0); + compositeShader.bind(); + compositeShader.setUniformi("u_texture", 0); + compositeShader.setUniformf("u_intensity", intensity); + quad.render(compositeShader, GL20.GL_TRIANGLES); + Gdx.gl.glDisable(GL20.GL_BLEND); + } + + private void drawQuad(ShaderProgram shader, Texture source, float stepX, float stepY) { + source.bind(0); + shader.bind(); + shader.setUniformi("u_texture", 0); + shader.setUniformf("u_step", stepX, stepY); + quad.render(shader, GL20.GL_TRIANGLES); + } + + private void disposeBuffers() { + if (glowBuffer != null) glowBuffer.dispose(); + if (pingBuffer != null) pingBuffer.dispose(); + glowBuffer = null; + pingBuffer = null; + } + + @Override + public void dispose() { + disposeBuffers(); + blurShader.dispose(); + compositeShader.dispose(); + quad.dispose(); + } +} diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/CubeGame.java.peb b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/CubeGame.java.peb new file mode 100644 index 00000000..b05a6bf3 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/CubeGame.java.peb @@ -0,0 +1,437 @@ +package ${{PACKAGE_NAME}}; + +import com.badlogic.gdx.ApplicationAdapter; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.InputAdapter; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.PerspectiveCamera; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.VertexAttributes.Usage; +import com.badlogic.gdx.graphics.g2d.BitmapFont; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import com.badlogic.gdx.graphics.g3d.Environment; +import com.badlogic.gdx.graphics.g3d.Material; +import com.badlogic.gdx.graphics.g3d.Model; +import com.badlogic.gdx.graphics.g3d.ModelBatch; +import com.badlogic.gdx.graphics.g3d.ModelInstance; +import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; +import com.badlogic.gdx.graphics.g3d.attributes.FloatAttribute; +import com.badlogic.gdx.graphics.g3d.attributes.TextureAttribute; +import com.badlogic.gdx.graphics.g3d.decals.CameraGroupStrategy; +import com.badlogic.gdx.graphics.g3d.decals.Decal; +import com.badlogic.gdx.graphics.g3d.decals.DecalBatch; +import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; +import com.badlogic.gdx.graphics.g3d.model.Node; +import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder; +import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.BoxShapeBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.CylinderShapeBuilder; +import com.badlogic.gdx.graphics.g3d.utils.shapebuilders.SphereShapeBuilder; +import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.math.Matrix4; +import com.badlogic.gdx.math.Quaternion; +import com.badlogic.gdx.math.Vector3; +import java.util.Random; + +public class CubeGame extends ApplicationAdapter { + + private PerspectiveCamera cam; + private ModelBatch modelBatch; + private Environment environment; + // Unlit except for emissive: used for the bloom glow pass + private Environment glowEnvironment; + private BloomEffect bloom; + + private Model starModel; + private Model earthModel; + private Model rocketModel; + private ModelInstance stars; + private ModelInstance cube; + private ModelInstance rocket; + private Material nozzleMat; + private Texture earthTexture; + + // Scene-graph state kept as plain transforms (libGDX has no Node hierarchy for instances) + private final Quaternion cubeRotation = new Quaternion(); + private float orbitAngle = 0f; // radians around the tilted orbit pivot's local Y + private float rocketSpin = 0f; // radians around the rocket's local Y + private final Matrix4 orbitNodeTransform = new Matrix4(); + + private SpriteBatch spriteBatch; + private BitmapFont font; + private int score = 0; + + // Trail particles + private static final int MAX_PARTICLES = 150; + private static final float PARTICLES_PER_SEC = 120f; + private static final float LOW_LIFE = 0.4f; + private static final float HIGH_LIFE = 0.65f; + private static final float START_SIZE = 0.25f; + private static final float END_SIZE = 0.02f; + private static final Color START_COLOR = new Color(1.0f, 0.65f, 0.10f, 1f); // bright orange-yellow + private static final Color END_COLOR = new Color(0.5f, 0.05f, 0.00f, 0f); // dark red, transparent + private Texture particleTexture; + private DecalBatch decalBatch; + private final Decal[] particles = new Decal[MAX_PARTICLES]; + private final float[] particleAge = new float[MAX_PARTICLES]; + private final float[] particleLife = new float[MAX_PARTICLES]; + private final boolean[] particleAlive = new boolean[MAX_PARTICLES]; + private float emitAccumulator = 0f; + private final Color tmpColor = new Color(); + private final Vector3 tmpVec = new Vector3(); + + // Drag state + private boolean isDragging = false; + private float lastX, lastY; + private float totalDragDistance = 0f; + + // Inertia: carry over drag velocity after finger lifts + private float velocityX = 0f; + private float velocityY = 0f; + + private static final float DRAG_SENSITIVITY = 0.008f; + private static final float TAP_THRESHOLD = 15f; // pixels + private static final float INERTIA_DECAY = 0.92f; + + // Screen y is converted to bottom-up so drag math matches the original jME TouchEvent coordinates + private final InputAdapter touchListener = new InputAdapter() { + + @Override + public boolean touchDown(int screenX, int screenY, int pointer, int button) { + if (pointer != 0) return false; + isDragging = true; + lastX = screenX; + lastY = Gdx.graphics.getHeight() - screenY; + totalDragDistance = 0f; + // Kill inertia when finger touches down + velocityX = 0f; + velocityY = 0f; + return true; + } + + @Override + public boolean touchDragged(int screenX, int screenY, int pointer) { + if (pointer != 0 || !isDragging) return false; + float x = screenX; + float y = Gdx.graphics.getHeight() - screenY; + float dx = x - lastX; + float dy = y - lastY; + + rotateCube(dx, dy); + + // Store as inertia velocity (pixels/frame, decayed on lift) + velocityX = dx; + velocityY = dy; + + totalDragDistance += (float) Math.sqrt(dx * dx + dy * dy); + lastX = x; + lastY = y; + return true; + } + + @Override + public boolean touchUp(int screenX, int screenY, int pointer, int button) { + if (pointer != 0) return false; + isDragging = false; + // Small movement = tap: award score + if (totalDragDistance < TAP_THRESHOLD) { + score += 10; + } + return true; + } + }; + + @Override + public void create() { + cam = new PerspectiveCamera(45f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + cam.position.set(0, 0, 6); + cam.lookAt(0, 0, 0); + cam.up.set(Vector3.Y); + cam.near = 1f; + cam.far = 1000f; + cam.update(); + + modelBatch = new ModelBatch(); + ModelBuilder modelBuilder = new ModelBuilder(); + + // Starfield — 250 tiny boxes on a sphere shell, merged into one mesh (one draw call) + modelBuilder.begin(); + MeshPartBuilder starBuilder = modelBuilder.part("stars", GL20.GL_TRIANGLES, Usage.Position, + new Material(ColorAttribute.createDiffuse(Color.WHITE))); + Random rand = new Random(12345L); // fixed seed for deterministic layout + for (int i = 0; i < 250; i++) { + // Uniform distribution on sphere surface + float theta = rand.nextFloat() * MathUtils.PI2; + float phi = (float) Math.acos(2f * rand.nextFloat() - 1f); + float r = 25f + rand.nextFloat() * 15f; // shell between radius 25 and 40 + float size = 0.12f * (0.5f + rand.nextFloat()); // vary star size + BoxShapeBuilder.build(starBuilder, + r * MathUtils.sin(phi) * MathUtils.cos(theta), + r * MathUtils.sin(phi) * MathUtils.sin(theta), + r * MathUtils.cos(phi), + size, size, size); + } + starModel = modelBuilder.end(); + stars = new ModelInstance(starModel); + + // Earth — textured sphere; diffuse texture drives colour, specular adds ocean glint + earthTexture = new Texture(Gdx.files.internal("Textures/earth.png"), true); + earthTexture.setFilter(Texture.TextureFilter.MipMapLinearLinear, Texture.TextureFilter.Linear); + Material mat = new Material( + TextureAttribute.createDiffuse(earthTexture), + ColorAttribute.createDiffuse(Color.WHITE), + ColorAttribute.createSpecular(0.40f, 0.55f, 0.85f, 1f), + FloatAttribute.createShininess(60f)); + earthModel = modelBuilder.createSphere(2f, 2f, 2f, 48, 48, mat, + Usage.Position | Usage.Normal | Usage.TextureCoordinates); + cube = new ModelInstance(earthModel); + + buildRocket(modelBuilder); + setUpTrail(); + + // Lights + environment = new Environment(); + environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.25f, 0.25f, 0.35f, 1f)); // cool blue-white fill + DirectionalLight sun = new DirectionalLight().set(Color.WHITE, new Vector3(-1, -1, -2).nor()); + // Color.set clamps to 1; write the channels directly to keep the original 1.5x sun + sun.color.r = sun.color.g = sun.color.b = 1.5f; + environment.add(sun); + + glowEnvironment = new Environment(); + glowEnvironment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0f, 0f, 0f, 1f)); + + // Bloom — only geometry with an emissive glow colour in the glow pass blooms, + // so the Earth and background are unaffected + bloom = new BloomEffect(1.5f, 1.2f, 2f); // downsample 2x for mobile perf + bloom.resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); + + // HUD + spriteBatch = new SpriteBatch(); + font = new BitmapFont(); + font.getData().setScale(2f); + font.setColor(Color.WHITE); + + Gdx.input.setInputProcessor(touchListener); + } + + /** Generic rocket ship built procedurally; one model, one node per part. */ + private void buildRocket(ModelBuilder modelBuilder) { + long attrs = Usage.Position | Usage.Normal; + + // Body — white/light gray hull + Material bodyMat = new Material("body", + ColorAttribute.createDiffuse(0.88f, 0.88f, 0.88f, 1f), + ColorAttribute.createSpecular(Color.WHITE), + FloatAttribute.createShininess(48f)); + + // Nose cone / fins accent — red + Material accentMat = new Material("accent", + ColorAttribute.createDiffuse(0.88f, 0.10f, 0.10f, 1f), + ColorAttribute.createSpecular(Color.WHITE), + FloatAttribute.createShininess(24f)); + + // Engine nozzle — dark metal; its orange glow is added only during the bloom pass + Material nozzle = new Material("nozzle", + ColorAttribute.createDiffuse(0.38f, 0.38f, 0.38f, 1f), + ColorAttribute.createSpecular(Color.WHITE), + FloatAttribute.createShininess(64f)); + + // Porthole window — dark blue + Material windowMat = new Material("window", + ColorAttribute.createDiffuse(0.10f, 0.20f, 0.55f, 1f), + ColorAttribute.createSpecular(Color.WHITE), + FloatAttribute.createShininess(128f)); + + modelBuilder.begin(); + + // Nose — red sphere at top of rocket + partNode(modelBuilder, "Nose", 0, 0.38f, 0); + SphereShapeBuilder.build(modelBuilder.part("Nose", GL20.GL_TRIANGLES, attrs, accentMat), + 0.20f, 0.20f, 0.20f, 16, 8); + + // Body cylinder — long axis along +Y (nose up) + partNode(modelBuilder, "Body", 0, 0.08f, 0); + CylinderShapeBuilder.build(modelBuilder.part("Body", GL20.GL_TRIANGLES, attrs, bodyMat), + 0.20f, 0.55f, 0.20f, 16); + + // Porthole window on the upper body + partNode(modelBuilder, "Window", 0.10f, 0.20f, 0); + SphereShapeBuilder.build(modelBuilder.part("Window", GL20.GL_TRIANGLES, attrs, windowMat), + 0.076f, 0.076f, 0.076f, 12, 6); + + // 4 stabiliser fins around the base (+X, -X along Z-thin; +Z, -Z along X-thin) + partNode(modelBuilder, "FinPX", 0.20f, -0.16f, 0); + BoxShapeBuilder.build(modelBuilder.part("FinPX", GL20.GL_TRIANGLES, attrs, accentMat), 0.26f, 0.32f, 0.03f); + partNode(modelBuilder, "FinNX", -0.20f, -0.16f, 0); + BoxShapeBuilder.build(modelBuilder.part("FinNX", GL20.GL_TRIANGLES, attrs, accentMat), 0.26f, 0.32f, 0.03f); + partNode(modelBuilder, "FinPZ", 0, -0.16f, 0.20f); + BoxShapeBuilder.build(modelBuilder.part("FinPZ", GL20.GL_TRIANGLES, attrs, accentMat), 0.03f, 0.32f, 0.26f); + partNode(modelBuilder, "FinNZ", 0, -0.16f, -0.20f); + BoxShapeBuilder.build(modelBuilder.part("FinNZ", GL20.GL_TRIANGLES, attrs, accentMat), 0.03f, 0.32f, 0.26f); + + // Engine nozzle bell — wider than body, glows orange + partNode(modelBuilder, "Nozzle", 0, -0.26f, 0); + CylinderShapeBuilder.build(modelBuilder.part("Nozzle", GL20.GL_TRIANGLES, attrs, nozzle), + 0.26f, 0.10f, 0.26f, 16); + + rocketModel = modelBuilder.end(); + rocket = new ModelInstance(rocketModel); + // ModelInstance copies materials, so grab the instance's copy to toggle the glow on + nozzleMat = rocket.getMaterial("nozzle"); + } + + private static void partNode(ModelBuilder modelBuilder, String id, float x, float y, float z) { + Node node = modelBuilder.node(); + node.id = id; + node.translation.set(x, y, z); + } + + private void setUpTrail() { + particleTexture = new Texture(Gdx.files.internal("Textures/Particle.png")); + TextureRegion region = new TextureRegion(particleTexture); + decalBatch = new DecalBatch(new CameraGroupStrategy(cam)); + for (int i = 0; i < MAX_PARTICLES; i++) { + // Additive alpha blending, matching jME's Particle.j3md + particles[i] = Decal.newDecal(1f, 1f, region, GL20.GL_SRC_ALPHA, GL20.GL_ONE); + } + } + + @Override + public void resize(int width, int height) { + cam.viewportWidth = width; + cam.viewportHeight = height; + cam.update(); + spriteBatch.getProjectionMatrix().setToOrtho2D(0, 0, width, height); + bloom.resize(width, height); + } + + @Override + public void render() { + float tpf = Math.min(Gdx.graphics.getDeltaTime(), 0.1f); + update(tpf); + + Gdx.gl.glViewport(0, 0, Gdx.graphics.getBackBufferWidth(), Gdx.graphics.getBackBufferHeight()); + Gdx.gl.glClearColor(0f, 0f, 0f, 1f); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); + + modelBatch.begin(cam); + modelBatch.render(stars); // no environment = unshaded + modelBatch.render(cube, environment); + modelBatch.render(rocket, environment); + modelBatch.end(); + + drawTrail(); + + // Glow pass: everything black except the nozzle's glow colour; other geometry still occludes it + nozzleMat.set(ColorAttribute.createEmissive(1.0f, 0.45f, 0.0f, 1f)); + bloom.beginGlow(); + modelBatch.begin(cam); + modelBatch.render(cube, glowEnvironment); + modelBatch.render(rocket, glowEnvironment); + modelBatch.end(); + bloom.endGlow(); + nozzleMat.remove(ColorAttribute.Emissive); + + spriteBatch.begin(); + font.draw(spriteBatch, "Score: " + score + " | Drag to rotate | Tap for points", + 10f, Gdx.graphics.getHeight() - 10f); + spriteBatch.end(); + } + + private void update(float tpf) { + if (isDragging) { + // Finger is down — cube is driven entirely by touch events + } else if (velocityX != 0f || velocityY != 0f) { + // Apply inertia after finger lifts + rotateCube(velocityX, velocityY); + velocityX *= INERTIA_DECAY; + velocityY *= INERTIA_DECAY; + if (Math.abs(velocityX) < 0.01f) velocityX = 0f; + if (Math.abs(velocityY) < 0.01f) velocityY = 0f; + } else { + // Idle auto-spin when untouched (local-space rotation, like jME's Spatial.rotate) + cubeRotation.mul(new Quaternion().setEulerAnglesRad(tpf * 0.35f, tpf * 0.5f, 0f)).nor(); + } + cube.transform.set(cubeRotation); + + // Orbit pivot rotates around its local Y — the 20° tilt makes the path look 3-D + orbitAngle += tpf * 1.2f; + // Rocket spins slowly so all angles are visible during the orbit + rocketSpin += tpf * 0.6f; + + // pivot (tilt 20° about X, then orbit about local Y) -> orbit node at radius 2.5 + orbitNodeTransform.idt() + .rotate(Vector3.X, 20f) + .rotateRad(Vector3.Y, orbitAngle) + .translate(2.5f, 0f, 0f); + rocket.transform.set(orbitNodeTransform).rotateRad(Vector3.Y, rocketSpin); + + updateTrail(tpf); + } + + private void updateTrail(float tpf) { + // Particles live in world space: spawned at the orbit node, then left behind as it moves + orbitNodeTransform.getTranslation(tmpVec); + + emitAccumulator += PARTICLES_PER_SEC * tpf; + for (int i = 0; i < MAX_PARTICLES && emitAccumulator >= 1f; i++) { + if (!particleAlive[i]) { + particleAlive[i] = true; + particleAge[i] = 0f; + particleLife[i] = MathUtils.random(LOW_LIFE, HIGH_LIFE); + particles[i].setPosition(tmpVec); + emitAccumulator -= 1f; + } + } + // Pool exhausted: drop the backlog rather than bursting later + if (emitAccumulator > 1f) emitAccumulator = 0f; + + for (int i = 0; i < MAX_PARTICLES; i++) { + if (!particleAlive[i]) continue; + particleAge[i] += tpf; + if (particleAge[i] >= particleLife[i]) { + particleAlive[i] = false; + continue; + } + float t = particleAge[i] / particleLife[i]; + // jME particle size is a half-extent, so the quad is twice as wide + float size = 2f * MathUtils.lerp(START_SIZE, END_SIZE, t); + tmpColor.set(START_COLOR).lerp(END_COLOR, t); + Decal d = particles[i]; + d.setDimensions(size, size); + d.setColor(tmpColor); + } + } + + private void drawTrail() { + for (int i = 0; i < MAX_PARTICLES; i++) { + if (!particleAlive[i]) continue; + particles[i].lookAt(cam.position, cam.up); + decalBatch.add(particles[i]); + } + decalBatch.flush(); + } + + // dx/dy are screen-pixel deltas (y up); horizontal drag rotates around Y, vertical around X + private void rotateCube(float dx, float dy) { + Quaternion rotY = new Quaternion().setFromAxisRad(Vector3.Y, dx * DRAG_SENSITIVITY); + Quaternion rotX = new Quaternion().setFromAxisRad(Vector3.X, -dy * DRAG_SENSITIVITY); + cubeRotation.set(rotY.mul(rotX).mul(cubeRotation)).nor(); + } + + @Override + public void dispose() { + modelBatch.dispose(); + decalBatch.dispose(); + spriteBatch.dispose(); + font.dispose(); + bloom.dispose(); + starModel.dispose(); + earthModel.dispose(); + rocketModel.dispose(); + earthTexture.dispose(); + particleTexture.dispose(); + } +} diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/MainActivity.java.peb b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/MainActivity.java.peb new file mode 100644 index 00000000..91a357d0 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/src/main/java/PACKAGE_NAME/MainActivity.java.peb @@ -0,0 +1,17 @@ +package ${{PACKAGE_NAME}}; + +import android.os.Bundle; + +import com.badlogic.gdx.backends.android.AndroidApplication; +import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration; + +public class MainActivity extends AndroidApplication { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); + config.useImmersiveMode = true; + initialize(new CubeGame(), config); + } +} diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..df162526 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..607ecca2 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..35742550 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..bd743c23 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..67fcb92b Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/values/strings.xml.peb b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/values/strings.xml.peb new file mode 100644 index 00000000..5987e8ef --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/app/src/main/res/values/strings.xml.peb @@ -0,0 +1,4 @@ + + + ${{APP_NAME}} + diff --git a/templates/Fun-and-Games-LibGDX/demo/build.gradle b/templates/Fun-and-Games-LibGDX/demo/build.gradle new file mode 100644 index 00000000..7ef56b07 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/build.gradle @@ -0,0 +1,4 @@ +// Top-level build file +plugins { + alias(libs.plugins.android.application) apply false +} diff --git a/templates/Fun-and-Games-LibGDX/demo/gradle.properties b/templates/Fun-and-Games-LibGDX/demo/gradle.properties new file mode 100644 index 00000000..08df1c9e --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Xmx2048m +android.useAndroidX=true diff --git a/templates/Fun-and-Games-LibGDX/demo/gradle/libs.versions.toml.peb b/templates/Fun-and-Games-LibGDX/demo/gradle/libs.versions.toml.peb new file mode 100644 index 00000000..700a4bf3 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/gradle/libs.versions.toml.peb @@ -0,0 +1,11 @@ +[versions] +agp = "${{AGP_VERSION}}" +gdx = "1.13.1" + +[libraries] +gdx-core = { group = "com.badlogicgames.gdx", name = "gdx", version.ref = "gdx" } +gdx-backend-android = { group = "com.badlogicgames.gdx", name = "gdx-backend-android", version.ref = "gdx" } +gdx-platform = { group = "com.badlogicgames.gdx", name = "gdx-platform", version.ref = "gdx" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.jar b/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.jar differ diff --git a/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.properties.peb b/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.properties.peb new file mode 100644 index 00000000..f62f9d39 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/gradle/wrapper/gradle-wrapper.properties.peb @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-${{GRADLE_VERSION}}-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/templates/Fun-and-Games-LibGDX/demo/gradlew b/templates/Fun-and-Games-LibGDX/demo/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/templates/Fun-and-Games-LibGDX/demo/gradlew.bat b/templates/Fun-and-Games-LibGDX/demo/gradlew.bat new file mode 100644 index 00000000..25da30db --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/templates/Fun-and-Games-LibGDX/demo/settings.gradle.peb b/templates/Fun-and-Games-LibGDX/demo/settings.gradle.peb new file mode 100644 index 00000000..0a6e40d2 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/settings.gradle.peb @@ -0,0 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "${{APP_NAME}}" +include(":app") diff --git a/templates/Fun-and-Games-LibGDX/demo/template/template.json b/templates/Fun-and-Games-LibGDX/demo/template/template.json new file mode 100644 index 00000000..3634a02a --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/demo/template/template.json @@ -0,0 +1,26 @@ +{ + "name": "Demo (libGDX)", + "description": "Creates a demo game (libGDX)", + "tooltipTag": "template.basic.activity", + "version": "0.1", + "parameters": { + "required": { + "appName": {identifier: "APP_NAME"}, + "packageName": {identifier: "PACKAGE_NAME"}, + "saveLocation": {identifier: "SAVE_LOCATION"} + }, + "optional": { + "minsdk": {identifier: "MIN_SDK"} + } + }, + "system": { + "agpVersion": { "identifier": "AGP_VERSION" }, + "kotlinVersion": { "identifier": "KOTLIN_VERSION" }, + "gradleVersion": { "identifier": "GRADLE_VERSION" }, + "compileSdk": { "identifier": "COMPILE_SDK" }, + "targetSdk": { "identifier": "TARGET_SDK" }, + "javaSourceCompat": { "identifier": "JAVA_SOURCE_COMPAT" }, + "javaTargetCompat": { "identifier": "JAVA_TARGET_COMPAT" }, + "javaTarget": { "identifier": "JAVA_TARGET" } + } +} diff --git a/templates/Fun-and-Games-LibGDX/demo/template/thumb.png b/templates/Fun-and-Games-LibGDX/demo/template/thumb.png new file mode 100644 index 00000000..51e656b5 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/demo/template/thumb.png differ diff --git a/templates/Fun-and-Games-LibGDX/fun-and-games-libgdx.html b/templates/Fun-and-Games-LibGDX/fun-and-games-libgdx.html new file mode 100644 index 00000000..e97c1a47 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/fun-and-games-libgdx.html @@ -0,0 +1,300 @@ + + + + + +Fun and Games LibGDX + + + + +
+

Fun and Games LibGDX

+

Adds three playable libGDX game projects to Code on the Go's New Project screen

+
Version 1.0.0 · Author: James Turner · Template bundle: org.appdevforall.funandgameslibgdx
+
+ + + +
+

1. Executive Overview

+

+ Fun and Games LibGDX is a template bundle for Code on the Go. It adds three + small Android games built with libGDX + to the New Project screen. Instead of starting from an empty activity, a developer picks + a game, enters an app name and package name, and gets a complete project that builds and plays + on the device straight away. They are libGDX ports of the games in Fun and Games JME. +

+

+ It runs no code. A .cgt bundle is a zip archive of project skeletons and their + metadata, which the IDE reads directly. There is nothing to enable, no permissions to + grant, and no process to load. +

+
+ +
+

2. Core Functionality

+ +

The three templates

+ + + + + + + + + +
TemplateLanguageGame
Demo (libGDX)JavaA 3D Earth you drag to spin, an + orbiting rocket with an exhaust trail and bloom glow, and a starfield. Tap for points.
Block Game (libGDX)KotlinThe falling-block puzzle.
Bubble Wand (libGDX)KotlinA first-person shooter: catch + floating balloon animals in bubbles.
+ +

Controls

+ + + + + + + + + +
GameControls
DemoDrag to rotate the Earth, flick to spin it with inertia, tap for +10 points.
Block GameDrag left or right to move, drag down to soft-drop, tap to rotate, + long-press to hard-drop.
Bubble WandLeft half: drag to walk. Right half: drag to look, tap to shoot. Or + use the on-screen D-pad and FIRE. HIDE UI toggles the buttons.
+

A hardware keyboard also works: arrow keys, space and WASD.

+ +

What each template generates

+
    +
  • A Gradle project with an app module, a version catalog + (gradle/libs.versions.toml) and the Gradle wrapper.
  • +
  • The game source under app/src/main/java/, in the package you entered.
  • +
  • libGDX 1.13.1 in app/libs/, which the build uses directly.
  • +
  • Launcher icons, and for Demo the Earth and particle textures it draws.
  • +
+ +

Parameter substitution

+

+ The app name and package name entered in the New Project dialog are substituted into the + generated files. The Android Gradle Plugin, Kotlin, Gradle, SDK and Java versions are taken + from the IDE, so a generated project follows whatever toolchain the installed Code on the Go + ships. +

+
+ +
+

3. Technical Architecture

+ +

What is in the archive

+

+ A .cgt is a zip archive. Its root holds templates.json, which lists + the templates, and one directory per template. Each of those carries + template/template.json for its name, description and parameters, and + template/thumb.png for the picture on the New Project screen. +

+
+ fun-and-games-libgdx.cgt + | + +-- templates.json lists the three templates + +-- cgt-build.properties the commit this bundle was built from + | + +-- demo/ + | +-- template/template.json + | +-- template/thumb.png + | +-- settings.gradle.peb + | +-- gradle/libs.versions.toml.peb + | +-- app/build.gradle.peb + | +-- app/src/main/java/PACKAGE_NAME/CubeGame.java.peb + | +-- app/libs/gdx-1.13.1.jar and the other libGDX files + | + +-- blockgame/ ... and bubblewand/
+ +

Authoring with Pebble

+

+ Files ending in .peb are processed by the Pebble template engine at + project-creation time (${{APP_NAME}}, ${{PACKAGE_NAME}} and friends + are substituted) and the suffix is dropped; other files are copied verbatim. This is the same + authoring format the core Android templates use. +

+ +

How the IDE finds it

+

+ Code on the Go lists the .cgt archives it finds in the Downloads folder in the + Extensions Manager, marked Not installed. Installing one with the + + button, as described under Usage, is what adds its templates to New Project. +

+ +

Provenance

+

+ Every published bundle carries cgt-build.properties at its archive root, + recording the commit it was built from and the date of that commit. Read it with + unzip -p fun-and-games-libgdx.cgt cgt-build.properties. +

+ +

Permissions

+

+ None. A template bundle declares no permissions, because it runs no code. The IDE writes the + generated project, exactly as it does for a built-in template. The games themselves request + no Android permissions either. +

+
+ +
+

4. Usage

+ +

Install

+
    +
  1. Download fun-and-games-libgdx.cgt from the add-on gallery.
  2. +
  3. In Code on the Go, open Preferences → Extensions Manager and switch to the + Templates tab.
  4. +
  5. Tap +, browse to the downloaded .cgt and select it.
  6. +
  7. A dialog lists the three templates. Tap Install.
  8. +
+ +

Create and run a game

+
    +
  1. Open New Project.
  2. +
  3. Pick Demo (libGDX), Block Game (libGDX) or + Bubble Wand (libGDX).
  4. +
  5. Enter an app name and a package name, then tap + Create.
  6. +
  7. Wait for Project initialized, then tap Run.
  8. +
+ +
+ libGDX comes with the bundle. Each generated project already holds libGDX in + app/libs/, so the first build does not download it. AndroidX comes from Code on + the Go's own offline repository. +
+
+ +
+

5. Key Benefits

+
    +
  • Playable from the first build. Each template is a finished game, not a + skeleton, so there is something to run and change straight away.
  • +
  • A widely used game framework. libGDX brings 2D and 3D rendering, input + and audio to a project made on the device.
  • +
  • Java and Kotlin. Demo is written in Java, Block Game and Bubble Wand in Kotlin.
  • +
  • Follows the IDE's toolchain. Plugin, language and SDK versions come from Code + on the Go, so the projects keep building as the IDE updates.
  • +
  • No code, no permissions. A data bundle the IDE reads. Nothing loads into + the editor's process.
  • +
+
+ +
+

6. Limitations & Scope

+
    +
  • Generated projects build for armeabi-v7a and arm64-v8a only. That + covers every phone and tablet that runs Code on the Go, but not x86 emulators.
  • +
  • The bundle is about 8 MB, because it carries the libGDX files for each game.
  • +
  • All three games use libGDX 1.13.1. Do not drop to 1.12.x: its native library is not + 16 KB-page aligned, and Google Play Protect blocks the builds.
  • +
+
+ +
+

7. Credits

+
    +
  • Author: James Turner.
  • +
  • Source: converted from the standalone + FunandGames-libGDX repository. The + game source is unchanged; the build files now take libGDX from app/libs.
  • +
  • A jMonkeyEngine version of the same games is the Fun and Games JME bundle.
  • +
+
+ + + + + diff --git a/templates/Fun-and-Games-LibGDX/icon_day.png b/templates/Fun-and-Games-LibGDX/icon_day.png new file mode 100644 index 00000000..c9b579db Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/icon_day.png differ diff --git a/templates/Fun-and-Games-LibGDX/icon_night.png b/templates/Fun-and-Games-LibGDX/icon_night.png new file mode 100644 index 00000000..036ca4e3 Binary files /dev/null and b/templates/Fun-and-Games-LibGDX/icon_night.png differ diff --git a/templates/Fun-and-Games-LibGDX/templates.json b/templates/Fun-and-Games-LibGDX/templates.json new file mode 100644 index 00000000..edc17b35 --- /dev/null +++ b/templates/Fun-and-Games-LibGDX/templates.json @@ -0,0 +1,86 @@ +{ + "displayName": "Fun and Games", + "templates": [ + { "path": "demo", + "libraries" : [ + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx/1.13.1/gdx-1.13.1.jar", + "dst": "app/libs", + "sha256": "d5860eaf5787a4083e14183ae47da99d3e6908029ca99691947bbd0852b49264" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-jnigen-loader/2.5.2/gdx-jnigen-loader-2.5.2.jar", + "dst": "app/libs", + "sha256": "df81f23cfd6785c52d35e108eeaa3930f559d4d2770a6102e75ca7bfa6f9f27a" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-backend-android/1.13.1/gdx-backend-android-1.13.1.aar", + "dst": "app/libs", + "sha256": "8ec3def7d7c26c0104e0edfdd78ffddefe63f84f84374a9aaa2e11ad4ac2a093" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-armeabi-v7a.jar", + "dst": "app/libs", + "sha256": "19c0a883172123518f885e667f867ebc841f6ed5d07d2aff0151d431cf90d38e" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-arm64-v8a.jar", + "dst": "app/libs", + "sha256": "e15f8a47932ae1928dbcc68dff012611928d59464afae9c6f239b8d143728a96" + } ] }, + { "path": "blockgame", + "libraries" : [ + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx/1.13.1/gdx-1.13.1.jar", + "dst": "app/libs", + "sha256": "d5860eaf5787a4083e14183ae47da99d3e6908029ca99691947bbd0852b49264" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-jnigen-loader/2.5.2/gdx-jnigen-loader-2.5.2.jar", + "dst": "app/libs", + "sha256": "df81f23cfd6785c52d35e108eeaa3930f559d4d2770a6102e75ca7bfa6f9f27a" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-backend-android/1.13.1/gdx-backend-android-1.13.1.aar", + "dst": "app/libs", + "sha256": "8ec3def7d7c26c0104e0edfdd78ffddefe63f84f84374a9aaa2e11ad4ac2a093" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-armeabi-v7a.jar", + "dst": "app/libs", + "sha256": "19c0a883172123518f885e667f867ebc841f6ed5d07d2aff0151d431cf90d38e" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-arm64-v8a.jar", + "dst": "app/libs", + "sha256": "e15f8a47932ae1928dbcc68dff012611928d59464afae9c6f239b8d143728a96" + } ] }, + { "path": "bubblewand", + "libraries" : [ + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx/1.13.1/gdx-1.13.1.jar", + "dst": "app/libs", + "sha256": "d5860eaf5787a4083e14183ae47da99d3e6908029ca99691947bbd0852b49264" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-jnigen-loader/2.5.2/gdx-jnigen-loader-2.5.2.jar", + "dst": "app/libs", + "sha256": "df81f23cfd6785c52d35e108eeaa3930f559d4d2770a6102e75ca7bfa6f9f27a" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-backend-android/1.13.1/gdx-backend-android-1.13.1.aar", + "dst": "app/libs", + "sha256": "8ec3def7d7c26c0104e0edfdd78ffddefe63f84f84374a9aaa2e11ad4ac2a093" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-armeabi-v7a.jar", + "dst": "app/libs", + "sha256": "19c0a883172123518f885e667f867ebc841f6ed5d07d2aff0151d431cf90d38e" + }, + { + "src": "https://repo1.maven.org/maven2/com/badlogicgames/gdx/gdx-platform/1.13.1/gdx-platform-1.13.1-natives-arm64-v8a.jar", + "dst": "app/libs", + "sha256": "e15f8a47932ae1928dbcc68dff012611928d59464afae9c6f239b8d143728a96" + } ] } + ] +}