This commit is contained in:
Leon Albrecht 2025-07-12 23:21:02 +02:00
commit e5e0da4dec
39 changed files with 1622 additions and 0 deletions

28
.gitignore vendored Normal file
View file

@ -0,0 +1,28 @@
# Gradle-Build-Ordner (generierte Dateien)
.gradle/
build/
# IntelliJ IDEA
.idea/
*.iml
out/
# Eclipse
.project
.classpath
.settings/
# MacOS
.DS_Store
# Windows
Thumbs.db
# Logdateien
*.log
# Betriebssystem-Dateien
desktop.ini
# Sonstiges
bin/

46
README.md Normal file
View file

@ -0,0 +1,46 @@
# 🪙 Catch the Coins
**Catch the Coins** ist ein kleines Java-Spiel, das ich zu Lernzwecken mit dem Framework [LibGDX](https://libgdx.com/) entwickelt habe. Ziel des Spiels ist es, mit einem Korb fallende Münzen einzufangen und Leben zu kaufen, um länger zu überleben.
---
## 🎮 Steuerung
| Taste | Funktion |
|-------|-------------------------------------------|
| **A** | Korb nach links bewegen |
| **D** | Korb nach rechts bewegen |
| **L** | Ein Leben kaufen (wenn du 10 Coins hast) |
---
## 🕹️ Spielprinzip
- Fange mit dem Korb so viele Münzen wie möglich.
- Für jeweils **10 gesammelte Münzen** kannst du dir durch Drücken von **L** ein zusätzliches Leben kaufen.
- Verliere keine Münzen sonst verlierst du Leben!
---
## 🛠️ Technologien
- **Programmiersprache:** Java
- **Framework:** LibGDX
- **Build-Tool:** Gradle (LWJGL3 Backend)
---
## 🚀 Voraussetzungen
- **Java Development Kit (JDK)** Version 17 oder höher
- **IntelliJ IDEA** (oder eine andere Java-IDE)
- Internetverbindung beim ersten Build (für Gradle-Abhängigkeiten)
---
## ▶️ Spiel starten
Öffne das Projekt in IntelliJ und führe folgenden Gradle-Task aus:
```bash
./gradlew lwjgl3:run

3
assets/.gitkeep Normal file
View file

@ -0,0 +1,3 @@
This file can be deleted if there are any other files present in this folder.
It is only here to ensure this folder is added to Git, instead of being
ignored because it is empty.

6
assets/assets.txt Normal file
View file

@ -0,0 +1,6 @@
.gitkeep
basket.png
coin.png
dead.png
font.fnt
hs_err_pid8522.log

BIN
assets/basket.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
assets/coin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

BIN
assets/dead.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

BIN
assets/font.fnt Normal file

Binary file not shown.

73
build.gradle Normal file
View file

@ -0,0 +1,73 @@
buildscript {
repositories {
mavenCentral()
maven { url 'https://s01.oss.sonatype.org' }
gradlePluginPortal()
mavenLocal()
google()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
}
dependencies {
}
}
allprojects {
apply plugin: 'eclipse'
apply plugin: 'idea'
// This allows you to "Build and run using IntelliJ IDEA", an option in IDEA's Settings.
idea {
module {
outputDir file('build/classes/java/main')
testOutputDir file('build/classes/java/test')
}
}
}
configure(subprojects) {
apply plugin: 'java-library'
sourceCompatibility = 8
// From https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/
// The article can be helpful when using assets.txt in your project.
tasks.register('generateAssetList') {
inputs.dir("${project.rootDir}/assets/")
// projectFolder/assets
File assetsFolder = new File("${project.rootDir}/assets/")
// projectFolder/assets/assets.txt
File assetsFile = new File(assetsFolder, "assets.txt")
// delete that file in case we've already created it
assetsFile.delete()
// iterate through all files inside that folder
// convert it to a relative path
// and append it to the file assets.txt
fileTree(assetsFolder).collect { assetsFolder.relativePath(it) }.sort().each {
assetsFile.append(it + "\n")
}
}
processResources.dependsOn 'generateAssetList'
compileJava {
options.incremental = true
}
}
subprojects {
version = "$projectVersion"
ext.appName = 'Catch_The_Coins'
repositories {
mavenCentral()
maven { url 'https://s01.oss.sonatype.org' }
// You may want to remove the following line if you have errors downloading dependencies.
mavenLocal()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://jitpack.io' }
}
}
eclipse.project.name = 'Catch_The_Coins' + '-parent'

15
core/build.gradle Normal file
View file

@ -0,0 +1,15 @@
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'
eclipse.project.name = appName + '-core'
dependencies {
api "com.badlogicgames.box2dlights:box2dlights:$box2dlightsVersion"
api "com.badlogicgames.gdx:gdx-ai:$aiVersion"
api "com.badlogicgames.gdx:gdx-box2d:$gdxVersion"
api "com.badlogicgames.gdx:gdx:$gdxVersion"
if(enableGraalNative == 'true') {
implementation "io.github.berstanio:gdx-svmhelper-annotations:$graalHelperVersion"
implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
}
}

View file

@ -0,0 +1,154 @@
package de.leon2k.studios;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
import de.leon2k.studios.objects.Coin;
import de.leon2k.studios.objects.Dead;
import de.leon2k.studios.objects.Player;
import de.leon2k.studios.objects.spawner.ObjectsSpawner;
import de.leon2k.studios.ui.Coins;
import de.leon2k.studios.ui.Lives;
import de.leon2k.studios.utilis.GameContext;
import de.leon2k.studios.utilis.KeyBoardManager;
public class FirstScreen implements Screen {
// Statische Konstanten für bessere Übersicht
public static final float PPM = 25f; // Pixels Per Meter - dieselbe wie in Player.java!
// Kamera und Rendering
private SpriteBatch batch;
private OrthographicCamera camera;
private Box2DDebugRenderer debugRenderer;
public Lives lives;
public Coins coins;
// Spielwelt und Objekte
private World world;
public static ObjectsSpawner objectsSpawner;
@Override
public void show() {
// --- 1. Kamera und Renderer einmalig erstellen ---
camera = new OrthographicCamera();
resize(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
batch = new SpriteBatch();
debugRenderer = new Box2DDebugRenderer();
lives = new Lives();
coins = new Coins();
// --- 2. Spielwelt initialisieren ---
world = new World(new Vector2(0, -9.81f), true);
world.setContactListener(new MyContactListener()); // Eigene Klasse für bessere Struktur
// --- 3. Spielobjekte erstellen ---
GameContext.playerspeed = 5;
GameContext.coins = 0;
GameContext.lives = 5;
GameContext.player = new Player(new Vector2(150, 150), world);
objectsSpawner = new ObjectsSpawner(world);
}
boolean gameOverTriggered = false;
public void update(float delta) {
// --- 1. Physik-Welt aktualisieren ---
world.step(1 / 60f, 6, 2);
// --- GEÄNDERT: Zerstöre Objekte sicher NACH dem Physik-Update ---
objectsSpawner.destroyQueuedObjects();
if (GameContext.lives <= 0 && !gameOverTriggered) {
gameOverTriggered = true;
((Game) Gdx.app.getApplicationListener()).setScreen(new FirstScreen());
GameContext.lives = 5;
GameContext.coins = 0;
}
// --- 2. Logik aktualisieren ---
KeyBoardManager.keyboard(delta);
objectsSpawner.update(delta);
GameContext.player.update();
camera.update();
}
@Override
public void render(float delta) {
update(delta);
Gdx.gl.glClearColor(0.1f, 0.1f, 0.2f, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined);
batch.begin();
GameContext.player.draw(batch);
objectsSpawner.draw(batch);
lives.draw(batch);
coins.draw(batch);
batch.end();
if(GameContext.dev_mode) {
Matrix4 debugMatrix = camera.combined.cpy().scale(PPM, PPM, 0);
debugRenderer.render(world, debugMatrix);
}
}
@Override
public void resize(int width, int height) {
camera.setToOrtho(false, width, height);
}
private static class MyContactListener implements ContactListener {
@Override
public void beginContact(Contact contact) {
Object a = contact.getFixtureA().getUserData();
Object b = contact.getFixtureB().getUserData();
// Kollision zwischen Spieler und Coin
if ((a instanceof Player && b instanceof Coin) || (a instanceof Coin && b instanceof Player)) {
Coin coin = (a instanceof Coin) ? (Coin) a : (Coin) b;
objectsSpawner.queueCoinForRemoval(coin);
GameContext.coins++; // Zähle die Münze
}
// Kollision zwischen Spieler und Dead
if ((a instanceof Player && b instanceof Dead) || (a instanceof Dead && b instanceof Player)) {
Dead dead = (a instanceof Dead) ? (Dead) a : (Dead) b;
objectsSpawner.queueDeadForRemoval(dead);
GameContext.lives--; // Reduziere Leben
}
}
@Override public void endContact(Contact contact) {}
@Override public void preSolve(Contact contact, Manifold oldManifold) {}
@Override public void postSolve(Contact contact, ContactImpulse impulse) {}
}
@Override
public void dispose() {
batch.dispose();
debugRenderer.dispose();
world.dispose();
GameContext.player.dispose();
objectsSpawner.dispose();
lives.dispose();
coins.dispose();
}
@Override public void pause() {}
@Override public void resume() {}
@Override public void hide() {}
}

View file

@ -0,0 +1,11 @@
package de.leon2k.studios;
import com.badlogic.gdx.Game;
/** {@link com.badlogic.gdx.ApplicationListener} implementation shared by all platforms. */
public class Main extends Game {
@Override
public void create() {
setScreen(new FirstScreen());
}
}

View file

@ -0,0 +1,61 @@
package de.leon2k.studios.objects;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
import de.leon2k.studios.FirstScreen;
public class Coin {
private Body body;
private final Texture texture;
private final float width = 64f, height = 64f;
public Coin(Vector2 position, World world) {
texture = new Texture("coin.png"); // Stellen Sie sicher, dass diese Datei existiert!
createBody(position, world);
}
private void createBody(Vector2 position, World world) {
BodyDef bdef = new BodyDef();
bdef.type = BodyDef.BodyType.DynamicBody;
bdef.position.set(position.x / FirstScreen.PPM, position.y / FirstScreen.PPM);
body = world.createBody(bdef);
PolygonShape shape = new PolygonShape();
shape.setAsBox(width / 2 / FirstScreen.PPM, height / 2 / FirstScreen.PPM);
FixtureDef fdef = new FixtureDef();
fdef.shape = shape;
fdef.isSensor = true; // Sensor, damit es keine physische Kollision gibt
body.createFixture(fdef).setUserData(this); // Wichtig: Setzt dieses Objekt als UserData
shape.dispose();
}
public void update(float delta) {
// Hier könnte Logik stehen, z.B. eine Animation
}
public void draw(SpriteBatch batch) {
Vector2 pos = body.getPosition();
float drawX = pos.x * FirstScreen.PPM - width / 2;
float drawY = pos.y * FirstScreen.PPM - height / 2;
batch.draw(texture, drawX, drawY, width, height);
}
public void dispose() {
texture.dispose();
}
// Wichtige Getter und Setter
public Body getBody() {
return body;
}
public void setBody(Body body) {
this.body = body;
}
}

View file

@ -0,0 +1,61 @@
package de.leon2k.studios.objects;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
import de.leon2k.studios.FirstScreen;
public class Dead {
private Body body;
private final Texture texture;
private final float width = 64f, height = 64f;
public Dead(Vector2 position, World world) {
texture = new Texture("dead.png"); // Stellen Sie sicher, dass diese Datei existiert!
createBody(position, world);
}
private void createBody(Vector2 position, World world) {
BodyDef bdef = new BodyDef();
bdef.type = BodyDef.BodyType.DynamicBody;
bdef.position.set(position.x / FirstScreen.PPM, position.y / FirstScreen.PPM);
body = world.createBody(bdef);
CircleShape shape = new CircleShape(); // Kann auch eine andere Form sein
shape.setRadius(width / 2 / FirstScreen.PPM);
FixtureDef fdef = new FixtureDef();
fdef.shape = shape;
fdef.isSensor = true; // Sensor, um durch den Spieler zu fallen
body.createFixture(fdef).setUserData(this); // Wichtig: Setzt dieses Objekt als UserData
shape.dispose();
}
public void update(float delta) {
// Update-Logik
}
public void draw(SpriteBatch batch) {
Vector2 pos = body.getPosition();
float drawX = pos.x * FirstScreen.PPM - width / 2;
float drawY = pos.y * FirstScreen.PPM - height / 2;
batch.draw(texture, drawX, drawY, width, height);
}
public void dispose() {
texture.dispose();
}
// Wichtige Getter und Setter
public Body getBody() {
return body;
}
public void setBody(Body body) {
this.body = body;
}
}

View file

@ -0,0 +1,60 @@
package de.leon2k.studios.objects;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.*;
public class Player {
private Texture player;
private Body ballBody;
public Vector2 position;
private final float PPM = 25f;
public Player(Vector2 pos, World world) {
player = new Texture("basket.png");
this.position = pos;
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.KinematicBody;
bodyDef.position.set(position.x / PPM, position.y / PPM);
ballBody = world.createBody(bodyDef);
CircleShape shape = new CircleShape();
shape.setRadius(87.5f / PPM);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.shape = shape;
fixtureDef.density = 1f;
Fixture fixture = ballBody.createFixture(fixtureDef);
fixture.setUserData(this);
shape.dispose();
}
public void update() {
// Der Steuer-Vektor gibt die Position an den Physik-Körper weiter
ballBody.setTransform(position.x / PPM, position.y / PPM, ballBody.getAngle());
}
public void draw(SpriteBatch batch) {
float width = 175f;
float height = 175f;
// --- ÄNDERUNG HIER ---
// Die Zeichenposition wird jetzt direkt vom 'position'-Vektor gelesen.
// ACHTUNG: Dies wird nicht empfohlen, da es zu Abweichungen zwischen
// sichtbarer Position und physikalischer Kollision kommen kann.
float x = position.x - width / 2f;
float y = position.y - height / 2f;
batch.draw(player, x, y, width, height);
}
public void dispose() {
player.dispose();
}
}

View file

@ -0,0 +1,115 @@
package de.leon2k.studios.objects.spawner;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.World;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;
import de.leon2k.studios.objects.Coin;
import de.leon2k.studios.objects.Dead;
import de.leon2k.studios.utilis.AISpawner;
import java.util.Random;
public class ObjectsSpawner {
private final World world;
private final Random random = new Random();
private final Array<Coin> activeCoins = new Array<>();
private final Array<Dead> activeDeads = new Array<>();
private final Array<Coin> coinsToRemove = new Array<>();
private final Array<Dead> deadsToRemove = new Array<>();
private float timer = 0f;
private float nextDelay = 0.5f;
public ObjectsSpawner(World world) {
this.world = world;
}
public void update(float delta) {
timer += delta;
if (timer >= nextDelay) {
float decision = random.nextFloat();
if (decision < 0.10f) {
// 10% nix
} else if (decision < 0.30f) {
spawnCoin(); // 20%
} else if (decision < 0.60f) {
spawnDead(); // 30%
} else {
spawnCoin();
spawnDead(); // 40%
}
timer = 0f;
setNextDelay();
}
for (Coin coin : activeCoins) coin.update(delta);
for (Dead dead : activeDeads) dead.update(delta);
}
private void setNextDelay() {
nextDelay = 0.4f + random.nextFloat() * 1.0f; // 0.4s bis 1.4s
}
private void spawnCoin() {
float x = AISpawner.getRandomX();
float y = 900f;
Coin coin = new Coin(new Vector2(x, y), world);
activeCoins.add(coin);
}
private void spawnDead() {
float x = AISpawner.getRandomX();
float y = 900f;
Dead dead = new Dead(new Vector2(x, y), world);
activeDeads.add(dead);
}
public void draw(SpriteBatch batch) {
for (Coin coin : activeCoins) coin.draw(batch);
for (Dead dead : activeDeads) dead.draw(batch);
}
public void queueCoinForRemoval(Coin coin) {
if (coin != null && !coinsToRemove.contains(coin, true)) coinsToRemove.add(coin);
}
public void queueDeadForRemoval(Dead dead) {
if (dead != null && !deadsToRemove.contains(dead, true)) deadsToRemove.add(dead);
}
public void destroyQueuedObjects() {
for (Coin coin : coinsToRemove) {
if (coin.getBody() != null) {
world.destroyBody(coin.getBody());
coin.setBody(null);
activeCoins.removeValue(coin, true);
}
}
coinsToRemove.clear();
for (Dead dead : deadsToRemove) {
if (dead.getBody() != null) {
world.destroyBody(dead.getBody());
dead.setBody(null);
activeDeads.removeValue(dead, true);
}
}
deadsToRemove.clear();
}
public void dispose() {
for (Coin coin : activeCoins) coin.dispose();
for (Dead dead : activeDeads) dead.dispose();
}
}
// AISpawner.java

View file

@ -0,0 +1,25 @@
package de.leon2k.studios.ui;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import de.leon2k.studios.utilis.GameContext;
public class Coins {
BitmapFont font;
public Coins() {
font = new BitmapFont(); // Standardfont, du kannst auch eigene Fonts laden
font.setColor(Color.WHITE); // Farbe des Textes
font.getData().setScale(2); // Größe des Textes
}
public void draw(SpriteBatch batch) {
font.draw(batch, "Coins: " + GameContext.coins.toString(), 50, 800); // Text, X-Position, Y-Position
}
public void dispose() {
font.dispose();
}
}

View file

@ -0,0 +1,25 @@
package de.leon2k.studios.ui;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import de.leon2k.studios.utilis.GameContext;
public class Lives {
BitmapFont font;
public Lives() {
font = new BitmapFont(); // Standardfont, du kannst auch eigene Fonts laden
font.setColor(Color.WHITE); // Farbe des Textes
font.getData().setScale(2); // Größe des Textes
}
public void draw(SpriteBatch batch) {
font.draw(batch, "Lives: " + GameContext.lives.toString(), 450, 800); // Text, X-Position, Y-Position
}
public void dispose() {
font.dispose();
}
}

View file

@ -0,0 +1,12 @@
package de.leon2k.studios.utilis;
import com.badlogic.gdx.Gdx;
public class AISpawner {
private static final float SCREEN_MARGIN = 64f;
public static float getRandomX() {
float screenWidth = Gdx.graphics.getWidth();
return SCREEN_MARGIN + (float) Math.random() * (screenWidth - 2 * SCREEN_MARGIN);
}
}

View file

@ -0,0 +1,17 @@
package de.leon2k.studios.utilis;
import de.leon2k.studios.objects.Player;
public class GameContext {
public static Player player;
public static Integer playerspeed;
public static Integer coins;
public static Integer lives;
public static Boolean dev_mode;
public static void setupvariables() {
dev_mode = false;
}
}

View file

@ -0,0 +1,44 @@
package de.leon2k.studios.utilis;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.math.Vector2;
import de.leon2k.studios.FirstScreen;
public class KeyBoardManager {
public static void keyboard(float delta) {
float screenWidth = Gdx.graphics.getWidth();
float playerWidth = 175f;
float halfWidth = playerWidth / 2f;
// Rechts (D)
if (Gdx.input.isKeyPressed(Input.Keys.D)) {
if (GameContext.player.position.x + halfWidth + GameContext.playerspeed <= screenWidth) {
GameContext.player.position.x += GameContext.playerspeed;
}
}
// Links (A)
if (Gdx.input.isKeyPressed(Input.Keys.A)) {
if (GameContext.player.position.x - halfWidth - GameContext.playerspeed >= 0) {
GameContext.player.position.x -= GameContext.playerspeed;
}
}
if (Gdx.input.isKeyJustPressed(Input.Keys.L)) {
if (GameContext.coins >= 10) {
GameContext.coins -= 10;
GameContext.lives++;
}
}
if(Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)){
Gdx.app.exit();
}
}
}

9
gradle.properties Normal file
View file

@ -0,0 +1,9 @@
org.gradle.daemon=false
org.gradle.jvmargs=-Xms512M -Xmx1G -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8
org.gradle.configureondemand=false
aiVersion=1.8.2
box2dlightsVersion=1.5
graalHelperVersion=2.0.1
enableGraalNative=false
gdxVersion=1.13.1
projectVersion=1.0.0

View file

@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/65aaef917b9f394804f058f1861225c9/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/c728c5388b044fbdbbc44b0c6acee0df/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/65aaef917b9f394804f058f1861225c9/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/c728c5388b044fbdbbc44b0c6acee0df/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/dc463b4a8183dbcaa1b32544189c7f03/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/cb7dc109dd590ebca2d703734d23c9d3/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/65aaef917b9f394804f058f1861225c9/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/c728c5388b044fbdbbc44b0c6acee0df/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/43ee83889b87bacad5d3071ae7bbd349/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/2d57bdd1e17a18f83ff073919daa35ba/redirect
toolchainVersion=17

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew vendored Normal file
View file

@ -0,0 +1,251 @@
#!/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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# 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/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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="\\\"\\\""
# 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, 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" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# 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" "$@"

94
gradlew.bat vendored Normal file
View file

@ -0,0 +1,94 @@
@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
@rem SPDX-License-Identifier: Apache-2.0
@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=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
: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

185
lwjgl3/build.gradle Normal file
View file

@ -0,0 +1,185 @@
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath "io.github.fourlastor:construo:1.7.1"
if(enableGraalNative == 'true') {
classpath "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin:0.9.28"
}
}
}
plugins {
id "application"
}
apply plugin: 'io.github.fourlastor.construo'
import io.github.fourlastor.construo.Target
sourceSets.main.resources.srcDirs += [ rootProject.file('assets').path ]
mainClassName = 'de.leon2k.studios.lwjgl3.Lwjgl3Launcher'
application.setMainClass(mainClassName)
eclipse.project.name = appName + '-lwjgl3'
java.sourceCompatibility = 8
java.targetCompatibility = 8
if (JavaVersion.current().isJava9Compatible()) {
compileJava.options.release.set(8)
}
dependencies {
implementation "com.badlogicgames.gdx:gdx-backend-lwjgl3:$gdxVersion"
implementation "com.badlogicgames.gdx:gdx-box2d-platform:$gdxVersion:natives-desktop"
implementation "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
implementation "com.badlogicgames.gdx:gdx-freetype:$gdxVersion"
implementation project(':core')
if(enableGraalNative == 'true') {
implementation "io.github.berstanio:gdx-svmhelper-backend-lwjgl3:$graalHelperVersion"
implementation "io.github.berstanio:gdx-svmhelper-extension-box2d:$graalHelperVersion"
}
}
def os = System.properties['os.name'].toLowerCase()
run {
workingDir = rootProject.file('assets').path
// You can uncomment the next line if your IDE claims a build failure even when the app closed properly.
//setIgnoreExitValue(true)
if (os.contains('mac')) jvmArgs += "-XstartOnFirstThread"
}
jar {
// sets the name of the .jar file this produces to the name of the game or app, with the version after.
archiveFileName.set("${appName}-${projectVersion}.jar")
// the duplicatesStrategy matters starting in Gradle 7.0; this setting works.
duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
dependsOn configurations.runtimeClasspath
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// these "exclude" lines remove some unnecessary duplicate files in the output JAR.
exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
dependencies {
exclude('META-INF/INDEX.LIST', 'META-INF/maven/**')
}
// setting the manifest makes the JAR runnable.
manifest {
attributes 'Main-Class': project.mainClassName
}
// this last step may help on some OSes that need extra instruction to make runnable JARs.
doLast {
file(archiveFile).setExecutable(true, false)
}
}
// Builds a JAR that only includes the files needed to run on macOS, not Windows or Linux.
// The file size for a Mac-only JAR is about 7MB smaller than a cross-platform JAR.
tasks.register("jarMac") {
dependsOn("jar")
group("build")
jar.archiveFileName.set("${appName}-${projectVersion}-mac.jar")
jar.exclude("windows/x86/**", "windows/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**", "**/*.dll", "**/*.so",
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
dependencies {
jar.exclude("windows/x86/**", "windows/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**",
'META-INF/INDEX.LIST', 'META-INF/maven/**')
}
}
// Builds a JAR that only includes the files needed to run on Linux, not Windows or macOS.
// The file size for a Linux-only JAR is about 5MB smaller than a cross-platform JAR.
tasks.register("jarLinux") {
dependsOn("jar")
group("build")
jar.archiveFileName.set("${appName}-${projectVersion}-linux.jar")
jar.exclude("windows/x86/**", "windows/x64/**", "macos/arm64/**", "macos/x64/**", "**/*.dll", "**/*.dylib",
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
dependencies {
jar.exclude("windows/x86/**", "windows/x64/**", "macos/arm64/**", "macos/x64/**",
'META-INF/INDEX.LIST', 'META-INF/maven/**')
}
}
// Builds a JAR that only includes the files needed to run on Windows, not Linux or macOS.
// The file size for a Windows-only JAR is about 6MB smaller than a cross-platform JAR.
tasks.register("jarWin") {
dependsOn("jar")
group("build")
jar.archiveFileName.set("${appName}-${projectVersion}-win.jar")
jar.exclude("macos/arm64/**", "macos/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**", "**/*.dylib", "**/*.so",
'META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA')
dependencies {
jar.exclude("macos/arm64/**", "macos/x64/**", "linux/arm32/**", "linux/arm64/**", "linux/x64/**",
'META-INF/INDEX.LIST', 'META-INF/maven/**')
}
}
construo {
// name of the executable
name.set(appName)
// human-readable name, used for example in the `.app` name for macOS
humanName.set(appName)
// Optional, defaults to project version property
version.set("$projectVersion")
targets.configure {
create("linuxX64", Target.Linux) {
architecture.set(Target.Architecture.X86_64)
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_linux_hotspot_17.0.15_6.tar.gz")
// Linux does not currently have a way to set the icon on the executable
}
create("macM1", Target.MacOs) {
architecture.set(Target.Architecture.AARCH64)
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.15_6.tar.gz")
// macOS needs an identifier
identifier.set("de.leon2k.studios." + appName)
// Optional: icon for macOS, as an ICNS file
macIcon.set(project.file("icons/logo.icns"))
}
create("macX64", Target.MacOs) {
architecture.set(Target.Architecture.X86_64)
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_mac_hotspot_17.0.15_6.tar.gz")
// macOS needs an identifier
identifier.set("de.leon2k.studios." + appName)
// Optional: icon for macOS, as an ICNS file
macIcon.set(project.file("icons/logo.icns"))
}
create("winX64", Target.Windows) {
architecture.set(Target.Architecture.X86_64)
// Optional: icon for Windows, as a PNG
icon.set(project.file("icons/logo.png"))
jdkUrl.set("https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.15%2B6/OpenJDK17U-jdk_x64_windows_hotspot_17.0.15_6.zip")
// Uncomment the next line to show a console when the game runs, to print messages.
//useConsole.set(true)
}
}
}
// Equivalent to the jar task; here for compatibility with gdx-setup.
tasks.register('dist') {
dependsOn 'jar'
}
distributions {
main {
contents {
into('libs') {
project.configurations.runtimeClasspath.files.findAll { file ->
file.getName() != project.tasks.jar.outputs.files.singleFile.name
}.each { file ->
exclude file.name
}
}
}
}
}
startScripts.dependsOn(':lwjgl3:jar')
startScripts.classpath = project.tasks.jar.outputs.files
if(enableGraalNative == 'true') {
apply from: file("nativeimage.gradle")
}

BIN
lwjgl3/icons/logo.icns Normal file

Binary file not shown.

BIN
lwjgl3/icons/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
lwjgl3/icons/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

54
lwjgl3/nativeimage.gradle Normal file
View file

@ -0,0 +1,54 @@
project(":lwjgl3") {
apply plugin: "org.graalvm.buildtools.native"
graalvmNative {
binaries {
main {
imageName = appName
mainClass = project.mainClassName
requiredVersion = '23.0'
buildArgs.add("-march=compatibility")
jvmArgs.addAll("-Dfile.encoding=UTF8")
sharedLibrary = false
resources.autodetect()
}
}
}
run {
doNotTrackState("Running the app should not be affected by Graal.")
}
// Modified from https://lyze.dev/2021/04/29/libGDX-Internal-Assets-List/ ; thanks again, Lyze!
// This creates a resource-config.json file based on the contents of the assets folder (and the libGDX icons).
// This file is used by Graal Native to embed those specific files.
// This has to run before nativeCompile, so it runs at the start of an unrelated resource-handling command.
generateResourcesConfigFile.doFirst {
def assetsFolder = new File("${project.rootDir}/assets/")
def lwjgl3 = project(':lwjgl3')
def resFolder = new File("${lwjgl3.projectDir}/src/main/resources/META-INF/native-image/${lwjgl3.ext.appName}")
resFolder.mkdirs()
def resFile = new File(resFolder, "resource-config.json")
resFile.delete()
resFile.append(
"""{
"resources":{
"includes":[
{
"pattern": ".*(""")
// This adds every filename in the assets/ folder to a pattern that adds those files as resources.
fileTree(assetsFolder).each {
// The backslash-Q and backslash-E escape the start and end of a literal string, respectively.
resFile.append("\\\\Q${it.name}\\\\E|")
}
// We also match all of the window icon images this way and the font files that are part of libGDX.
resFile.append(
"""libgdx.+\\\\.png|lsans.+)"
}
]},
"bundles":[]
}"""
)
}
}

View file

@ -0,0 +1,42 @@
package de.leon2k.studios.lwjgl3;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
import de.leon2k.studios.Main;
import de.leon2k.studios.utilis.GameContext;
/** Launches the desktop (LWJGL3) application. */
public class Lwjgl3Launcher {
public static void main(String[] args) {
if (StartupHelper.startNewJvmIfRequired()) return; // This handles macOS support and helps on Windows.
createApplication();
}
private static Lwjgl3Application createApplication() {
return new Lwjgl3Application(new Main(), getDefaultConfiguration());
}
private static Lwjgl3ApplicationConfiguration getDefaultConfiguration() {
Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration();
configuration.setTitle("Catch_The_Coins");
//// Vsync limits the frames per second to what your hardware can display, and helps eliminate
//// screen tearing. This setting doesn't always work on Linux, so the line after is a safeguard.
configuration.setForegroundFPS(60);
//// Limits FPS to the refresh rate of the currently active monitor, plus 1 to try to match fractional
//// refresh rates. The Vsync setting above should limit the actual FPS to match the monitor.
//// If you remove the above line and set Vsync to false, you can get unlimited FPS, which can be
//// useful for testing performance, but can also be very stressful to some hardware.
//// You may also need to configure GPU drivers to fully disable Vsync; this can cause screen tearing.
configuration.setWindowedMode(600, 850);
configuration.setResizable(false);
//// You can change these files; they are in lwjgl3/src/main/resources/ .
//// They can also be loaded from the root of assets/ .
configuration.setWindowIcon("basket.png");
GameContext.setupvariables();
return configuration;
}
}

View file

@ -0,0 +1,204 @@
/*
* Copyright 2020 damios
*
* 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.
*/
//Note, the above license and copyright applies to this file only.
package de.leon2k.studios.lwjgl3;
import com.badlogic.gdx.Version;
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3NativesLoader;
import org.lwjgl.system.macosx.LibC;
import org.lwjgl.system.macosx.ObjCRuntime;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import static org.lwjgl.system.JNI.invokePPP;
import static org.lwjgl.system.JNI.invokePPZ;
import static org.lwjgl.system.macosx.ObjCRuntime.objc_getClass;
import static org.lwjgl.system.macosx.ObjCRuntime.sel_getUid;
/**
* Adds some utilities to ensure that the JVM was started with the
* {@code -XstartOnFirstThread} argument, which is required on macOS for LWJGL 3
* to function. Also helps on Windows when users have names with characters from
* outside the Latin alphabet, a common cause of startup crashes.
* <br>
* <a href="https://jvm-gaming.org/t/starting-jvm-on-mac-with-xstartonfirstthread-programmatically/57547">Based on this java-gaming.org post by kappa</a>
* @author damios
*/
public class StartupHelper {
private static final String JVM_RESTARTED_ARG = "jvmIsRestarted";
private StartupHelper() {
throw new UnsupportedOperationException();
}
/**
* Starts a new JVM if the application was started on macOS without the
* {@code -XstartOnFirstThread} argument. This also includes some code for
* Windows, for the case where the user's home directory includes certain
* non-Latin-alphabet characters (without this code, most LWJGL3 apps fail
* immediately for those users). Returns whether a new JVM was started and
* thus no code should be executed.
* <p>
* <u>Usage:</u>
*
* <pre><code>
* public static void main(String... args) {
* if (StartupHelper.startNewJvmIfRequired(true)) return; // This handles macOS support and helps on Windows.
* // after this is the actual main method code
* }
* </code></pre>
*
* @param redirectOutput
* whether the output of the new JVM should be rerouted to the
* old JVM, so it can be accessed in the same place; keeps the
* old JVM running if enabled
* @return whether a new JVM was started and thus no code should be executed
* in this one
*/
public static boolean startNewJvmIfRequired(boolean redirectOutput) {
String osName = System.getProperty("os.name").toLowerCase();
if (!osName.contains("mac")) {
if (osName.contains("windows")) {
// Here, we are trying to work around an issue with how LWJGL3 loads its extracted .dll files.
// By default, LWJGL3 extracts to the directory specified by "java.io.tmpdir", which is usually the user's home.
// If the user's name has non-ASCII (or some non-alphanumeric) characters in it, that would fail.
// By extracting to the relevant "ProgramData" folder, which is usually "C:\ProgramData", we avoid this.
// We also temporarily change the "user.name" property to one without any chars that would be invalid.
// We revert our changes immediately after loading LWJGL3 natives.
String programData = System.getenv("ProgramData");
if(programData == null) programData = "C:\\Temp\\"; // if ProgramData isn't set, try some fallback.
String prevTmpDir = System.getProperty("java.io.tmpdir", programData);
String prevUser = System.getProperty("user.name", "libGDX_User");
System.setProperty("java.io.tmpdir", programData + "/libGDX-temp");
System.setProperty("user.name", ("User_" + prevUser.hashCode() + "_GDX" + Version.VERSION).replace('.', '_'));
Lwjgl3NativesLoader.load();
System.setProperty("java.io.tmpdir", prevTmpDir);
System.setProperty("user.name", prevUser);
}
return false;
}
// There is no need for -XstartOnFirstThread on Graal native image
if (!System.getProperty("org.graalvm.nativeimage.imagecode", "").isEmpty()) {
return false;
}
// Checks if we are already on the main thread, such as from running via Construo.
long objc_msgSend = ObjCRuntime.getLibrary().getFunctionAddress("objc_msgSend");
long NSThread = objc_getClass("NSThread");
long currentThread = invokePPP(NSThread, sel_getUid("currentThread"), objc_msgSend);
boolean isMainThread = invokePPZ(currentThread, sel_getUid("isMainThread"), objc_msgSend);
if(isMainThread) return false;
long pid = LibC.getpid();
// check whether -XstartOnFirstThread is enabled
if ("1".equals(System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + pid))) {
return false;
}
// check whether the JVM was previously restarted
// avoids looping, but most certainly leads to a crash
if ("true".equals(System.getProperty(JVM_RESTARTED_ARG))) {
System.err.println(
"There was a problem evaluating whether the JVM was started with the -XstartOnFirstThread argument.");
return false;
}
// Restart the JVM with -XstartOnFirstThread
ArrayList<String> jvmArgs = new ArrayList<>();
String separator = System.getProperty("file.separator", "/");
// The following line is used assuming you target Java 8, the minimum for LWJGL3.
String javaExecPath = System.getProperty("java.home") + separator + "bin" + separator + "java";
// If targeting Java 9 or higher, you could use the following instead of the above line:
//String javaExecPath = ProcessHandle.current().info().command().orElseThrow();
if (!(new File(javaExecPath)).exists()) {
System.err.println(
"A Java installation could not be found. If you are distributing this app with a bundled JRE, be sure to set the -XstartOnFirstThread argument manually!");
return false;
}
jvmArgs.add(javaExecPath);
jvmArgs.add("-XstartOnFirstThread");
jvmArgs.add("-D" + JVM_RESTARTED_ARG + "=true");
jvmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
jvmArgs.add("-cp");
jvmArgs.add(System.getProperty("java.class.path"));
String mainClass = System.getenv("JAVA_MAIN_CLASS_" + pid);
if (mainClass == null) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (trace.length > 0) {
mainClass = trace[trace.length - 1].getClassName();
} else {
System.err.println("The main class could not be determined.");
return false;
}
}
jvmArgs.add(mainClass);
try {
if (!redirectOutput) {
ProcessBuilder processBuilder = new ProcessBuilder(jvmArgs);
processBuilder.start();
} else {
Process process = (new ProcessBuilder(jvmArgs))
.redirectErrorStream(true).start();
BufferedReader processOutput = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String line;
while ((line = processOutput.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
}
} catch (Exception e) {
System.err.println("There was a problem restarting the JVM");
e.printStackTrace();
}
return true;
}
/**
* Starts a new JVM if the application was started on macOS without the
* {@code -XstartOnFirstThread} argument. Returns whether a new JVM was
* started and thus no code should be executed. Redirects the output of the
* new JVM to the old one.
* <p>
* <u>Usage:</u>
*
* <pre>
* public static void main(String... args) {
* if (StartupHelper.startNewJvmIfRequired()) return; // This handles macOS support and helps on Windows.
* // the actual main method code
* }
* </pre>
*
* @return whether a new JVM was started and thus no code should be executed
* in this one
*/
public static boolean startNewJvmIfRequired() {
return startNewJvmIfRequired(true);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

8
settings.gradle Normal file
View file

@ -0,0 +1,8 @@
plugins {
// Applies the foojay-resolver plugin to allow automatic download of JDKs.
id("org.gradle.toolchains.foojay-resolver-convention") version "0.9.0"
}
// A list of which subprojects to load as part of the same larger project.
// You can remove Strings from the list and reload the Gradle project
// if you want to temporarily disable a subproject.
include 'lwjgl3', 'core'